[AutoLISP x ChatGPT] 3Dポリラインの頂点座標をエクセルの数値をもとに変更する

AutoLisp

こんにちは、ISDです。
前回は、3Dポリライン頂点座標Excel に出力するオリジナルコマンドを作成しました。


そしたら次は、やはり Excel から頂点座標を読み取って3Dポリライン頂点座標に反映したくなります。よね?

というわけで、次のお題はこれです。
「3Dポリラインの頂点座標をエクセルの数値をもとに変更する」



ChatGPTでオリジナルコマンドを作成する

では、ChatGPT にお願いしてみようと思います。

コマンドの概要は以下の通りとします。

「選択した1つの3Dポリラインについて、各頂点のXYZ座標を、Excelワークシートから読み取った値で置き換える。」

  • Excel が起動していなければ、コマンド終了。
  • 読み取る対象のシート名は”3DPL_頂点座標”。シートがなければコマンド終了。
  • 3Dポリライン頂点座標を順番に1行ずつ読み込む
  • 頂点X座標は Aの列、Y座標は Bの列、Z座標は Cの列

コマンドの概要の他に、
AutoLISPのコードを書く際にChatGPTがやらかしそうなことを予防するため、あらかじめクギを刺しておくことにします。
追加して、前回のコマンド作成で、vlax-get-property で エラーになっていた Cells は使わず、Range を使うように指示します。

では、ChatGPTプロンプトを投げます。

AutoCADで 以下の仕様のオリジナルコマンドを作成したい。
使用言語はAutolisp

### コマンドの概要
選択した1つの3Dポリラインについて、各頂点のXYZ座標を、Excelワークシートから読み取った値で置き換える。
・EXCEL が起動していなければ、コマンド終了。
・読み取る対象のシート名は”3DPL_頂点座標”。シートがなければコマンド終了。
・3Dポリラインの頂点の座標を順番に1行ずつ読み込む、ただし1行目はヘッダー行
・頂点のX座標は Aの列、Y座標は Bの列、Z座標は Cの列

### AutoLISP 禁則事項
以下は必ず守ること。
・予約定数に代入しない【厳禁】
t / T / nil / NIL / pi / PIなど
・組み込み関数名を変数名に使わない
if, list, car, cdr, min, max, command, entget, vlax-* など
・vlax-get-property で Cells(row,col) は使わない。Range(“A2”) の形に変換して書く
・Range / Worksheet / Workbook / Workbooks は必ず release
・Excel.Application の release は原則行わない(既存 Excel への影響を避けるため)

そして、ChatGPTの返答

以下は、指定仕様・禁則事項を守った 全体完成版コマンドです。
Excel が起動していなければ終了シートが無ければ終了1行目ヘッダRange(“A2”)方式Range等はreleaseExcel.Applicationは原則releaseしない


コマンド名:3DPLXLS2VTX

  • 3Dポリライン(AcDb3dPolyline)を1つ選択
  • Excel(起動済み必須)のシート "3DPL_頂点座標"
    • A列=X, B列=Y, C列=Z
    • 2行目から(1行目はヘッダ)
  • 頂点の座標を順番に置換(行数が足りない場合はそこで停止)
  • 値が数値として読めない場合は その頂点は変更しない(ログ出す)

(vl-load-com)

;; ----------------------------
;; logger
;; ----------------------------
(defun _3dplxls2vtx-log (msg)
  (princ (strcat "\n[3DPLXLS2VTX] " msg))
)

;; ----------------------------
;; 列番号→列名(1->A, 2->B, 27->AA ...)
;; ----------------------------
(defun _3dplxls2vtx-col->name (colNum / n r s)
  (setq n colNum)
  (setq s "")
  (while (> n 0)
    (setq r (rem (1- n) 26))
    (setq s (strcat (chr (+ 65 r)) s))
    (setq n (/ (- n r 1) 26))
  )
  s
)

;; ----------------------------
;; Excel セル読み取り(Range("A2"))
;; - 返り値: Value2 の生値(nil/数値/文字列など)
;; - Range は必ず release
;; ----------------------------
(defun _3dplxls2vtx-read-cell (ws rowNum colNum / addr rng raw)
  (setq addr (strcat (_3dplxls2vtx-col->name colNum) (itoa rowNum)))
  (setq rng (vlax-get-property ws 'Range addr))
  (setq raw (vlax-get-property rng 'Value2))
  (vlax-release-object rng)
  raw
)

;; ----------------------------
;; Excelの値→数値に寄せる(読めなければ nil)
;; - INT/REAL はそのまま
;; - STR は distof で解釈(失敗したら nil)
;; ----------------------------
(defun _3dplxls2vtx-read-num (ws rowNum colNum / v n)
  (setq v (_3dplxls2vtx-read-cell ws rowNum colNum))
  (cond
    ((null v) nil)
    ((= (type v) 'INT) (float v))
    ((= (type v) 'REAL) v)
    ((= (type v) 'STR)
      (setq n (distof v 2))
      (if n n nil)
    )
    (T nil)
  )
)

;; ----------------------------
;; Excel が起動している前提で取得(起動してなければ nil)
;; ----------------------------
(defun _3dplxls2vtx-get-running-excel ( / r app)
  (setq r (vl-catch-all-apply 'vlax-get-object (list "Excel.Application")))
  (if (vl-catch-all-error-p r)
    nil
    r
  )
)

;; ----------------------------
;; ブック中からシート名一致を取得(無ければ nil)
;; - Worksheets/Sheet は必ず release(戻りとして返すシート以外)
;; ----------------------------
(defun _3dplxls2vtx-get-sheet-by-name (wb targetName / wss cnt idx ws found)
  (setq wss (vlax-get-property wb 'Worksheets))
  (setq cnt (vlax-get-property wss 'Count))

  (setq found nil)
  (setq idx 1)
  (while (and (<= idx cnt) (null found))
    (setq ws (vlax-get-property wss 'Item idx))
    (if (= (vlax-get-property ws 'Name) targetName)
      (setq found ws)
      (vlax-release-object ws)
    )
    (setq idx (1+ idx))
  )

  (vlax-release-object wss)
  found
)

;; ----------------------------
;; 3Dポリライン(旧形式 POLYLINE/VERTEX/SEQEND)頂点エンティティを順に走査
;; - fn は (fn vertexEname index) の形で呼ぶ
;; ----------------------------
(defun _3dplxls2vtx-iter-3dpl-vertex-entities (plEnt fn / e ed etype idx)
  (setq idx 0)
  (setq e (entnext plEnt))
  (while e
    (setq ed (entget e))
    (setq etype (cdr (assoc 0 ed)))
    (cond
      ((= etype "VERTEX")
        (setq idx (1+ idx))
        (fn e idx)
        (setq e (entnext e))
      )
      ((= etype "SEQEND")
        (setq e nil)
      )
      (T
        (setq e (entnext e))
      )
    )
  )
  idx
)

;; ----------------------------
;; VERTEX の 10座標を書き換え
;; - 失敗したら nil
;; ----------------------------
(defun _3dplxls2vtx-set-vertex-xyz (vtxEnt x y z / ed newEd oldP)
  (setq ed (entget vtxEnt))
  (setq oldP (assoc 10 ed))
  (if (null oldP)
    nil
    (progn
      (setq newEd (subst (cons 10 (list x y z)) oldP ed))
      (entmod newEd)
      (entupd vtxEnt)
      T
    )
  )
)

(defun c:3DPLXLS2VTX
  ( /
    entPick entName vlaObj objName
    xlApp xlBook xlSheet sheetName
    vtxEnt vtxIdx rowNum
    xVal yVal zVal
    changedCount
  )

  ;; ============================
  ;; Main
  ;; ============================
  (_3dplxls2vtx-log "コマンド開始")

  ;; 1) Excel が起動しているか(起動してなければ終了)
  (setq xlApp (_3dplxls2vtx-get-running-excel))
  (if (null xlApp)
    (progn
      (_3dplxls2vtx-log "Excel が起動していません。コマンド終了。")
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log "Excel 起動中を確認")

  ;; 2) ActiveWorkbook 取得(無ければ終了)
  (setq xlBook (vlax-get-property xlApp 'ActiveWorkbook))
  (if (null xlBook)
    (progn
      (_3dplxls2vtx-log "ActiveWorkbook が取得できません。コマンド終了。")
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log "ActiveWorkbook を取得")

  ;; 3) 対象シート取得(無ければ終了)
  (setq sheetName "3DPL_頂点座標")
  (setq xlSheet (_3dplxls2vtx-get-sheet-by-name xlBook sheetName))
  (if (null xlSheet)
    (progn
      (_3dplxls2vtx-log (strcat "対象シートがありません: " sheetName " / コマンド終了。"))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log (strcat "対象シート取得: " sheetName))

  ;; 安定化:Activate → ActiveSheet を取り直す
  (vlax-invoke-method xlSheet 'Activate)
  (vlax-release-object xlSheet)
  (setq xlSheet (vlax-get-property xlApp 'ActiveSheet))

  ;; 4) 3Dポリライン選択
  (setq entPick (entsel "\n3Dポリラインを1つ選択: "))
  (if (null entPick)
    (progn
      (_3dplxls2vtx-log "選択がキャンセルされました。")
      (if xlSheet (vlax-release-object xlSheet))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (setq entName (car entPick))

  ;; 5) 3Dポリライン判定
  (setq vlaObj (vlax-ename->vla-object entName))
  (setq objName (vlax-get-property vlaObj 'ObjectName))
  (_3dplxls2vtx-log (strcat "Selected ObjectName = " objName))

  (if (not (wcmatch (strcase objName) "*3DPOLYLINE*"))
    (progn
      (_3dplxls2vtx-log "3Dポリラインではありません。コマンド終了。")
      (vlax-release-object vlaObj)
      (if xlSheet (vlax-release-object xlSheet))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (vlax-release-object vlaObj)

  ;; 6) 頂点を順に読み替え(行=idx+1、1行目はヘッダ)
  (_3dplxls2vtx-log "Excel 2行目から A/B/C を読み、頂点座標を置換します")
  (setq changedCount 0)

  (_3dplxls2vtx-iter-3dpl-vertex-entities
    entName
    (function
      (lambda (vertexEname index / r x y z ok)
        (setq r (+ index 1)) ;; 1行目ヘッダなので 2行目から
        ;; 行が空なら終了扱いにしたい場合:
        ;; 今回は「x,y,z のどれかが nil」ならその頂点はスキップ(ログ)にする
        (setq x (_3dplxls2vtx-read-num xlSheet r 1))
        (setq y (_3dplxls2vtx-read-num xlSheet r 2))
        (setq z (_3dplxls2vtx-read-num xlSheet r 3))

        (if (and x y z)
          (progn
            (setq ok (_3dplxls2vtx-set-vertex-xyz vertexEname x y z))
            (if ok
              (progn
                (setq changedCount (1+ changedCount))
                (if (= (rem index 20) 0)
                  (_3dplxls2vtx-log (strcat "  changed vertex " (itoa index) " ..."))
                )
              )
              (_3dplxls2vtx-log (strcat "  頂点更新失敗 index=" (itoa index)))
            )
          )
          (_3dplxls2vtx-log (strcat "  数値読取不可/空欄のためスキップ index=" (itoa index) " row=" (itoa r)))
        )
      )
    )
  )

  (_3dplxls2vtx-log (strcat "置換完了: changed vertices = " (itoa changedCount)))

  ;; 7) 後始末(Range/Worksheet/Workbook は release)
  (if xlSheet (vlax-release-object xlSheet))
  (if xlBook  (vlax-release-object xlBook))
  ;; Excel.Application は原則 release しない(既存Excelへの影響を避ける)

  (_3dplxls2vtx-log "コマンド終了")
  (princ)
)


実務メモ(仕様に合わせて調整しやすい点)

  • 行が足りない時の挙動
    • 現状:値が読めない行は「その頂点だけスキップ」して続行(ログ出す)
    • 「空行が出たらそこで終了」にしたいなら、x y z のどれかが nil のときに 以降を止めるようにもできます(仕様次第)
  • 3Dポリラインの形式
    • この実装は「旧形式(POLYLINE + VERTEX + SEQEND)」を想定(あなたの AcDb3dPolyline ログと整合)
    • もし “3D Polyline(軽量)/別形式” が混ざるなら分岐を追加します

必要なら次の一手として、
「Excelの行数と頂点数が一致しない場合はエラー終了(巻き戻し)」みたいな安全運転モードも入れられます。

何か、出してきました。

コマンドの実行とデバッグ

では、実際に試してみます。

適当な 3Dポリライン を用意し、前回の「3Dポリラインの頂点座標をエクセルに出力する」コマンドを使って、3Dポリライン頂点座標Excel に出力します。

テストとして、各頂点Z座標を適当に変更してみます。

これで、準備はOK。
では、ChatGPTが書いてくれたコマンド 「3DPLXLS2VTX」を起動します。

おっと、エラーが出ました。

そういえば、今回のプロンプトで、「要所にログを仕込む」という指示を忘れていましたが、ChatGPTが過去のやりとりを覚えていてくれたようで、気を利かせてログを仕込んでくれています。

ログChatGPTにフィードバックします。

エラーが出ました。

###
コマンド: 3DPLXLS2VTX
[3DPLVTX2XLS] コマンド開始
[3DPLVTX2XLS] Excel 起動中を確認
[3DPLVTX2XLS] ActiveWorkbook を取得
[3DPLVTX2XLS] 対象シート取得: 3DPL_頂点座標 3Dポリラインを1つ選択:
[3DPLVTX2XLS] Selected ObjectName = AcDb3dPolyline
[3DPLVTX2XLS] Excel 2行目から A/B/C を読み、頂点座標を置換します
; エラー: 関数が間違っています: (quote #)

原因はこれです:

; エラー: 関数が間違っています: (quote #<SUBR ... -lambda->)

つまり function / lambda を渡すやり方が、あなたの環境の AutoLISP では受け付けられていません(Visual LISP の関数オブジェクトとして評価されてしまい、呼び出し側で fn として実行できない)。

なので設計を変えます。

修正方針

  • 「コールバック関数(lambda)」を使わない
  • 3Dポリラインの VERTEX を while で直接走査
  • 各頂点に対して Excel の行(2行目~)を読み、entmod で座標差し替え

これなら 古い/厳しめの AutoLISP でも確実に動きます


修正版:全体完成版(lambda/function 不使用)

コマンド名は同じ 3DPLXLS2VTX です。

(vl-load-com)

;; ----------------------------
;; logger
;; ----------------------------
(defun _3dplxls2vtx-log (msg)
  (princ (strcat "\n[3DPLXLS2VTX] " msg))
)

;; ----------------------------
;; 列番号→列名(1->A, 2->B, 27->AA ...)
;; ----------------------------
(defun _3dplxls2vtx-col->name (colNum / n r s)
  (setq n colNum)
  (setq s "")
  (while (> n 0)
    (setq r (rem (1- n) 26))
    (setq s (strcat (chr (+ 65 r)) s))
    (setq n (/ (- n r 1) 26))
  )
  s
)

;; ----------------------------
;; Excel セル読み取り(Range("A2"))
;; ----------------------------
(defun _3dplxls2vtx-read-cell (ws rowNum colNum / addr rng raw)
  (setq addr (strcat (_3dplxls2vtx-col->name colNum) (itoa rowNum)))
  (setq rng (vlax-get-property ws 'Range addr))
  (setq raw (vlax-get-property rng 'Value2))
  (vlax-release-object rng)
  raw
)

;; ----------------------------
;; Excel値→数値(読めなければ nil)
;; ----------------------------
(defun _3dplxls2vtx-read-num (ws rowNum colNum / v n)
  (setq v (_3dplxls2vtx-read-cell ws rowNum colNum))
  (cond
    ((null v) nil)
    ((= (type v) 'INT) (float v))
    ((= (type v) 'REAL) v)
    ((= (type v) 'STR)
      (setq n (distof v 2))
      (if n n nil)
    )
    (T nil)
  )
)

;; ----------------------------
;; 起動中の Excel を取得(起動してなければ nil)
;; ----------------------------
(defun _3dplxls2vtx-get-running-excel ( / r)
  (setq r (vl-catch-all-apply 'vlax-get-object (list "Excel.Application")))
  (if (vl-catch-all-error-p r) nil r)
)

;; ----------------------------
;; ブック中からシート名一致を取得(無ければ nil)
;; ----------------------------
(defun _3dplxls2vtx-get-sheet-by-name (wb targetName / wss cnt idx ws found)
  (setq wss (vlax-get-property wb 'Worksheets))
  (setq cnt (vlax-get-property wss 'Count))

  (setq found nil)
  (setq idx 1)
  (while (and (<= idx cnt) (null found))
    (setq ws (vlax-get-property wss 'Item idx))
    (if (= (vlax-get-property ws 'Name) targetName)
      (setq found ws)
      (vlax-release-object ws)
    )
    (setq idx (1+ idx))
  )

  (vlax-release-object wss)
  found
)

;; ----------------------------
;; VERTEX の 10座標を書き換え
;; ----------------------------
(defun _3dplxls2vtx-set-vertex-xyz (vtxEnt x y z / ed oldP newEd)
  (setq ed (entget vtxEnt))
  (setq oldP (assoc 10 ed))
  (if (null oldP)
    nil
    (progn
      (setq newEd (subst (cons 10 (list x y z)) oldP ed))
      (entmod newEd)
      (entupd vtxEnt)
      T
    )
  )
)

(defun c:3DPLXLS2VTX
  ( /
    entPick entName vlaObj objName
    xlApp xlBook xlSheet sheetName
    e ed etype vtxIdx rowNum
    xVal yVal zVal ok changedCount
  )

  ;; ============================
  ;; Main
  ;; ============================
  (_3dplxls2vtx-log "コマンド開始")

  ;; Excel 起動確認
  (setq xlApp (_3dplxls2vtx-get-running-excel))
  (if (null xlApp)
    (progn
      (_3dplxls2vtx-log "Excel が起動していません。コマンド終了。")
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log "Excel 起動中を確認")

  ;; ActiveWorkbook
  (setq xlBook (vlax-get-property xlApp 'ActiveWorkbook))
  (if (null xlBook)
    (progn
      (_3dplxls2vtx-log "ActiveWorkbook が取得できません。コマンド終了。")
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log "ActiveWorkbook を取得")

  ;; Sheet
  (setq sheetName "3DPL_頂点座標")
  (setq xlSheet (_3dplxls2vtx-get-sheet-by-name xlBook sheetName))
  (if (null xlSheet)
    (progn
      (_3dplxls2vtx-log (strcat "対象シートがありません: " sheetName " / コマンド終了。"))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log (strcat "対象シート取得: " sheetName))

  ;; 安定化:Activate → ActiveSheet 再取得
  (vlax-invoke-method xlSheet 'Activate)
  (vlax-release-object xlSheet)
  (setq xlSheet (vlax-get-property xlApp 'ActiveSheet))

  ;; 3Dポリライン選択
  (setq entPick (entsel "\n3Dポリラインを1つ選択: "))
  (if (null entPick)
    (progn
      (_3dplxls2vtx-log "選択がキャンセルされました。")
      (if xlSheet (vlax-release-object xlSheet))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (setq entName (car entPick))

  ;; 判定
  (setq vlaObj (vlax-ename->vla-object entName))
  (setq objName (vlax-get-property vlaObj 'ObjectName))
  (_3dplxls2vtx-log (strcat "Selected ObjectName = " objName))
  (vlax-release-object vlaObj)

  (if (not (wcmatch (strcase objName) "*3DPOLYLINE*"))
    (progn
      (_3dplxls2vtx-log "3Dポリラインではありません。コマンド終了。")
      (if xlSheet (vlax-release-object xlSheet))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )

  ;; 頂点走査しながら Excel を読む(1行目ヘッダ→2行目から)
  (_3dplxls2vtx-log "Excel 2行目から A/B/C を読み、頂点座標を置換します")
  (setq changedCount 0)
  (setq vtxIdx 0)

  (setq e (entnext entName))
  (while e
    (setq ed (entget e))
    (setq etype (cdr (assoc 0 ed)))
    (cond
      ((= etype "VERTEX")
        (setq vtxIdx (1+ vtxIdx))
        (setq rowNum (+ vtxIdx 1)) ;; 2行目から

        (setq xVal (_3dplxls2vtx-read-num xlSheet rowNum 1))
        (setq yVal (_3dplxls2vtx-read-num xlSheet rowNum 2))
        (setq zVal (_3dplxls2vtx-read-num xlSheet rowNum 3))

        (if (and xVal yVal zVal)
          (progn
            (setq ok (_3dplxls2vtx-set-vertex-xyz e xVal yVal zVal))
            (if ok
              (progn
                (setq changedCount (1+ changedCount))
                (if (= (rem vtxIdx 20) 0)
                  (_3dplxls2vtx-log (strcat "  changed vertex " (itoa vtxIdx) " ..."))
                )
              )
              (_3dplxls2vtx-log (strcat "  頂点更新失敗 index=" (itoa vtxIdx)))
            )
          )
          (_3dplxls2vtx-log (strcat "  数値読取不可/空欄のためスキップ index=" (itoa vtxIdx) " row=" (itoa rowNum)))
        )
      )
      ((= etype "SEQEND")
        (setq e nil)
      )
    )

    (if e (setq e (entnext e)))
  )

  (_3dplxls2vtx-log (strcat "置換完了: changed vertices = " (itoa changedCount)))

  ;; 後始末(規約通り)
  (if xlSheet (vlax-release-object xlSheet))
  (if xlBook  (vlax-release-object xlBook))
  ;; Excel.Application は原則 release しない

  (_3dplxls2vtx-log "コマンド終了")
  (princ)
)

この修正で直る点

  • lambda / function を一切使わないので、あなたの環境でも「関数が間違っています」系が出ません。
  • 走査も Excel 読みも while 直書きなので、デバッグがさらに楽です。

次に決めたい仕様(確認いらないならこちらでデフォルト採用します)

  • Excel 側の行が途中で空欄になった場合
    • 現状:その頂点は スキップして続行
    • 代案:空欄が出たら そこで処理終了(以降は変更しない)

どっちが運用に合います?

修正コードを出してきたので、試してみます。

処理は進みましたが、Excel から値が読み取れていないですね。
さらに、フィードバックします。

Excel から数値が読み取れていません

###
コマンド: 3DPLXLS2VTX
[3DPLVTX2XLS] コマンド開始
[3DPLVTX2XLS] Excel 起動中を確認
[3DPLVTX2XLS] ActiveWorkbook を取得
[3DPLVTX2XLS] 対象シート取得: 3DPL_頂点座標 3Dポリラインを1つ選択:
[3DPLVTX2XLS] Selected ObjectName = AcDb3dPolyline
[3DPLVTX2XLS] Excel 2行目から A/B/C を読み、頂点座標を置換します
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=1 row=2
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=2 row=3
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=3 row=4
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=4 row=5
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=5 row=6
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=6 row=7
[3DPLVTX2XLS] 数値読取不可/空欄のためスキップ index=7 row=8
[3DPLVTX2XLS] 置換完了: changed vertices = 0
[3DPLVTX2XLS] コマンド終了

ログを見る限り、Excel 側のセルは空ではないのに、AutoLISP 側で “nil 扱い” になっているパターンです。
この症状の第一容疑はこれです:

原因(ほぼこれ)

(vlax-get-property rng 'Value2) の戻り値が VARIANT になっていて、
そのまま type 判定すると INT/REAL/STR に当たらず、結果的に nil扱いになっています。

あなたが以前の書き込み側で vlax-make-variant を使っていた流れとも整合します。
読む側も vlax-variant-value で剥がす必要があります。


修正:Excelセル読み取りを「VARIANT対応」にする

下の2点だけ差し替えればOKです。


1) _3dplxls2vtx-read-cell を差し替え(VARIANTを剥がす)

(defun _3dplxls2vtx-read-cell (ws rowNum colNum / addr rng raw val)
  (setq addr (strcat (_3dplxls2vtx-col->name colNum) (itoa rowNum)))
  (setq rng (vlax-get-property ws 'Range addr))
  (setq raw (vlax-get-property rng 'Value2))
  (vlax-release-object rng)

  ;; ★重要:VARIANTなら中身を取り出す
  (cond
    ((null raw) nil)
    ((= (type raw) 'VARIANT) (vlax-variant-value raw))
    (T raw)
  )
)

2) _3dplxls2vtx-read-num はそのままでOK(念のため STR→atof も追加推奨)

Excel が「文字列の数値」を返す場合に強くするなら、STR分岐をこうしておくと安定します(カンマ対応):

(defun _3dplxls2vtx-read-num (ws rowNum colNum / v s n)
  (setq v (_3dplxls2vtx-read-cell ws rowNum colNum))
  (cond
    ((null v) nil)
    ((= (type v) 'INT) (float v))
    ((= (type v) 'REAL) v)
    ((= (type v) 'STR)
      ;; "1,234.56" 対応
      (setq s (vl-string-subst "" "," v))
      (setq n (distof s 2))
      (if n n (atof s))
    )
    (T nil)
  )
)

追加で効く「切り分けログ」(1回だけ出す)

もしまだ読めない場合に備えて、最初の頂点だけ raw の型と値をログ出しするのが早いです。
頂点ループの最初(index=1 のとき)にこれを入れてください:

(if (= vtxIdx 1)
  (_3dplxls2vtx-log
    (strcat "DEBUG A2 type="
            (vl-princ-to-string (type (_3dplxls2vtx-read-cell xlSheet 2 1)))
            " val="
            (vl-princ-to-string (_3dplxls2vtx-read-cell xlSheet 2 1))
    )
  )
)

もう1つの落とし穴(念のため)

今のコードは Activate → ActiveSheet 取り直し をしていますが、読み取りは ActiveSheet を取り直さなくてもOKです。
(シートを検索して取得した xlSheet をそのまま使う方が “別ブックに切り替わった”事故を減らせます)

もし不安なら、この2行は削除しても動きます:

(vlax-invoke-method xlSheet 'Activate)
(vlax-release-object xlSheet)
(setq xlSheet (vlax-get-property xlApp 'ActiveSheet))

代わりに Activate だけにして xlSheet は保持:

(vlax-invoke-method xlSheet 'Activate)
;; xlSheet はそのまま使う

(最後に xlSheet を release すればOK)


次の返答で欲しいもの(最短で直すため)

上の VARIANT対応版に差し替えしたあとに、まだスキップされるなら、

  • Excel の A2 の中身が「数値」か「文字列」か(見た目でOK)
  • そして上の DEBUG ログ1行の結果

これだけで原因を確実に潰せます。

修正箇所を出してくれましたが、経験上コピペでの修正はあまりよろしくないので、コードをまるまる出し直してもらいます。

上記の修正を施した、完全版のコードを書いてください。

(vl-load-com)

;; ----------------------------
;; logger
;; ----------------------------
(defun _3dplxls2vtx-log (msg)
  (princ (strcat "\n[3DPLXLS2VTX] " msg))
)

;; ----------------------------
;; 列番号→列名(1->A, 2->B, 27->AA ...)
;; ----------------------------
(defun _3dplxls2vtx-col->name (colNum / n r s)
  (setq n colNum)
  (setq s "")
  (while (> n 0)
    (setq r (rem (1- n) 26))
    (setq s (strcat (chr (+ 65 r)) s))
    (setq n (/ (- n r 1) 26))
  )
  s
)

;; ----------------------------
;; Excel セル読み取り(Range("A2"))
;; - Value2 の戻りが VARIANT の場合があるため剥がす
;; - Range は必ず release
;; ----------------------------
(defun _3dplxls2vtx-read-cell (ws rowNum colNum / addr rng raw val)
  (setq addr (strcat (_3dplxls2vtx-col->name colNum) (itoa rowNum)))
  (setq rng (vlax-get-property ws 'Range addr))
  (setq raw (vlax-get-property rng 'Value2))
  (vlax-release-object rng)

  (cond
    ((null raw) nil)
    ((= (type raw) 'VARIANT) (vlax-variant-value raw))
    (T raw)
  )
)

;; ----------------------------
;; Excel値→数値(読めなければ nil)
;; - STR は distof / atof で解釈(カンマ除去も対応)
;; ----------------------------
(defun _3dplxls2vtx-read-num (ws rowNum colNum / v s n)
  (setq v (_3dplxls2vtx-read-cell ws rowNum colNum))
  (cond
    ((null v) nil)
    ((= (type v) 'INT) (float v))
    ((= (type v) 'REAL) v)
    ((= (type v) 'STR)
      (setq s (vl-string-subst "" "," v))
      (setq n (distof s 2))
      (if n n (atof s))
    )
    (T nil)
  )
)

;; ----------------------------
;; 起動中の Excel を取得(起動してなければ nil)
;; ----------------------------
(defun _3dplxls2vtx-get-running-excel ( / r)
  (setq r (vl-catch-all-apply 'vlax-get-object (list "Excel.Application")))
  (if (vl-catch-all-error-p r) nil r)
)

;; ----------------------------
;; ブック中からシート名一致を取得(無ければ nil)
;; - Worksheets/Sheet は必ず release(返すシート以外)
;; ----------------------------
(defun _3dplxls2vtx-get-sheet-by-name (wb targetName / wss cnt idx ws found)
  (setq wss (vlax-get-property wb 'Worksheets))
  (setq cnt (vlax-get-property wss 'Count))

  (setq found nil)
  (setq idx 1)
  (while (and (<= idx cnt) (null found))
    (setq ws (vlax-get-property wss 'Item idx))
    (if (= (vlax-get-property ws 'Name) targetName)
      (setq found ws)
      (vlax-release-object ws)
    )
    (setq idx (1+ idx))
  )

  (vlax-release-object wss)
  found
)

;; ----------------------------
;; VERTEX の 10座標を書き換え
;; ----------------------------
(defun _3dplxls2vtx-set-vertex-xyz (vtxEnt x y z / ed oldP newEd)
  (setq ed (entget vtxEnt))
  (setq oldP (assoc 10 ed))
  (if (null oldP)
    nil
    (progn
      (setq newEd (subst (cons 10 (list x y z)) oldP ed))
      (entmod newEd)
      (entupd vtxEnt)
      T
    )
  )
)

(defun c:3DPLXLS2VTX
  ( /
    entPick entName vlaObj objName
    xlApp xlBook xlSheet sheetName
    e ed etype vtxIdx rowNum
    xVal yVal zVal ok changedCount
  )

  ;; ============================
  ;; Main
  ;; ============================
  (_3dplxls2vtx-log "コマンド開始")

  ;; Excel 起動確認(起動してなければ終了)
  (setq xlApp (_3dplxls2vtx-get-running-excel))
  (if (null xlApp)
    (progn
      (_3dplxls2vtx-log "Excel が起動していません。コマンド終了。")
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log "Excel 起動中を確認")

  ;; ActiveWorkbook 取得(無ければ終了)
  (setq xlBook (vlax-get-property xlApp 'ActiveWorkbook))
  (if (null xlBook)
    (progn
      (_3dplxls2vtx-log "ActiveWorkbook が取得できません。コマンド終了。")
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log "ActiveWorkbook を取得")

  ;; 対象シート取得(無ければ終了)
  (setq sheetName "3DPL_頂点座標")
  (setq xlSheet (_3dplxls2vtx-get-sheet-by-name xlBook sheetName))
  (if (null xlSheet)
    (progn
      (_3dplxls2vtx-log (strcat "対象シートがありません: " sheetName " / コマンド終了。"))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (_3dplxls2vtx-log (strcat "対象シート取得: " sheetName))

  ;; ここは「取り直し事故」を避けるため、xlSheet を保持したまま Activate のみにする
  (vlax-invoke-method xlSheet 'Activate)

  ;; 3Dポリライン選択
  (setq entPick (entsel "\n3Dポリラインを1つ選択: "))
  (if (null entPick)
    (progn
      (_3dplxls2vtx-log "選択がキャンセルされました。")
      (if xlSheet (vlax-release-object xlSheet))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )
  (setq entName (car entPick))

  ;; 3Dポリライン判定
  (setq vlaObj (vlax-ename->vla-object entName))
  (setq objName (vlax-get-property vlaObj 'ObjectName))
  (_3dplxls2vtx-log (strcat "Selected ObjectName = " objName))
  (vlax-release-object vlaObj)

  (if (not (wcmatch (strcase objName) "*3DPOLYLINE*"))
    (progn
      (_3dplxls2vtx-log "3Dポリラインではありません。コマンド終了。")
      (if xlSheet (vlax-release-object xlSheet))
      (if xlBook (vlax-release-object xlBook))
      (princ)
      (exit)
    )
  )

  ;; 頂点走査しながら Excel を読む(1行目ヘッダ→2行目から)
  (_3dplxls2vtx-log "Excel 2行目から A/B/C を読み、頂点座標を置換します")
  (setq changedCount 0)
  (setq vtxIdx 0)

  (setq e (entnext entName))
  (while e
    (setq ed (entget e))
    (setq etype (cdr (assoc 0 ed)))
    (cond
      ((= etype "VERTEX")
        (setq vtxIdx (1+ vtxIdx))
        (setq rowNum (+ vtxIdx 1)) ;; 2行目から

        (setq xVal (_3dplxls2vtx-read-num xlSheet rowNum 1))
        (setq yVal (_3dplxls2vtx-read-num xlSheet rowNum 2))
        (setq zVal (_3dplxls2vtx-read-num xlSheet rowNum 3))

        ;; デバッグ:最初の行だけ型と値を確認したい場合(必要ならコメント解除)
        ;; (if (= vtxIdx 1)
        ;;   (_3dplxls2vtx-log
        ;;     (strcat "DEBUG A2 type="
        ;;             (vl-princ-to-string (type (_3dplxls2vtx-read-cell xlSheet 2 1)))
        ;;             " val="
        ;;             (vl-princ-to-string (_3dplxls2vtx-read-cell xlSheet 2 1))
        ;;     )
        ;;   )
        ;; )

        (if (and xVal yVal zVal)
          (progn
            (setq ok (_3dplxls2vtx-set-vertex-xyz e xVal yVal zVal))
            (if ok
              (progn
                (setq changedCount (1+ changedCount))
                (if (= (rem vtxIdx 20) 0)
                  (_3dplxls2vtx-log (strcat "  changed vertex " (itoa vtxIdx) " ..."))
                )
              )
              (_3dplxls2vtx-log (strcat "  頂点更新失敗 index=" (itoa vtxIdx)))
            )
          )
          (_3dplxls2vtx-log (strcat "  数値読取不可/空欄のためスキップ index=" (itoa vtxIdx) " row=" (itoa rowNum)))
        )
      )
      ((= etype "SEQEND")
        (setq e nil)
      )
    )

    (if e (setq e (entnext e)))
  )

  (_3dplxls2vtx-log (strcat "置換完了: changed vertices = " (itoa changedCount)))

  ;; 後始末(規約通り)
  (if xlSheet (vlax-release-object xlSheet))
  (if xlBook  (vlax-release-object xlBook))
  ;; Excel.Application は原則 release しない

  (_3dplxls2vtx-log "コマンド終了")
  (princ)
)

試してみます。

OKです。
3Dポリラインの各頂点座標値が、Excel から読み取ったに変更されています。
※ 黄色のラインは変更前の3Dポリラインの形状です。

まとめ

ChatGPTを使って、「3Dポリラインの頂点座標をエクセルの数値をもとに変更する」オリジナルコマンドを作成することが出来ました。

前回の「3Dポリラインの頂点座標を Excel に出力する」コマンドとあわせて使えば、Excel3Dポリラインの作業パレットのように用いることができます。

また、ショートカットキーにこれらのコマンドを割り当てておくと、さらにストレスフリーで作業効率が上がると思います。


コメント

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