blender のメッシュはNgonが可能なので、三角形だけカウントすると四角形以降がカウントされなかったりする。
mesh.calc_loop_triangles()関数を使えば、全て三角形として頂点を取り出すことができる。
注意点としてこれを呼び出したからと言ってモデルが三角形メッシュになるわけではないらしい。また三角形メッシュに変換した結果とこの結果が異なったりする。
import bpy
# @brief メッシュを構成する三角形を三頂点の組み合わせのリストで取得 # @param [in] obj オブジェクト def get_triangles(obj): ret = [] mesh = obj.data mesh.calc_loop_triangles() for tri in mesh.loop_triangles: ret.append([tri.vertices[0],tri.vertices[1],tri.vertices[2] ]) # [index0,index1,index2]のリスト return ret
# @brief 与えられたオブジェクトの重心を求める # @param [in] obj オブジェクト # @param [in] vid0 obj.data.verticesのindex # @param [in] vid1 obj.data.verticesのindex # @param [in] vid2 obj.data.verticesのindex # @return 三次元座標 <Vector ( x , y , z )> def calc_triangle_center(obj,vid0,vid1,vid2): mesh = obj.data tri_center = (mesh.vertices[vid0].co) +\ (mesh.vertices[vid1].co) +\ (mesh.vertices[vid2].co) tri_center /= 3.0 return tri_center
# @brief オブジェクトの全ての三角形の重心にsphereを配置する # @param [in] オブジェクト def disp_triangle_centers(obj): # 三角形をindex listで取得 PIDs = get_triangles(obj) for pid in PIDs: # 三角形の重心を算出 c = calc_triangle_center(obj, pid[0],pid[1],pid[2]) # 座標 c にsphereを配置 bpy.ops.mesh.primitive_uv_sphere_add( radius=0.05, segments=8, ring_count=8, enter_editmode=False, location=c )
# 使用例 myobj = bpy.context.active_object disp_triangle_centers(myobj)
まずコードを用意して、上記フォルダへ適当な名前で保存する
例 script-test.scm
; 画像をPNG保存する自作関数。この関数を呼び出したい ; Image 画像番号 ; Drawable レイヤー番号 ; outpath 保存先(ファイル名を含まない) ; filename ファイル名(.pngを含まない) (define (my-save-to-png Image Drawable outpath filename) ; 保存処理 (file-png-save-defaults RUN-NONINTERACTIVE Image Drawable (string-append outpath filename ".png") (string-append filename ".png") ) )
; GUIダイアログの項目を設定 ;; 参考 http://sampodo.la.coocan.jp/graphics/gimp2/gimp2_1.html (script-fu-register ; ; 呼び出す関数 ; メニュー上の情報 ; ダイアログの項目設定 ; "my-save-to-png" ;呼び出す関数 "PNGで保存" ;メニューに表示する項目 "テスト用スクリプト\ の説明文" ;説明文章 "空鳥夜鳥" ;制作者 "copyright 2021, szl" ;著作編表示 "2021/11/08" ;制作日時 "*" ;スクリプト関数が扱う画像のタイプ ; 以降、引数を指定するGUIパーツの作成、個数は任意。 ; ; 呼び出す関数が my-save-to-png であるので、 ; my-save-to-png の引数の順番と同じでなければいけない ; ; 指定方法 : ; GUIアイテムの種類 "表示用ラベル" デフォルト値 SF-IMAGE "Image" 0
SF-DRAWABLE "Drawable" 0 SF-STRING "パス\\" "C:\\test\\" ; テキストボックスになる SF-STRING "ファイル名" "default-name" )
; メニューに登録する (script-fu-menu-register ; 呼び出す関数名 "my-save-to-png" ; メニューのどの位置に項目を挿入するか "<Image>/File/オリジナル項目" )
save-layers関数を作成。
;; レイヤー一覧取得 ;; image (car (gimp-image-list)) で取得 (define (get-layer-list image) (vector->list (car (cdr (gimp-image-get-layers image) ) ) ) )
;; outpath 出力先のディレクトリ ;; filename ファイル名 拡張子なし ;; image 画像番号 ;; drawablelayer レイヤー番号 (define (save-to-image outpath filename image drawablelayer) (begin ; 保存情報を表示 (display "saving:") (display (string-append outpath filename ".png") ) (newline) ; 保存処理 (file-png-save-defaults RUN-NONINTERACTIVE image drawablelayer (string-append outpath filename ".png") (string-append filename ".png") ) ) )
;; outpath 出力先のパス ;; image 画像番号 ;; layerlist 残りのレイヤー一覧 (define (save-more-layers outpath image layerlist) ; 画像一覧が空リストでないなら保存 (if (not (null? layerlist) ) (begin ; リストの先頭要素をPNGで保存 (save-to-image outpath ; 出力先パス (car (gimp-layer-get-name (car layerlist))) ; ファイル名としてレイヤー名を取得 image ; 画像番号 (car layerlist) ) ; レイヤー番号リストの一番左 ; 残りのリストを保存(再帰) (save-more-layers outpath ; 出力先パス image ; 画像番号 (cdr layerlist) ) ; 残りのリスト ; ) ) )
;; レイヤーをPNGファイルに保存する関数 ;; outpath ファイルパス 例 C:\\test\\ ;; image 画像番号 (car (gimp-image-list))で取得可 (define (save-laysers outpath image) (save-more-layers outpath image (get-layer-list image) ) )
パスの文字列を加工したりとかいろいろ複合的に使ってみたやつ。
;---------------------------------- ;---------------------------------- ; フォルダから画像を読み込む ; ファイルパスからファイル名を取得する (define (my-extract-file-name text) (car (reverse (strbreakup text "\\") ) ) ) ; 画像を読み込んだ後、タブを表示する (define (my-tab-update image) (begin (gimp-display-new image) (gimp-displays-flush) image ) ) ; リストで与えられた画像ファイルを全て読み込む (define (my-open-all-images flist) (if (null? flist) '() (cons (my-tab-update (car (gimp-file-load 0 (car flist) (my-extract-file-name (car flist) ) ) ) ) (my-open-all-images (cdr flist) ) ) ) ) ; パスで指定したディレクトリから画像を読み込む。 ; パスの最後は\\*で終わっている必要がある ; 例 (my-open-images "C:\\dev\\*") (define (my-open-images pathnamelist) ( my-open-all-images (car (cdr (file-glob pathnamelist 0))) ) ) ;---------------------------------- ;---------------------------------- ; 画像をxcf形式で保存する ; 画像を一枚xcf形式で保存する。 ; ファイル名は元の名前+xcf (define (my-save-as-xcf-single image outdir basename) (gimp-xcf-save 0 image (car (gimp-image-get-active-drawable image) ) (string-append outdir basename ".xcf") (string-append basename ".xcf") ) ) ; outdirへ画像群を保存。outdirは"\\"で終わっている必要がある (define (my-save-images outdir images) (if (null? images) '() (begin (my-save-as-xcf-single (car images) outdir (my-extract-file-name (car (gimp-image-get-filename (car images) ) ) ) ;画像番号からファイルパスを取得しファイル名を切り出す ) (my-save-images outdir (cdr images) ) ) ) )
;(my-save-as-xcf-images "H:\\input\\*" "H:\\output\\") (define (my-save-as-xcf-images indir outdir) (my-save-images outdir (my-open-images indir) ) )
保存にはgimp-xcf-saveを使う。
; 画像番号は (gimp-image-list) で取得できる数字。今回は「1」 (gimp-xcf-save 0 ; ダミーパラメータ 1 ; 画像番号 (car (gimp-image-get-active-drawable 1)) ; 有効にしたいレイヤー(現在選択中のレイヤー) "c:/data/test.xcf" ; 保存ファイル名 URI形式 "test.xcf" ; 保存ファイルのベース名 )
第一引数はダミーパラメータ。0でも入れておけばいい
第二引数は保存したい画像。一度に複数の画像を開いていたり、開いた画像のいくつかを閉じたりすると「1」の画像がなくなる可能性がある。ので、「gimp-image-list」で確認するといい
第三引数はアクティブにしたいレイヤー。(gimp-image-get-active-drawable image)で取得するのが無難。
第四引数は保存するファイル名。URI形式で指定。
第五引数はベースファイル名(ファイル名+拡張子)だが色々指定したが無視されているようにしか思えない。
式自体は何のことはない。問題はGimpのScript-Fuでやりたいので結構手間取った。
さすがにGimpのScript-Fuコンソールから実験するのは環境が悪すぎたので、RacketをインストールしてScheme環境を作った。
remainderは剰余。
;; 画素番号と画像幅からx座標を計算 (define (getx pos width) (remainder pos width) ) ;; x座標と画素番号と画像幅からy座標を計算し、x,yで返す (define (getxy x pos width) (list x (/ (- pos x) width) ) ) ;; 画素番号と画像幅からx,y座標を計算する (define (2dcoord pos width) (getxy (getx pos width) pos width) )
width=50の画像の1162番のx,y座標は(12,23)。
// x,yから画素番号を算出 inline int calcpos(int x, int y,int width) { return y * width + x; } // 画素番号と画像幅からx座標を計算 int getx(int pos, int width) { return pos % width; } // 画素番号とx座標と画像幅からy座標を計算 int gety(int x, int pos, int width) { return (pos - x) / width; } int main() { int width = 50; int height = 25; int x = 12; int y = 23; int pos = calcpos(x, y, width); //1162 printf("pos %d\n", pos); int xx = getx(pos, width); int yy = gety(xx, pos, width); printf("xx %d\n", xx); printf("yy %d\n", yy); }
パス一覧は (gimp-image-get-vectors 画像番号) で取得できる
gimp-image-get-layersで取得できるのだが、vectorではなくlistでほしいのでラップする。
; レイヤー一覧 (define (get-layer-list image) (vector->list (car (cdr (gimp-image-get-layers image) ) ) ) )
レイヤーに対して、gimp-drawable-is-text-layerでテキストレイヤーかどうかを判定できる。
; imagelistの中からテキストレイヤーだけを抽出する (define (get-text-layers imagelist) (if ( null? imagelist ) '() (if (= 1 (car (gimp-drawable-is-text-layer (car imagelist) ) ) ) (cons (car imagelist) (get-text-layers (cdr imagelist) ) ) (get-text-layers (cdr imagelist) ) ) ) )
Script-Fuは フィルター → Script-Fu → Script-Fu コンソール からコンソールのウィンドウを開き一行ずつ入力できる。
初めてのGimpのスクリプト。
;; 画像一覧 (define Images (vector->list (car (cdr (gimp-image-list))))) (define ImageIndex (car Images) ) ;; ImageIndex内のレイヤー一覧 (define Layers (vector->list (car (cdr (gimp-image-get-layers ImageIndex))))) (define LayerIndex (car Layers) ) ;; 編集対象は以後Drawable (define Drawable LayerIndex) ;; 画像サイズ確認 (gimp-drawable-width Drawable) (gimp-drawable-height Drawable) ;; 座標 (define x 10) (define y 10) ;; ピクセルの更新 (gimp-drawable-set-pixel Drawable x x 3 #(255 0 0) ) (gimp-displays-flush) (gimp-drawable-update Drawable x y 1 1)
https://developer.gimp.org/api/2.0/libgimp/libgimp-gimpdrawable.html
C++でバリアント型を使う。C++17以降。
#include <cstdio> #include <string> #include <variant> enum VARIANT_TYPES { V_INT = 0, V_STR = 1, V_DBL = 2 }; int main() { std::variant<int, std::string, double> val; val = 2; //val = 5.5; //val = "abc"; if (val.index() == V_INT) { int& x = std::get<V_INT>(val); printf("--- %d\n", x); }
else if(val.index()==V_STR){ std::string& s = std::get<V_STR>(val); printf("--- %s\n", s.c_str()); }
else if (val.index() == V_DBL) { double& d = std::get<V_DBL>(val); printf("--- %lf\n", d); } int k = getchar(); }
std::variant型を引数で取る関数を作ってもいいが、std::visit関数でoperator()を定義したクラスのインスタンスを渡して呼び出す方が美しいらしい。
#include <cstdio> #include <string> #include <variant>
struct Print { // int が代入されたときに呼び出される void operator()(const int& data) { printf("--- %d\n", data); } // std::string が代入されたときに呼び出される void operator()(const std::string& data) { printf("--- %s\n", data.c_str()); } // double が代入されたときに呼び出される void operator()(const double& data) { printf("--- %lf\n", data); } };
int main() { std::variant<int, std::string, double> val; val = 2; std::visit(Print(), val); val = 5.5; std::visit(Print(), val); val = "abc"; std::visit(Print(), val); int k = getchar(); }
std::visitは第一引数に関数、第二引数以降はstd::variant型でなければならない(らしい)。
std::visit( 関数() , variant1 , variant2 , variant3 , … )
なので、std::visitの引数で渡す場合、std::variantで渡さなければいけない。
#include <cstdio> #include <string> #include <variant> struct Print { // int が代入されたときに呼び出される void operator()(const int& data) { printf("--- %d\n", data); } // std::string が代入されたときに呼び出される void operator()(const std::string& data) { printf("--- %s\n", data.c_str()); } // double が代入されたときに呼び出される void operator()(const double& data) { printf("--- %lf\n", data); } };
struct Add { // int が代入されたときに呼び出される void operator()(int& data, const int val) { data += val; } // std::string が代入されたときに呼び出される void operator()(std::string& data, const int val) { data += std::to_string(val); } // double が代入されたときに呼び出される void operator()(double& data, const int val) { data += val; } };
int main() { std::variant<int, std::string, double> val; val = 2; std::visit( Add(), val,std::variant<int>(4) ); std::visit(Print(),val); val = 5.5; std::visit( Add(), val, std::variant<int>(4)); std::visit(Print(), val); val = "abc"; std::visit( Add(), val, std::variant<int>(4)); std::visit(Print(), val); int k = getchar(); }
引数を渡すならコンストラクタに渡した方がスマートだと思う。
#include <cstdio> #include <string> #include <variant>
struct Print { std::string _sign; public: Print(const std::string& sign):_sign(sign) {} // int が代入されたときに呼び出される void operator()(const int& data) { printf("%s %d\n",_sign.c_str(), data); } // std::string が代入されたときに呼び出される void operator()(const std::string& data) { printf("%s %s\n", _sign.c_str(), data.c_str()); } // double が代入されたときに呼び出される void operator()(const double& data) { printf("%s %lf\n", _sign.c_str(), data); } };
int main() { std::variant<int, std::string, double> val; val = 2; std::visit(Print(">>"),val); val = 5.5; std::visit(Print(">>"), val); val = "abc"; std::visit(Print(">>"), val); int k = getchar(); }
PPM入出力:
https://www.study.suzulang.com/cppppm-readerwriter
NByteDataを扱うクラス
https://www.study.suzulang.com/2dcg-functions/nbyte-data-type
#include <cstdio> #include <vector> #include <cassert> #include "NByteData.hpp" #include "ppmP3_read.hpp" namespace szl{ inline int calc_position2d(const int x, const int y, const int width) { return x + y * width; }
//canvasの一部に矩形でアクセスできるためのクラス class D2CanvasWindow { public: //! @brief start ~ end の範囲の画素番号一覧 std::vector<size_t> m_imgp; int startx,starty; int endx,endy; int canvaswidth; //!< 元画像の範囲 int canvasheight;//!< 元画像の範囲 inline int width() {return endx - startx + 1;} inline int height() {return endy - starty + 1;} //! @brief 元画像サイズとクリップサイズの設定 //! @param [in] canvaswidth 元画像のサイズ //! @param [in] canvasheight 元画像のサイズ //! @param [in] sx 始点 //! @param [in] sy 始点 //! @param [in] ex 終点 //! @param [in] ey 終点 //! @return なし void set_size(const int canvaswidth, const int canvasheight, const int sx, const int sy, const int ex, const int ey) { startx = sx; starty = sy; endx = ex; endy = ey; const int Width = width(); const int Height = height(); m_imgp.resize(Width * Height); int wx, wy;// クリップする範囲の画素indexの一覧を作成 for (int cx = sx; cx <= ex; cx++) { for (int cy = sy; cy <= ey; cy++) { wx = cx - sx; wy = cy - sy; int ci = calc_position2d(cx, cy, canvaswidth); int wi = calc_position2d(wx, wy, Width); m_imgp[wi] = ci; } } } //! @brief 画像を切り出す //! @param [in] dst 結果画像 //! @param [in] src 元画像 //! @return なし template<typename Canvas> void Clip(Canvas* dst, const Canvas& src) { assert(iwidth(*dst) == width()); assert(iheight(*dst) == height()); for(size_t i =0;i<m_imgp.size();i++){ (*dst)[i] = src[m_imgp[i]]; } } };
} ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// using PixelT = NByteData<3>; //画像データ struct Img2d { std::vector<PixelT> m_img; int width; int height; Img2d(int w, int h) { m_img.resize(w*h); width = w; height = h; } PixelT& operator[](const size_t index) {return m_img[index];} const PixelT& operator[](const size_t index)const {return m_img[index];} }; //! @brief 画像の幅の取得関数 //! @param [in] i 画像 //! @return 画像の幅 int iwidth(const Img2d& i) { return i.width; } //! @brief 画像の高さ取得関数 //! @param [in] i 画像 //! @return 画像の幅 int iheight(const Img2d& i) { return i.height; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// //! @brief 画像の書き込み void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax );
int main() { // 画像読み込み PixelT* pimg; int width; int height; int vmax; ppmP3_read( "C:\\test\\p.ppm", &width, &height, &vmax, (unsigned char**)&pimg ); Img2d img(width, height); for (size_t i = 0; i < width*height; i++) { img.m_img[i] = pimg[i]; } delete[]pimg; ///////////////////////////////// ///////////////////////////////// // 画像のクリップ szl::D2CanvasWindow win; win.set_size(img.width,img.height, 2, 3, 8, 10); Img2d clip(win.width(), win.height()); win.Clip(&clip, img); ///////////////////////////////// ///////////////////////////////// ppmP3_write( "C:\\test\\q.ppm", clip.width, clip.height, (unsigned char*)clip.m_img.data(), 255); }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }