feat: 推进一级关系参数化编辑与参数导出
This commit is contained in:
+187
-11
@@ -127,6 +127,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
self._edge_first_level_topology_cache: dict[int, dict[str, object]] = {}
|
||||
self._edge_first_level_fact_cache: dict[int, dict[str, object]] = {}
|
||||
self._local_face_deform_readiness_cache: dict[int, dict[str, object]] = {}
|
||||
self._part_solid_count_cache: dict[int, int] = {}
|
||||
self._open_shell_context_cache: dict[int, dict[str, object]] = {}
|
||||
self._edge_duplicate_key_ids_cache: dict[tuple[object, ...], list[int]] | None = None
|
||||
self._same_domain_internal_edge_ids_cache: set[int] | None = None
|
||||
@@ -233,6 +234,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
self._edge_first_level_topology_cache.clear()
|
||||
self._edge_first_level_fact_cache.clear()
|
||||
self._local_face_deform_readiness_cache.clear()
|
||||
self._part_solid_count_cache.clear()
|
||||
self._open_shell_context_cache.clear()
|
||||
self._edge_duplicate_key_ids_cache = None
|
||||
self._same_domain_internal_edge_ids_cache = None
|
||||
@@ -247,6 +249,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
solid_id = 0
|
||||
for part in self.display_parts():
|
||||
part_solids = _explore(part.shape, TopAbs_SOLID)
|
||||
self._part_solid_count_cache[part.id] = len(part_solids)
|
||||
part_solid_edge_maps: list[tuple[int, TopTools_IndexedDataMapOfShapeListOfShape]] = []
|
||||
part_solid_entries: list[tuple[int, TopoDS_Shape]] = []
|
||||
if part_solids:
|
||||
@@ -406,15 +409,6 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
info["feature_source_face_id"] = face_id
|
||||
info["feature_highlight_face_ids"] = (face_id,)
|
||||
info["feature_edit_actions"] = "拉伸/切除平面"
|
||||
if bool(info.get("has_inner_boundaries")):
|
||||
info["local_face_deform_ready"] = False
|
||||
info["local_face_deform_face_count"] = 1
|
||||
info["local_face_deform_blocker"] = "当前 Face 有内孔/内边界;请优先使用拉伸/切除、孔或槽的专门修改入口。"
|
||||
else:
|
||||
info["local_face_deform_ready"] = True
|
||||
info["local_face_deform_face_count"] = 1
|
||||
info["local_face_deform_blocker"] = ""
|
||||
info["local_face_deform_status"] = "deferred"
|
||||
info.update(
|
||||
self._local_face_plane_size_info(
|
||||
face_id,
|
||||
@@ -423,6 +417,15 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
center=_tuple_or_none(info.get("area_center")),
|
||||
)
|
||||
)
|
||||
if bool(info.get("has_inner_boundaries")):
|
||||
info["local_face_deform_ready"] = False
|
||||
info["local_face_deform_face_count"] = 1
|
||||
info["local_face_deform_blocker"] = "当前 Face 有内孔/内边界;请优先使用拉伸/切除、孔或槽的专门修改入口。"
|
||||
else:
|
||||
info.update(self._local_face_deform_readiness(face_id))
|
||||
if bool(info.get("local_face_deform_ready")):
|
||||
info["local_face_deform_status"] = "ready"
|
||||
info.update(self._local_face_size_parameter_readiness(face_id, info))
|
||||
elif surface_type == GeomAbs_Cylinder:
|
||||
cyl = surf.Cylinder()
|
||||
axis = cyl.Axis()
|
||||
@@ -604,6 +607,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
result: dict[str, object] = {
|
||||
"same_domain_face_ids": tuple(side_face_ids),
|
||||
"same_domain_face_count": len(side_face_ids),
|
||||
"feature_highlight_face_ids": tuple(side_face_ids),
|
||||
"angular_span": combined_span,
|
||||
"same_domain_angular_span": combined_span,
|
||||
"same_domain_note": same_domain_note,
|
||||
@@ -748,6 +752,7 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
center=_tuple_or_none(info.get("area_center")),
|
||||
)
|
||||
)
|
||||
info.update(self._local_face_size_parameter_readiness(face_id, info))
|
||||
try:
|
||||
info.update(self.face_first_level_topology(face_id))
|
||||
info.update(self.face_first_level_facts(face_id, scope="face"))
|
||||
@@ -2069,19 +2074,51 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
"local_face_width_direction": direction_groups[0]["direction"],
|
||||
"local_face_height_direction": direction_groups[1]["direction"],
|
||||
}
|
||||
result.update(self._local_face_size_parameter_readiness(face_id, {**info, **result}))
|
||||
prismatic_rebuild_blockers: list[str] = []
|
||||
if extrusion_candidate:
|
||||
extrusion = _float_or_none(shell_info.get("shell_thickness_estimate"))
|
||||
extrusion_confidence = "high" if len(connected_side_ids) == 4 else "medium"
|
||||
reference_face_id = opposite_face_id
|
||||
part_id = self.face_part_ids[face_id] if 0 <= face_id < len(self.face_part_ids) else -1
|
||||
part_solid_count = self._part_solid_count_cache.get(part_id, 0)
|
||||
if feature_semantics not in {"additive-boss", "subtractive-pocket"}:
|
||||
prismatic_rebuild_blockers.append("当前矩形平面还没有稳定判断为凸台或口袋。")
|
||||
if len(connected_side_ids) != 4:
|
||||
prismatic_rebuild_blockers.append("当前矩形特征需要四个侧壁都能识别后才开放长宽局部重建。")
|
||||
if reference_face_id is None:
|
||||
prismatic_rebuild_blockers.append("当前矩形特征缺少稳定参考面。")
|
||||
if extrusion is None or extrusion <= 1e-9:
|
||||
prismatic_rebuild_blockers.append("当前矩形特征缺少稳定高度/深度。")
|
||||
if part_solid_count != 1:
|
||||
prismatic_rebuild_blockers.append("矩形凸台/口袋长宽局部重建当前只对单 Solid 零件开放。")
|
||||
result.update(
|
||||
{
|
||||
"prismatic_extrusion_status": "candidate",
|
||||
"prismatic_extrusion_estimate": extrusion if extrusion is not None else "",
|
||||
"prismatic_reference_face_id": opposite_face_id,
|
||||
"prismatic_reference_face_id": reference_face_id,
|
||||
"prismatic_extrusion_confidence": extrusion_confidence,
|
||||
"confidence": extrusion_confidence,
|
||||
"prismatic_reference_source": "side-wall-topology" if topology_reference else "overlapping-plane",
|
||||
"prismatic_highlight_face_ids": tuple(sorted({face_id, *reference_face_ids, *connected_side_ids})),
|
||||
"prismatic_extrusion_note": "相对平面通过至少两个侧壁与当前矩形面相连。",
|
||||
"part_solid_count": part_solid_count,
|
||||
}
|
||||
)
|
||||
else:
|
||||
prismatic_rebuild_blockers.append("当前矩形平面还没有识别出稳定高度/深度,不能按完整凸台/口袋局部重建。")
|
||||
if prismatic_rebuild_blockers:
|
||||
result.update(
|
||||
{
|
||||
"rectangular_prismatic_size_rebuild_ready": False,
|
||||
"rectangular_prismatic_size_rebuild_blocker": ";".join(prismatic_rebuild_blockers),
|
||||
}
|
||||
)
|
||||
else:
|
||||
result.update(
|
||||
{
|
||||
"rectangular_prismatic_size_rebuild_ready": True,
|
||||
"rectangular_prismatic_size_rebuild_blocker": "",
|
||||
}
|
||||
)
|
||||
return result
|
||||
@@ -2588,6 +2625,22 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
**fillet_info,
|
||||
}
|
||||
)
|
||||
if angular_span >= math.tau * 0.92:
|
||||
result.update(
|
||||
{
|
||||
"slot_kind": "",
|
||||
"slot_status": "",
|
||||
"slot_risk": "",
|
||||
"slot_blockers": "",
|
||||
"slot_angular_span": "",
|
||||
"slot_open_angle": "",
|
||||
"slot_chord_width_estimate": "",
|
||||
"slot_arc_length_estimate": "",
|
||||
"slot_sagitta_depth_estimate": "",
|
||||
"feature_slot_face_ids": (),
|
||||
"feature_slot_boundary_face_ids": (),
|
||||
}
|
||||
)
|
||||
scoped_readiness_info = dict(result)
|
||||
scoped_readiness_info["angular_span"] = combined_angular_span
|
||||
scoped_readiness_info["height_estimate"] = axis_range["span"]
|
||||
@@ -3685,7 +3738,13 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
adjacent_face_ids=adjacent_face_ids,
|
||||
relation_scope="cylindrical-feature",
|
||||
)
|
||||
angular_span = feature.get("slot_angular_span", feature.get("angular_span", info.get("angular_span")))
|
||||
angular_span = (
|
||||
_float_or_none(feature.get("slot_angular_span"))
|
||||
or _float_or_none(feature.get("same_domain_angular_span"))
|
||||
or _float_or_none(feature.get("angular_span"))
|
||||
or _float_or_none(info.get("same_domain_angular_span"))
|
||||
or _float_or_none(info.get("angular_span"))
|
||||
)
|
||||
topology = {
|
||||
"topology_relation_model": "STEP/B-Rep cylindrical-feature shared-edge first-level",
|
||||
"topology_relation_depth": 1,
|
||||
@@ -3913,6 +3972,123 @@ class StepModel(FeatureMixin, ExportMixin, TransformMixin, OperationMixin, Polyd
|
||||
"has_inner_boundaries": inner_boundary_wires > 0,
|
||||
}
|
||||
|
||||
def _local_face_size_parameter_readiness(self, face_id: int, info: dict[str, object]) -> dict[str, object]:
|
||||
blockers: list[str] = []
|
||||
if face_id < 0 or face_id >= len(self.faces):
|
||||
blockers.append("Face ID 不存在。")
|
||||
if str(info.get("surface") or "") != "plane":
|
||||
blockers.append("面内长度/宽度只对平面矩形 Face 开放。")
|
||||
if bool(info.get("has_inner_boundaries")):
|
||||
blockers.append("当前 Face 有内孔/内边界,请优先使用孔或槽的专门修改入口。")
|
||||
try:
|
||||
boundary_wires = int(info.get("boundary_wires", 0) or 0)
|
||||
except (TypeError, ValueError):
|
||||
boundary_wires = 0
|
||||
if boundary_wires and boundary_wires != 1:
|
||||
blockers.append("当前 Face 不是单一外轮廓。")
|
||||
|
||||
width = _float_or_none(info.get("local_face_width"))
|
||||
height = _float_or_none(info.get("local_face_height"))
|
||||
width_direction = _tuple_or_none(info.get("local_face_width_direction"))
|
||||
height_direction = _tuple_or_none(info.get("local_face_height_direction"))
|
||||
center = _tuple_or_none(info.get("local_face_size_center")) or _tuple_or_none(info.get("area_center"))
|
||||
if width is None or width <= 1e-9:
|
||||
blockers.append("当前 Face 缺少稳定的面内长度。")
|
||||
if height is None or height <= 1e-9:
|
||||
blockers.append("当前 Face 缺少稳定的面内宽度。")
|
||||
if width_direction is None or height_direction is None or center is None:
|
||||
blockers.append("当前 Face 缺少稳定的面内方向或中心。")
|
||||
|
||||
edge_ids: list[int] = []
|
||||
if 0 <= face_id < len(self.faces):
|
||||
try:
|
||||
edge_ids = self._face_boundary_edge_ids(face_id)
|
||||
except Exception:
|
||||
edge_ids = []
|
||||
try:
|
||||
boundary_edges = int(info.get("boundary_edges", len(edge_ids)) or len(edge_ids))
|
||||
except (TypeError, ValueError):
|
||||
boundary_edges = len(edge_ids)
|
||||
rectangular_groups: list[dict[str, object]] = []
|
||||
if boundary_edges != 4 or len(edge_ids) != 4:
|
||||
blockers.append("面内长度/宽度当前只对四边矩形平面开放。")
|
||||
else:
|
||||
direction_groups: list[dict[str, object]] = []
|
||||
for edge_id in edge_ids:
|
||||
try:
|
||||
curve = BRepAdaptor_Curve(self.edges[edge_id])
|
||||
if curve.GetType() != GeomAbs_Line:
|
||||
blockers.append("当前 Face 的边界包含非直线边。")
|
||||
break
|
||||
start = _point_tuple(curve.Value(curve.FirstParameter()))
|
||||
end = _point_tuple(curve.Value(curve.LastParameter()))
|
||||
vector = _tuple_sub(end, start)
|
||||
length = math.sqrt(_tuple_dot(vector, vector))
|
||||
direction = _tuple_normalized(vector)
|
||||
except Exception:
|
||||
blockers.append("当前 Face 的边界直线不能稳定读取。")
|
||||
break
|
||||
if direction is None or length <= 1e-9:
|
||||
blockers.append("当前 Face 的边界存在退化直线边。")
|
||||
break
|
||||
matched_group = None
|
||||
for group in direction_groups:
|
||||
group_direction = _tuple_or_none(group.get("direction"))
|
||||
if group_direction is not None and abs(_tuple_dot(direction, group_direction)) >= 0.999:
|
||||
matched_group = group
|
||||
break
|
||||
if matched_group is None:
|
||||
matched_group = {"direction": direction, "lengths": []}
|
||||
direction_groups.append(matched_group)
|
||||
matched_group["lengths"].append(length)
|
||||
if len(direction_groups) != 2 or any(len(group["lengths"]) != 2 for group in direction_groups):
|
||||
blockers.append("当前 Face 的四条边没有形成两组稳定平行对边。")
|
||||
else:
|
||||
first_direction = _tuple_or_none(direction_groups[0].get("direction"))
|
||||
second_direction = _tuple_or_none(direction_groups[1].get("direction"))
|
||||
if (
|
||||
first_direction is None
|
||||
or second_direction is None
|
||||
or abs(_tuple_dot(first_direction, second_direction)) > 0.01
|
||||
):
|
||||
blockers.append("当前 Face 的两组对边不是稳定垂直关系。")
|
||||
for group in direction_groups:
|
||||
lengths = [float(item) for item in group["lengths"]]
|
||||
average = sum(lengths) / max(len(lengths), 1)
|
||||
if max(abs(item - average) for item in lengths) > max(average * 1e-4, 1e-7):
|
||||
blockers.append("当前 Face 的相对边长度不一致。")
|
||||
break
|
||||
rectangular_groups = direction_groups
|
||||
|
||||
if blockers:
|
||||
return {
|
||||
"local_face_size_edit_ready": False,
|
||||
"local_face_size_edit_blocker": ";".join(dict.fromkeys(blockers)),
|
||||
}
|
||||
result: dict[str, object] = {
|
||||
"local_face_size_edit_ready": True,
|
||||
"local_face_size_edit_blocker": "",
|
||||
}
|
||||
if rectangular_groups:
|
||||
rectangular_groups.sort(
|
||||
key=lambda group: sum(float(item) for item in group["lengths"]) / max(len(group["lengths"]), 1),
|
||||
reverse=True,
|
||||
)
|
||||
length_group = rectangular_groups[0]
|
||||
width_group = rectangular_groups[1]
|
||||
length_values = [float(item) for item in length_group["lengths"]]
|
||||
width_values = [float(item) for item in width_group["lengths"]]
|
||||
result.update(
|
||||
{
|
||||
"local_face_width": sum(length_values) / len(length_values),
|
||||
"local_face_height": sum(width_values) / len(width_values),
|
||||
"local_face_width_direction": length_group["direction"],
|
||||
"local_face_height_direction": width_group["direction"],
|
||||
"local_face_size_center": center,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def _local_face_deform_readiness(self, face_id: int) -> dict[str, object]:
|
||||
if face_id < 0 or face_id >= len(self.faces):
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user