- 桌面应用
- 3D建模
【免费下载链接】CAD_Sketcher
Constraint-based geometry sketcher for blender
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 的工具(画线、圆、矩形、弧、尺寸、约束……)往往只作用于 1~2 个元素,且元素顺序、类型、先后关系本身就是语义——例如"起点"必须先于"终点"、"圆心"必须先于"半径"。默认的选择—执行范式在这种语境下并不理想。
正如 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 Operations)
2.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", description="Pick or place circle's center point.", pointer="ct", types=types_point_2d, ), state_from_args( "Radius", description="Set circle's radius.", property="radius", state_func="get_radius", interactive=True, allow_prefill=False, ), )要点:
- 第一个状态Center是"指针"(
pointer="ct"),类型限定为types_point_2d,由用户在视口中拾取或放置; - 第二个状态Radius是"属性"(
property="radius"),并且interactive=True意味着它的值随鼠标位置实时变化,state_func="get_radius"指定了从屏幕坐标到半径的换算(见 operators/add_circle.py 的get_radius:先取工作平面坐标,若吸附命中的是EDGE/EDGE_MIDPOINT则把半径对齐到该线段上、VERTEX/FACE_MIDPOINT则对齐到该点,再取到圆心距离); allow_prefill=False表示"半径"不接受"选择→调用"范式的预填,用户总是通过拖动/数值输入完成。
state_from_args是 stateful_operator/state.py 提供的便捷构造器,底层落到OperatorState这个frozendataclass(stateful_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_registry;CAD Sketcher 层的 operators/base_stateful.py 又叠加了CurveRef(PointRef/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 状态推进与校验
状态机本身非常克制:_StateMachineMixin(stateful_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为 True);is_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在implicit=True时把"可序列化标识"写入这些属性,get_state_pointer(implicit=True)反方向还原;_pointer_display再把 id 翻译成 "line: Line_3" 之类的可读标签(配合 icon_manager.py 的实体图标)。Redo 面板的绘制逻辑在 stateful_operator/integration.py 的draw / _draw_state_row / _draw_repick_button中,edit_state=i允许用户"回到第 i 个状态重挑一次"而保留其余输入。
3. 选择与操作:三种可混用的范式
docs/content/interaction_system.md 归纳了三种使用范式,且强调"可以任意混用":
- Select → Invoke Operation:Blender 的标准路径,先选择再触发工具;
- 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(default=True)(stateful_operator/logic.py); - 通过
wm.tool_set_by_id+ 快捷键进入工具时,stateful_operator/invoke_op.py 的View3D_OT_invoke_tool.execute会显式把wait_for_input=True传给算子的INVOKE_DEFAULT; - stateful_operator/logic.py 的
invoke中,wait_for_input=True分支先调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_input=False(工具点击本身确认首状态)的分支则会把当前LEFTMOUSE PRESS坐标当作第一个 Property 的初始值。
这就是为什么"先框选一条线,再按 H(水平约束)快捷键"能一步完成约束——选择被预填,check_props通过,run_op直接跑。
3.2 运行中切换工具:switch与invoke_tool
Invoke → 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或(当fallthrough=True时)透传; - 绑定到其它工具算子的项:走
register_switch_operator注册的表,keep_running=True的(如设置切换)用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_length:array_length>1时按向量分量循环,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_description(stateful_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_label(stateful_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 segment's 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_chain=True(继承自ChainDraw)并默认continuous_draw=True;弧工具在 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_committed=True)且端点为新点"时调用;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 元素 | 实现中的type |
|---|---|---|
| Vertex | Vertex | VERTEX |
| Edge | Edge | EDGE |
| Edge Center | Edge Center | EDGE_MIDPOINT |
| Face Center | Face Center | FACE_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 及其 body(utilities/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 can't 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(..., default=True)。三种映射:
| 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_constraints(Shift 仍会覆盖它),并复用已有的投影引用(_is_projected_reference),避免重复投影;- 只有
snap.type ∈ {VERTEX, EDGE_MIDPOINT, EDGE}且源对象是MESH / CURVES时才会请求投影;对 mesh,evaluated顶点索引要经resolve_source_vertex_index映射回原始顶点(见 utilities/projection_anchor.py),对 curve 则控制点索引就是原始索引; _apply_projection调project_mesh_vertex / project_mesh_edge生成构造实体(construction=True),把新投影的curve_id写回placement.hovered,EDGE_MIDPOINT还会把link_kind置为MIDPOINT并anchored=True(防止自动水平/垂直约束和"已被钉住的点"打架);- 最终约束由 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 / Vertical; - Auto coincident:新点落在旧点上 →
Coincident;落在 line/arc/circle 上 → 与曲线重合;落在吸附边中点上 →Midpoint; - Auto joints:新段与既有段的连接方式——弧从线/弧平滑继续 →
Tangent;两线直角相接或直线延伸 →Perpendicular / Parallel。
并强调"约束只有在既能让 sketch 保持可解、又确实少了一个自由度时才会被保留"——即"已隐含的关系不再重复加约束"。
9.1 判定:inference.relation_for
Tangent / Parallel / Perpendicular三者的判定集中在 operators/inference.py:
THRESHOLD = 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 alignment's 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_constraint:
constraint = add(**kwargs) solve_system(context, sketch=self.sketch, write=False) if self.sketch.solver_state == "OKAY": return constraint self.sketch.constraints.remove(constraint) solve_system(context, sketch=self.sketch, write=False) return None要点:
- 用一次不写回(
write=False)的求解读取solver_state:OKAY→ 约束一致且确实减少了一个自由度,保留;REDUNDANT_OK→ 什么都没约束住(关系已隐含),回滚;- 其它(
OVER_CONSTRAINED等)→ 会过约束,回滚;
- 假设"试加之前 sketch 是一致的"(绘制中的常见情况),因此不需要额外做一次基线求解;
- 回滚后再跑一次
write=False的求解来复位 solver 状态,避免把"失败"标在既有约束上。
回归测试在 testing/test_auto_constraints.py:test_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_constraints(operators/base_stateful.py)把joint_relations(sketch, ref)产出的每条(relation, other)依次投喂给add_auto_constraint——"先落地的先占走自由度"。
更细的一处:预览阶段就能看到即将生成的联合约束。线工具的preview_structure(operators/add_line_2d.py)把_alignment(along)和_joint_key(...)拼进 preview key,_joint_key调joint_relation_key(operators/base_stateful.py)算出"当前悬停端若放置会生成什么约束"的元组;key 一变,预览就会重建,从而让约束在拖拽过程中即时可见而不是等到松手。joint_relation_key还刻意排除了正在被重建的预览 target(否则每次重建都产生新 id,key 永远不同,就无法原地更新预览了)。
9.4 开关与 Shift 语义
- 全局开关:
scene.sketcher.auto_axis_constraints,default=True(model/group_sketcher.py),在 Line / Rectangle / Circle / Arc / Point 等 sketch 工具的设置栏中显示为 "Auto Constraints"(例如 workspacetools/add_line2d.py); use_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 Constraints) | sketch 工具设置栏 | scene.sketcher.auto_axis_constraints | True |
| 实时投影吸附(Live Project Snaps) | 绘图工具设置栏 | scene.sketcher.use_snap_project | True |
| 连续绘制(Continuous Draw) | 线/弧等工具头部 | 算子属性continuous_draw | True |
| 几何吸附 | Blender 3D 视口标题栏Snap/Snap To | tool_settings.use_snap与 snap 元素 | Blender 默认 |
| 临时跳过吸附/自动约束 | 放置/微调时按住Shift | global_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.md
- Blender 集成与原生指针: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_Sketcher
Constraint-based geometry sketcher for blender
相关推荐
CAD Sketcher:基于约束的Blender草图绘制扩展
CAD Sketcher:基于约束的Blender草图绘制扩展 CAD Sketcher是一款基于约束的草图绘制扩展,专门为Blender设计。它允许用户通过定
桌面应用3D建模SnappyMail dark mode使用体验:护眼又高效的邮件浏览方式
SnappyMail dark mode使用体验:护眼又高效的邮件浏览方式 SnappyMail作为一款Simple, modern & fast web ba
CAD Sketcher 开发测试指南:在 Blender 内驱动有状态操作符的完整测试体系
CAD Sketcher 开发测试指南:在 Blender 内驱动有状态操作符的完整测试体系 CAD Sketcher 的官方开发文档( docs/conten
桌面应用3D建模
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考