[AutoLISP X ChatGPT] 3Dポリラインの頂点から3D面で構成されたTINを生成する その1

AutoLisp

こんにちは、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)

使い方

  1. 上のコードを 3DPL2TIN.lsp として保存
  2. AutoCADで APPLOAD → 読み込み
  3. コマンドラインで 3DPL2TIN
  4. 複数の 3Dポリラインを選択 → -TIN レイヤに 3DFACE が生成されます

仕様に対する実装メモ(重要)

  • WCS処理:頂点取得は vlax-curve-getPointAtParam を使い、WCS座標を取得する前提です。
  • 重複統合:XY を tol=0.001 で丸めて統合し、Z は 平均しています(同一点のZが微妙に違うケースの安定性優先)。
    • 「平均じゃなく最初のZを採用」等にしたければ _dedup-points-xy を差し替え箇所にできます。
  • 2.5D:分割は XY、面の高さは各頂点の Z。
  • 3点未満:何もしない。
  • 元オブジェクト不変:作図のみ。

次にやるべき改良(この構造なら差し替えやすい)

優先度に挙げると:

  1. 境界(外周)を推定して外側三角形を除去(TINの“外”を削る)
  2. 最小角しきい値最大辺長でスリバー除去 → リトライ/ローカルフリップ
  3. **ブレークライン(尾根線・谷線)**対応(制約付き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」なら、次の順が最短。

  1. 三角形の向き統一(即効・副作用なし)
  2. 凸包/凹形境界で外側削除(針状三角形の主因を除去)
  3. (必要なら)最小角しきい値でスリバー除去

必要なら、次の返信で「三角形の頂点順をCCW統一するパッチ」+「外側削除の基本実装(凸包版→凹形境界版へ拡張可能な構造)」をそのまま貼る。
どっちから行く?(結論だけでいい:向き or 外側削除

この先どうするか、色々提案してくれてますね。
「あなたに確認なしで」と言っているのが、少し気になりますが。
確認はして欲しいなぁ……

TIN の三角形の向きを統一してもらう

ChatGPT が、TINの三角家の向きを統一すること提案しています。
先ほど、テストで生成したTIN の三角形の法線の向きがどうなっているのか確認してみます。

以前、作った自作コマンドで三角形法線が上向きか下向きかを調べます。
結果は、下図の通り。

3D面の色が
青:法線が上向き(CCW)
赤:法線が下向き(CW)
になってます。
この状態は、さすがに気持ち悪いので 三角形の向きを CCW に統一してもらいましょう。

とりあえず、プロンプトを投げます。

三角形の頂点順をCCWに統一するパッチを書いて下さい。


ChatGPT の返答がこちら

了解。3DFACE を作る直前で、XY 平面で CCW(反時計回り)になるように頂点順を揃えるパッチを入れればいい。

パッチ内容

1) CCWに並べ替える関数を追加

_area2(2D符号付き面積)が 正ならCCW、負ならCWなので、負なら p2p3 を入れ替えます。

(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ポリラインをブレイクラインとして三角形分割
・外縁部にできてしまう針状三角形の生成抑制
など、色々と手を入れていきたいと思います。

コメント

タイトルとURLをコピーしました