Merge pull request '几个几何操作的微小bug' (#18) from ganjihong into main

Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
2026-09-08 20:01:55 +08:00
4 changed files with 51 additions and 109 deletions
+4 -4
View File
@@ -403,17 +403,17 @@ class CapabilityAnalyzer:
"booleanBodies requires explicit target and tool feature ids", parameter=parameter, "booleanBodies requires explicit target and tool feature ids", parameter=parameter,
)) ))
continue continue
for feature_id in feature_ids: for source_id in feature_ids:
source = nodes_by_id.get(str(feature_id)) source = nodes_by_id.get(str(source_id))
if source is None: if source is None:
blockers.append(self._blocker( blockers.append(self._blocker(
node.feature_id, "boolean_body_unavailable", node.feature_id, "boolean_body_unavailable",
"booleanBodies source feature does not exist", feature_id=feature_id, "booleanBodies source feature does not exist", missing_feature_id=source_id,
)) ))
elif source.feature_id not in completed: elif source.feature_id not in completed:
blockers.append(self._blocker( blockers.append(self._blocker(
node.feature_id, "boolean_body_unavailable", node.feature_id, "boolean_body_unavailable",
"booleanBodies source feature did not become executable", feature_id=feature_id, "booleanBodies source feature did not become executable", missing_feature_id=source_id,
)) ))
if isinstance(target_ids, list) and isinstance(tool_ids, list) and set(target_ids) & set(tool_ids): if isinstance(target_ids, list) and isinstance(tool_ids, list) and set(target_ids) & set(tool_ids):
blockers.append(self._blocker( blockers.append(self._blocker(
+15 -8
View File
@@ -184,7 +184,8 @@
"type": "object", "type": "object",
"properties": { "properties": {
"radius_mm": {"$ref": "#/$defs/positive"}, "radius_mm": {"$ref": "#/$defs/positive"},
"center_mm": {"$ref": "#/$defs/point3"} "center_mm": {"$ref": "#/$defs/point3"},
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["radius_mm", "center_mm"], "required": ["radius_mm", "center_mm"],
"additionalProperties": false "additionalProperties": false
@@ -195,7 +196,8 @@
"length_mm": {"$ref": "#/$defs/positive"}, "length_mm": {"$ref": "#/$defs/positive"},
"width_mm": {"$ref": "#/$defs/positive"}, "width_mm": {"$ref": "#/$defs/positive"},
"height_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"},
"center_mm": {"$ref": "#/$defs/point3"} "center_mm": {"$ref": "#/$defs/point3"},
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["length_mm", "width_mm", "height_mm", "center_mm"], "required": ["length_mm", "width_mm", "height_mm", "center_mm"],
"additionalProperties": false "additionalProperties": false
@@ -205,7 +207,8 @@
"properties": { "properties": {
"radius_mm": {"$ref": "#/$defs/positive"}, "radius_mm": {"$ref": "#/$defs/positive"},
"height_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"},
"axis": {"$ref": "#/$defs/axis"} "axis": {"$ref": "#/$defs/axis"},
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["radius_mm", "height_mm"], "required": ["radius_mm", "height_mm"],
"additionalProperties": false "additionalProperties": false
@@ -237,7 +240,8 @@
}, },
"required": ["origin_mm", "x_dir", "y_dir", "normal"], "required": ["origin_mm", "x_dir", "y_dir", "normal"],
"additionalProperties": false "additionalProperties": false
} },
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["thickness_mm", "width_mm", "chain"], "required": ["thickness_mm", "width_mm", "chain"],
"additionalProperties": false "additionalProperties": false
@@ -251,7 +255,8 @@
"axis": {"$ref": "#/$defs/axis"}, "axis": {"$ref": "#/$defs/axis"},
"helix_angle_rad": {"type": "number", "minimum": 0, "exclusiveMaximum": 0.7853981633974483}, "helix_angle_rad": {"type": "number", "minimum": 0, "exclusiveMaximum": 0.7853981633974483},
"pressure_angle_rad": {"type": "number", "exclusiveMinimum": 0, "maximum": 0.6108652381980153}, "pressure_angle_rad": {"type": "number", "exclusiveMinimum": 0, "maximum": 0.6108652381980153},
"herringbone": {"type": "boolean"} "herringbone": {"type": "boolean"},
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["module_mm", "teeth_count", "width_mm", "axis"], "required": ["module_mm", "teeth_count", "width_mm", "axis"],
"additionalProperties": false "additionalProperties": false
@@ -263,7 +268,8 @@
"teeth_count": {"type": "integer", "minimum": 1, "maximum": 2000}, "teeth_count": {"type": "integer", "minimum": 1, "maximum": 2000},
"thickness_mm": {"$ref": "#/$defs/positive"}, "thickness_mm": {"$ref": "#/$defs/positive"},
"axis": {"$ref": "#/$defs/axis"}, "axis": {"$ref": "#/$defs/axis"},
"pressure_angle_rad": {"type": "number", "exclusiveMinimum": 0, "maximum": 0.6108652381980153} "pressure_angle_rad": {"type": "number", "exclusiveMinimum": 0, "maximum": 0.6108652381980153},
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["module_mm", "teeth_count", "thickness_mm", "axis"], "required": ["module_mm", "teeth_count", "thickness_mm", "axis"],
"additionalProperties": false "additionalProperties": false
@@ -280,7 +286,8 @@
"lefthand": {"type": "boolean"}, "lefthand": {"type": "boolean"},
"relief_length_mm": {"type": "number", "minimum": 0}, "relief_length_mm": {"type": "number", "minimum": 0},
"crest_radius_mm": {"type": "number", "minimum": 0}, "crest_radius_mm": {"type": "number", "minimum": 0},
"root_radius_mm": {"type": "number", "minimum": 0} "root_radius_mm": {"type": "number", "minimum": 0},
"result_mode": {"enum": ["fuse", "new_body"]}
}, },
"required": ["major_diameter_mm", "minor_diameter_mm", "pitch_mm", "length_mm", "axis"], "required": ["major_diameter_mm", "minor_diameter_mm", "pitch_mm", "length_mm", "axis"],
"additionalProperties": false "additionalProperties": false
@@ -548,7 +555,7 @@
"required": ["type", "contours"], "required": ["type", "contours"],
"additionalProperties": false "additionalProperties": false
}, },
"feature_atomic_ids": {"enum": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_cut_through", "loft_add", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", "thread_add", "thread_cut", "bend_add", "gear_add", "rack_add", "fillet", "chamfer", "pattern_linear", "pattern_mirror", "pattern_circular", "reference_plane", "reference_axis", "hole_wizard"]}, "feature_atomic_ids": {"enum": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_cut_through", "loft_add", "sweep_add", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", "thread_add", "thread_cut", "bend_add", "gear_add", "rack_add", "fillet", "chamfer", "pattern_linear", "pattern_mirror", "pattern_circular", "boolean_bodies", "reference_plane", "reference_axis", "hole_wizard"]},
"feature": { "feature": {
"type": "object", "type": "object",
"properties": { "properties": {
File diff suppressed because one or more lines are too long
+31 -8
View File
@@ -831,9 +831,32 @@ def _execute_reference_axis(node: FeaturePlanNode, session: ExecutionSession) ->
return session.result(node, context=axis) return session.result(node, context=axis)
def _register_added_solid(
session: ExecutionSession,
node: FeaturePlanNode,
solid: Any,
) -> None:
"""Register an additive primitive solid (box/cyl/sphere/thread/gear/rack/bend).
When ``node.params['result_mode'] == "new_body"`` the primitive is kept as
an independent body member so that downstream ``boolean_bodies`` can
reference it without pulling in the accumulated fuse history. The current
body is replaced by a Compound that preserves both, matching the
``extrude_add_blind`` ``new_body`` semantics. Any other value (including
missing) falls back to the legacy fuse-into-body behavior.
"""
if node.params.get("result_mode") == "new_body":
combined = session.adapter.combine(session.body, solid)
members = {**session.body_members, node.feature_id: solid}
session.register_body(node.feature_id, combined, replay_node=node, body_members=members)
return
fused = session.adapter.fuse(session.body, solid)
session.register_body(node.feature_id, fused, replay_node=node)
def _execute_sphere(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult: def _execute_sphere(node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult:
# 球体特征(sphere_add)执行入口:按球心与半径生成球体并并入当前主体。 # 球体特征(sphere_add)执行入口:按球心与半径生成球体并并入当前主体。
# 1. 解析参数:半径 radius_mm 与球心 center_mm。 # 1. 解析参数:半径 radius_mm 与球心 center_mm。
radius = float(node.params.get("radius_mm") or 0.0) radius = float(node.params.get("radius_mm") or 0.0)
center = node.params.get("center_mm") or [] center = node.params.get("center_mm") or []
@@ -843,7 +866,7 @@ def _execute_sphere(node: FeaturePlanNode, session: ExecutionSession) -> Feature
# 3. 由适配器创建球体实体。 # 3. 由适配器创建球体实体。
solid = session.adapter.sphere(radius, (float(center[0]), float(center[1]), float(center[2]))) solid = session.adapter.sphere(radius, (float(center[0]), float(center[1]), float(center[2])))
# 4. 球体与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。 # 4. 球体与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
return session.result(node) return session.result(node)
@@ -869,7 +892,7 @@ def _execute_box(node: FeaturePlanNode, session: ExecutionSession) -> FeatureRes
plane = PlaneSpec.from_mapping({"origin_mm": corner, "x_dir": [1, 0, 0], "normal": [0, 0, 1]}) plane = PlaneSpec.from_mapping({"origin_mm": corner, "x_dir": [1, 0, 0], "normal": [0, 0, 1]})
solid = session.adapter.box(length, width, height, plane) solid = session.adapter.box(length, width, height, plane)
# 3. 与当前主体做布尔并后登记为新主体,并返回该特征的结果对象。 # 3. 与当前主体做布尔并后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
return session.result(node) return session.result(node)
@@ -893,7 +916,7 @@ def _execute_cylinder(node: FeaturePlanNode, session: ExecutionSession) -> Featu
# 2. 由适配器创建原生圆柱(axis=None 即世界 +Z 过原点)。 # 2. 由适配器创建原生圆柱(axis=None 即世界 +Z 过原点)。
solid = session.adapter.cylinder(radius, height, axis) solid = session.adapter.cylinder(radius, height, axis)
# 3. 与当前主体做布尔并后登记为新主体,并返回该特征的结果对象。 # 3. 与当前主体做布尔并后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
return session.result(node) return session.result(node)
@@ -904,7 +927,7 @@ def _execute_thread(node: FeaturePlanNode, session: ExecutionSession) -> Feature
# 2. 由适配器门面生成沿 spec.axis 放置的外螺纹实心段。 # 2. 由适配器门面生成沿 spec.axis 放置的外螺纹实心段。
solid = session.adapter.thread_solid(spec) solid = session.adapter.thread_solid(spec)
# 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。 # 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
return session.result(node) return session.result(node)
@@ -938,7 +961,7 @@ def _execute_bend(node: FeaturePlanNode, session: ExecutionSession) -> FeatureRe
# 2. 由适配器门面生成沿 spec.frame 放置的折弯实心段。 # 2. 由适配器门面生成沿 spec.frame 放置的折弯实心段。
solid = session.adapter.bend_solid(spec) solid = session.adapter.bend_solid(spec)
# 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。 # 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
return session.result(node) return session.result(node)
@@ -955,7 +978,7 @@ def _execute_gear(node: FeaturePlanNode, session: ExecutionSession) -> FeatureRe
# 2. 由适配器门面生成沿 spec.axis 放置的齿轮实体(直齿/斜齿/人字齿)。 # 2. 由适配器门面生成沿 spec.axis 放置的齿轮实体(直齿/斜齿/人字齿)。
solid = session.adapter.gear_solid(spec) solid = session.adapter.gear_solid(spec)
# 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。 # 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
# 4. 小齿数根切风险:不阻断执行,附加 info 级诊断供完成报告如实披露。 # 4. 小齿数根切风险:不阻断执行,附加 info 级诊断供完成报告如实披露。
diagnostics: list[RuntimeDiagnostic] = [] diagnostics: list[RuntimeDiagnostic] = []
if spec.teeth_count < 17: if spec.teeth_count < 17:
@@ -983,7 +1006,7 @@ def _execute_rack(node: FeaturePlanNode, session: ExecutionSession) -> FeatureRe
# 2. 由适配器门面生成沿 spec.axis 放置的齿条实体(齿沿轴方向伸出)。 # 2. 由适配器门面生成沿 spec.axis 放置的齿条实体(齿沿轴方向伸出)。
solid = session.adapter.rack_solid(spec) solid = session.adapter.rack_solid(spec)
# 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。 # 3. 与当前主体做布尔并(fuse)后登记为新主体,并返回该特征的结果对象。
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node) _register_added_solid(session, node, solid)
return session.result(node) return session.result(node)