docs(runtime): 为特征执行入口补充中文注释
This commit is contained in:
@@ -320,71 +320,105 @@ def _revolve_axis(node: FeaturePlanNode, session: ExecutionSession) -> AxisSpec:
|
||||
|
||||
|
||||
def _shape_from_primary(node: FeaturePlanNode, session: ExecutionSession, *, sketch: dict[str, Any] | None = None) -> FeatureResult:
|
||||
# 主形状特征(拉伸 / 旋转)的统一入口:由草图生成实体并与当前主体做布尔合并或切除。
|
||||
|
||||
# 1. 取草图:优先使用外部传入的 sketch_override(阵列/镜像等重放场景),
|
||||
# 否则按 sketch_id 从会话草图表中取原始草图。
|
||||
selected_sketch = sketch or session.sketches.get(str(node.sketch_id))
|
||||
if selected_sketch is None:
|
||||
raise ValueError("primary feature has no resolved sketch")
|
||||
# 2. 从草图解析闭合轮廓区域(faces),没有闭合区域就无法生成实体。
|
||||
faces = session.adapter.faces_for_sketch(selected_sketch)
|
||||
if not faces:
|
||||
raise ValueError("sketch does not create a closed profile region")
|
||||
# 3. 按特征类型生成子实体:
|
||||
if node.atomic_id.startswith("extrude_"):
|
||||
# 拉伸:先按终止条件(盲孔/贯穿/至面/双侧等)求出位移向量,
|
||||
# 再对每个面沿每个向量做拉伸,得到实体列表。
|
||||
vectors = _extent_vectors(node, faces, selected_sketch, session)
|
||||
solids = [session.adapter.extrude(face, vector) for face in faces for vector in vectors]
|
||||
else:
|
||||
# 旋转:解析旋转轴并校验旋转角,然后绕轴旋转每个面得到实体列表。
|
||||
axis = _revolve_axis(node, session)
|
||||
angle = float(node.params.get("angle_deg") or 0.0)
|
||||
if angle <= 0:
|
||||
raise ValueError("revolve requires angle_deg > 0")
|
||||
solids = [session.adapter.revolve(face, angle, axis) for face in faces]
|
||||
# 4. 将所有子实体做布尔并(fuse)合并为一个工具体(tool)。
|
||||
tool = None
|
||||
for solid in solids:
|
||||
tool = session.adapter.fuse(tool, solid)
|
||||
if tool is None:
|
||||
raise ValueError("primary feature produced no solid")
|
||||
# 5. 与当前主体做布尔操作:
|
||||
if "cut" in node.atomic_id:
|
||||
# 切除类特征:要求已有主体,从主体上减去工具体(cut)。
|
||||
if session.body is None:
|
||||
raise ValueError("cut feature has no body")
|
||||
body = session.adapter.cut(session.body, tool)
|
||||
else:
|
||||
# 添加类特征:将工具体并到当前主体上(fuse),首个特征时 body 为 None 也能直接成立。
|
||||
body = session.adapter.fuse(session.body, tool)
|
||||
# 6. 登记新主体(更新拓扑、记录重放定义),并返回该特征的结果对象。
|
||||
session.register_body(node.feature_id, body, replay_node=node)
|
||||
return session.result(node)
|
||||
|
||||
|
||||
def _execute_reference_plane(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult:
|
||||
# 基准面特征(reference_plane)执行入口:从参数解析平面并登记为拓扑上下文。
|
||||
|
||||
# 1. 从特征参数 plane 中解析出平面定义 PlaneSpec(原点到法向)。
|
||||
plane = PlaneSpec.from_mapping(node.params.get("plane") or {})
|
||||
# 2. 将该平面注册到拓扑上下文,供后续特征(如草图基准、参考轴)引用。
|
||||
session.topology.register_context(node.feature_id, plane)
|
||||
# 3. 返回结果对象,并将该平面作为上下文一并携带。
|
||||
return session.result(node, context=plane)
|
||||
|
||||
|
||||
def _execute_reference_axis(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult:
|
||||
# 基准轴特征(reference_axis)执行入口:由参数直接定义轴,或由两个基准平面求交线得到轴。
|
||||
|
||||
# 1. 尝试直接取参数:若同时给出原点 origin_mm 与方向 direction,则直接构造轴。
|
||||
params = node.params.get("axis") or {}
|
||||
if params.get("origin_mm") and params.get("direction"):
|
||||
axis = AxisSpec.from_mapping(params)
|
||||
else:
|
||||
# 2. 否则从特征选择器中筛选出已解析的基准平面。
|
||||
planes = [session.resolve(selector) for selector in node.selectors if selector.get("kind") == "plane"]
|
||||
resolved = [item.record.value for item in planes if item.status == "resolved" and isinstance(item.record.value, PlaneSpec)]
|
||||
# 3. 校验:轴需要两个非平行的平面,不足两个则报错。
|
||||
if len(resolved) < 2:
|
||||
raise ValueError("reference axis requires two uniquely resolved planes")
|
||||
# 4. 用两平面法线叉积求交线方向;若方向长度接近 0 说明两平面平行,无法成轴。
|
||||
first, second = resolved[0], resolved[1]
|
||||
n1, n2 = first.normal, second.normal
|
||||
direction = vector_cross(n1, n2)
|
||||
squared_length = vector_dot(direction, direction)
|
||||
if squared_length <= 1e-18:
|
||||
raise ValueError("reference planes are parallel and cannot define an axis")
|
||||
# 5. 求交线上的一点:两平面到各自原点的垂距参与线性组合,得到交线上的最近点。
|
||||
d1 = vector_dot(n1, first.origin_mm)
|
||||
d2 = vector_dot(n2, second.origin_mm)
|
||||
point = vector_scale(vector_add(vector_scale(vector_cross(n2, direction), d1), vector_scale(vector_cross(direction, n1), d2)), 1 / squared_length)
|
||||
# 6. 由该点与归一化的交线方向组合成基准轴 AxisSpec。
|
||||
axis = AxisSpec(origin_mm=point, direction=vector_unit(direction, field_name="reference axis"))
|
||||
# 7. 注册为拓扑上下文,并返回结果对象(携带该轴)。
|
||||
session.topology.register_context(node.feature_id, axis)
|
||||
return session.result(node, context=axis)
|
||||
|
||||
|
||||
def _execute_sphere(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult:
|
||||
# 球体特征(sphere_add)执行入口:按球心与半径生成球体并并入当前主体。
|
||||
|
||||
# 1. 解析参数:半径 radius_mm 与球心 center_mm。
|
||||
radius = float(node.params.get("radius_mm") or 0.0)
|
||||
center = node.params.get("center_mm") or []
|
||||
# 2. 校验:半径必须大于 0,球心必须是三维坐标。
|
||||
if radius <= 0 or len(center) != 3:
|
||||
raise ValueError("sphere_add requires radius_mm and a three-dimensional center_mm")
|
||||
# 3. 由适配器创建球体实体。
|
||||
solid = session.adapter.sphere(radius, (float(center[0]), float(center[1]), float(center[2])))
|
||||
# 4. 球体与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。
|
||||
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node)
|
||||
return session.result(node)
|
||||
|
||||
@@ -423,13 +457,19 @@ def _hole_starts(
|
||||
|
||||
|
||||
def _execute_hole(node: FeaturePlanNode, session: ExecutionSession, *, wizard: bool = False) -> FeatureResult:
|
||||
# 孔特征(hole)执行入口:在指定宿主面上按孔规格生成切除工具,并从主体上减去。
|
||||
|
||||
# 1. 校验:孔是切除操作,必须先有主体。
|
||||
if session.body is None:
|
||||
raise ValueError("hole feature has no body")
|
||||
# 2. 确定宿主面 host_face:
|
||||
host_selector = node.params.get("host_face")
|
||||
if isinstance(host_selector, dict) and isinstance(host_selector.get("frame"), dict):
|
||||
# 若直接带 frame(平面定义),则以该平面为宿主,孔位按局部坐标解释。
|
||||
host = PlaneSpec.from_mapping(host_selector["frame"])
|
||||
positions_are_local = True
|
||||
else:
|
||||
# 否则从特征选择器中取 face,解析出宿主平面,孔位按世界坐标解释。
|
||||
selectors = list(node.selectors)
|
||||
if isinstance(host_selector, dict):
|
||||
selectors.append(host_selector)
|
||||
@@ -438,15 +478,19 @@ def _execute_hole(node: FeaturePlanNode, session: ExecutionSession, *, wizard: b
|
||||
raise ValueError("hole requires host_face selector or frame")
|
||||
host = _host_plane(session.resolve(selector))
|
||||
positions_are_local = False
|
||||
# 3. 解析孔规格 HoleSpec(直径、深度、类型等,wizard 模式提供额外默认值)。
|
||||
spec = HoleSpec.from_feature(node.atomic_id, node.params, wizard=wizard)
|
||||
# 4. 确定孔轴向:默认沿宿主面法向,但需保证指向主体内部(按主体中心与面原点的相对位置取反)。
|
||||
normal = host.normal
|
||||
inward = normal if vector_dot(vector_subtract(session.adapter.body_center(session.body), host.origin_mm), normal) >= 0 else vector_scale(normal, -1)
|
||||
# 5. 生成孔切除工具:按孔规格、起始位置、内方向及“贯穿到主体底面”的深度构造工具实体。
|
||||
tool = session.adapter.hole_tool(
|
||||
spec,
|
||||
_hole_starts(spec, host_plane=host, positions_are_local=positions_are_local),
|
||||
inward,
|
||||
session.adapter.body_span(session.body, inward) + 2.0,
|
||||
)
|
||||
# 6. 从主体上减去工具实体,登记新主体并返回结果。
|
||||
session.register_body(node.feature_id, session.adapter.cut(session.body, tool), replay_node=node)
|
||||
return session.result(node)
|
||||
|
||||
@@ -468,28 +512,40 @@ def _selector_edges(node: FeaturePlanNode, session: ExecutionSession, *, tangent
|
||||
|
||||
|
||||
def _execute_fillet(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult:
|
||||
# 圆角特征(fillet)执行入口:对选中边按半径做圆角,平滑尖角与棱边。
|
||||
|
||||
# 1. 校验:圆角作用于已有主体,必须先有主体。
|
||||
if session.body is None:
|
||||
raise ValueError("fillet has no body")
|
||||
# 2. 解析圆角半径并校验必须大于 0。
|
||||
radius = float(node.params.get("radius_mm") or 0)
|
||||
if radius <= 0:
|
||||
raise ValueError("fillet radius_mm must be > 0")
|
||||
# 3. 解析目标边(支持 tangent_propagation 相切传播),并执行圆角。
|
||||
body = session.adapter.fillet(
|
||||
session.body, radius, _selector_edges(node, session, tangent_propagation=bool(node.params.get("tangent_propagation"))),
|
||||
)
|
||||
# 4. 登记新主体并返回结果。
|
||||
session.register_body(node.feature_id, body, replay_node=node)
|
||||
return session.result(node)
|
||||
|
||||
|
||||
def _execute_chamfer(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult:
|
||||
# 倒角特征(chamfer)执行入口:对选中边按距离做倒角(可带第二距离形成不对称倒角)。
|
||||
|
||||
# 1. 校验:倒角作用于已有主体,必须先有主体。
|
||||
if session.body is None:
|
||||
raise ValueError("chamfer has no body")
|
||||
# 2. 解析主距离并校验必须大于 0。
|
||||
distance = float(node.params.get("distance_mm") or 0)
|
||||
if distance <= 0:
|
||||
raise ValueError("chamfer distance_mm must be > 0")
|
||||
# 3. 解析目标边(支持相切传播),执行倒角;distance_2_mm 提供时产生非对称倒角。
|
||||
body = session.adapter.chamfer(
|
||||
session.body, distance, node.params.get("distance_2_mm"),
|
||||
_selector_edges(node, session, tangent_propagation=bool(node.params.get("tangent_propagation"))),
|
||||
)
|
||||
# 4. 登记新主体并返回结果。
|
||||
session.register_body(node.feature_id, body, replay_node=node)
|
||||
return session.result(node)
|
||||
|
||||
@@ -539,26 +595,36 @@ def _translated_node(node: FeaturePlanNode, instance_id: str, offset: Vector3) -
|
||||
|
||||
|
||||
def _execute_linear_pattern(node: FeaturePlanNode, session: ExecutionSession, execute: Callable[[FeaturePlanNode, ExecutionSession, dict[str, Any] | None], FeatureResult]) -> FeatureResult:
|
||||
# 线性阵列特征(pattern)执行入口:沿两个方向按数量与间距重放源特征形成阵列。
|
||||
|
||||
# 1. 取源特征的 replay 定义(源特征按 feature_id 在会话中登记,供本阵列重放)。
|
||||
params = node.params
|
||||
sources = session.replay_sources(params.get("source_feature_ids") or [])
|
||||
if not sources:
|
||||
raise ValueError("pattern source features have no replay definitions")
|
||||
# 2. 解析两个方向的实例数量。
|
||||
count_1 = int(params.get("pattern_count_1") or 1)
|
||||
count_2 = int(params.get("pattern_count_2") or 1)
|
||||
# 3. 解析两个方向的步长向量(方向单位向量 × 间距),作为阵列位移基准。
|
||||
direction_1 = vector_scale(vector_unit(tuple(float(value) for value in (params.get("direction_1") or [1, 0, 0])), field_name="pattern direction_1"), float(params.get("spacing_1_mm") or 0))
|
||||
direction_2 = vector_scale(vector_unit(tuple(float(value) for value in (params.get("direction_2") or [0, 1, 0])), field_name="pattern direction_2"), float(params.get("spacing_2_mm") or 0))
|
||||
# 4. 双重循环生成每个阵列实例(跳过原点 0,0 处,那里是源特征本身)。
|
||||
for first in range(count_1):
|
||||
for second in range(count_2):
|
||||
if first == 0 and second == 0:
|
||||
continue
|
||||
# 计算当前实例相对源特征的偏移向量。
|
||||
offset = vector_add(vector_scale(direction_1, first), vector_scale(direction_2, second))
|
||||
for source in sources:
|
||||
# 逐个源特征克隆并按偏移平移后重放执行(草图也同步平移)。
|
||||
dependency = pattern_transform_blocker(source)
|
||||
if dependency:
|
||||
raise ValueError(f"pattern source uses an unsupported {dependency}")
|
||||
cloned = _translated_node(source, f"{node.feature_id}.p{first}_{second}.{source.feature_id}", offset)
|
||||
sketch = session.sketches.get(str(source.sketch_id))
|
||||
execute(cloned, session, _translated_sketch(sketch, offset) if sketch else None)
|
||||
# 5. 记录本阵列的 replay 定义:后续阵列若选中本阵列,按定义递归重放,
|
||||
# 而非复制当前主体做近似。
|
||||
# A later pattern may select this pattern feature. The definition is
|
||||
# replayed recursively, never approximated by copying the current body.
|
||||
session.replay_definitions[node.feature_id] = node
|
||||
|
||||
Reference in New Issue
Block a user