図面では、部材の名称や数量、仕様などを2段、3段に分けて書くことがあります。この段付きの文字を線や曲線の向きに合わせようとすると、一つずつ回転させるだけでも意外と手間がかかります。
文字が少なければSAで一つずつ沿わせられますが、注記の数が多くなると、そのたびに同じ操作を繰り返さなければなりません。
AutoLISP「MTA」は、複数の文字をひとまとまりで選択し、文字の位置関係を保ったまま線や曲線の向きへ沿わせて配置するLISPです。
MTA
複数の文字を、線・曲線に沿わせて配置
MTA_MultiTextAlign.lsp
💡このLISPでできること
- 複数のTEXT・MTEXTをまとめて選択し、線や曲線の向きへ沿わせる
- 2段・3段に配置した文字の位置関係や角度差を保ったまま移動・回転する
- マウスを動かしながら配置する位置と向きを確認する
SAとMTAは、文字を線へ沿わせる基本的な動きは同じです。ただし、MTAは複数の文字を選択したあと、Enterで選択を確定する操作が1回増えます。
文字を一つだけ沿わせるならSA、2段書きや3段書きなど、複数の文字をまとめて沿わせるならMTA、という使い分けです。

SAを一つずつ繰り返さなくても、段付きの文字がまとまったまま線についてきます。
MTAは「Multi Text Align(複数文字を整列)」の略です。
🛠️MTAの使い方
- コマンドラインへ MTAと入力し、EnterまたはSpaceで確定します。
- 線や曲線へ沿わせたいTEXT・MTEXTを複数選択し、Enterで確定します。
- 文字を沿わせたい線または曲線を選択します。
- マウスを動かし、文字全体の位置と角度を確認します。
- 配置したい位置で左クリックして確定します。
⚠️使う前に知っておきたいこと
- MTAは選択した元の文字をまとめて移動・回転します。コピーを作る機能ではありません。
- 文字の角度が異なる場合は、最後に選択した文字が線や曲線と平行になります。ほかの文字は、元の位置関係と角度差を保ったまま一緒に動きます。
- 段の並びは保たれますが、線からの離れや最終的な位置は、確定後に必要に応じてM(MOVE)で微調整します。
💾 ダウンロードとコード
LISPファイルをダウンロード
収録ファイル:MTA_MultiTextAlign.lsp
※ダウンロード後、「すべて展開(解凍)」してから使用してください。
LISPを初めて使う方は、読み込み方法をこちらの記事で確認できます。
AutoCADでLISPをロードする3つの方法
このLISPはECW_Utilityがなくても基本機能を使用できます。ECW_Utilityを導入している環境では、共通のUNDO・エラー処理と連携します。
ECW_Utilityの役割と導入方法
コードのコピーは+をクリック↓
;;; Support: https://easycadwork.com
;;; X(follow me!): https://x.com/easycadwork
;;; 概要: 複数の文字(TEXT/MTEXT)を線または曲線に平行にリアルタイム配置
;;; Command: MTA
;;; Version: 2026-09-25 ECW正式版(線分エラー修正・移動動作・半透明プレビュー)
(vl-load-com)
;; 選択セットをエンティティ名リストに変換
(defun MTA_GetEntsList (ss / i lst)
(setq i 0 lst nil)
(repeat (sslength ss)
(setq lst (append lst (list (ssname ss i))))
(setq i (1+ i))
)
lst
)
;; 複数エンティティ全体のバウンディングボックス(ワールド座標)
(defun MTA_GetGroupBBoxW (ents / obj tMin tMax allMin allMax)
(setq allMin nil allMax nil)
(foreach ent ents
(setq obj (vlax-ename->vla-object ent))
(if (not (vl-catch-all-error-p
(vl-catch-all-apply 'vla-GetBoundingBox (list obj 'tMin 'tMax))))
(progn
(setq tMin (vlax-safearray->list tMin)
tMax (vlax-safearray->list tMax))
(if (null allMin)
(progn (setq allMin tMin) (setq allMax tMax))
(progn
(setq allMin (list (min (car allMin) (car tMin))
(min (cadr allMin) (cadr tMin))
(min (caddr allMin) (caddr tMin))))
(setq allMax (list (max (car allMax) (car tMax))
(max (cadr allMax) (cadr tMax))
(max (caddr allMax) (caddr tMax))))
)
)
)
)
)
(if allMin (list allMin allMax) nil)
)
;; 参照がなくなった一時ブロック定義を削除
(defun MTA_DeleteBlockDef (blkName / blkCol)
(if (and blkName (tblsearch "BLOCK" blkName))
(vl-catch-all-apply
(function (lambda ()
(setq blkCol (vla-get-Blocks (vla-get-ActiveDocument (vlax-get-acad-object))))
(vla-delete (vla-Item blkCol blkName))
))
)
)
)
;; 一時的な複製文字・ブロック参照だけを消す(消去済みの再表示を防ぐ)
(defun MTA_DeleteTempObjects (objs / erased)
(foreach obj objs
(setq erased (vl-catch-all-apply 'vlax-erased-p (list obj)))
(if (and (not (vl-catch-all-error-p erased)) (not erased))
(vl-catch-all-apply 'vla-Delete (list obj))
)
)
)
;; 元文字の透明度を一時的に変える。戻すための (object . 元の値) を返す。
(defun MTA_DimOriginals (objs / states oldValue result)
(setq states nil)
(foreach obj objs
(setq oldValue (vl-catch-all-apply 'vla-get-EntityTransparency (list obj)))
(if (not (vl-catch-all-error-p oldValue))
(progn
(setq states (cons (cons obj oldValue) states))
(setq result (vl-catch-all-apply 'vla-put-EntityTransparency (list obj "50")))
(if (not (vl-catch-all-error-p result))
(vl-catch-all-apply 'vla-Update (list obj))
)
)
)
)
states
)
(defun MTA_RestoreTransparency (states / pair)
(foreach pair states
(if (not (vlax-erased-p (car pair)))
(progn
(vl-catch-all-apply 'vla-put-EntityTransparency
(list (car pair) (cdr pair)))
(vl-catch-all-apply 'vla-Update (list (car pair)))
)
)
)
)
;; 確定処理の途中で失敗した場合だけ、消えた元文字を戻す。
(defun MTA_ReviveOriginals (ents objs / ent obj)
(while (and ents objs)
(setq ent (car ents) obj (car objs))
(if (vlax-erased-p obj) (entdel ent))
(setq ents (cdr ents) objs (cdr objs))
)
)
(defun c:MTA (/
*error* oldError useUtility started cancelled committed
oldOsnap oldBlockMode oldCmdecho
ss ents origObjs ghostStates copySs copyObjs newObj lastEnt origAng
groupBBox alignedBBox basePtW alignDx alignDy insertPtW
tempName blockEnt blockObj beforeBlock afterBlock
convertedObj beforeInsert
crvSel crvEnt vlaCrv isLine
loop code hasPreview
ptCurUCS ptCurWCS ptNearWCS
param deriv angO angNorm
blkData)
(setq oldError *error*
useUtility (and (type ECW_Start) (type ECW_End) (type ECW_Error))
oldOsnap (getvar "OSMODE")
oldBlockMode (getvar "BLOCKCREATEMODE")
oldCmdecho (getvar "CMDECHO")
started nil
cancelled nil
committed nil
tempName nil
blockEnt nil
blockObj nil
copyObjs nil
convertedObj nil
origObjs nil
ghostStates nil)
;; Esc・エラー時は元文字を復元し、一時オブジェクトと設定を片付ける。
(defun *error* (msg)
(if (and blockEnt blockObj
(not (vl-catch-all-error-p
(vl-catch-all-apply 'vlax-erased-p (list blockObj))))
(not (vlax-erased-p blockObj)))
(progn
(entdel blockEnt)
(setq blockEnt nil)
)
)
(MTA_DeleteTempObjects copyObjs)
(if convertedObj (MTA_DeleteTempObjects (list convertedObj)))
(if (not committed) (MTA_ReviveOriginals ents origObjs))
(MTA_RestoreTransparency ghostStates)
(MTA_DeleteBlockDef tempName)
(if oldOsnap (setvar "OSMODE" oldOsnap))
(if oldBlockMode (setvar "BLOCKCREATEMODE" oldBlockMode))
;; ECW_Error -> ECW_End は内部で (command) を呼ぶため、
;; エラーハンドラ内では使わず、MTAが変更した値とUNDOだけ直接戻す。
(if (and useUtility started)
(progn
(if oldCmdecho (setvar "CMDECHO" oldCmdecho))
(vl-catch-all-apply
'vla-EndUndoMark
(list (vla-get-ActiveDocument (vlax-get-acad-object)))
)
(setq *ECW_Settings* nil)
)
(if oldCmdecho (setvar "CMDECHO" oldCmdecho))
)
(if (not (or cancelled
(wcmatch (strcase msg) "*CANCEL*,*QUIT*,*中止*,*キャンセル*")))
(princ (strcat "\n** MTA_Error: " msg " **"))
)
(setq *error* oldError)
(princ)
)
(setq started T)
(if useUtility (ECW_Start))
(setq started useUtility)
(princ "\n========================================")
(princ "\n MTA : 複数文字を線・曲線上に配置 (MultiTextAlign)")
(princ "\n========================================")
;; STEP 1: TEXT/MTEXTを選択
(princ "\n[STEP 1] TEXT / MTEXT を複数選択してください(Enterで確定)")
(setq ss (ssget '((0 . "TEXT,MTEXT"))))
(if (null ss)
(progn (princ "\n選択がキャンセルされました。") (setq cancelled T) (exit))
)
(setq ents (MTA_GetEntsList ss))
(setq origObjs (mapcar 'vlax-ename->vla-object ents))
;; 選択のハイライトだけ解除する。文字実体と選択セットは保持する。
(sssetfirst nil nil)
;; 最後に選択した文字の元角度を記録(TEXT/MTEXT共通)
(setq lastEnt (last ents))
(setq origAng (vla-get-Rotation (vlax-ename->vla-object lastEnt)))
;; 選択した文字群の下端中央を挿入基点にする
(setq groupBBox (MTA_GetGroupBBoxW ents))
(if (null groupBBox)
(progn (princ "\nバウンディングボックスの取得に失敗しました。") (setq cancelled T) (exit))
)
(setq basePtW
(list
(/ (+ (car (car groupBBox)) (car (cadr groupBBox))) 2.0)
(cadr (car groupBBox))
(caddr (car groupBBox))
)
)
;; 元文字とは別の複製を作り、その複製だけを一時ブロック化する。
;; -BLOCK側の設定や表示状態が変わっても、元文字は処理対象にしない。
(setq copySs (ssadd) copyObjs nil)
(foreach ent ents
(setq newObj (vla-Copy (vlax-ename->vla-object ent)))
(setq copyObjs (cons newObj copyObjs))
(ssadd (vlax-vla-object->ename newObj) copySs)
)
(setvar "OSMODE" 0)
(setvar "BLOCKCREATEMODE" 1)
(setq tempName (strcat "MTA_T" (rtos (getvar "CDATE") 2 8)
"_" (itoa (getvar "MILLISECS"))))
(setq beforeBlock (entlast))
(command "_.-BLOCK" tempName basePtW copySs "")
;; 万一 -BLOCK が複製をブロック参照に置き換えた場合も、その参照を消す。
(setq afterBlock (entlast))
(if (and afterBlock
(not (eq beforeBlock afterBlock))
(= "INSERT" (cdr (assoc 0 (entget afterBlock))))
(= (strcase tempName) (strcase (cdr (assoc 2 (entget afterBlock))))))
(setq convertedObj (vlax-ename->vla-object afterBlock))
)
(MTA_DeleteTempObjects copyObjs)
(setq copyObjs nil)
(if convertedObj (MTA_DeleteTempObjects (list convertedObj)))
(setq convertedObj nil)
(setq beforeInsert (entlast))
(command "_.INSERT" tempName basePtW 1.0 1.0 0.0)
(setq blockEnt (entlast))
(if (or (null blockEnt)
(eq blockEnt beforeInsert)
(/= "INSERT" (cdr (assoc 0 (entget blockEnt)))))
(progn (princ "\n一時ブロックの作成に失敗しました。") (setq cancelled T) (exit))
)
(setq blockObj (vlax-ename->vla-object blockEnt))
;; 最後の文字を水平にした姿勢で外接範囲を測る。
;; 回転前のWCS外接範囲では、斜めの文字群の「下辺中央」を正しく取れない。
(vla-put-Rotation blockObj (- origAng))
(vla-Update blockObj)
(setq alignedBBox (MTA_GetGroupBBoxW (list blockEnt)))
(if (null alignedBBox)
(progn (princ "\n文字群の基点を取得できませんでした。") (setq cancelled T) (exit))
)
(setq alignDx (- (/ (+ (car (car alignedBBox)) (car (cadr alignedBBox))) 2.0)
(car basePtW))
alignDy (- (cadr (car alignedBBox)) (cadr basePtW)))
;; -BLOCK/-INSERT後も元文字を表示し、追従中に「移動済み」に見せない。
(foreach ent ents (redraw ent 1))
;; STEP 2: 沿わせる線または曲線を選択
(redraw blockEnt 3)
(princ "\n[STEP 2] 沿わせる線または曲線を選択してください")
(setq crvSel (entsel "\n線または曲線を選択: "))
(redraw blockEnt 4)
(if (null crvSel)
(progn (princ "\n選択がキャンセルされました。") (setq cancelled T) (exit))
)
(setq crvEnt (car crvSel))
(if (vl-catch-all-error-p
(vl-catch-all-apply 'vlax-curve-getEndParam (list crvEnt)))
(progn (princ "\n有効な線または曲線が選択されませんでした。") (setq cancelled T) (exit))
)
(setq vlaCrv (vlax-ename->vla-object crvEnt))
(setq isLine (= "LINE" (cdr (assoc 0 (entget crvEnt)))))
(foreach ent ents (redraw ent 1))
;; プレビュー中だけ元文字を薄くする。透過表示OFFなら元の濃さで表示される。
(setq ghostStates (MTA_DimOriginals origObjs))
;; STEP 3: マウス追従。元の角度差と配置計算は実機版のまま。
(princ "\n[STEP 3] マウスで配置位置を決定")
(princ "\n 左クリック: 確定 / 右クリックまたはESC: 中止")
(setq loop T hasPreview nil)
(while loop
(setq code (grread T 13 0))
(cond
;; マウス移動
((= (car code) 5)
(setq ptCurUCS (cadr code)
ptCurWCS (trans ptCurUCS 1 0))
(setq ptNearWCS (vl-catch-all-apply
'vlax-curve-getClosestPointTo
(list vlaCrv ptCurWCS)))
(if (and (listp ptNearWCS) (= (length ptNearWCS) 3))
(progn
;; LINEの接線方向は一定。DXFの始点・終点から直接求める。
(setq param
(if isLine
nil
(vl-catch-all-apply 'vlax-curve-getParamAtPoint
(list vlaCrv ptNearWCS))))
(setq deriv
(if isLine
(mapcar '- (cdr (assoc 11 (entget crvEnt)))
(cdr (assoc 10 (entget crvEnt))))
(if (numberp param)
(vl-catch-all-apply 'vlax-curve-getFirstDeriv (list vlaCrv param)))
)
)
(if (and (listp deriv)
(numberp (car deriv))
(numberp (cadr deriv)))
(progn
(setq angO (angle '(0 0 0) deriv))
(setq angNorm (rem (+ angO (* pi 2.0)) (* pi 2.0)))
(if (and (> angNorm (/ pi 2.0)) (<= angNorm (* pi 1.5)))
(setq angNorm (- angNorm pi))
)
;; 整列後の文字群の下辺中央を、線や曲線上の接点に置く。
(setq insertPtW
(list
(- (car ptNearWCS)
(- (* alignDx (cos angNorm)) (* alignDy (sin angNorm))))
(- (cadr ptNearWCS)
(+ (* alignDx (sin angNorm)) (* alignDy (cos angNorm))))
(caddr ptNearWCS)
)
)
(setq blkData (entget blockEnt))
(setq blkData (subst (cons 10 insertPtW)
(assoc 10 blkData) blkData))
(setq blkData (subst (cons 50 (- angNorm origAng))
(assoc 50 blkData) blkData))
(entmod blkData)
(setq hasPreview T)
)
)
)
)
)
;; 左クリック: 複製を配置してから元文字を消し、移動として確定する
((= (car code) 3)
(if hasPreview
(progn
(setq loop nil)
(command "_.EXPLODE" blockEnt "")
(if (vlax-erased-p blockObj)
(progn
(setq blockEnt nil)
(MTA_RestoreTransparency ghostStates)
(setq ghostStates nil)
(foreach ent ents (entdel ent))
(setq committed T)
(MTA_DeleteBlockDef tempName)
(princ (strcat "\n 配置完了: " (itoa (length ents)) " 個の文字を移動しました。"))
)
(progn
(if (not (vlax-erased-p blockObj)) (entdel blockEnt))
(setq blockEnt nil)
(MTA_RestoreTransparency ghostStates)
(setq ghostStates nil)
(MTA_DeleteBlockDef tempName)
(princ "\n 配置に失敗しました。元の文字は残っています。")
)
)
)
(princ "\n マウスを動かして配置位置を確認してください。")
)
)
;; 右クリック / Esc: 一時ブロックだけ消し、元文字は残す
((or (= (car code) 11) (= (car code) 25))
(setq loop nil)
(entdel blockEnt)
(setq blockEnt nil)
(MTA_RestoreTransparency ghostStates)
(setq ghostStates nil)
(MTA_DeleteBlockDef tempName)
(princ "\n 中止しました。元の文字はそのまま残ります。")
)
)
)
(setvar "OSMODE" oldOsnap)
(setvar "BLOCKCREATEMODE" oldBlockMode)
(if useUtility (ECW_End))
(setq *error* oldError)
(princ)
)
(princ "\n[MTA_MultiTextAlign] 2026-09-25 ECW正式版をロード - コマンド: MTA")
(princ)
✏️まとめ:SAを何度も繰り返さなくていい
SAは、文字を一つずつ線へ沿わせるには便利ですが、2段・3段の注記を何組も配置すると、そのたびに同じ操作を繰り返す必要があります。この手間を何とか減らしたくて作ったのがMTAです。
最初は複数の文字が思うように動かず、形になるまでかなり苦労しました。
図面に十分なスペースがないと、注記を2段、3段に分けて配置することがよくあります。今は、排水管の管種・管径、勾配、延長を3段で記載するときによく使っています。
段付きの文字をまとめて管の向きへ沿わせられるので、一つずつ回転・移動する必要がありません。文字数が多い図面ほど、作業の手間を減らせます。

私はSAの操作を指が覚えているので、1文字なら今でもついSAを使います(笑)。でもMTAでも同じように動かせるので、最初からMTAだけ覚えてもいいかも。



コメント