プログラミング」カテゴリーアーカイブ

STLファイルの寸法を知りたい

3Dプリンタでも良く使われるSTLファイルですが,人からもらったSTLファイルの外寸(寸法,サイズ)をさくっと知りたいときはRubyで以下のような感じです。

#!/usr/bin/ruby
# stldimensions.rb: STLの外寸を求める。
# 要 stl (Gem)
# usage: $0 stlfile

require 'stl'

class Points < Array
  def dimension(axis)
    (self.map(&axis).max-self.map(&axis).min)
  end
end

# STLファイル中の全平面の全座標を得る。各要素はGeometry::Point。
points = Points.new(STL.read(ARGV.shift).map(&:last).map(&:points).flatten)

# 最大外寸を調べて表示
printf("%.2f x %.2f x %.2f\n",
       points.dimension(:x),
       points.dimension(:y),
       points.dimension(:z))

stlはgemでインストールできます。

$ gem install stl

STLビューアを立ち上げるのが面倒or不可能なときに。

OpenCVでCAP_PROP_FRAME_WIDTHが効かないカメラをEWCLIBの併用で何とかする

OpenCV (C++) でWebカメラからのキャプチャ解像度を変更したい場合,本来は以下のようにすれば良いはずです。

cv::VideoCapture vc;
vc.set(cv::CAP_PROP_FRAME_WIDTH, 1920);
vc.set(cv::CAP_PROP_FRAME_HEIGHT, 1080);

しかし,このAPIはカメラによっては効果がないことが良くあります。特にLogicoolのカメラでは16:9のキャプチャをしようと思っても無視されてしまう傾向にある気がします。長年未解決な問題です。
(Windowsでの問題。LinuxやMacでは違うかも知れません)

この問題については,EWCLIB を併用するのが良いようです。キャプチャ部分を他のライブラリに頼る方法は EWCLIB 以外にもいくつかあるようですが,EWCLIB には以下のようなメリットがあります。

  • ヘッダのみなので導入が簡単。
  • Visual Studioのバージョンを殆ど選ばない1
  • x64向けにも簡単にビルドできる。

デメリットもありますが2それほど根本的なものでもありません。導入は以下のようにします。

Windows 10 + Visual Studio Professional 2015 で動作確認しています。

qedit.hを用意する

EWCLIBには別途Windows SDKの「qedit.h」が必要です。これは以下のように用意できます3

  1. Microsoft Windows Software Development Kit Update for Windows Vistaのページで [DOWNLOAD] をクリックして 6.1.6000.16384.10.WindowsSDK_Vista_Feb2007Update_rtm.DVD.Rel.iso をダウンロードします。
  2. 上記isoファイルをマウントします。例えばWindows10の場合はダブルクリックすればドライブレターが割り振られてマウントされます。
  3. マウントされたドライブの Setup フォルダ内の WinSDKBuild-SDK_DirectShow_BLD_Common-common.0.cab を探します。qedit.h はこの cab ファイル内にあります。
  4. cab ファイルを伸長できるツールで上記 cab ファイルを伸長します。Windows 10 の場合はダブルクリックすればフォルダのように扱えます。
  5. qedit_h.99023124_2CFC_4698_80A9_F84FC02DCB6C を探して,任意のフォルダにコピーし,qedit.h にリネームします。
Visual Studioから使えるようにする

ewclib.h と qedit.h を任意のフォルダにおいて,自作プロジェクト等から「追加のインクルードディレクトリ」等を設定します。
私はプロジェクト内に置いて相対パスで指定するようにしています。

なお .lib は無いので「追加のライブラリディレクトリ」等を設定する必要はありません。

OpenCV と併用するので OpenCV の設定も必要です。EWCLIB 2.5 と OpenCV 3.4.2 の併用で特に問題はありませんでした。

サンプルコードその1

640×360 という,16:9 の解像度でキャプチャする例です。
CAP_PROP_FRAME_WIDTH での設定ではこの解像度はうまくいかないのですが,このコードだときちんと 640×360 でのキャプチャになります。

#include <opencv2/opencv.hpp>
#include "ewclib.h"

#ifdef _DEBUG
#pragma comment(lib, "opencv_world342d.lib")
#else
#pragma comment(lib, "opencv_world342.lib")
#endif

int main(void)
{
    const int cameraID = 0;
    const int width = 640;
    const int height = 360;

    int ret = EWC_Open(cameraID, width, height, 30, cameraID, MEDIASUBTYPE_RGB24);
    if (ret != 0) {
        fprintf(stderr, "EWC_Open failed.(%d: %d x %d)\n", cameraID, width, height);
        std::exit(1);
    }
    EWC_SetValue(cameraID, EWC_FOCUS, 0.0);
    cv::Mat_<cv::Vec3b> camera = cv::Mat_<cv::Vec3b>(height, width);

    cv::namedWindow("camera", CV_WINDOW_AUTOSIZE);
    for (;;) {
        EWC_GetImage(0, camera.data);
        cv::imshow("camera", camera);
        if (cv::waitKey(1) == 0x1b) { // ESC
            break;
        }
    }

    EWC_Close(cameraID);

    return 0;
}
サンプルコードその2

Logicool の C615 の詳細ページによると,このカメラは以下の解像度でビデオキャプチャできるとあります。

[4:3 SD] 320×240,640×480
[16:9 W] 360P,480P,720P,1080P

以下のコードは,これらの解像度で順にキャプチャをしていく例です。
なお,16:9の方の480Pというのは良くわかりませんでした(以下のコードに854×480を追加してみてもEWC_Open()が失敗します)。

#include <opencv2/opencv.hpp>
#include <vector>
#include <string>
#include "ewclib.h"

#ifdef _DEBUG
#pragma comment(lib, "opencv_world342d.lib")
#else
#pragma comment(lib, "opencv_world342.lib")
#endif

int main(void)
{
    const int cameraID = 0;

    const std::vector<cv::Size> capsizes = {
        // 4:3
        cv::Size(320, 240),
        cv::Size(640, 480),
        // 16:9
        cv::Size(640, 360),
        cv::Size(1280, 720),
        cv::Size(1920, 1080)
        // logicool c615 は上記全部OK
    };

    for (auto size : capsizes) {
        int ret = EWC_Open(cameraID, size.width, size.height, 30, cameraID, MEDIASUBTYPE_RGB24);
        if (ret != 0){
            fprintf(stderr, "EWC_Open failed.(%d: %d x %d)\n", cameraID, size.width, size.height);
            std::exit(1);
        }
        EWC_SetValue(cameraID, EWC_FOCUS, 0.0);
        cv::Mat_<cv::Vec3b> camera = cv::Mat_<cv::Vec3b>(size.height, size.width);

        std::string sizestr = std::to_string(size.width) + " x " + std::to_string(size.height);

        cv::namedWindow("camera", CV_WINDOW_AUTOSIZE);
        for (;;) {
            EWC_GetImage(0, camera.data);
            cv::putText(camera, sizestr.c_str(), cv::Point(40, 40), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 255), 2, CV_AA);
            cv::imshow("camera", camera);
            if (cv::waitKey(1) == 0x1b) { // ESCで次の解像度へ
                break;
            }
        }

        EWC_Close(cameraID);
    }

    return 0;
}

LaTeXのlistingsパッケージ用Arduinoスタイル

LaTeXのlistings向けに,Arduino言語用のスタイルの設定を作ってみました。
Arduino IDEでの見た目と同じような感じでシンタックスハイライトを行います。

ライセンスはMIT licenseでお願いします。
TeX Live 2016 で動作を確認しました。

ダウンロード: listings-arduino.sty 1.8.5a

サンプル:

上記サンプルのファイル一式(TeX Live 2016で動作確認。Ubuntuではtexlive-fonts-extraも入れて):
listings-arduino-sample

背景:
うちの研究室の卒業論文はソースコードも掲載してもらうことにしています。
Arduinoを使う研究も多いのでそのソースコードも良く載せるのですが,その際listingsではlanguage=Cかlanguage=C++を使っていました。しかしどうせならArduino IDEと同じ見た目の方がカッコ良いので,作ってみることにしました。

どのトークンをシンタックスハイライトするかですが,Arduino IDEのソースコードには,シンタックスハイライトのためのルールファイルのようなものが含まれているようです。
例えば,build/shared/lib/keywords.txt にTSV形式で以下のように書かれています。

# LITERAL1 specifies constants
HIGH	LITERAL1	Constants	RESERVED_WORD_2
LOW	LITERAL1	Constants	RESERVED_WORD_2
INPUT	LITERAL1	Constants	RESERVED_WORD_2
INPUT_PULLUP	LITERAL1	Constants	RESERVED_WORD_2
OUTPUT	LITERAL1	Constants	RESERVED_WORD_2
…
delay	KEYWORD2	Delay
delayMicroseconds	KEYWORD2	DelayMicroseconds
digitalWrite	KEYWORD2	DigitalWrite
digitalRead	KEYWORD2	DigitalRead

これを見ると第1コラムが終端記号で,第2コラムがその種類のようです。
これらを機械的に抽出して色付けをしました。

ちなみに keywords.txt というファイルは複数個存在します。
Arduino-1.8.5 時点では以下の個数あるようです。

hardware/arduino/avr/libraries/EEPROM/keywords.txt
hardware/arduino/avr/libraries/SoftwareSerial/keywords.txt
hardware/arduino/avr/libraries/SPI/keywords.txt
hardware/arduino/avr/libraries/Wire/keywords.txt
hardware/arduino/avr/libraries/HID/keywords.txt
build/shared/lib/keywords.txt
libraries/Stepper/keywords.txt
libraries/TFT/src/utility/keywords.txt
libraries/TFT/keywords.txt
libraries/Ethernet/keywords.txt
libraries/GSM/keywords.txt
libraries/WiFi/keywords.txt

これらから想像付くように,ライブラリそれぞれに keywords.txt は存在し,予約語だけではなくライブラリのクラス名にももれなく色を付ける方針のようなので,Arduino IDEがバージョンアップして標準ライブラリが増えるたびにこのlistings-arduino.styも
同期させる必要があります。
将来的にそのような気力が続くかどうかはともかく,混乱しないようにこのスタイルファイルのバージョンもそれが依拠するArduino IDEのバージョンと同じにしておきます。

さらに,Arduino IDEはユーザが後から入れたライブラリにも色をつけてくれます。
これはライブラリ配布者が keywords.txt を同梱すればそれを見に行くというしかけのようです。
例えば私の環境だと,

C:/Users/ユーザ名/AppData/Local/Arduino15/packages
C:/Users/ユーザ名/Documents/Arduino/libraries

にもライブラリがインストールされていますので,Arduino IDEではここの keywords.txt も考慮されて色が付きます。

というわけで,どこにArduino IDEとライブラリとがインストールされているかを指定すれば,そこから keywords.txt を探し出してそこからトークンを拾って listings-arduino.sty を生成する,というスクリプトが以下です(Ruby)。

#!/usr/bin/ruby
# coding: utf-8
# generate listings-arduino.sty from Arduino IDE

class ArduinoIDEParser
  def initialize(*ref_dirs)
    # enum of "keywords.txt"s.
    # Note: These depend on installed libraries.
    @keywords_txts = []
    ref_dirs.each do |dir|
      @keywords_txts += Dir.glob(File.join(dir, "**/keywords.txt")).to_a
    end

    # table of the tokens(LITERAL1, KEYWORD1, ...)
    @tokens = Hash.new{|h, k| h[k] = []}
    @keywords_txts.each do |f|
      File.readlines(f).map(&:chomp).
        select{|l| l[0] != "#" && !l.strip.empty? && l.match(/^[a-zA-Z_]/)}.
        map{|l| l.split(/\s+/)}.each do |l|
        @tokens[l[1]] << l[0] unless @tokens[l[1]].include?(l[0])
      end
    end

    @ref_dirs = ref_dirs
  end

  def header
    header_str = <<~'EOS'
      % Arduino language style for listings package
      __GENERATED_FROM__
      % usage: \lstinputlisting[language=Arduino]{somesketch.ino}
      %
      __KEYWORDS_TXTS__
      \usepackage[dvipdfmx]{color}

      \definecolor{lst-arduino-sngq}{rgb}{0.000, 0.592, 0.612}
      \definecolor{lst-arduino-dblq}{rgb}{0.000, 0.361, 0.373}
      \definecolor{lst-arduino-literal1}{rgb}{0.000, 0.592, 0.612} % BLUE #00979C
      \definecolor{lst-arduino-keyword1}{rgb}{0.827, 0.329, 0.000} % ORANGE #D35400
      \definecolor{lst-arduino-keyword2}{rgb}{0.827, 0.329, 0.000} % ORANGE #D35400
      \definecolor{lst-arduino-keyword3}{rgb}{0.447, 0.557, 0.000} % GREEN #728E00
      \definecolor{lst-arduino-directive}{rgb}{0.369, 0.427, 0.012}
      \definecolor{lst-arduino-commentc}{rgb}{0.584, 0.647, 0.651}
      \definecolor{lst-arduino-commentcpp}{rgb}{0.263, 0.310, 0.329}
      \definecolor{lst-arduino-identifier}{rgb}{0.000, 0.000, 0.000}

      \lstdefinelanguage{Arduino}
      {
        showspaces=false,
        showstringspaces=false,
        showtabs=false,
        morestring=[s][\color{lst-arduino-sngq}]{'}{'},
        morestring=[s][\color{lst-arduino-dblq}]{"}{"},
        keywordstyle=[1]{\color{lst-arduino-literal1}},% LITERAL1(BLUE)
        keywordstyle=[2]{\color{lst-arduino-keyword1}\bfseries},% KEYWORD1(ORANGE)
        keywordstyle=[3]{\color{lst-arduino-keyword2}},% KEYWORD2(ORANGE)
        keywordstyle=[4]{\color{lst-arduino-keyword3}},% KEYWORD3(GREEN)
        directivestyle={\color{lst-arduino-directive}},
        morecomment=[s][\color{lst-arduino-commentc}]{/*}{*/},
        morecomment=[l][\color{lst-arduino-commentcpp}]{//},
        identifierstyle={\color{lst-arduino-identifier}},
    EOS
    header_str.
      sub(/__GENERATED_FROM__/, @ref_dirs.map{|x| "% Generated from: #{x}\n"}.join).
      sub(/__KEYWORDS_TXTS__/,  @keywords_txts.map{|x| "% #{x}\n"}.join)
  end

  def footer
    <<~'EOS'
      moredelim=*[directive]\#,
      moredirectives={
        define,elif,else,endif,error,if,ifdef,ifndef,line,%
        include,pragma,undef,warning
      },
      sensitive=true
    }
    EOS
  end

  def middle
    tex_lines = []
    %w(LITERAL1 KEYWORD1 KEYWORD2 KEYWORD3).each.with_index(1) do |k, i|
      tex_lines << "morekeywords=[#{i}]{"
      tex_lines << "  %%% #{k}"
      ary = @tokens[k].each_slice(5).to_a
      ary.take(ary.size-1).map{|a| "  " + a.join(",") + ","}.each do |l|
        tex_lines << l
      end
      tex_lines << "  " + ary.last.join(",")
      tex_lines << "},"
    end
    tex_lines.map{|s| "  #{s}\n"}.join
  end

  def to_listings
    header + middle + footer
  end
end

print ArduinoIDEParser.new("C:/Program Files (x86)/Arduino",
                           "C:/Users/foo/AppData/Local/Arduino15/packages",
                           "C:/Users/foo/Documents/Arduino/libraries").to_listings

RubyでPDFのページ数を数える

Rubyスクリプトから,既存のPDFファイルの総ページ数をカウントしたいことがたまにあります。

方法はいくつか考えられます(多少強引なのも含めて)。

PDF::Readerを使う方法

たぶん一番スマート。

#!/usr/bin/env ruby
require 'pdf/reader'

fname = "test.pdf"
p PDF::Reader.new(fname).page_count

PDF::Readerはgemでインストールできます。

$ gem install pdf-reader

続きを読む

PowerPointでCなどのソースコードをシンタックスハイライト

プログラミングの講義などを担当しているので,ソースコードをPowerPointに載せる機会が非常に多いです。そこで,少しでもわかりやすくしようと,Visual Studio や今どきのblogみたいにシンタックスハイライトをしたいのですが,当然PowerPointにはそんな機能はありません。いくつか方法はあるみたいですが,今のところ私はこうやっています。

テキストエディタ SciTEをまずインストール。
SciTEは source code editor というくらいなのでデフォルトでシンタックスハイライト機能があります。

ソースコードをSciTE上で編集します。他のエディタ等から貼り付けても良いです。自動的にシンタックスハイライトされます。

Ctrl+a で全選択し,Edit -> Copy as RTF します。

なお,SciTEから Copy as RTF するときにいちいちプルダウンメニューからやるのも大変です。Cops as RTFには元々ショートカットキーが無いみたいですが,以下のようにするとCtrl+cに割り当てることができます。

Options -> Open User Options File で,以下のように記入。

user.shortcuts=\
Ctrl+c|IDM_COPYASRTF|

PowerPointでテキストボックスを作って貼り付けます。このとき,「貼付けのオプション」で「元の書式を保持」という方を選びます。

シンタックスハイライトされた状態で貼り付けることができました。

とりあえずシンタックスハイライトだけなら以上で良いでしょう。

私はさらに,以下をやりたいと思いました。

  • 等幅フォントで表示したい。フォントサイズも変えたい。
  • 行間をもう少し詰めたい。
  • 背景色やドロップシャドウもつけて目立たせたい。

SciTEのデフォルト設定ではVerdanaフォントでコピーされます。しかしソースコードはやはり,Inconsolata 等の等幅フォントで表示したいものです。

この場合,等幅フォント化するには2つの方法があるようです。

  1. SciTEの設定で,はじめからInconsolataにしておく。
  2. PowerPointに貼り付けたあとでフォントを変更する。

1の場合は,SciTEの設定ファイル SciTEGlobal.properties の

font.base=font:Verdana,size:10

あたりをいじります。この設定は,SciTE上での表示と,コピーしたときに属性としてくっついていく情報の両方に使われます。

しかし,上記のように他にも変えたい部分がいくつかあるので,2.の方法の方が良さそうです。ただ,これらを毎回やっていたら非常に大変なので,マクロ(vba)にしてみました。(こちらを大変参考にさせて頂きました)

Sub 行間カンマ8フォントInconsolata背景色薄灰色()
  With ActiveWindow.Selection

    'シェイプが選択されていない場合はマクロを終了する
    If .Type = ppSelectionNone Or .Type = ppSelectionSlides Then
      MsgBox "行間を変更したいプレースホルダなどを選択してください。"
      Exit Sub
    End If

    'テキスト編集モードの場合にはシェイプを選択する
    If .Type = ppSelectionText Then .ShapeRange.Select

    '自動調整をいったんオフにする
    .ShapeRange.TextFrame.AutoSize = ppAutoSizeNone

    With .TextRange
      '行間をポイント数で指定するように設定
      .ParagraphFormat.LineRuleWithin = msoTrue
      '行間を0.8にする
      .ParagraphFormat.SpaceWithin = 0.8
      '段落前を0にする
      .ParagraphFormat.SpaceBefore = 0

      'フォントをInconsolataへ変更
      .Font.Name = "Inconsolata"

      'フォントサイズを18へ変更
      .Font.Size = 18
    End With

    '自動調整を再度オンにする
    .ShapeRange.TextFrame.AutoSize = ppAutoSizeShapeToFitText

    '背景色を薄灰色へ
    .ShapeRange.Fill.ForeColor.RGB = RGB(250, 250, 250)
  End With
End Sub

なお,この場合,ソースを貼り付ける先は,普通のテキストボックスではなく,図形描画の「正方形/長方形」にする必要があります。「正方形/長方形」のテキスト領域にSciTEからソースを「形式を選択して」貼付け,その図形を選択状態にした上で,この「行間カンマ8フォントInconsolata背景色薄灰色」マクロを適用します。最終的には以下のような感じになります。

Inconsolataフォントは以下からダウンロードできます。http://www.levien.com/type/myfonts/inconsolata.html