明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 160|回复: 3

[基础] 关于矩阵变换的几个配套函数,高手请忽略

  [复制链接]
发表于 昨天 18:08 | 显示全部楼层 |阅读模式
本帖最后由 caoyin 于 2025-12-11 19:27 编辑

话题引自网友 xiaocainiao 的帖子,高手请绕行
http://bbs.mjtd.com/forum.php?mo ... 4232&extra=page%3D1



简单描述:
圆是块A的子图元,块A是块B的子图元,
块参照B是当前空间的对象,要求获取块参照A的插入点的UCS坐标并转换到当前空间。

为了把问题说清楚,我把上面的应用场景再稍加复杂化


块定义A的子图元,块参照A块定义B的子图元,块参照B块定义C的子图元
块参照C是当前空间的对象,要求获取块参照A的插入点的UCS坐标并转换到当前空间坐标系。


1.nentselp函数可以获取最底层的对象。
(setq sel (nentselp "\n选择圆(多重套嵌块的子图元):"))
返回的四个元素:
1).圆的图元名
2).拾取点UCS
3).最底层对象变换到当前空间的4*4变换矩阵
4).父系块图元名列表(块参照C图元名 块参照B图元名 块参照A图元名)

2.如果我们想把圆心的坐标转换到当前空间,使用nentselp返回的变换矩阵就可以了,因为圆心是最底层对象

3.我们现在要获得的块参照A的的插入点,并转换到当前空间坐标系,而块参照A不是最底层的对象,因此nentselp提供的变换矩阵不配套。

4.获得变换矩阵:
1)获得块参照A块定义A的逆变换矩阵。
我们直接使用gile大神的 RevRefGeom 函数得到3*4变换矩阵(3*3矩阵+位移向量):
(setq mat1 (RevRefGeom (last (last sel))))
(2) 获得最底层对象变换到当前空间的4*4变换矩阵,
(setq mat2 (caddr sel));;此变换矩阵等价于gile大神的 RefGeom 函数(格式不同)

5.变换矩阵格式的处理
我们可以两次进行矩阵变换,也可以将矩阵合并进行一次变换。
但从第四步可以看出mat1是3*4变换矩阵,mat2是AutoCAD标注格式的变换矩阵。
我们可以把mat1为4*4变换矩阵,或者把mat2转换成3*4变换矩阵
由于AutoCAD针对图形对象的矩阵变换使用的是4*4格式,我们遵循这个惯例把3*4矩阵转换为4*4矩阵。

(setq mat1 (apply 'LT:Tmatrix-3x4->4x4 mat1))
LT:Tmatrix-3x4->4x4是我自用函数,在后面分享

引用大神的矩阵相乘函数MxM,合并矩阵,得到我们最终需要的变换矩阵
(MxM mat1 mat2)

这里做一个总结,无论块的套嵌级别有多深,我们不需要获取每一层套嵌块的变换矩阵,因此上文的块参照B可以不参与计算(计算已经在ACAD内部完成)
以上是为了把问题说明白,更直接的方法见三楼(无需两次使用矩阵)
6.全部转换过程

(defun C:TT (/ sel enx)
  (setq sel (nentselp "\n选择圆(多重套嵌块的子图元): ")
        enx (entget (last (last sel)))
  )
  ;; M4xP函数是我自用的应用4*4矩阵变换函数,在后面分享
  (M4xP
        ;;把块参照A的OCS插入点转换为UCS点(原帖帖主的要求,改成0即是WCS点)
        (trans (cdr (assoc 10 enx)) (cdr (assoc 210 enx)) 1)
        ;;把两个变换矩阵合并
        (MxM (apply 'LT:Tmatrix-3x4->4x4 (RevRefGeom (last (last sel))))
                 (caddr sel)
        )
  )
)


7.矩阵变换的相关配套函数
;;;先分享几个我自用的函数---------------------------------------------------

;; [功能] 将点应用3*3变换矩阵到向量(点的3x3仿射变换)
(defun M3xP (P M V) (mapcar '+ (MxV M P) V))


;; [功能] 将点应用4*4变换矩阵(点的4x4齐次变换)
;; 注意:以下函数返回的矩阵格式不同:
;;        nentselp 返回的矩阵: 直接使用 (M4xP P Mat)
;;        nentsel  返回的矩阵: 需要转置 (M4xP P (TRP Mat))
;; nentsel函数帮助说明:
;; nentsel是唯一一个使用这种类型矩阵的 AutoLISP 函数。(非标准的ACAD4*4矩阵格式)

(defun M4xP (P M / W)
  (setq P (MxV M (append P '(1.0)))
        W (last P)
  )
  (if (equal W 0.0 1E-10)
    (setq W 1.0)
  )
  (mapcar '/ P (list W W W))
)

;; [功能] 将点按照指定的矩阵变换
;; [参数] PT - 点
;;        MAT - 3x3矩阵或4x4矩阵
;;        VET - 向量。该参数为真,MAT被认为是3x3矩阵;为nil,MAT则被认为是4x4矩阵。
(defun LT:PointTransformBy (P MAT VEC)
  (if VEC
    (M3xP P MAT VEC)
    (M4xP P MAT)
  )
)



;; [功能] 将3x4矩阵(3x3矩阵+向量)转换为4x4矩阵
;; (LT:Tmatrix-3x4->4x4 '( (1 0 0) (0 1 0) (0 0 1)   (0 0 0)) nil)
;; (LT:Tmatrix-3x4->4x4 '(((1 0 0) (0 1 0) (0 0 1))  (0 0 0)) nil)
;; (LT:Tmatrix-3x4->4x4 '( (1 0 0) (0 1 0) (0 0 1)) '(0 0 0))
(defun LT:Tmatrix-3x4->4x4  (M V)
  (cond
    (V)
    ((setq V (last M))
     (if (= (length M) 2)
       (setq M (car M))
     )
    )
  )
  (append (mapcar '(lambda (A B) (append A (list B))) M V)
          '((0.0 0.0 0.0 1.0))
  )
)


;;;各路大神的的函数---------------------------------------------------

;; [功能] 向量点积
(defun VxV (V1 V2) (apply '+ (mapcar '* V1 V2)))

;; [功能] 向量数乘(向量x标量)
(defun VxS (V S) (mapcar '(lambda (N) (* N S)) V))

;; [功能] 应用变换矩阵到向量 --- Vladimir Nesterovsky
(defun MxV (M V) (mapcar '(lambda (R) (VxV R V)) M))


;; [功能] 矩阵相乘 --- Vladimir Nesterovsky
;; 在两个参数顺序不能错,顺序错了,结果也错了
(defun MxM (M Q) (mapcar (function (lambda (R) (MxV (TRP Q) R))) M))


;; [功能] 矩阵转置 --- Doug Wilson
(defun TRP (M) (apply 'mapcar (cons 'LIST M)))

;; [功能] 创建一个多维初始矩阵
;; [参数] N --- 矩阵维数
;; Identity Matrix  -  Lee Mac
;; Args: n - matrix dimension
(defun IMat (N / I J L M)
  (repeat (setq I N)
    (repeat (setq J N) (setq L (cons (if (= I J) 1.0 0.0) l) J (1- J)))
    (setq M (cons L M) L nil I (1- I))
  )
  M
)

;; [功能] 返回矩阵的逆向矩阵
;;        使用高斯-若尔当消元法返回非奇异(可逆) nxn 矩阵的逆矩阵。
;; Matrix Inverse  -  gile & Lee Mac
;; Uses Gauss-Jordan Elimination to return the inverse of a non-singular nxn matrix.
;; Args: m - nxn matrix
(defun InvM (M / F C P R)
  (defun F (P M)
    (mapcar '(lambda (X) (mapcar '(lambda (A B) (- A (* (car X) B))) (cdr X) P)) M)
  )
  (setq M (mapcar 'append M (IMat (length M))))
  (while M
    (setq C (mapcar '(lambda (X) (abs (car X))) M))
    (repeat (vl-position (apply 'MAX C) C)
      (setq M (append (cdr M) (list (caR M))))
    )
    (if (equal 0. (caar M) 1E-14)
      (setq M nil R nil)
      (setq P (mapcar '(lambda ( x ) (/ (float X) (caar M))) (cdar M))
            M (F P (cdr M))
            R (cons P (F P R))
      )
    )
  )
  (reverse R)
)


;; [功能] 获取块定义到块参照的变换矩阵
;; RefGeom (gile)
;; Returns a list whose first item is a 3x3 transformation matrix and
;; second item the object insertion point in its parent (xref, block or space)
(defun RefGeom (ent / ang enx mat ocs)
  (setq enx (entget ent)
        ang (cdr (assoc 050 enx))
        ocs (cdr (assoc 210 enx))
  )
  (list (setq mat (MxM (mapcar '(lambda (v) (trans v 0 ocs T))
                               '((1.0 0.0 0.0) (0.0 1.0 0.0) (0.0 0.0 1.0))
                       )
                       (MxM (list (list (cos ang) (- (sin ang)) 0.0)
                                  (list (sin ang) (cos ang) 0.0)
                                  '(0.0 0.0 1.0)
                            )
                            (list (list (cdr (assoc 41 enx)) 0.0 0.0)
                                  (list 0.0 (cdr (assoc 42 enx)) 0.0)
                                  (list 0.0 0.0 (cdr (assoc 43 enx)))
                            )
                       )
                    )
        )
        (mapcar '- (trans (cdr (assoc 10 enx)) ocs 0)
                   (MxV mat (cdr (assoc 10 (tblsearch "BLOCK" (cdr (assoc 2 enx))))))
        )
  )
)

;; [功能] 获取块参照到块定义的变换矩阵
;; RevRefGeom (gile)
;; The inverse of RefGeom
(defun RevRefGeom (ent / ang enx mat ocs)
  (setq enx (entget ent)
        ang (cdr (assoc 050 enx))
        ocs (cdr (assoc 210 enx))
  )
  (list (setq mat (MxM (list (list (/ 1.0 (cdr (assoc 41 enx))) 0.0 0.0)
                             (list 0.0 (/ 1.0 (cdr (assoc 42 enx))) 0.0)
                             (list 0.0 0.0 (/ 1.0 (cdr (assoc 43 enx))))
                       )
                       (MxM (list (list (cos ang) (sin ang) 0.0)
                                  (list (- (sin ang)) (cos ang) 0.0)
                                  '(0.0 0.0 1.0)
                            )
                            (mapcar '(lambda (v) (trans v ocs 0 T))
                                    '((1.0 0.0 0.0) (0.0 1.0 0.0) (0.0 0.0 1.0))
                            )
                       )
                  )
        )
        (mapcar '- (cdr (assoc 10 (tblsearch "BLOCK" (cdr (assoc 2 enx)))))
                   (MxV mat (trans (cdr (assoc 10 enx)) ocs 0))
        )
  )
)


评分

参与人数 3明经币 +3 金钱 +10 收起 理由
Bao_lai + 1 很给力!
tigcat + 1 + 10 很给力!
chen3732088 + 1 很给力!

查看全部评分

"觉得好,就打赏"
还没有人打赏,支持一下
回复

使用道具 举报

发表于 昨天 18:20 | 显示全部楼层
本帖最后由 你有种再说一遍 于 2025-12-11 18:21 编辑

都怪nentselp封装太狠了,没有正向过程导致逆向而行反而卡壳.
建议先学组块,这样才知道矩阵是什么.
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 19:25 | 显示全部楼层
本帖最后由 caoyin 于 2025-12-11 19:41 编辑

方法2:
更直接的方法,只需要使用一次矩阵:

(defun C:TT2 (/ sel enx)
  (setq sel (nentselp "\n选择套嵌块: ")
        enx (entget (last (last sel)))
  )
  (M4xP (trans (cdr (assoc 10 enx)) (cdr (assoc 210 enx)) 1)
        ;; 因为需要转换的点在块定义B内,直接获取块参照B的变换矩阵
        (apply 'LT:Tmatrix-3x4->4x4 (RevRefGeom (cadr (last sel))))
  )
)

;;; TT2简化为如下:
;;; 使用3*4矩阵,计算量最小
(defun C:TT3 (/ sel enx)
  (setq sel (nentselp "\n选择套嵌块: ")
        enx (entget (last (last sel)))
  )
  (apply 'M3xP
         (cons
           (trans (cdr (assoc 10 enx)) (cdr (assoc 210 enx)) 1)
           ;; 因为需要转换的点在块定义B内,直接获取块参照B的变换矩阵
           (RevRefGeom (cadr (last sel)))
         )
  )
)

赘述:
其实AutoCAD内部的矩阵变换大多是仿射变换,使用3*3或3*4矩阵足够了,
而4*4矩阵的齐次变换的最后一行往往是(0 0 0 1)多了不少冗余计算。





回复 支持 反对

使用道具 举报

发表于 昨天 20:28 | 显示全部楼层
caoyin 发表于 2025-12-11 19:25
方法2:
更直接的方法,只需要使用一次矩阵:

看了大佬的帖子,感觉快明白变换矩阵了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2025-12-12 06:12 , Processed in 0.171653 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表