桌面应用3D建模【免费下载链接】CAD_SketcherConstraint-based geometry sketcher for blender项目地址https://gitcode.com/gh_mirrors/ca/CAD_Sketcher点击查看免费下载CAD Sketcher 为 Blender 引入了一个以有状态操作为核心的交互框架每一个绘图/约束工具都被拆解为若干顺序推进的状态Pointer/Property配合选择—执行—部分选择的三种混用范式、连续绘制、即时执行、复用 Blender 原生吸附以及自动约束推断形成一套比 Blender 默认先选择再操作更贴近参数化 CAD 工作流的交互体系。下文围绕 docs/content/interaction_system.md 中给出的交互模型逐一说明每个机制的行为、配置入口、Shift 快捷键语义并结合 stateful_operator/、operators/、utilities/view.py、model/group_sketcher.py 等源码给出可实现细节和验证路径。1. 为什么 CAD Sketcher 需要一套自己的交互系统Blender 的默认选择模型偏向先选择、再操作用户先选中目标实体再触发一个作用于已选集合的算子。而 CAD Sketcher 的工具画线、圆、矩形、弧、尺寸、约束……往往只作用于 12 个元素且元素顺序、类型、先后关系本身就是语义——例如起点必须先于终点、圆心必须先于半径。默认的选择—执行范式在这种语境下并不理想。正如 docs/content/interaction_system.md 开篇所述本扩展因此defines a generic interaction system which allows for a powerful and flexible workflow把工具的输入流程抽象为状态机让用户可以按需混用不同的选择/执行范式。从源码结构看这套交互系统由三层构成通用状态机stateful_operator/state_machine.py 的_StateMachineMixin负责状态定义、索引推进、check_props与get_state_pointer/set_state_pointer的调度Blender 集成层stateful_operator/integration.py 的StatefulOperator接入原生Object、MeshVertex/Edge/Polygon指针、原生数值输入和选择预填扩展层operators/base_stateful.py 的GenericEntityOp叠加 CAD Sketcher 特有的CurveRef/SlvsGenericEntity指针、Shift 语义、自动约束校验与联合约束推断。这套分层允许每个具体工具View3D_OT_slvs_add_*只声明自己的状态表和少量回调而交互的推进、数值编辑、预览重建、快照与回滚都由基类负责。2. 有状态操作Stateful Operations2.1 状态机模型与 Circle 工具示例docs/content/interaction_system.md给出了 Circle 工具的经典例子Since a circle is represented by its center and radius the tool will have two states. One to define the center element and one to set the radius.在 operators/add_circle.py 中这段描述直接翻译为states元组states ( state_from_args( Center, descriptionPick or place circles center point., pointerct, typestypes_point_2d, ), state_from_args( Radius, descriptionSet circles radius., propertyradius, state_funcget_radius, interactiveTrue, allow_prefillFalse, ), )要点第一个状态Center是指针pointerct类型限定为types_point_2d由用户在视口中拾取或放置第二个状态Radius是属性propertyradius并且interactiveTrue意味着它的值随鼠标位置实时变化state_funcget_radius指定了从屏幕坐标到半径的换算见 operators/add_circle.py 的get_radius先取工作平面坐标若吸附命中的是EDGE/EDGE_MIDPOINT则把半径对齐到该线段上、VERTEX/FACE_MIDPOINT则对齐到该点再取到圆心距离allow_prefillFalse表示半径不接受选择→调用范式的预填用户总是通过拖动/数值输入完成。state_from_args是 stateful_operator/state.py 提供的便捷构造器底层落到OperatorState这个frozendataclassstateful_operator/state.py字段包括name / description / property / pointer / types / interactive / use_create / state_func / pick_element / create_element / check_pointer / allow_prefill / parse_selection / optional / axis_lock等——几乎每个字段都在 stateful_operator/docs.md 里给出了回调应实现什么的约定。2.2 状态目标Pointer 与 Property交互文档明确区分了两类目标且允许同时存在Pointer一次选择。Pointer 状态会提示用户拾取一个符合types的元素Property一个值int / float / 位置向量。Property 状态对应算子的一个可编辑 RNA 属性。两者同时定义时property用于新建pointer用于命中已有的——正是没有悬停就放置新点有悬停就复用旧点这套常用逻辑。在实现上Pointer 与 Property 并非互斥的两套机制而是通过_state_data[index]这个每状态字典统一承载Pointer 状态保存我拾到什么类型、什么 id/索引——stateful_operator/integration.py 的PointerKind系列把Object、MeshVertex/Edge/Polygon三种原生指针抽象成handles/resolve/store/type_registryCAD Sketcher 层的 operators/base_stateful.py 又叠加了CurveRefPointRef/LineRef/ArcRef/CircleRef等见 model/curve_ref.py和SlvsGenericEntity两类指针并在register_properties中动态注册ptr0_kind/ptr0_existing/ptr0_name/ptr0_index等隐藏属性用于redo 面板的持久化回放否则_state_data是瞬时的无法跨越 execute 路径Property 状态则通过state_func鼠标坐标→属性值或原生NumericInput键盘输入更新 RNA 属性。Pointer 状态的拾取回调由pick_element实现——默认在 stateful_operator/integration.py 里做射线/网格元素拾取扩展层 operators/base_stateful.py 的pick_element会先尝试父类原生网格/对象再回退到get_hovered(context, *state.types)——即当前视口 hover 到的 sketch 元素这就是为什么在 CAD Sketcher 中点击空白就是放新点、点击已有元素就是复用。2.3 状态推进与校验状态机本身非常克制_StateMachineMixinstateful_operator/state_machine.py只负责state属性的读写与_index_from_state索引换算state_data/get_state_data(index)的按状态字典get_property(index)解析当前状态对应哪个/哪些 RNA 属性支持字符串、列表、可调用且会级联到算子自己的state_property(index)方法get_func(state, name)三级回调解析state.name为可调用 → 直接用为字符串 → 查算子方法都没有 → 用算子的默认方法check_props()校验每个非optional状态都已就位Pointer 已 set、Property 的is_property_set为 Trueis_in_previous_states(entity)判断当前实体是否已被前面的状态占用用于避免拾到同一个点两次。推进循环由 stateful_operator/logic.py 的StatefulOperatorLogic.invoke / modal / check_event / evaluate_state / _end完成check_event里明确区分点击确认与拖拽释放确认LEFTMOUSE PRESS与LEFTMOUSE RELEASE这直接支撑了拖拽出尺寸的交互拖拽长度即 Property 值。2.4 状态持久化与 Redo 面板一个容易忽略但工程上很关键的点_state_data是运行时字典execute路径redo中它是空的。为了让 Redo 面板可以显示我选的是哪条线并提供 EYEDROPPER 重选按钮stateful_operator/integration.py 的register_properties会为每个 pointer 状态注册隐藏的ptr{i}_kind/existing/name/index属性set_state_pointer在implicitTrue时把可序列化标识写入这些属性get_state_pointer(implicitTrue)反方向还原_pointer_display再把 id 翻译成 line: Line_3 之类的可读标签配合 icon_manager.py 的实体图标。Redo 面板的绘制逻辑在 stateful_operator/integration.py 的draw / _draw_state_row / _draw_repick_button中edit_statei允许用户回到第 i 个状态重挑一次而保留其余输入。3. 选择与操作三种可混用的范式docs/content/interaction_system.md 归纳了三种使用范式且强调可以任意混用Select → Invoke OperationBlender 的标准路径先选择再触发工具Invoke Operation → Select先按快捷键/点击工具再在视口里逐个挑Partial Select → Invoke → Select rest或填参数部分元素提前选、剩余在工具内补。3.1 即时执行Immediate Execution第三种范式的极端形式如果调用工具时当前选择已经满足所有非 optional 状态就直接执行完不必进入 modal。这在 docs/content/interaction_system.md 里被单独强调为Immediate Execution并注明仅通过快捷键调用工具时才会触发。实现链路算子声明了wait_for_input: BoolProperty(defaultTrue)stateful_operator/logic.py通过wm.tool_set_by_id 快捷键进入工具时stateful_operator/invoke_op.py 的View3D_OT_invoke_tool.execute会显式把wait_for_inputTrue传给算子的INVOKE_DEFAULTstateful_operator/logic.py 的invoke中wait_for_inputTrue分支先调prefill_state_props(context)按gather_selection 每状态的parse_selection填指针随后if not self.executed and self.check_props(): self.run_op(context)——如果check_props直接通过就不会进入modal_handler_add工具即点即完成。反之wait_for_inputFalse工具点击本身确认首状态的分支则会把当前LEFTMOUSE PRESS坐标当作第一个 Property 的初始值。这就是为什么先框选一条线再按 H水平约束快捷键能一步完成约束——选择被预填check_props通过run_op直接跑。3.2 运行中切换工具switch与invoke_toolInvoke → Select和Partial Select → Invoke之所以能混用依赖于运行中的算子能把快捷键让给 keymap。这一点由 stateful_operator/utilities/switch.py 实现它给每个 keymap 项分类为SKIP / SWITCH / FORWARD / BLOCK / CANCEL五种语义然后按 Blender 原生顺序先工具 keymap后Object Mode兜底取第一个能动作的调用view3d.invoke_tool的项如果指向的是当前正在运行的算子则BLOCK不重启如果指向一个可用的工具则SWITCH结束当前算子并把事件透传否则SKIP或当fallthroughTrue时透传绑定到其它工具算子的项走register_switch_operator注册的表keep_runningTrue的如设置切换用FORWARD继续跑否则SWITCH绑定到ed.undo的项CANCEL等价 Esc。这套机制是画线到一半按 Arc 快捷键能继续画弧下一节的底层前提。4. 数值编辑Numerical Edit文档说明When the state property is a set of multiple values (e.g. XYZ Location) they will be treated as sub-states, meaning you can iterate (TAB) through them and enter values sequentially.对应实现是 stateful_operator/utilities/numeric.py 的NumericInput_buffer: dict[substate_index - str]为向量的每个分量各留一个字符串缓冲substate_count prop.array_lengtharray_length1时按向量分量循环TAB调iterate()轮换evaluate_event支持BACK_SPACE、MINUS / NUMPAD_MINUS、单位输入is_unit_input→get_unit_value和普通数字_validate会把空字符串 .自动补成0.输入完成RET/NUMPAD_ENTER后由 stateful_operator/keymap.py 的parse_numeric结合bpy.utils.units.to_value把字符串按算子属性的unit如DISTANCE换算成内部单位避免输入 10mm 却按米解释。状态栏会实时把当前子分量用*前缀标出stateful_operator/logic.py 的set_status_text让用户知道 TAB 到第几个了。对于单值 Property如半径则直接用一次RET提交。5. 描述信息Tooltip 与状态栏文档建议学习新工具先看 tooltip——CAD Sketcher 把整个状态表写进了工具 tooltip。这条链路是算子通过bl_description或自定义description(context, item)返回一段文本stateful_operator/utilities/description.py 的stateful_op_desc(base, *state_descs)把States:段落拼出来state_desc(name, desc, types)追加Types: [SlvsPoint2D, ...]GenericStateTool.bl_descriptionstateful_operator/tool.py在工具注册期把这段文本从算子的bl_idname反查回来运行中set_status_text每帧把当前状态的name / description / types 数值缓冲 轴锁信息写到 3D 视口状态栏。Pointer 状态的types元组会原样出现在 tooltip 里——这就是文档里for pointer states this will additionally display the accepted types that can be picked的来源pick_types_labelstateful_operator/utilities/description.py还提供了 line or point 这种可读化拼法供 UI 复用。6. 连续绘制Continuous Drawing文档描述The line and arc tools keep drawing: once a segment is placed the next one starts from its end point, sharing that point rather than putting a second one on top of it. The run ends when the segments end lands on existing geometry (which is how a loop is closed), or with Esc.以及运行途中按另一个工具的快捷键能从当前链尾继续。这一整套由 operators/placement.py 的ChainDraw混入 stateful_operator/utilities/continuation.py 的链点传递 stateful_operator/utilities/switch.py 的快捷键透传共同实现。6.1 链内继续共享终点而不是叠加新点ChainDraw的关键判定是continue_draw()def continue_draw(self) - bool: last self._state_data[self._last_pointer_index()] if last.get(is_existing_entity): return False return not placement_of(last).coincident含义只有当上一段的端点是本链自己放出来的新点既不是拾取的既有元素is_existing_entity也没有产生 coincident 约束时下一段才从它继续。落到既有几何拾取或 coincident 放置会结束整次运行——这正是文档里which is how a loop is closed的实现把链尾放回到已有顶点上链自然合拢且不会产生重顶点。线工具在 operators/add_line_2d.py 中accepts_chainTrue继承自ChainDraw并默认continuous_drawTrue弧工具在 operators/add_arc.py 也启用了它。工具头部的 Continuous Draw 复选框来自工具draw_settings例如 workspacetools/add_line2d.py 中layout.prop(props, continuous_draw)。6.2 跨工具继续continuation.publish / take难点在于运行中的算子在被另一个快捷键终止时内存中的端点无法直接交给下一个算子前一个算子已经不存在了。stateful_operator/utilities/continuation.py 用一个模块级_offer单槽位解决这个问题publish(values, kind, scope)由 stateful_operator/logic.py 的_offer_chain在链已经 commit 过至少一段_chain_committedTrue且端点为新点时调用take(scope)下一个运行的算子在invoke里通过_seed_from_chain消费stateful_operator/logic.py——无论是否成功使用都会被丢弃避免陈旧的链点泄漏到后续无关的运行scope是算子所称的上下文对 CAD Sketcher 来说是当前 sketch id保证链点不会被带进另一个 sketch。accepts注册表register_accepting记录了哪些算子 id 能从链点继续stateful_operator/invoke_op.py 的_chain_member更进一步当多个工具共享同一个工具栏按钮如矩形变体、阵列且快捷键代表整个组时若当前存在待传递的链点就自动挑一个能继续链的成员启动例如从圆心弧切换到端点弧而不是把链断掉。对应测试见 testing/test_chain_draw.py覆盖链点只被消费一次不同 scope 拿不到从线切换到弧等回归场景。7. 吸附Snapping文档说明 CAD Sketcher 的吸附直接复用 Blender 原生 Snap 设置3D 视口标题栏的Snap磁贴 Snap To而不是另设一套开关。支持的四类目标与实现对应关系文档中名称Blender Snap To 元素实现中的typeVertexVertexVERTEXEdgeEdgeEDGEEdge CenterEdge CenterEDGE_MIDPOINTFace CenterFace CenterFACE_MIDPOINT入口函数是 utilities/view.py 的get_blender_snap_info(context, coords)global_data.snap_bypass为 True按住 Shift时直接跳过连ray_cast都不做未开tool_settings.use_snap或选中元素与{VERTEX, EDGE, EDGE_MIDPOINT, FACE_MIDPOINT}无交集时返回 None从拾取射线起点开始最多 16 次scene.ray_cast跳过正在绘制的 sketch 及其 bodyutilities/view.py 的snap_skipped_objects对应 issue #591——否则自己刚画的东西会挡住吸附以及隐藏对象命中MESH时走_screen_snap_candidates在命中面的局部顶点/边/面心做屏幕空间距离搜索避免全场扫描命中CURVES时走_curve_snap_candidates曲线没有可 raycast 的几何直接用 numpy 把控制点批量投到屏幕空间找最近段——同一份数据也被 hover gizmo 用保证两者一致候选按(priority, distance)取最小者返回附带type / world_point / world_edge / edge_vertices / object等键。文档同时提醒了两点源码里都有对应Only the object directly under the cursor is considered, which keeps snapping responsive even in dense scenes —— 即上文的命中面局部搜索且故意不启用 X-Ray 全几何扫描注释里明确提到 sketch 模式默认开 Auto Fade 会导致 x-ray 打开若据此判定范围会把每次鼠标移动都变成全场景扫描Face-center snapping to a shape built from several separate lines is not supported — that region only exists in the generated mesh, which cant be inspected while sketching —— 曲线对象没有可回读的生成网格所以 Face Center 只对单一闭合曲线如整圆有意义这在 utilities/view.py 的curve_segment_under_cursor中体现为按curve.points段对找最近段不做闭合区域中心计算。Shift 语义在 operators/base_stateful.py 的check_event里落地global_data.snap_bypass bool(event.shift) ... placement_of(self.state_data).skip_auto_constraints bool(event.shift)即按住 Shift 放置/微调 同时跳过吸附与跳过自动约束与文档 Hold Shift while placing or tweaking a point to temporarily bypass snapping 以及 Auto Constraints 节的 Hold Shift ... to skip the auto constraints for that step only 一一对应。8. 实时投影吸附Live Project Snaps文档说Snapping onto geometry outside the sketch normally keeps the reference: the element you snapped to is projected into the sketch, and the placed point is constrained to that projection, so editing or moving the source updates the sketch afterwards.这就是Live Project Snaps开关工具设置栏中默认开启——在 model/group_sketcher.py 中对应use_snap_project: BoolProperty(..., defaultTrue)。三种映射Snap 元素投影方式约束Vertex投影为构造点Coincident与投影点重合Edge投影为构造线Coincident点在线上可沿线滑动Edge Center投影为构造线Midpoint点保持在中点其他 snap 类型、或无法回溯到源元素的情况回退为静态点位置记录在放置那一刻源几何移动时它不动。实现链路集中在 operators/base_2d.py 的_link_placement / _decide_link / _apply_projection_decide_link先检查use_snap_project与skip_auto_constraintsShift 仍会覆盖它并复用已有的投影引用_is_projected_reference避免重复投影只有snap.type ∈ {VERTEX, EDGE_MIDPOINT, EDGE}且源对象是MESH / CURVES时才会请求投影对 meshevaluated顶点索引要经resolve_source_vertex_index映射回原始顶点见 utilities/projection_anchor.py对 curve 则控制点索引就是原始索引_apply_projection调project_mesh_vertex / project_mesh_edge生成构造实体constructionTrue把新投影的curve_id写回placement.hoveredEDGE_MIDPOINT还会把link_kind置为MIDPOINT并anchoredTrue防止自动水平/垂直约束和已被钉住的点打架最终约束由 operators/base_stateful.py 的add_coincident添加——投影产生的重合/中点约束不受 Auto Constraints 开关控制因为它本身就是吸附的活链接属于 snap 的一部分测试 testing/test_operators.py 的test_live_snap_does_not_require_auto_constraints专门验证了关掉 Auto Constraints 时投影coincident 仍然生成。PointPlacement数据类operators/placement.py把过去散落在state_data里的hovered / snapped / snap / snap_projected / snap_link_kind / snap_anchored / coincident / skip_auto_constraints收拢成一个类型化对象reset_link / link_existing提供每次鼠标移动的清场避免上一帧的旗标污染下一帧源码注释明确指出stale flags leaked from one mouse move into the next就是旧实现的问题。9. 自动约束Auto Constraints文档描述了三类自动推断Auto axis alignment接近水平/垂直的线段自动加Horizontal / VerticalAuto coincident新点落在旧点上 →Coincident落在 line/arc/circle 上 → 与曲线重合落在吸附边中点上 →MidpointAuto joints新段与既有段的连接方式——弧从线/弧平滑继续 →Tangent两线直角相接或直线延伸 →Perpendicular / Parallel。并强调约束只有在既能让 sketch 保持可解、又确实少了一个自由度时才会被保留——即已隐含的关系不再重复加约束。9.1 判定inference.relation_forTangent / Parallel / Perpendicular三者的判定集中在 operators/inference.pyTHRESHOLD 0.1 _ALIGNED math.cos(THRESHOLD) _SQUARE math.sin(THRESHOLD) def _relation(curved: bool, other: CurveRef, alignment: float) - Optional[str]: curved curved or _is_curved(other) if alignment _ALIGNED: return TANGENT if curved else PARALLEL if alignment _SQUARE and not curved: return PERPENDICULAR return None其中alignment |dot(direction_of_ref, direction_of_other)|是两个方向在共享点处有多平行的无向度量。阈值 0.1 与轴对齐判定的阈值刻意保持一致源码注释As wide as the axis alignments tolerance (see add_line_2d._alignment)。轴对齐本身由 operators/add_line_2d.py 的_alignment(vec)决定angle 0.1或 π-0.1→ HORIZONTALπ/2 ± 0.1→ VERTICAL零向量按水平处理。ordered_for(relation, ref, other)operators/inference.py处理了SlvsTangent的参数顺序Tangent 声明要求曲线在前因此线与弧相切会以弧、线的顺序保存——源码注释特别指出 Solvespace 会因参数类型不对直接让 Blender 中止这不是表面问题。9.2 校验add_auto_constraint的试解回滚真正决定是否保留一条推断约束的是 operators/base_stateful.py 的add_auto_constraintconstraint add(**kwargs) solve_system(context, sketchself.sketch, writeFalse) if self.sketch.solver_state OKAY: return constraint self.sketch.constraints.remove(constraint) solve_system(context, sketchself.sketch, writeFalse) return None要点用一次不写回writeFalse的求解读取solver_stateOKAY→ 约束一致且确实减少了一个自由度保留REDUNDANT_OK→ 什么都没约束住关系已隐含回滚其它OVER_CONSTRAINED等→ 会过约束回滚假设试加之前 sketch 是一致的绘制中的常见情况因此不需要额外做一次基线求解回滚后再跑一次writeFalse的求解来复位 solver 状态避免把失败标在既有约束上。回归测试在 testing/test_auto_constraints.pytest_rejected_constraint_is_removed_and_failure_flags_are_cleared验证被拒的 Vertical 会被移除、sketch 状态回到OKAY且既有约束不带failed标记test_rectangle_validates_auto_constraints_sequentially验证矩形四边按H/V/H/V顺序逐条过校验前一条占掉自由度后一条是否保留随之变化。9.3 联合约束与预览联动add_joint_constraintsoperators/base_stateful.py把joint_relations(sketch, ref)产出的每条(relation, other)依次投喂给add_auto_constraint——先落地的先占走自由度。更细的一处预览阶段就能看到即将生成的联合约束。线工具的preview_structureoperators/add_line_2d.py把_alignment(along)和_joint_key(...)拼进 preview key_joint_key调joint_relation_keyoperators/base_stateful.py算出当前悬停端若放置会生成什么约束的元组key 一变预览就会重建从而让约束在拖拽过程中即时可见而不是等到松手。joint_relation_key还刻意排除了正在被重建的预览 target否则每次重建都产生新 idkey 永远不同就无法原地更新预览了。9.4 开关与 Shift 语义全局开关scene.sketcher.auto_axis_constraintsdefaultTruemodel/group_sketcher.py在 Line / Rectangle / Circle / Arc / Point 等 sketch 工具的设置栏中显示为 Auto Constraints例如 workspacetools/add_line2d.pyuse_auto_constraints(context, state_data)operators/base_stateful.py 全局开关 AND 非本次 Shift 跳过确认点击时的 Shift 会被写入placement_of(state_data).skip_auto_constraints非粘滞松开即恢复——与文档 Hold Shift while placing or confirming a point to skip the auto constraints for that step only 精确对应。10. 交互系统的配置入口一览功能开关位置源码字段默认自动约束Auto Constraintssketch 工具设置栏scene.sketcher.auto_axis_constraintsTrue实时投影吸附Live Project Snaps绘图工具设置栏scene.sketcher.use_snap_projectTrue连续绘制Continuous Draw线/弧等工具头部算子属性continuous_drawTrue几何吸附Blender 3D 视口标题栏Snap/Snap Totool_settings.use_snap与 snap 元素Blender 默认临时跳过吸附/自动约束放置/微调时按住Shiftglobal_data.snap_bypass/placement.skip_auto_constraints—这些开关全部标注options{SKIP_SAVE}model/group_sketcher.py意在把它们视为运行期偏好而非随场景保存的设置工具栏的具体呈现由WorkSpaceTool.draw_settings负责例如 workspacetools/add_line2d.py 一次性列出三个复选框。11. 相关源码与测试索引交互文档docs/content/interaction_system.md状态机与状态定义stateful_operator/state_machine.py、stateful_operator/state.py、stateful_operator/docs.mdBlender 集成与原生指针stateful_operator/integration.py运行循环、数值输入、状态栏stateful_operator/logic.py、stateful_operator/utilities/numeric.py、stateful_operator/utilities/keymap.py描述/tooltip 拼装stateful_operator/utilities/description.py、stateful_operator/tool.py快捷键透传与即时执行stateful_operator/utilities/switch.py、stateful_operator/invoke_op.py连续绘制operators/placement.py、stateful_operator/utilities/continuation.py、operators/add_line_2d.py吸附utilities/view.py、drawing/snap.py、gizmos/preselection.py实时投影operators/base_2d.py、operators/base_stateful.py、utilities/projection_anchor.py自动约束operators/inference.py、operators/base_stateful.py、model/group_sketcher.py回归测试testing/test_auto_constraints.py、testing/test_chain_draw.py、testing/test_operators.py、testing/test_snapping.py、testing/test_joint_inference.py、testing/test_prefill.py、testing/test_redo_panel_states.py这套交互系统让 CAD Sketcher 的工具在选择顺序即语义的参数化绘图场景下同时保留了 Blender 用户熟悉的选择/快捷键习惯并通过指针—属性—状态机的抽象把拾取、放置、数值编辑、吸附、投影、约束推断这些看似零散的动作统一到一条可测试、可 Redo、可预填、可切换的运行主线上。赞分享桌面应用3D建模【免费下载链接】CAD_SketcherConstraint-based geometry sketcher for blender项目地址https://gitcode.com/gh_mirrors/ca/CAD_Sketcher点击查看免费下载相关推荐CAD Sketcher基于约束的Blender草图绘制扩展CAD Sketcher基于约束的Blender草图绘制扩展 CAD Sketcher是一款基于约束的草图绘制扩展专门为Blender设计。它允许用户通过定桌面应用3D建模SnappyMail dark mode使用体验护眼又高效的邮件浏览方式SnappyMail dark mode使用体验护眼又高效的邮件浏览方式 SnappyMail作为一款Simple, modern fast web baCAD Sketcher 开发测试指南在 Blender 内驱动有状态操作符的完整测试体系CAD Sketcher 开发测试指南在 Blender 内驱动有状态操作符的完整测试体系 CAD Sketcher 的官方开发文档 docs/conten桌面应用3D建模创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?