PLM之家PLMHome-工业软件与AI结合践行者

Catia二次开发CAA中的交互操作开发原理和过程大全

[复制链接]

2018-1-16 16:42:12 6660 0

admin 发表于 2018-1-16 16:42:12 |阅读模式

admin 楼主

2018-1-16 16:42:12

请使用QQ关联注册PLM之家,学习更多关于内容,更多精彩原创视频供你学习!

您需要 登录 才可以下载或查看,没有账号?注册

x
CAA中的交互操作开发,主要包括原理说明,开发过程说明和案例探究。9 {( p; y  V. x8 x# h

5 F2 \- L/ Z1 h$ s1 J参考阅读:
+ B8 k" N) c/ s百科全书->UserInterFace->WinTopCommand) u8 m* [& a+ K6 x5 m" M* m

# n8 K& |0 r# {+ J; z3 j01.CATIA命令
% r' s: s/ z% r, L$ k+ q6 j5 S7 S7 }1 x- B& N+ u
CATIA中命令主要有三种:单步命令,对话框命令,状态转换命令。0 f& z/ L; S) P7 a/ m
•(1)基本命令,也称(basicCommands):运行时用户不能有附加选项,从其开始运行直至其结束期间无法停止,也称单步命令,该类命令派生自CATCommand类。•(2)对话框命令(DialogBoxCommands):用户可以输入参数值或选择选项,对话框本身即命令,而不是其它命令的一部分,该类命令派生自CATDlgDialog类。•(3)状态对话命令(StateChartCommands):状态对话命令被模拟为状态机,通过状态、迁移(或转换)的组合可构成高级对话命令。命令中可有数个状态,每个状态让用户选择对象、输入参数或选择选项。根据选择的对象、输入参数或选项判断是否满足条件,如果满足相应条件则触发迁移,跳转到下一状态执行,直到命令结束。对话框可用于状态对话命令作参数或选项输入界面。该类命令派生自CATStateCommand类。
7 p+ o; y4 [1 N' h4 W  a% }" T5 F1 E, _7 @  b
本文重点说明第三种命令,即状态命令。  T0 _2 M/ n+ T
9 [% H; A3 j7 H
. k) p7 e& @, s$ p* i8 ]' p
在CATIA操作中,交互操作情景很多,如点击屏幕画点、线,- H* `4 w% j& b$ ?1 e: `
% t  I; h. E  c5 C3 D7 x. X9 J, W! s
如交互方式输入数值创建特征,
/ f3 S! r- J$ q, F: s. S' w5 J; b/ x9 r) p: o
如用鼠标点选已有模型特征......
6 d8 }& s* I; F
* T' Q  m! a8 w% G0 J/ z1 m在这些交互操作中,前一步操作往往作为下一步操作的输入或者执行条件,因此需要程序识别每次操作的状态。而维持这一过程有序进行的正是状态机制(satemachine).3 H% o0 Z4 {% M/ |* q
02.状态机制4 w( g7 q% d) V4 _

' i( L' g2 ^9 G•状态机制(state-machine)是由状态和迁移组成的图,通常状态机附属于类,描述了类实例对接收事件的响应。•状态机是某个类的对象所有可能生命历史的模型,所有外部世界对对象的影响被总结为事件。从命令激活到最后取消,命令生命周期中所有事件都可以用状态机表示。 5 S( ^( R5 w+ f0 D5 _( E+ t
如上图中点击屏幕获取三点并绘制成折线的状态图可表示如下! m/ Y* a. u9 I+ l) j- Y2 b6 [

5 c# B/ r8 y% a2 P& T•事件是具有时间和空间位置的显著发生的某件事,如鼠标在窗口某个位置点击、控件的某个操作,鼠标移动等。 & k! f% E6 x4 k! f6 w+ D2 |& O
•当状态机检测到事件,将对事件作出判断,并以相关于当前状态的方式来响应,这里的判断称为迁移条件。响应可能包括动作的执行和改变到新的状态。  K. ]4 x$ u$ p& o1 b

) U8 e& z1 I7 A) L9 J- A1 l因此,上图中状态机制运行如下:状态机制从开始状态进行到结束状态。从开始状态进入到状态1的转移中无任何函数响应调用,命令自动跳转到状态1的执行,即在鼠标点击事件执行后,在鼠标点击位置创建一个点,只要用户指定一个有效的点,当鼠标左键按下去的时候,鼠标按键事件就已经被检测出,并判断满足创建点的条件,执行响应函数绘制了一个点,同时状态就从第一个状态转移到第二个状态即状态2,第二个状态也是等待用户用鼠标指定一个点。当鼠标第二次按下去的时候,创建第二个点,同时状态就从状态2转移到状态3,鼠标按键事件已经被检测出,直至三个点创建完成,判断了绘制折线的条件,执行了响应函数绘制了一条折线,同时状态从第三个状态转移到最终状态,即结束操作。$ i3 [9 O" i1 I/ r) K; c. n

1 e' h+ H( M% }$ Y: U' O03.状态命令创建
6 E+ w) R; b: ~( B5 J! _
$ o1 D# n7 U5 n# k' t状态命令主要继承于CATStateCommand,该命令类主要用于接收CATIA中用户的交互输入。2 O, n' Y; M, c, j+ r
一个状态命令的执行是基于三个工具而完成的,即代理(Agents),状态(States),转移(Transitions)。
- L/ t7 U, P) O- ?Agents:获取用户操作的事件及选择的对象4 q, U$ Q8 R: G( Y# n2 U" }
States:对话框中等待用于输入6 D& `$ E5 Z* @, w5 K% f
Transitions:定义哪些指令将被执行
% ]3 r4 D* N+ L1 P
% J+ T: _- n' u4 R) L$ ^状态命令执行条件:
  C; a" _0 z6 u+ e( ]1.为每个状态定义一个输入类型1 S7 e. ~+ O& ~  y' v# |& [5 ^
2.一个状态转移需定义由源状态到目标状态的转变2 ?/ E( p3 `1 r: e+ k' P3 L# X+ Q8 z
3.基于条件的状态触发:包括最终事件驱动——用户的交互操作,条件确认用户输入。当某个状态被触发时,目标状态将被激活。: Q: P2 s! m% c$ ~+ ?: q% i: h
4.状态转移过程中,可以执行一个或多个指令(Action)- S" X) W# y8 P/ _
状态命令开发说明:- Y5 r2 ~) m( F2 T, G2 h
1.创建一个新类(Class)继承于CATStateCommand& z$ {0 n! O! X
2.至少覆盖:描述自己的状态(State)的变迁:BuildGraph()
* c. W9 p% d6 ^0 ~: q     适时管理命令的生命周期:Activate()、Desactivate()、Cancel()
% R3 k. |$ w; ?& u7 G) P4 ]3.定义一些方法,作为条件的验证及指令(Action)的执行% A( Q8 B4 d( N
4.保存命令对话框中agent的数据成员
$ W5 q$ X) R) T! ~/ B- ^* J7 V! R6 v1 ?" J# r
- @7 `( K5 \( |4 y, m, S: ]
状态命令执行条件:7 t- H- v6 N. F8 B: x' M8 g
1.为每个状态定义一个输入类型/ {2 Z% }7 X) T% ~. c( a! t9 ^* x
2.一个状态转移需定义由源状态到目标状态的转变
+ a! z1 t0 e" g; A- Y7 f3.基于条件的状态触发:包括最终事件驱动——用户的交互操作,条件确认用户输入。当某个状态被触发时,目标状态将被激活。
# l( Y( }/ G4 o4.状态转移过程中,可以执行一个或多个指令(Action)
; @7 S$ S: \2 _6 W  u状态命令开发说明:3 |) Y9 o+ Q4 a# u# t# X
1.创建一个新类(Class)继承于CATStateCommand& o% U5 N; ~6 J
2.至少覆盖:描述自己的状态(State)的变迁:BuildGraph()
! [( r1 v% n' s" C7 d6 T1 x  x; y     适时管理命令的生命周期:Activate()、Desactivate()、Cancel()
1 `  H3 T, C% W" Z5 U3.定义一些方法,作为条件的验证及指令(Action)的执行
: ]9 E+ ?6 t* e1 S/ p: o: B4.保存命令对话框中agent的数据成员
  Q. U6 h0 x+ c/ j6 Z8 |( z$ |
. z) F. }; x& L. H本文将重点说明三个概念(代理,状态和转移)及其在CAA开发中的体现。(部分文字显示不全,建议横屏查看或者联系作者获取文档。)
  t1 U, @+ T% h. y! i  G7 R1
9 D4 n6 }% `" ?# h对话框代理(Agents)% q- }2 {2 `1 [( y, L% F1 j
•对话代理(DialogAgent)是处理与界面命令相关的一个接口CATDialogAgent,其中与人机交互有关的代理大致分为2类,选择代理(PathElementAgent)和指定代理(IndicationAgent),他们分别对应接口CATPathElementAgent和CATIndicationAgent。
) [6 M1 `+ i4 D1 E9 [; u3 M: Q5 H1.对话框代理(CATDialogAgent):4 N# [4 V7 |+ ?& N0 {- t2 {
CATDialogAgent是主要类,该类可以用来定义一个Agent与命令对话框中对象的连接
2 t  \% I. G# G* ], F当一个事件发生时,这个Agent将被赋值.通常用于面板的交互和输入消息提示。
" W" |% X% |' h  r, d& F2.选择代理(PathElementAgent):! X% C, S3 U6 W$ N3 o) E8 l
如果文件中的对象显示在视图中,用户用左键选择它,这个对象可以作为后续工作的输入,如选择两个点绘制一条直线,选择三个点绘制一条折线等交互式选择。CATPathElementAgent代理主要处理这种与选择有关的操作。
' g* J+ x  r1 D说明:  E& ~8 E& h" @1 A/ K% w; Q
•在catia中鼠标的每次选择对象都会产生路径,就是所选对象的位置路径,比如选中了某个点后会产生路径:Product.1\part.1\几何图形集\点.1。若选择的是实体表面可能会产生路径:part.1\零件几何体\凸台.1\面。这种路径未必会按照CATIA中的结构树的上下级关系产生。它是按照几何元素对象的拓扑关系产生的。
) `7 b' m! b/ Q3 Y•在选择后会自动产生路径,并将路径存放在CATPathElementAgent的实例对象中。所以我们在选择了零件中的一个几何对象后从选择代理实例追溯它的路径中包含的对象。 ( w# V( \; @6 s# S7 d7 o& F! A
•常用情形是在装配体中选择某个几何对象后获取几何对象所在的part名称,product名称,对象名称等。( P* p* V1 t& Q9 }# D# M* t
选择代理的定义:# S  M- _$ O9 B" a% n
在.h文件中声明,
( c. t, }4 i( E4 C2 M8 n* sclassCAADegCreatePlaneCmd:publicCATStateCommand
8 [6 B5 O1 B. H% z{+ P0 Z: n  f2 z
8 t* ~$ j% k3 F, l& ?3 z
private:# T8 p7 W+ `: M, f9 i0 a" \  i
   CATPathElementAgent*_daPathElement;
$ ~8 S8 O( [" ~7 n3 O0 T1 d6 I  Y9 u  Q5 W4 T6 F! G* d
}
2 X, q" w7 ?3 I  |& t! J在.cpp文件BuildGraph()中实例化,2 [- m8 V7 w- H. J
' w/ C+ Z# Q$ R1 r9 J; i
voidCAADegCreatePlaneCmd::BuildGraph()) q( u$ G9 b) O( i8 G6 X
{3 M0 S1 i, @/ |- r; S+ x
...
1 T- H" Q2 m8 n5 b% n _daPathElement=newCATPathElementAgent("GetPoint");
+ \9 d. `9 v+ w: W) \( ?8 o _daPathElement->AddElementType(IID_CATIGSMPoint);2 s2 B$ S( q" E% j1 O9 n' S- P
...
- m4 Y% i' I$ v+ |( I7 }}
* z8 k1 m8 c1 {% \. x; V' s8 \注意:
, ?: `7 t" I6 N/ c% ]( Z这里字符串“GetPoint”定义了一个选择代理在程序中的唯一识别符号。AddElementType方法的参数给定了选择代理只能识别CATIGSMPoint类型的点对象,其他对象一概不能识别。& k) X  f4 {2 c/ Q
AddElementType也可以通过字符串的方式给定,比如:  u6 U" V4 m/ I. ~% b, u( [$ x: i
_daPathElement->AddElementType("CATIGSMPoint");
3 {- q& V! ~7 S  N6 j6 s若可以选择的对象较多是可以采用字符串链表的形式给定,如下所示:
9 g: N3 T- c, K0 w) D+ M- j   CATListOfCATStringTypes;* E0 u" g  e0 {1 I/ T% A
   Types.Append("CATPoint");
( R1 a+ Z' k, m! J: X: w* k* `   Types.Append("CATINewHole");
  d, _( D' g; G2 i) G% m' B   Types.Append("CATISketch");
% u$ C- h  i" {6 M5 P   Types.Append("CATIGSMLineNormal");$ G+ t, T  v, Q- W% V+ \
   Types.Append("CATIGSMLinePtPt");3 {- w9 X( z) ~. A
   _daPathElement->SetOrderedTypeList(Types);! Y5 Q8 n$ H, W9 a/ c+ Y7 K: D* L7 v
' q1 h6 A  d4 K) |) x" W( N# n
3.指示代理(IndicationAgent)3 r5 k9 U1 t& F0 }! P0 E( A
•指定代理处理指鼠标在屏幕上点击(空白处点击)或者移动的时候发生的事件。9 ]0 g3 q% B+ o( `+ A
•点击或者移动的过程中产生临时2D点。指定代理能够获取文件中不存在的临时2D点的坐标,这2D点是由用户在2D(如草图或工程图中)或3D视图(如装配视图)中屏幕上点击左键或移动鼠标产生的。
- d. A* T) y' B) V' g  o! q) l" x•一个指定代理致力于怎么从2D屏幕所在的平面中获取2D点坐标,并将坐标转化在绝对坐标系。鼠标在屏幕上的点击是不确定的(只给定了2个坐标值),如果给定一个投影平面,那么在屏幕上的点击会根据视线方向(当前视角给定)投影到投影平面。(默认的投影平面就是与屏幕平行的平面,也可给指定代理提供一个平面)。
5 v3 O9 B; N& \9 E% K, e指示代理的定义:
- a5 v8 U+ N2 p6 K在.h文件中声明,CATIndicationAgent *_daIndication;# P4 o- [6 m' F5 w1 w" v
在.cpp文件BuildGraph()中实例化
; i) |% _$ g% [* c1 l( w_daIndication=newCATIndicationAgent("PointIndication");' U3 N$ |7 c( K% j4 d

4 @6 Y- u) P! t8 y. h4 X5 o代理的行为定义:# ]3 X7 T1 p1 x# `  x8 _
voidCATDialogAgent::SetBehavior(CATDlgEngBehavioriBehavior);
( d+ @4 I# ~4 g& }+ l; e  _8 G7 e2 f7 D: d2 a( {+ k  C5 B
代理行为表现在代理接收通知消息后的反映,如定义一个平面类型的选择代理并设置代理行为为高亮提示,当鼠标移动到平面时即可看到该平面高亮。
8 l. T8 a9 V8 }& c- f; D. K. F_daIndicationP->SetBehavior(CATDlgEngAcceptOnPrevaluate|              CATDlgEngWithPrevaluation);
% p* h6 v1 X0 K" H' F通用对话框代理的组成包括:
" o+ f+ ^* a6 ?行为模式:Thebehaviormode5 x) ?1 F& J$ G- @, y! J, \
通告集:Thenotifierset
+ {+ I" j( v! \, Q通知组合:Thenotificationpattern.
* n6 W" n& N) R4 `0 f3 F+ X$ \
( A$ E3 _/ s1 t如一个可重复/可撤销的代理行为设置如下
* b- A& N* H& K4 ^MyDialogAgent->SetBehavior(CATDlgEngRepeat|CATDlgEngWithUndo);+ z# m8 N. S1 H, J" y0 C
2
8 Z5 d! w) z7 d, z- Z状态(States)1 V& w1 E% f% o. U7 W3 H5 Q$ G) L- h# n
对话框代理用于用户交互式输入,它通常需要匹配一个或者多个状态(States),而且代理的赋值也通常需要对状态进行校验。如下图通过两点绘制直线的交互操作状态图中,整个操作过程包括四个状态:初始状态,选择起点状态,选择重点状态,最终状态。因此“状态”可以理解为交互命令执行的每个步骤。0 D& W3 f; X5 s0 V  y( m0 u
图片来自百库全书( d# p7 F6 D% I% S
状态的创建:
" [5 A9 F  [! i; h注意:6 E/ T: g5 e7 ]
  The GetInitialState methodforthefirststate# V0 m$ A: ^. i" q9 M0 D& {# [
CATDialogState*stStartState=GetInitialState("Start");/ l% m/ i% z9 F+ o  Y  n. A( j! o! @3 w
The AddDialogState methodforthesecondstate# p5 X8 O2 t2 w1 ]) G
CATDialogState*stEndState=AddDialogState("Second");& T. J& t  N" y, Y" s
其中 Start 和 Second为状态提示信息。, |/ w& E* o' `  ?* P
不用创建初始状态和最终结束状态。
( T/ n+ S0 b$ U" l3 P$ B状态随着命令的删除而删除。3 ^% Z$ F5 w1 J* m; O, J
其次将代理添加到状态中:4 T) u3 d$ y, P7 E$ J  U" S
stStartState->AddDialogAgent(_daIndicationAgent);
+ M5 ^; \  o- E, k# v- Y注意:0 t/ Z, M6 }: L7 h' r0 ~
1.通常一个对话框代理只关联一个状态,但是可以在对话框的循环使用中管理几个状态,使用InitializeAcquisition 方法。
* Y; ?2 Y+ i, z- g. P3 c_daIndication->InitializeAcquisition();
& d: X+ W* h- ~; [' g. L1 t2.另一方面,多个对话框代理可以关联一个状态。
) r5 Q6 S% Z: ]! B! I. `' v- L+ HSourceState->AddDialogAgent(_peAgent);SourceState->AddDialogAgent(_daAgent);
$ S! _% C  x2 A# r: h+ `* u- t7 l3
4 r$ n3 j6 P+ ^; Z' U6 Z& [) Z状态转移(Transitions)
' p+ C, C1 m& ?( q* C状态转移连接着前后两个状态并触发相应指令(Actions),当代理被赋值时状态转移会触发,比如当用户选择一个点时状态转移会被触发,并且若返回值是true,那么状态转移函数就会执行。
0 Q: n, \  G3 |$ }5 }  S5 \状态转移的定义:$ u* Y" n- q1 [' k$ C4 @8 F
状态转移代码也是在BuildGraph中编写。
5 Y3 g. x" e' GCATDialogTransition*pFirstTransition
. K( [5 M7 Z# G3 U1 m+ ~2 T =AddTransition(SourceState,TargetState,...)
) r, i" Q6 ~% {( b- g1 `& m* {如利用两个点绘制一条直线的状态转移定义如下:! G* ~( A/ z% I  `& j
CATDialogTransition*pFirstTransition* C1 W! f$ L0 T) [7 Z* Q  _1 y
=AddTransition(stStartState,stEndState,...);CATDialogTransition*pSecondTransition
8 U9 R! x5 m! a  @% r( u) A =AddTransition(stEndState,NULL,...);
1 H! y' e. b3 O  H! x( t  oCATDialogTransition*pFirstTransition=AddTransition(stStartState,stEndState,
( {# m5 j# R0 n  [3 ~AndCondition(IsOutputSetCondition(_daIndication),        Condition((ConditionMethod)&CAADegCreateLineCmd::CheckStartPoint)),  ...);CATDialogTransition*pSecondTransition=AddTransition(stEndState,NULL, % o: V+ q+ }4 c* Y" i
AndCondition(IsOutputSetCondition(_daIndication),         Condition((ConditionMethod)&CAADegCreateLineCmd::CheckEndPoint)),          ...);- A/ [% H: F7 H" y
状态转移的第一个参数为转移之前的状态,第二个参数为转移之后的状态,第三个参数为判断是否转移的条件,第四个参数为转移条件满足时执行的转移函数,这儿的转移函数为对输入点的检查。0 n5 c" S# N' t5 o; B
4
" r3 J1 U, V& L4 J小结
8 ^. _8 f! {4 E' |; O1 ~: Z+ T状态命令创建步骤:- I0 c0 i+ G1 O" \* E% ?" M
声明和定义代理(CATPathElementAgent/CATIndicationAgent)
8 ^2 d3 B- `3 b% u$ ?; b添加代理行为(SetBehavior)
9 q0 x% I: B# G7 }2 X创建状态(CATDialogState)8 p" w  D! E/ X! x
将代理添加到状态中(AddDialogAgent)" S) ]7 K  u& @' n$ G# F7 w
创建状态转移(Transitions)
0 p7 n! ^) o  M' b( a1 j6 E9 y创建指令函数(Actions)5 L! U4 ^+ R0 I* ~
释放选择代理指针,命令结束的时候应该在析构函数或者取消中添加释放选择指针的代码。如下所示:
% A: r0 y+ Q$ Q* wCAADegCreatePlaneCmd::~CAADegCreatePlaneCmd()
/ v2 `4 \& J1 ^9 l{
8 ?# A( q1 f( \1 D ...& u( a6 A' Z- d. G+ T
if(_daPathElement)_daPathElement->RequestDelayedDestruction();
0 @$ W: X9 c9 f2 e: K; l _daPathElement=NULL;
5 C" _' @1 j  x  ^4 d ...1 w6 k. H. T/ S3 F1 @9 r" E! ?
核心代码格式如下:
3 w, F& b) y7 O7 h4 yvoidCAADegCreateLineCmd::BuildGraph()* x3 E/ X' e. Z# G1 n
{' z2 R3 O5 D, A9 B0 Q9 y
//代理
4 e% [- j( n+ D  }9 ~. U# X _daIndication=newCATIndicationAgent("PointIndication");  s2 z7 Z1 U  h' D. x0 k
_daIndication->SetMathPlane(_ProjPlane);$ F! w& `6 u& L! T/ x$ |' w6 f
//状态
7 s" k2 b( D; k CATDialogState*stStartState=GetInitialState("stStartPointId");- r  u1 f/ L& ]7 T+ }6 Y/ I" \
CATDialogState*stEndState=AddDialogState("stEndPointId");- ]. S0 _1 H9 G2 G
stStartState->AddDialogAgent(_daIndication);
( W/ d5 ^: j) L2 G stEndState->AddDialogAgent(_daIndication);7 |& l/ Q  a" H" p0 [
//状态转移
  U( c: S" \& d; M' @ CATDialogTransition*pFirstTransition=AddTransition(stStartState,stEndState,
% G7 ^% Y- ?% D& G8 H2 W                AndCondition(IsOutputSetCondition(_daIndication),
5 J1 M) h0 @3 d                 Condition((ConditionMethod)&CAADegCreateLineCmd::CheckStartPoint)),  ) }8 r/ ?" a& p
   Action((ActionMethod)&CAADegCreateLineCmd::CreatePoint));
- L, m( d0 A9 j
6 x3 i1 T0 O: ^ CATDialogTransition*pSecondTransition=AddTransition(stEndState,NULL,( k! A6 u: F/ a: e' N, m: r
    AndCondition(IsOutputSetCondition(_daIndication),8 f: O) F% e7 f  v3 c/ O- g/ s
                 Condition((ConditionMethod)&CAADegCreateLineCmd::CheckEndPoint)),   " h* M  ?, ^/ Q  g. m: c0 a
Action((ActionMethod)&CAADegCreateLineCmd::CreateLine));
* H/ ^& Q) {4 X* ~5 A/ i}7 {0 }! U& c' A4 w& Y( G: ]2 F

. f4 H# S9 A$ E9 m: m6 J* O# Z) y本文旨在说明状态命令的定义和编程过程,重点是基于状态机制,明确代理/状态/状态转移的定义。在了解基本概念的基础上,仍要结合百库全书中技术文章和案例熟悉其用法,欢迎大家在这块开发中提供相关意见和建议。% ~( B2 Y7 V! A0 p/ ]! Z" \
2 }# [. P8 x: M+ X( p

" w4 n; P5 i' V) I( o: X
上海点团信息科技有限公司,承接UG NX,CATIA,CREO,Solidworks 等CAx软件,Teamcenter,3D Experience等PLM软件,工业4.0数字化软件的实施\二次开发\培训相关业务,详情QQ 939801026 Tel 18301858168 网址 doTeam.tech
回复

使用道具 举报

发表回复

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

返回列表 本版积分规则

  • 发布新帖

  • 在线客服

  • 微信

  • 客户端

  • 返回顶部

  • x
    温馨提示

    本网站(plmhome.com)为PLM之家工业软件学习官网站

    展示的视频材料全部免费,需要高清和特殊技术支持请联系 QQ: 939801026

    PLM之家NX CAM二次开发专题模块培训报名开始啦

    我知道了