こんにちは、ISDです。
私の環境でも、ChatGPT5.2 が使えるようになってました。
どのくらい性能が上がっているのかは良く分かりませんが……
多分、賢くなっているのでしょう!
というわけで、お手並み拝見と行きましょう。
3Dポリラインの頂点から3D面で構成されたTINを生成する
さて、今回のお題はこれです。
「3Dポリラインの頂点から3D面で構成されたTINを生成する」
設計対象の構造物のほかに、周辺の地形や既設の道路などのモデルを作る機会も多いですよね。
地形は、国土地理院の5mメッシュを使うのが定番?ですが、
5mメッシュだと表現力がチョッと足りないとか、既存道路の形状を簡易的にでもモデル化したいとか、そういう時あります……よね?
そんな時は、等高線や道路の外郭線などからTINを作るということになります。
こんな風に

まぁ、Civil3D なら3DポリラインからTINサーフェスを作れますが、わざわざ Civil3D 立ち上げるのが面倒……「ChatGPT に作らせてみる」というのが重要なのです!
まぁ、自由研究ですね。
というわけで、「3Dポリラインの頂点から3D面で構成されたTINを生成する」オリジナルコマンドを作成してみましょう。
ChatGPTでオリジナルコマンドを作成する
では、ChatGPT にお願いしてみようと思います。
プロンプトを考えなければいけませんが、
今回は少し難易度が高そうなので、いつもよりもプロンプトを練ってみることにします。
条件を整理する
コマンドの概要としては
複数の 3Dポリラインから頂点を抽出し、頂点群から三角形分割を行い、TINを形成する。
TINの条件をもう少し詳しく言うと、
TINは、XY平面上で三角形分割を行い、それぞれの三角形の頂点のZに、元の頂点のZを割り当てたもので、いわゆる2.5次元のメッシュとします。
大まかなフローとしては
① 複数の 3Dポリラインから頂点を抽出
② 頂点群から2.5次元の地形TIN として XY平面で三角形分割を行う。
③ 三角形の各頂点のZは元の頂点のZを割り当てる
④ 3DFACEの集合(TIN) を生成する。
という感じです。
問題になりそうなところをあらかじめチェック
TINを作成する際に不具合が起こりそうなケースをあらかじめ想定して、どう対処させるかを考えます。
思いつくところでは、以下のようなケース
・重複点があった場合どうするか? → 重複点は三角形分割前に削除しておく
・頂点数が少なかった場合どうするか? → 頂点数が3点未満なら何もしない
・オーバーハング等の形状は考慮しない。
・LWポリラインの扱いはどうするか? → 今回は3Dポリラインに限定
品質や開発方針をチェック
次は、出来上がりのTINの品質についてチェックします。
・TINは、なるべく正三角形に近い三角形で構築する
・三角形分割のアルゴリズムはDelaunay法を想定してるが、他に有用な手法がないか検討する
あと、コードを書いてもらうときにChatGPTに気を付けて欲しいことをチェック。
ChatGPTは、チョイチョイ余計なこと 気を利かせて色々なことをやろうとしますが、結果的にドロ沼にハマることが多いので、余計なことをしないで、まずは正常に動くコードを作成することを優先させます。高速化、効率化は次の段階にまわします。
また、コードを試行錯誤していくことになると思うので、
将来的に仕様変更しやすい構造にしてもらいます。
・まずは 正常に動くコードを作成すること優先
・高速化・効率化は現段階では不要
・将来的な仕様変更や検証に耐える構造であること
・堅牢性を重視して落ちにくいコード設計とする
こうした優先順位や開発の方針は ChatGPT 側で勝手に推測してくれません。
プロンプトとしてちゃんと伝えてあげる必要があります。
今回のプロンプト
そして、上記の整理をふまえて作成したプロンプトがこちらです。
AutoCADで 以下の仕様のオリジナルコマンドを作成したい。
使用言語はAutolisp### 概要
「3Dポリラインの頂点からTINを生成するコマンド」
コマンド名:3DPL2TIN
選択した複数の 3Dポリラインから頂点(XYZ)を抽出し、
点群を 2.5D地形TIN として XY平面で三角形分割 → 各頂点のZを使って 3DFACEの集合(TIN) を生成する。### 手順
①複数の 3Dポリラインを選択
②選択した3Dポリラインの全頂点を抽出(重複点は許容誤差 0.001 として統合)
③点群を 三角形分割 して 3DFACE を作成
④結果を新規レイヤ「-TIN」に作成。元オブジェクトは変更しない### 入力条件
・「2.5次元の地形TIN」を想定。点群は「地形点」とみなし、XY平面上で三角形分割し、各頂点のZを使って3DFACE化する (※ オーバーハング等は想定しない)
・点の数が3点未満の場合は何もしない
・座標はWCSで処理する### 品質要件
・目標は「なるべく正三角形に近い」で三角形で構成されるメッシュ
・Delaunay三角形分割 を考えているが、他に有利な手法があるか検討して
・先ずは、動作するコードを作成することを優先する、ブルートフォース的なアプローチを可とする。
・現段階では高速化・効率化は考慮しないでよい。
・ただし将来的な対応を見据え、関数のモジュール化を行い、アルゴリズムやデータ管理手法の検証が可能な構成とすること。
・仕様変更に容易に対応できる、堅牢なプログラム構造とすること。
ChatGPTの返答
そして、ChatGPT の返答がこちら。
先に言っておくと、この段階のコードでは まだ動きません。
とりあえず、見てみましょう。
以下は、AutoCAD 2026 でそのまま動かせることを最優先にした、モジュール構成の 3DPL2TIN(3Dポリライン頂点→2.5D TIN→3DFACE) AutoLISP です。
(高速化は後回し/ただし将来の差し替えがしやすいように、関数を分割してあります)
方針(品質要件への回答)
- 「なるべく正三角形に近い」メッシュ目的なら、基本は Delaunay三角形分割が妥当です(細長い三角形が出にくい傾向)。
- 今回は **Bowyer–Watson(逐次挿入)**で 2D Delaunay を作り、各頂点の Z を持ち上げて 3DFACE 化します(= 2.5D TIN)。
※「外周の針状三角形抑制」「ブレークライン」「凹形境界の扱い」などは、次段階の拡張ポイントです(このコードはまず“動くTIN生成”)。
AutoLISPコード(3DPL2TIN)
;;; ------------------------------------------------------------ ;;; 3DPL2TIN : 3D Polyline vertices -> 2D Delaunay -> 3DFACE TIN ;;; Layer: "-TIN" (created if missing) ;;; Dedup tolerance: 0.001 (XY-based bucket rounding) ;;; WCS processing ;;; ------------------------------------------------------------ (vl-load-com) ;; ---------------------------- ;; Utilities ;; ---------------------------- (defun _abs (x) (if (< x 0.0) (- x) x)) (defun _v2 (p) (list (car p) (cadr p))) (defun _x (p) (car p)) (defun _y (p) (cadr p)) (defun _z (p) (caddr p)) (defun _dist2 (a b / dx dy) (setq dx (- (_x a) (_x b)) dy (- (_y a) (_y b))) (+ (* dx dx) (* dy dy)) ) (defun _area2 (a b c) ;; 2D signed double-area (- (* (- (_x b) (_x a)) (- (_y c) (_y a))) (* (- (_y b) (_y a)) (- (_x c) (_x a)))) ) (defun _ensure-layer (lname / t) (if (not (tblsearch "LAYER" lname)) (entmakex (list (cons 0 "LAYER") (cons 2 lname) (cons 70 0) (cons 62 7))) ; color 7 (By default) ) lname ) (defun _make-3dface (p1 p2 p3 lname) (entmakex (list (cons 0 "3DFACE") (cons 8 lname) (cons 10 p1) (cons 11 p2) (cons 12 p3) (cons 13 p3) ; triangle -> last = third ) ) ) ;; ---------------------------- ;; 1) Select + extract vertices from 3D polylines ;; ---------------------------- (defun _is-3dpolyline (e / ed f70) (setq ed (entget e)) (and (= (cdr (assoc 0 ed)) "POLYLINE") (setq f70 (cdr (assoc 70 ed))) (/= 0 (logand f70 8))) ; 8 bit = 3D polyline ) (defun _collect-3dpoly-vertices (ent / obj endp i p out) ;; returns list of WCS points (x y z) (setq out '()) (setq obj (vlax-ename->vla-object ent)) (if (vlax-method-applicable-p obj 'GetEndParam) (progn (setq endp (vlax-curve-getEndParam ent)) (setq i 0.0) (while (<= i endp) (setq p (vlax-curve-getPointAtParam ent i)) ; WCS (setq out (cons (list (car p) (cadr p) (caddr p)) out)) (setq i (+ i 1.0)) ) (reverse out) ) out ) ) (defun _extract-points-from-selection (ss / i n e pts) (setq pts '()) (if ss (progn (setq n (sslength ss)) (setq i 0) (while (< i n) (setq e (ssname ss i)) (if (_is-3dpolyline e) (setq pts (append pts (_collect-3dpoly-vertices e))) ) (setq i (1+ i)) ) ) ) pts ) ;; ---------------------------- ;; 2) Dedup points (tolerance by XY bucket), keep avg Z ;; ---------------------------- (defun _qkey (x tol) ;; round(x/tol) as integer (fix (+ 0.5 (/ x tol))) ) (defun _dedup-points-xy (pts tol / al out p kx ky key rec cnt sx sy sz) ;; al: (key . (cnt sx sy sz)) store sums to average duplicates (setq al '()) (foreach p pts (setq kx (_qkey (_x p) tol)) (setq ky (_qkey (_y p) tol)) (setq key (cons kx ky)) (setq rec (assoc key al)) (if rec (progn (setq cnt (nth 0 (cdr rec)) sx (nth 1 (cdr rec)) sy (nth 2 (cdr rec)) sz (nth 3 (cdr rec))) (setq cnt (1+ cnt) sx (+ sx (_x p)) sy (+ sy (_y p)) sz (+ sz (_z p))) (setq al (subst (cons key (list cnt sx sy sz)) rec al)) ) (setq al (cons (cons key (list 1 (_x p) (_y p) (_z p))) al)) ) ) (setq out '()) (foreach rec al (setq cnt (nth 0 (cdr rec)) sx (nth 1 (cdr rec)) sy (nth 2 (cdr rec)) sz (nth 3 (cdr rec))) (setq out (cons (list (/ sx cnt) (/ sy cnt) (/ sz cnt)) out)) ) out ) ;; ---------------------------- ;; 3) Delaunay triangulation (Bowyer-Watson) in XY ;; Returns triangles as list of (i j k) indices into pts ;; ---------------------------- (defun _bbox (pts / p xmin xmax ymin ymax) (setq xmin 1e99 xmax -1e99 ymin 1e99 ymax -1e99) (foreach p pts (if (< (_x p) xmin) (setq xmin (_x p))) (if (> (_x p) xmax) (setq xmax (_x p))) (if (< (_y p) ymin) (setq ymin (_y p))) (if (> (_y p) ymax) (setq ymax (_y p))) ) (list xmin ymin xmax ymax) ) (defun _supertriangle (pts / bb xmin ymin xmax ymax dx dy d cx cy p1 p2 p3) ;; big triangle enclosing all points (setq bb (_bbox pts)) (setq xmin (nth 0 bb) ymin (nth 1 bb) xmax (nth 2 bb) ymax (nth 3 bb)) (setq dx (- xmax xmin) dy (- ymax ymin)) (setq d (max dx dy)) (setq cx (/ (+ xmin xmax) 2.0) cy (/ (+ ymin ymax) 2.0)) ;; construct far points (setq p1 (list (- cx (* 20.0 d)) (- cy (* 20.0 d)) 0.0)) (setq p2 (list (+ cx (* 20.0 d)) (- cy (* 20.0 d)) 0.0)) (setq p3 (list cx (+ cy (* 20.0 d)) 0.0)) (list p1 p2 p3) ) (defun _circumcircle (a b c / ax ay bx by cx cy d ux uy r2) ;; returns (centerX centerY r2) or nil if colinear (setq ax (_x a) ay (_y a) bx (_x b) by (_y b) cx (_x c) cy (_y c)) (setq d (* 2.0 (+ (* ax (- by cy)) (* bx (- cy ay)) (* cx (- ay by))))) (if (< (_abs d) 1e-12) nil (progn (setq ux (/ (+ (* (+ (* ax ax) (* ay ay)) (- by cy)) (* (+ (* bx bx) (* by by)) (- cy ay)) (* (+ (* cx cx) (* cy cy)) (- ay by))) d)) (setq uy (/ (+ (* (+ (* ax ax) (* ay ay)) (- cx bx)) (* (+ (* bx bx) (* by by)) (- ax cx)) (* (+ (* cx cx) (* cy cy)) (- bx ax))) d)) (setq r2 (+ (expt (- ux ax) 2.0) (expt (- uy ay) 2.0))) (list ux uy r2) ) ) ) (defun _in-circumcircle (p a b c / cc ux uy r2 d2) (setq cc (_circumcircle a b c)) (if (null cc) nil (progn (setq ux (nth 0 cc) uy (nth 1 cc) r2 (nth 2 cc)) (setq d2 (+ (expt (- (_x p) ux) 2.0) (expt (- (_y p) uy) 2.0))) (<= d2 (+ r2 1e-10)) ) ) ) (defun _edge (i j) ;; undirected edge canonical representation: (min max) (if (< i j) (list i j) (list j i)) ) (defun _edge-count-add (ec e / r) ;; ec: alist (edge . count) (if (setq r (assoc e ec)) (subst (cons e (1+ (cdr r))) r ec) (cons (cons e 1) ec) ) ) (defun _triangulate-delaunay (pts / st n0 n1 n2 all tris i p bad ec t a b c e1 e2 e3 newtris polyedges out) ;; pts: list of 3D points, triangulation uses XY (setq st (_supertriangle pts)) (setq all (append pts st)) (setq n0 (length pts)) (setq n1 (1+ n0)) (setq n2 (+ n0 2)) ;; initial triangle uses supertriangle indices (setq tris (list (list n0 n1 n2))) (setq i 0) (while (< i (length pts)) (setq p (nth i all)) ;; find bad triangles (setq bad '()) (foreach t tris (setq a (nth (nth 0 t) all)) (setq b (nth (nth 1 t) all)) (setq c (nth (nth 2 t) all)) (if (_in-circumcircle p a b c) (setq bad (cons t bad)) ) ) ;; boundary polygon edges = edges that appear only once among bad triangles (setq ec '()) (foreach t bad (setq e1 (_edge (nth 0 t) (nth 1 t))) (setq e2 (_edge (nth 1 t) (nth 2 t))) (setq e3 (_edge (nth 2 t) (nth 0 t))) (setq ec (_edge-count-add ec e1)) (setq ec (_edge-count-add ec e2)) (setq ec (_edge-count-add ec e3)) ) (setq polyedges '()) (foreach e ec (if (= (cdr e) 1) (setq polyedges (cons (car e) polyedges)) ) ) ;; remove bad triangles (foreach t bad (setq tris (vl-remove t tris)) ) ;; re-triangulate the cavity with point i (setq newtris '()) (foreach e polyedges (setq newtris (cons (list (nth 0 e) (nth 1 e) i) newtris)) ) (setq tris (append tris newtris)) (setq i (1+ i)) ) ;; remove triangles touching supertriangle vertices (setq out '()) (foreach t tris (if (and (< (nth 0 t) n0) (< (nth 1 t) n0) (< (nth 2 t) n0)) (setq out (cons t out)) ) ) out ) ;; ---------------------------- ;; 4) Build 3DFACE TIN on layer ;; ---------------------------- (defun _build-tin-3dfaces (pts tris lname / t i j k p1 p2 p3) (foreach t tris (setq i (nth 0 t) j (nth 1 t) k (nth 2 t)) (setq p1 (nth i pts) p2 (nth j pts) p3 (nth k pts)) ;; skip degenerate (if (> (_abs (_area2 p1 p2 p3)) 1e-12) (_make-3dface p1 p2 p3 lname) ) ) (princ) ) ;; ---------------------------- ;; Command ;; ---------------------------- (defun c:3DPL2TIN (/ ss pts tol uniq tris lname) (setq tol 0.001) (setq lname "-TIN") (_ensure-layer lname) (prompt "\n[3DPL2TIN] 3Dポリラインを選択してください: ") (setq ss (ssget '((0 . "POLYLINE")))) (if (not ss) (progn (prompt "\n[3DPL2TIN] 選択なし。終了。") (princ)) (progn (command "_.UNDO" "_BEGIN") (setq pts (_extract-points-from-selection ss)) (if (< (length pts) 3) (prompt "\n[3DPL2TIN] 点が3点未満のため何もしません。") (progn (setq uniq (_dedup-points-xy pts tol)) (if (< (length uniq) 3) (prompt "\n[3DPL2TIN] 重複統合後に点が3点未満のため何もしません。") (progn (prompt (strcat "\n[3DPL2TIN] 点数: " (itoa (length pts)) " / 重複統合後: " (itoa (length uniq)) )) (setq tris (_triangulate-delaunay uniq)) (prompt (strcat "\n[3DPL2TIN] 三角形数: " (itoa (length tris)) )) (_build-tin-3dfaces uniq tris lname) (prompt "\n[3DPL2TIN] 完了。") ) ) ) ) (command "_.UNDO" "_END") (princ) ) ) ) (princ "\n3DPL2TIN loaded. Command: 3DPL2TIN") (princ)
使い方
- 上のコードを
3DPL2TIN.lspとして保存- AutoCADで
APPLOAD→ 読み込み- コマンドラインで
3DPL2TIN- 複数の 3Dポリラインを選択 →
-TINレイヤに 3DFACE が生成されます
仕様に対する実装メモ(重要)
- WCS処理:頂点取得は
vlax-curve-getPointAtParamを使い、WCS座標を取得する前提です。- 重複統合:XY を tol=0.001 で丸めて統合し、Z は 平均しています(同一点のZが微妙に違うケースの安定性優先)。
- 「平均じゃなく最初のZを採用」等にしたければ
_dedup-points-xyを差し替え箇所にできます。- 2.5D:分割は XY、面の高さは各頂点の Z。
- 3点未満:何もしない。
- 元オブジェクト不変:作図のみ。
次にやるべき改良(この構造なら差し替えやすい)
優先度に挙げると:
- 境界(外周)を推定して外側三角形を除去(TINの“外”を削る)
- 最小角しきい値や最大辺長でスリバー除去 → リトライ/ローカルフリップ
- **ブレークライン(尾根線・谷線)**対応(制約付きDelaunay)
必要なら、次の返信でこちらをやります(確認なしで進めます):
- 「3Dポリライン群が地形の外周も含む」前提で、外周を抽出→外側三角形を削除して「穴あき外周」も扱える版に拡張(針状三角形の主因を潰す)。
返答を見てみると、
処理の手順などは理解してくれているようですね。
三角形分割のアルゴリズムもDelaunay三角形分割が妥当と評価しています。
コードは、チョッと問題ありですが……
エラーの修正
とりあえず、コードを試してみます。
すると、以下のエラーが出ました。

変数名に、”T” を使っちゃってますね。
多分、Triangle の ”T” なんでしょうけど、AutoLISP では ”T” は予約語なので、使えません。
というわけで、エラー画面のスクリーンショットと共に修正するように ChatGPT に投げます。
エラーが出ました。
変数名に “t” “T” や他の予約語を使わないで下さい。
それに対する ChatGPT の返答
了解。AutoLISP はシンボルが基本的に大文字小文字を区別しないので、
tは 定数T(真)と衝突して壊れます。
あなたの指摘どおり、t/Tっぽい名前は一切使わないように修正します。下が **修正版(置き換え用)**です。ここでは
tを全部排除して、ループ変数もtri/badTri/edgeRecなどに変更しています。
修正版コード(t/T を不使用)
そのまま丸ごと差し替えてください。
;;; ------------------------------------------------------------ ;;; 3DPL2TIN : 3D Polyline vertices -> 2D Delaunay -> 3DFACE TIN ;;; Layer: "-TIN" ;;; Dedup tolerance: 0.001 ;;; WCS processing ;;; ------------------------------------------------------------ (vl-load-com) ;; ---------------------------- ;; Utilities ;; ---------------------------- (defun _abs (x) (if (< x 0.0) (- x) x)) (defun _x (p) (car p)) (defun _y (p) (cadr p)) (defun _z (p) (caddr p)) (defun _area2 (a b c) ;; 2D signed double-area (- (* (- (_x b) (_x a)) (- (_y c) (_y a))) (* (- (_y b) (_y a)) (- (_x c) (_x a)))) ) (defun _ensure-layer (layerName) (if (not (tblsearch "LAYER" layerName)) (entmakex (list (cons 0 "LAYER") (cons 2 layerName) (cons 70 0) (cons 62 7) ; color 7 ) ) ) layerName ) (defun _make-3dface (p1 p2 p3 layerName) (entmakex (list (cons 0 "3DFACE") (cons 8 layerName) (cons 10 p1) (cons 11 p2) (cons 12 p3) (cons 13 p3) ) ) ) ;; ---------------------------- ;; 1) Select + extract vertices from 3D polylines ;; ---------------------------- (defun _is-3dpolyline (ent / ed flag70) (setq ed (entget ent)) (and (= (cdr (assoc 0 ed)) "POLYLINE") (setq flag70 (cdr (assoc 70 ed))) (/= 0 (logand flag70 8))) ) (defun _collect-3dpoly-vertices (ent / obj endParam param pt outPts) (setq outPts '()) (setq obj (vlax-ename->vla-object ent)) (if (vlax-method-applicable-p obj 'GetEndParam) (progn (setq endParam (vlax-curve-getEndParam ent)) (setq param 0.0) (while (<= param endParam) (setq pt (vlax-curve-getPointAtParam ent param)) ; WCS (setq outPts (cons (list (car pt) (cadr pt) (caddr pt)) outPts)) (setq param (+ param 1.0)) ) (reverse outPts) ) outPts ) ) (defun _extract-points-from-selection (ss / idx n ent pts) (setq pts '()) (if ss (progn (setq n (sslength ss)) (setq idx 0) (while (< idx n) (setq ent (ssname ss idx)) (if (_is-3dpolyline ent) (setq pts (append pts (_collect-3dpoly-vertices ent))) ) (setq idx (1+ idx)) ) ) ) pts ) ;; ---------------------------- ;; 2) Dedup points (tolerance by XY bucket), keep avg Z ;; ---------------------------- (defun _qkey (x tol) (fix (+ 0.5 (/ x tol))) ) (defun _dedup-points-xy (pts tol / al out p kx ky key rec cnt sx sy sz) (setq al '()) (foreach p pts (setq kx (_qkey (_x p) tol)) (setq ky (_qkey (_y p) tol)) (setq key (cons kx ky)) (setq rec (assoc key al)) (if rec (progn (setq cnt (nth 0 (cdr rec)) sx (nth 1 (cdr rec)) sy (nth 2 (cdr rec)) sz (nth 3 (cdr rec))) (setq cnt (1+ cnt) sx (+ sx (_x p)) sy (+ sy (_y p)) sz (+ sz (_z p))) (setq al (subst (cons key (list cnt sx sy sz)) rec al)) ) (setq al (cons (cons key (list 1 (_x p) (_y p) (_z p))) al)) ) ) (setq out '()) (foreach rec al (setq cnt (nth 0 (cdr rec)) sx (nth 1 (cdr rec)) sy (nth 2 (cdr rec)) sz (nth 3 (cdr rec))) (setq out (cons (list (/ sx cnt) (/ sy cnt) (/ sz cnt)) out)) ) out ) ;; ---------------------------- ;; 3) Delaunay triangulation (Bowyer-Watson) in XY ;; ---------------------------- (defun _bbox (pts / p xmin xmax ymin ymax) (setq xmin 1e99 xmax -1e99 ymin 1e99 ymax -1e99) (foreach p pts (if (< (_x p) xmin) (setq xmin (_x p))) (if (> (_x p) xmax) (setq xmax (_x p))) (if (< (_y p) ymin) (setq ymin (_y p))) (if (> (_y p) ymax) (setq ymax (_y p))) ) (list xmin ymin xmax ymax) ) (defun _supertriangle (pts / bb xmin ymin xmax ymax dx dy d cx cy p1 p2 p3) (setq bb (_bbox pts)) (setq xmin (nth 0 bb) ymin (nth 1 bb) xmax (nth 2 bb) ymax (nth 3 bb)) (setq dx (- xmax xmin) dy (- ymax ymin)) (setq d (max dx dy)) (setq cx (/ (+ xmin xmax) 2.0) cy (/ (+ ymin ymax) 2.0)) (setq p1 (list (- cx (* 20.0 d)) (- cy (* 20.0 d)) 0.0)) (setq p2 (list (+ cx (* 20.0 d)) (- cy (* 20.0 d)) 0.0)) (setq p3 (list cx (+ cy (* 20.0 d)) 0.0)) (list p1 p2 p3) ) (defun _circumcircle (a b c / ax ay bx by cx cy denom ux uy r2) (setq ax (_x a) ay (_y a) bx (_x b) by (_y b) cx (_x c) cy (_y c)) (setq denom (* 2.0 (+ (* ax (- by cy)) (* bx (- cy ay)) (* cx (- ay by))))) (if (< (_abs denom) 1e-12) nil (progn (setq ux (/ (+ (* (+ (* ax ax) (* ay ay)) (- by cy)) (* (+ (* bx bx) (* by by)) (- cy ay)) (* (+ (* cx cx) (* cy cy)) (- ay by))) denom)) (setq uy (/ (+ (* (+ (* ax ax) (* ay ay)) (- cx bx)) (* (+ (* bx bx) (* by by)) (- ax cx)) (* (+ (* cx cx) (* cy cy)) (- bx ax))) denom)) (setq r2 (+ (expt (- ux ax) 2.0) (expt (- uy ay) 2.0))) (list ux uy r2) ) ) ) (defun _in-circumcircle (p a b c / cc ux uy r2 d2) (setq cc (_circumcircle a b c)) (if (null cc) nil (progn (setq ux (nth 0 cc) uy (nth 1 cc) r2 (nth 2 cc)) (setq d2 (+ (expt (- (_x p) ux) 2.0) (expt (- (_y p) uy) 2.0))) (<= d2 (+ r2 1e-10)) ) ) ) (defun _edge (i j) (if (< i j) (list i j) (list j i)) ) (defun _edge-count-add (edgeCounts oneEdge / foundRec) (if (setq foundRec (assoc oneEdge edgeCounts)) (subst (cons oneEdge (1+ (cdr foundRec))) foundRec edgeCounts) (cons (cons oneEdge 1) edgeCounts) ) ) (defun _triangulate-delaunay (pts / st all n0 n1 n2 tris idx p badTris edgeCounts tri a b c e1 e2 e3 polyEdges newTris outTris edgeRec) (setq st (_supertriangle pts)) (setq all (append pts st)) (setq n0 (length pts)) (setq n1 (1+ n0)) (setq n2 (+ n0 2)) (setq tris (list (list n0 n1 n2))) (setq idx 0) (while (< idx (length pts)) (setq p (nth idx all)) ;; bad triangles (setq badTris '()) (foreach tri tris (setq a (nth (nth 0 tri) all)) (setq b (nth (nth 1 tri) all)) (setq c (nth (nth 2 tri) all)) (if (_in-circumcircle p a b c) (setq badTris (cons tri badTris)) ) ) ;; boundary edges (setq edgeCounts '()) (foreach tri badTris (setq e1 (_edge (nth 0 tri) (nth 1 tri))) (setq e2 (_edge (nth 1 tri) (nth 2 tri))) (setq e3 (_edge (nth 2 tri) (nth 0 tri))) (setq edgeCounts (_edge-count-add edgeCounts e1)) (setq edgeCounts (_edge-count-add edgeCounts e2)) (setq edgeCounts (_edge-count-add edgeCounts e3)) ) (setq polyEdges '()) (foreach edgeRec edgeCounts (if (= (cdr edgeRec) 1) (setq polyEdges (cons (car edgeRec) polyEdges)) ) ) ;; remove bad triangles (foreach tri badTris (setq tris (vl-remove tri tris)) ) ;; fill cavity (setq newTris '()) (foreach oneEdge polyEdges (setq newTris (cons (list (nth 0 oneEdge) (nth 1 oneEdge) idx) newTris)) ) (setq tris (append tris newTris)) (setq idx (1+ idx)) ) ;; remove triangles touching supertriangle vertices (setq outTris '()) (foreach tri tris (if (and (< (nth 0 tri) n0) (< (nth 1 tri) n0) (< (nth 2 tri) n0)) (setq outTris (cons tri outTris)) ) ) outTris ) ;; ---------------------------- ;; 4) Build 3DFACE TIN on layer ;; ---------------------------- (defun _build-tin-3dfaces (pts tris layerName / tri i j k p1 p2 p3) (foreach tri tris (setq i (nth 0 tri) j (nth 1 tri) k (nth 2 tri)) (setq p1 (nth i pts) p2 (nth j pts) p3 (nth k pts)) (if (> (_abs (_area2 p1 p2 p3)) 1e-12) (_make-3dface p1 p2 p3 layerName) ) ) (princ) ) ;; ---------------------------- ;; Command ;; ---------------------------- (defun c:3DPL2TIN (/ ss pts tol uniq tris layerName) (setq tol 0.001) (setq layerName "-TIN") (_ensure-layer layerName) (prompt "\n[3DPL2TIN] 3Dポリラインを選択してください: ") (setq ss (ssget '((0 . "POLYLINE")))) (if (not ss) (progn (prompt "\n[3DPL2TIN] 選択なし。終了。") (princ)) (progn (command "_.UNDO" "_BEGIN") (setq pts (_extract-points-from-selection ss)) (if (< (length pts) 3) (prompt "\n[3DPL2TIN] 点が3点未満のため何もしません。") (progn (setq uniq (_dedup-points-xy pts tol)) (if (< (length uniq) 3) (prompt "\n[3DPL2TIN] 重複統合後に点が3点未満のため何もしません。") (progn (prompt (strcat "\n[3DPL2TIN] 点数: " (itoa (length pts)) " / 重複統合後: " (itoa (length uniq)))) (setq tris (_triangulate-delaunay uniq)) (prompt (strcat "\n[3DPL2TIN] 三角形数: " (itoa (length tris)))) (_build-tin-3dfaces uniq tris layerName) (prompt "\n[3DPL2TIN] 完了。") ) ) ) ) (command "_.UNDO" "_END") (princ) ) ) ) (princ "\n3DPL2TIN loaded. Command: 3DPL2TIN") (princ)
追加で注意(ここも地雷)
foreach t ...も全部アウトです(今回の修正で全撤去済み)。- もし他のファイル断片を混ぜているなら、変数名に
t/T/nil/null/pi/eあたりを使ってないかを機械的に検索してください。
次。あなたの環境で次に出そうなエラーは「3Dポリライン頂点取得(param走査)が頂点数と一致しない」系です。
もし次のエラーが出たら、対象の3Dポリラインが “POLYLINE(3D)” なのか “POLYLINE(2D) / LWPOLYLINE / SPLINE” が混ざってるのかが原因なので、そこで分岐を入れて潰します。
”T” に関するエラーは修正してくれたようです。
次にエラーが出そうな個所も予想してくれてます。
実際、コードを走らせると ChatGPT の予想通りにエラーが出ます。
うーむ。
有能なんだか、無能なんだか……
エラーが出た旨を、ChatGPT にフィードバックして検証してもらいます。
エラーです。
3点以上の頂点を持つ3Dポリラインを選択しても、TINが生成されません。
頂点取得の処理を検証してください。
### ログ
[3DPL2TIN] 点が3点未満のため何もしません。_.UNDO 現在の設定: 自動 = オン, コントロール = すべて, 合成 = はい, 画層 = はい
そして、ChatGPT の返答がこちら
原因はほぼ確定です。
前回の頂点取得がvlax-curve-getEndParam / getPointAtParam前提になっていて、**古典的な 3D POLYLINE(AcDb3dPolyline / POLYLINE+VERTEX+SEQEND 構造)**では期待通りに動かないケースが普通にあります。結果として **頂点リストが空→「3点未満」**になっています。なので、頂点取得を 確実に動く「VERTEX を entnext で走査」方式に差し替えます。
修正:頂点取得を VERTEX 走査に置換
あなたの LSP の中で、次の2つの関数を 丸ごと置き換えてください。
1) _collect-3dpoly-vertices(差し替え)
(defun _collect-3dpoly-vertices (polyEnt / e ed v outPts) ;; 古典 POLYLINE の VERTEX を SEQEND まで走査して (x y z) を回収(WCS) (setq outPts '()) (setq e (entnext polyEnt)) (while e (setq ed (entget e)) (cond ((= (cdr (assoc 0 ed)) "VERTEX") (setq v (cdr (assoc 10 ed))) ;; v は (x y z) で入ってくる (if (and v (= (length v) 3)) (setq outPts (cons (list (car v) (cadr v) (caddr v)) outPts)) ) ) ((= (cdr (assoc 0 ed)) "SEQEND") (setq e nil) ; 終了 ) ) (if e (setq e (entnext e))) ) (reverse outPts) )2) _extract-points-from-selection(検証ログ付きに置換)
(defun _extract-points-from-selection (ss / idx n ent pts vtx okCnt vtxCnt) (setq pts '()) (setq okCnt 0) (setq vtxCnt 0) (if ss (progn (setq n (sslength ss)) (setq idx 0) (while (< idx n) (setq ent (ssname ss idx)) (if (_is-3dpolyline ent) (progn (setq okCnt (1+ okCnt)) (setq vtx (_collect-3dpoly-vertices ent)) (setq vtxCnt (+ vtxCnt (length vtx))) (setq pts (append pts vtx)) ) ) (setq idx (1+ idx)) ) (prompt (strcat "\n[3DPL2TIN][DBG] 選択数=" (itoa n) " / 3D判定OK=" (itoa okCnt) " / 取得頂点総数=" (itoa vtxCnt) ) ) ) ) pts )
これで何が確認できるか
実行するとログにこう出ます:
選択数=:ssget で拾った数3D判定OK=:_is-3dpolylineを通過した数(= 70フラグbit8が立ってる数)取得頂点総数=:実際に拾えた VERTEX の合計ここで、
- 3D判定OK=0 → そもそも選んでるのが「3Dポリラインではない」(2D POLYLINE / LWPOLYLINE 等)
- 3D判定OK>0 なのに 取得頂点総数=0 → そのエンティティが想定外(メッシュ等)か、壊れたデータ
まで切り分けできます。
追加で1点だけ(超重要)
ssgetのフィルタを((0 . "POLYLINE"))にしてるので、2Dポリラインも混ざります。
でも今回のデバッグログで 3D判定OK が見えるので、まずはこれで現状を確定させてください。
次の返信で、あなたの実行ログ(DBG行が出るはず)を貼って。
それだけで「3D判定が落ちてるのか/VERTEXが取れてないのか」を即断して、次の修正を出します。
頂点の情報を取得する手段を変更してきました。
VERTEX のエンティティを順次取得していく方式ですね。
これなら問題ないと思います。
では、実際に ChatGPT が修正してきた関数を差し替えて、テストしてみます。
テスト用の3Dポリラインです。

そして、テストの結果

おお、TIN が生成されています。
ChatGPT に報告です。
TIN が生成されました。
### ログ
[3DPL2TIN][DBG] 選択数=10 / 3D判定OK=10 / 取得頂点総数=159
[3DPL2TIN] 点数: 159 / 重複統合後: 159
[3DPL2TIN] 三角形数: 289
[3DPL2TIN] 完了。
ChatGPT の返答
OK。原因は頂点取得方式のミスマッチで確定、VERTEX走査に切り替えたのが正解。ログも整合しています:
- 10本全部が3D判定OK
- 取得頂点159点(=抽出成功)
- 重複統合後も159(このデータは tol=0.001 では重複なし)
- 三角形289(N=159のDelaunayとして妥当レンジ)
この状態で次にやるべきは、「将来の高速化・仕様変更に備えて」コードを固めること。今のままだと、データ次第で確実に地雷を踏む。
今すぐ入れるべき堅牢化(優先順)
1) _is-3dpolyline をより確実にする(フラグだけだと誤判定あり)
3D POLYLINEでもデータによってはフラグが怪しいことがある。
VERTEXが1つでも取れたらOKに寄せる方が壊れにくい。→
_extract-points-from-selection内で_is-3dpolylineを通さず、まず頂点を取って(>= (length vtx) 2)で採用、みたいにするのが実戦向き。2) 3DFACE生成の「法線向き」を統一
現状は三角形の頂点順がバラつくので、表示(シェーディング)や後工程で問題になる。
→ XYで反時計回りに統一する。(欲しければそのパッチ出す)
3) 外周の「外側三角形」を消す(現状は全点の凸包まで埋まる)
今回のTINは、点群全体の凸包まで三角形が張られる。
地形の外形が凹んでいると、凹部に“架橋”する細長い三角形が出る(あなたが前に問題視してたやつ)。→ 次の拡張はこれ一択:
- 外周境界(点群またはポリライン群の外形)を作る
- その内側だけを残す(point-in-polygon / triangle centroid test)
あなたに確認なしで次に進める提案(どれから潰すか)
あなたの目的が「地形TIN」なら、次の順が最短。
- 三角形の向き統一(即効・副作用なし)
- 凸包/凹形境界で外側削除(針状三角形の主因を除去)
- (必要なら)最小角しきい値でスリバー除去
必要なら、次の返信で「三角形の頂点順をCCW統一するパッチ」+「外側削除の基本実装(凸包版→凹形境界版へ拡張可能な構造)」をそのまま貼る。
どっちから行く?(結論だけでいい:向きor外側削除)
この先どうするか、色々提案してくれてますね。
「あなたに確認なしで」と言っているのが、少し気になりますが。
確認はして欲しいなぁ……
TIN の三角形の向きを統一してもらう
ChatGPT が、TINの三角家の向きを統一すること提案しています。
先ほど、テストで生成したTIN の三角形の法線の向きがどうなっているのか確認してみます。
以前、作った自作コマンドで三角形の法線が上向きか下向きかを調べます。
結果は、下図の通り。

3D面の色が
青:法線が上向き(CCW)
赤:法線が下向き(CW)
になってます。
この状態は、さすがに気持ち悪いので 三角形の向きを CCW に統一してもらいましょう。
とりあえず、プロンプトを投げます。
三角形の頂点順をCCWに統一するパッチを書いて下さい。
ChatGPT の返答がこちら
了解。3DFACE を作る直前で、XY 平面で CCW(反時計回り)になるように頂点順を揃えるパッチを入れればいい。
パッチ内容
1) CCWに並べ替える関数を追加
_area2(2D符号付き面積)が 正ならCCW、負ならCWなので、負ならp2とp3を入れ替えます。(defun _ccw-tri (p1 p2 p3) ;; XYで反時計回り(CCW)に統一して返す (if (< (_area2 p1 p2 p3) 0.0) (list p1 p3 p2) ; CWなら入替 (list p1 p2 p3) ; 既にCCW ) )2) _build-tin-3dfaces を差し替え
3DFACE作成前に
_ccw-triを通します。(defun _build-tin-3dfaces (pts tris layerName / tri i j k p1 p2 p3 triPts) (foreach tri tris (setq i (nth 0 tri) j (nth 1 tri) k (nth 2 tri)) (setq p1 (nth i pts) p2 (nth j pts) p3 (nth k pts)) ;; degenerate skip (if (> (_abs (_area2 p1 p2 p3)) 1e-12) (progn (setq triPts (_ccw-tri p1 p2 p3)) (_make-3dface (nth 0 triPts) (nth 1 triPts) (nth 2 triPts) layerName) ) ) ) (princ) )
注意点(重要)
- これは XYでCCW統一です(= 2.5D 前提に一致)。
- Z がどうであれ、XY 投影での向きを揃えるので、TIN用途ではこれが正解。
- もし「画面上で裏表が逆に見える」等がある場合は、CCW/CWの基準があなたの後工程と逆なので、
(< 0.0)の判定を> 0.0に反転すれば一発で揃います。これを入れた状態で、
SHADEMODEを Gouraud とかにしてハイライトが揃うか確認して。
指定されたとおりにコードを修正して、テストしてみます。
結果

OKです!
TIN の三角形が CCW に統一されていますね。
まとめ
今回は、
「3Dポリラインの頂点から3D面で構成されたTINを生成する」
というお題で、ChatGPT を 用いて AutoLISP のオリジナルコマンドを作成してみました。
とりあえずのところ、
複数の3Dポリラインの頂点から TIN を生成するところまではできました。
が、まだ必要最低限のプロトタイプといったところですね。
とは言え、ChatGPT 頑張ってくれますね。
期待以上です。
この後は、
・三角形分割の高速化
・3Dポリラインをブレイクラインとして三角形分割
・外縁部にできてしまう針状三角形の生成抑制
など、色々と手を入れていきたいと思います。

コメント