news 2026/10/14 17:03:11

CAD Sketcher 建模工具深度解析:用节点修改器实现可编辑的 Extrude、Revolve、Array 与 Boolean

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CAD Sketcher 建模工具深度解析:用节点修改器实现可编辑的 Extrude、Revolve、Array 与 Boolean
  • 桌面应用
  • 3D建模

【免费下载链接】CAD_Sketcher

Constraint-based geometry sketcher for blender

项目地址:https://gitcode.com/gh_mirrors/ca/CAD_Sketcher
点击查看免费下载

CAD Sketcher 的建模工具(Modeling Tools)作用于对象而非草图实体:它们在被操作对象上添加一个节点组(Geometry Nodes)修改器,使生成的实体始终跟随其来源草图变化,之后仍可在修改器面板中随时调整。读完本文,你将掌握 Extrude、Revolve、Linear/Circular Array、Boolean 四类工具的操作流程、全部参数含义与默认值,以及它们背后的节点图构建、布尔求解器选择与 Auto Boolean 自动检测机制的源码级原理。

建模工具的共同工作方式

建模工具与草图绘图工具的关键区别在于作用层级:草图工具编辑约束求解器管理的 2D 实体,而建模工具把 2D 截面"抬升"成 3D 实体,且以非破坏性的节点修改器形式实现。

  • 入口:在无活动草图时从视口工具栏(toolbar)触达,或在 Object Mode 下任意位置使用其全局快捷键(见 docs/content/tools.md 的 Global Shortcuts 一节:Ctrl+Shift+EExtrude、Ctrl+Shift+RRevolve、Ctrl+Shift+DArray、Ctrl+Shift+BBoolean)。
  • 选取语义:每个工具优先拾取当前已选对象;完成后自动返回 Select 工具(源码中即return_to_tool = BLENDER_SELECT_TOOL,见 operators/modifiers.py)。
  • 可修正性:所有交互中设置的数值,既可以在工具结束后的 redo panel 中立即修改,之后也可以在对象修改器堆栈里继续编辑。
  • 作用对象是 body:修改器挂在 body(草图实体化的承载对象)上而非草图 Curves 对象上。从源码结构看,这是刻意的:operators/modifiers.py中solid_target()会把选中的草图解析为其 body,因为 Blender 对 Curves 对象上产出网格的节点堆栈拒绝执行 apply 操作(源码注释引用了 issue #723)。

五个工具操作符均在 operators/modifiers.py 中定义,共享基类NodeOperator(L547),并在 declarations.py 中注册了各自的bl_idname:

工具类名bl_idname节点组名
ExtrudeView3D_OT_node_extrude(L790)view3d.slvs_node_extrudeCAD Sketcher Extrude
RevolveView3D_OT_node_revolve(L1295)view3d.slvs_node_revolveCAD Sketcher Revolve
Linear ArrayView3D_OT_node_array_linear(L1130)view3d.slvs_node_array_linearCAD Sketcher Linear Array
Circular ArrayView3D_OT_node_array_circular(L1422)view3d.slvs_node_array_circularCAD Sketcher Circular Array
BooleanView3D_OT_node_boolean(L1557)view3d.slvs_node_booleanCAD Sketcher Boolean

NodeOperator基类还实现了两个通用能力:

  1. redo panel 编辑回灌:read_props()(L676)在目标对象已带有本工具修改器时被调用,从修改器输入种子化操作符属性——因此对同一对象重复调用工具会基于当前值继续编辑,而不是重置为默认值。
  2. eyedropper 重定向:editable = True(L561)让 redo panel 中的指针字段支持重新拾取;_prepare_edit()(L745)记录旧对象与修改器名,main()会把修改器从旧目标搬走,避免留下孤儿修改器。

Extrude:拉伸

操作:选取一个截面(草图的 body 或一条曲线),拖动深度或输入数值。验证目标由is_2d_profile()决定:曲线对象或草图 body(operators/modifiers.py)。

参数(redo panel 中由draw_settings(),L864-L871 绘制):

参数属性默认值说明
Offsetoffset拖动确定拉伸深度;get_offset()(L822)把光标在对象局部空间的 Z 分量作为深度,所以负值向反方向拉伸
Mirror ExtrudemirrorFalse实体向截面两侧对称生长
AsymmetricasymmetryFalse允许两侧厚度不同
Asymmetry Distanceasymmetry_distance—Asymmetric 开启时另一侧的深度

实现要点(utilities/extrude_nodes.py):

  • 节点组按代码构建而非随附二进制资产,带版本号EXTRUDE_VERSION = 2;重建时先_snapshot_modifier_inputs()按 socket 名快照所有绑定修改器的输入值,重建后再按名字恢复——否则从旧资产升级会静默重置所有已有拉伸。
  • 输入接口为Size(默认 1.0)、Mirror Extrude、Asymmetry Override、Asymmetry Distance(默认 1.0)。Size的符号决定哪一侧被翻转,保证端帽法线始终朝外。
  • 未填充截面变成开放壁面:ensure_extrude_edge_walls()(L301)幂等地给组打补丁——当输入没有面(wire profile)时,把边界边沿局部 +Z 按Size挤出为开口壁,并遵守Mirror Extrude;已填充的输入走原面挤出路径不受影响。这就是文档所说"A profile that isn't filled is extruded into open walls rather than a solid"。
  • 接缝焊接在 Blender 5.2+ 上按 identity(Merge Points的 Merge ID)执行,完全与模型尺度无关;旧版本回退到 1 微米距离焊接——源码注释指出旧资产硬编码的 1 mm 会吞掉小模型上的亚毫米细节(issue #670)。

Revolve:旋转

操作:先选截面,再选旋转轴——可以是网格边、草图/曲线线段,或部件自身的轴(选轴过程中这些轴会画出来)。轴的拾取由PickedAxisMixin(operators/modifiers.py)实现:pick_element()(L1002)优先命中画出的基础平面轴(hit_test_axis),其次网格边,再次是曲线段(curve_segment_under_cursor,屏幕空间距离 12 px × UI scale);resolve_axis()(L1078)把拾取结果换算为对象局部空间的(origin, direction)并持久化到axis_origin/axis_direction属性,redo 无需重选轴。

参数:

参数属性默认值/范围说明
Angleangle2π,范围[-2π, 2π]扫掠角
Flip DirectionflipFalse反转旋转方向。set_props()(L1403)在写入时才对存储的轴取反,以便 redo panel 中切换生效
Angular Resolutionangular_resolution2°,min 0.5°,soft_max 90°每段最大角;步数随扫掠角自适应,圆度保持一致

实现要点(utilities/revolve_nodes.py,REVOLVE_VERSION = 7):该模块文档字符串完整解释了节点组策略——先把任意截面归一化为边界环(网格取其相邻面少于 2 的边界边转成曲线;曲线直接通过),从而无论草图的Fill开关状态如何,扫掠出的侧面都一致(修复了 issue #634:填充截面被三角化后内部边被扫进表面)。侧面用逐环挤出+逐角旋转扫掠而非单个网格,因此带孔截面能旋出真正贯通孔洞的实体;仅当截面已填充且扫掠小于 360° 时才补端帽;最后用有向体积(散度定理)判断朝向并整体翻正,法线恒朝外。

Arrays:线性阵列与圆形阵列

两种阵列共享一个工具栏按钮和一个快捷键Ctrl+Shift+D,启动时执行工具栏当前显示的那一个(keymaps.py 中NODE_TOOL_KEYS把NodeArrayLinear标记为GROUP键)。两者都用节点组复制被选对象,因此副本随原始对象联动变化。

Linear Array

操作:拖出方向和间距。拖动过程中,如果光标经过坐标轴、网格边或草图线段,行列会贴合该方向,从而可以把阵列"放"在已有方向上而非目测对齐——get_offset()(operators/modifiers.py)先检查axis_under_cursor()命中,用offset_along_axis()把拖动投影到该轴上;无命中时投影到过原点、面向视图的平面上自由拖动。

参数:

参数属性默认值说明
Countcount2,min 2副本数;拖动计数用get_count()(L1241),取abs(值)+2保证至少 2
Use Total Distanceuse_total_distanceFalse开启后距离解释为整排跨度而非副本间距
Count 2 / Offset 2count_2/offset_21 / 零向量第二方向;count_2 = 1保持单行。offset_2为零时,second_array_axis()(L1101)在对象 XY 平面内取垂直方向、沿用第一间距,得到正方形网格
Align Rotationalign_rotationFalse让副本随阵列方向旋转
Merge by Distancemerge/merge_distanceFalse/ 0.001焊接相遇的副本

节点组CAD Sketcher Linear Array由 utilities/array_nodes.py 构建;set_props()(L1245-L1272)把offset分解为方向向量与长度,分别写入Direction与Spacing / Total distance两个输入。

Circular Array

操作:先拾取旋转轴(与 Revolve 完全相同的轴拾取规则:网格边、草图线段或部件自身轴),再拖动出副本数。

参数:

参数属性默认值说明
Countcount6,min 2副本数
Angle / Total Angleangle2π,范围[-2π, 2π]副本分布的总扫掠角;use_total_angle关闭时则为相邻副本间的转角
Use Total Angleuse_total_angleTrue上表 Angle 的语义开关
Align Rotationalign_rotationTrue像螺栓圆一样让每个副本随阵列旋转;关闭时副本绕行时保持原朝向
Merge by Distancemerge/merge_distanceFalse/ 0.001焊接相遇的副本

节点组构建见 utilities/circular_array_nodes.py;轴与中心分别写入Axis/Center输入(set_props(),operators/modifiers.py)。

Boolean:布尔运算

操作:选中接收布尔的 body 与 cutter(刀具),可以事先同时选中两者(body 为 active),也可以在视口中依次拾取——操作符声明了两个对象状态:Object(body)与Cutter(operators/modifiers.py)。

参数:

参数属性默认值说明
OperationoperationDifferenceDifference/Union/Intersect
Boolean Solverboolean_solver由偏好决定(见下)Exact(稳健但慢)或Manifold(快,仅接受干净闭包);选项来自 utilities/boolean_nodes.py 的SOLVER_ITEMS
Self Intersectionself_intersectionFalse仅 Exact 求解器有效,代价约为 5 倍求值时间
Hole Toleranthole_tolerantFalse仅 Exact 求解器有效
Cutter Displaycutter_displayAUTOAUTO自动规则 /WIRE线框 /SOLID保持实体着色

关键实现:

  • 修改器挂在 body 上、每个 cutter 一个:boolean_modifier_name()生成CAD_Sketcher Boolean {cutter.name}(operators/modifiers.py),多个布尔在同一 body 上叠放而非互相覆盖;read_props()还能识别既有布尔并回灌参数。
  • 循环依赖防护:creates_boolean_cycle()(L211)沿 body 已有的 cutter 链做 DFS,若 cutter 已(间接)依赖 body——包括cutter is body的零长度环——则拒绝创建。源码注释说明:闭合 depsgraph 依赖环会导致 Blender 崩溃,main()(L1708)在创建修改器前就拦截并给出 WARNING。
  • 求解器默认值:新布尔的默认求解器是扩展偏好 base/preferences.py 中的boolean_solver,当前仓库默认值为Manifold(源码注释称其约快 7 倍,且处理不了的操作数会自动回退);但View3D_OT_node_boolean的invoke()(L1643)每次从偏好取新值,避免沿用操作符上次记住的值。_solver_for()(L277)在运行期做最终裁决:若选了 Manifold 但 body 或 cutter 任一不是闭包(is_closed_solid(),utilities/boolean_targets.py 逐边校验每条边恰好被两个面共享),强制改用Exact——否则 Manifold 会静默丢弃非闭包操作数,让"用实体切平面截面"变成空结果。已保存文件在载入时由repair_solver_choice()(utilities/boolean_nodes.py)执行同样的修复。
  • 修改器输入的双版本兼容:Blender ≤ 5.1 把节点修改器输入存为 ID 属性(modifier["Input_2"]),5.2 起改为 RNA 接口modifier.properties.inputs.<id>.value;set/get/draw_modifier_input()(operators/modifiers.py)统一封装了这个分叉,这也是所有工具都能兼容两个版本读写输入的原因。
  • Cutter 显示规则:实体 cutter 会遮住布尔结果,所以一旦参与切割,其视口显示默认切换为线框或隐藏(cutter_display默认AUTO);设为Solid可保持实体着色。实际执行规则在 utilities/part.py 的update_cutter_display():切割归属于部件的 cutter 被hide_viewport隐藏(眼形hide_set不够,因为实例化副本仍会画出它),跨多个部件切割的 cutter 或"想切但够不到任何 body"的 cutter 保持线框可见——用户需要找到它。cutter 在部件/装配体中如何呈现与随行,另见 docs/content/parts.md。

Automatic Booleans:自动布尔

行为:新建的 Extrude 或 Revolve 若与既有 body 重叠,会立即被布尔进这些 body——在面上画一个 pocket 直接切入所依附的实体,无需第二步。工具的 redo panel 提供Boolean下拉(None/Difference/Union/Intersect,见BOOLEAN_TOOL_OPERATIONS,operators/modifiers.py)用于选择或取消运算;Extrude/Revolve 工具设置栏中的Auto Boolean开关(scene.sketcher.use_auto_boolean,定义于 model/group_sketcher.py,默认True,UI 挂接在 workspacetools/extrude.py 与 workspacetools/revolve.py)关闭自动检测,让新实体保持独立——此时仍可在 redo panel 手动指定运算。

检测机制(BooleanFromToolMixin.finish_booleans(),operators/modifiers.py + utilities/boolean_targets.py)由两类信号驱动:

  1. 来源优先(Provenance):画在网格面上的草图,其 workplane 空物体记录了源 body(sketch_source_body()读取face_anchor写入的slvs_wp_source)。该 body 无条件成为首个目标——即"push/pull 你所画的实体"。
  2. 空间重叠:overlapping_bodies()(L196)先对候选做廉价的世界空间 AABB 预筛(_world_bounds_map直接读bound_box,不建网格),再用BVHTree.overlap做精确相交测试;候选集由candidate_bodies()排除 cutter 自身、隐藏对象及会闭合依赖环的对象。几何通过 depsgraph 实例单次批量读取(_world_geometry_map),因此对"节点修改器从 Curves 对象产出网格"的草图同样有效。

默认运算的推断(default_operation(),utilities/boolean_targets.py):有来源 body 且offset > 0(向外推)时默认Union(加材料),向实体内推(offset < 0)或无来源时默认Difference。这只是默认值,用户覆盖始终生效:redo 时每次重新检测目标,但逐项的 enabled 开关和手动指定的 operation 会被保留;拉得更长的拉伸若新够到某 body,也会自动补上该目标。

性能上,整轮检测只取一次 depsgraph 求值(finish_booleans()中注释解释了为何不能在写第一个修改器后再取新 depsgraph),重叠测试用的 numpy 批量读取(_mesh_arrays)取代逐元素 RNA 访问,据源码注释这是此前每次鼠标移动时最昂贵的开销。相关行为由 testing/test_boolean_targets.py 覆盖,其中包含use_auto_boolean开/关两种路径的测试。

小结:参数与源码索引

关注点文档/源码位置
工具操作与快捷键(Ctrl+Shift+E/R/D/B)docs/content/tools.md、keymaps.py
五个操作符与共享基类operators/modifiers.py
拉伸节点组(含开放壁面补丁)utilities/extrude_nodes.py
旋转节点组(边界环归一化、端帽、朝向修正)utilities/revolve_nodes.py
线性/圆形阵列节点组utilities/array_nodes.py、utilities/circular_array_nodes.py
布尔节点组与求解器定义utilities/boolean_nodes.py
自动布尔目标检测与默认运算utilities/boolean_targets.py
新布尔默认求解器偏好(当前默认 Manifold)base/preferences.py
Auto Boolean 场景开关model/group_sketcher.py
部件中的 cutter 显示规则utilities/part.py、docs/content/parts.md
  • 桌面应用
  • 3D建模

【免费下载链接】CAD_Sketcher

Constraint-based geometry sketcher for blender

项目地址:https://gitcode.com/gh_mirrors/ca/CAD_Sketcher
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:02:57

嵌入式面试必问:UART、I2C、SPI三大串行协议核心考点全解析

做了好几年嵌入式开发&#xff0c;也断断续续当过技术面试官&#xff0c;我有个很深的感触&#xff1a;每次聊到底层通信协议&#xff0c;候选人的差距几乎是瞬间拉开的。有的人能把UART、I2C、SPI的时序图画得明明白白&#xff0c;有的人背了一堆概念却说不出“为什么UART要事…

作者头像 李华
网站建设 2026/10/14 17:02:44

手把手教你用Python和FastAPI封装图书馆座位预约系统API

简介&#xff1a;面向Python与Golang开发者的济南大学图书馆座位预约系统API开源项目&#xff0c;针对图书馆选座场景中的HTTP接口调用与自动化预约问题&#xff0c;提供一套可运行的爬虫级RESTful客户端实现。包体共9个文件&#xff0c;包括4个Python脚本、2个Go文件以及READM…

作者头像 李华
网站建设 2026/10/14 17:02:34

华为S5700交换机固件批量升级:SFTP与自动化脚本实践

1. 为什么批量升级固件这件事值得认真对待手里管着几十台接入层交换机&#xff0c;每次固件升级都是一场硬仗。厂商发来安全通告说某个版本修复了若干缺陷&#xff0c;你看着机房里那一排排设备&#xff0c;心里盘算的是&#xff1a;一台一台手动传固件、改启动项、重启、验证&…

作者头像 李华
网站建设 2026/10/14 17:02:20

RabbitMQ实战:从演示环境到生产级消息可靠传输的桥梁

简介&#xff1a;这是一份演示MFC应用集成RabbitMQ的示例工程&#xff0c;由开发者根据网上零散资料自行梳理完成&#xff0c;目标是在Windows桌面程序中实现消息队列的发送与接收。项目遵循AMQP协议&#xff0c;通过RabbitMQ客户端库&#xff08;rabbitmq.4.lib/dll&#xff0…

作者头像 李华
网站建设 2026/10/14 17:01:15

手动搭建MySQL MHA高可用架构:从主库宕机到自动切换实战

1. 为什么手动搭建MHA依然值得学1.1 从一次主库宕机说起凌晨两点&#xff0c;监控告警响了&#xff0c;主库连接数直接掉零。当时我们那套业务用的是单点MySQL&#xff0c;没有做任何高可用。运维同学从被窝里爬起来&#xff0c;手动切从库、改配置、重启服务&#xff0c;前后折…

作者头像 李华