from __future__ import annotations from collections.abc import Mapping from dataclasses import dataclass @dataclass(frozen=True) class ScdmCapabilityDefinition: key: str display_name: str object_types: tuple[str, ...] value_kind: str current_fields: tuple[str, ...] default_intent: str backend_operation: str post_check: str required_backend_command_groups: tuple[tuple[str, ...], ...] = () productized: bool = True roadmap_stage: str = "S5" block_reason: str = "" def to_payload(self) -> dict[str, object]: return { "key": self.key, "displayName": self.display_name, "objectTypes": self.object_types, "valueKind": self.value_kind, "currentFields": self.current_fields, "defaultIntent": self.default_intent, "backendOperation": self.backend_operation, "postCheck": self.post_check, "requiredBackendCommandGroups": self.required_backend_command_groups, "productized": self.productized, "roadmapStage": self.roadmap_stage, "blockReason": self.block_reason, } # 产品能力字典:SCDM raw 对象只有进入这里,才会被翻译成客户可见的中文参数。 # 新能力要同时补 backend_operation、post_check 和验证脚本,避免只显示不能执行的参数。 CAPABILITY_DEFINITIONS: dict[str, ScdmCapabilityDefinition] = { "hole.diameter": ScdmCapabilityDefinition( key="hole.diameter", display_name="直径", object_types=("hole", "cylindrical_hole", "cylindrical_face_group"), value_kind="number", current_fields=("geometry.diameter", "geometry.radius*2"), default_intent="修改孔径", backend_operation="change_hole_diameter", post_check="target_hole_diameter", required_backend_command_groups=(("StandardHoles",), ("OffsetFaces",)), roadmap_stage="S5", ), "hole.position": ScdmCapabilityDefinition( key="hole.position", display_name="位置", object_types=("hole", "cylindrical_hole", "cylindrical_face_group"), value_kind="vector3", current_fields=("geometry.center", "geometry.axisCenter"), default_intent="移动孔", backend_operation="move_hole_axis", post_check="target_hole_axis_center", required_backend_command_groups=(("Move",),), roadmap_stage="S5", ), "face.offset": ScdmCapabilityDefinition( key="face.offset", display_name="偏移", object_types=("face", "planar_face"), value_kind="number", current_fields=("geometry.offset", "geometry.planeOffset", "0"), default_intent="推拉平面", backend_operation="pull_face_offset", post_check="target_face_offset", required_backend_command_groups=(("OffsetFaces",),), roadmap_stage="S5", ), "feature.fill": ScdmCapabilityDefinition( key="feature.fill", display_name="填孔/删除小特征", object_types=("hole", "small_feature"), value_kind="command", current_fields=("1",), default_intent="删除并补面", backend_operation="fill_feature", post_check="target_feature_removed", required_backend_command_groups=(("Fill",), ("Delete",)), roadmap_stage="S5", ), "slot.width": ScdmCapabilityDefinition( key="slot.width", display_name="槽宽", object_types=("slot", "obround_slot", "rectangular_slot"), value_kind="number", current_fields=("geometry.width",), default_intent="修改槽宽", backend_operation="change_slot_width", post_check="target_slot_width", required_backend_command_groups=(("OffsetFaces",),), roadmap_stage="S7.2", ), "slot.depth": ScdmCapabilityDefinition( key="slot.depth", display_name="槽深", object_types=("slot", "obround_slot", "rectangular_slot"), value_kind="number", current_fields=("geometry.slotInfo.depth", "geometry.depth"), default_intent="修改槽深", backend_operation="change_slot_depth", post_check="target_slot_depth", required_backend_command_groups=(("Move",), ("OffsetFaces",)), roadmap_stage="S7.2", ), "slot.position": ScdmCapabilityDefinition( key="slot.position", display_name="槽位置", object_types=("slot", "obround_slot", "rectangular_slot"), value_kind="vector3", current_fields=("geometry.center", "geometry.axisCenter"), default_intent="移动槽", backend_operation="move_slot", post_check="target_slot_center", required_backend_command_groups=(("Move",),), roadmap_stage="S7.2", ), "boss.height": ScdmCapabilityDefinition( key="boss.height", display_name="凸台高度", object_types=("boss", "cylindrical_boss", "rectangular_boss"), value_kind="number", current_fields=("geometry.height",), default_intent="修改凸台高度", backend_operation="change_boss_height", post_check="target_boss_height", required_backend_command_groups=(("Move",), ("OffsetFaces",)), roadmap_stage="S7.3", ), "boss.diameter": ScdmCapabilityDefinition( key="boss.diameter", display_name="凸台直径", object_types=("boss", "cylindrical_boss"), value_kind="number", current_fields=("geometry.diameter", "geometry.radius*2"), default_intent="修改凸台直径", backend_operation="change_boss_diameter", post_check="target_boss_diameter", required_backend_command_groups=(("OffsetFaces",),), roadmap_stage="S7.3", ), "boss.position": ScdmCapabilityDefinition( key="boss.position", display_name="凸台位置", object_types=("boss", "cylindrical_boss", "rectangular_boss"), value_kind="vector3", current_fields=("geometry.center", "geometry.axisCenter"), default_intent="移动凸台", backend_operation="move_boss", post_check="target_boss_center", required_backend_command_groups=(("Move",),), roadmap_stage="S7.3", ), "round.radius": ScdmCapabilityDefinition( key="round.radius", display_name="圆角半径", object_types=("round", "fillet"), value_kind="number", current_fields=("geometry.roundInfo.radius", "geometry.radius"), default_intent="修改圆角半径", backend_operation="change_round_radius", post_check="target_round_radius", required_backend_command_groups=(("ConstantRound",),), roadmap_stage="S7.4", ), "chamfer.distance": ScdmCapabilityDefinition( key="chamfer.distance", display_name="倒角距离", object_types=("chamfer",), value_kind="number", current_fields=("geometry.chamferInfo.distance", "geometry.distance", "geometry.offset"), default_intent="修改倒角距离", backend_operation="change_chamfer_distance", post_check="target_chamfer_distance", required_backend_command_groups=(("Chamfer",),), roadmap_stage="S7.4", ), "feature.delete_round_or_chamfer": ScdmCapabilityDefinition( key="feature.delete_round_or_chamfer", display_name="删除圆角/倒角", object_types=("round", "fillet", "chamfer"), value_kind="command", current_fields=("1",), default_intent="删除圆角/倒角并补面", backend_operation="delete_round_or_chamfer", post_check="target_feature_removed", required_backend_command_groups=(("Fill",), ("Delete",)), roadmap_stage="S7.4", ), "pattern.spacing": ScdmCapabilityDefinition( key="pattern.spacing", display_name="阵列间距", object_types=("pattern", "linear_pattern"), value_kind="number", current_fields=("geometry.spacing", "geometry.pitch"), default_intent="修改阵列间距", backend_operation="change_pattern_spacing", post_check="target_pattern_spacing", required_backend_command_groups=(("Move",),), roadmap_stage="S7.5", ), "pattern.segment_spacing": ScdmCapabilityDefinition( key="pattern.segment_spacing", display_name="局部间距", object_types=("pattern", "linear_pattern"), value_kind="number", current_fields=("geometry.spacing", "geometry.pitch"), default_intent="修改相邻阵列成员间距", backend_operation="change_pattern_segment_spacing", post_check="target_pattern_segment_spacing", required_backend_command_groups=(("Move",),), roadmap_stage="S7.5", ), "pattern.instance_position": ScdmCapabilityDefinition( key="pattern.instance_position", display_name="阵列实例位置", object_types=("pattern", "linear_pattern"), value_kind="vector3", current_fields=("geometry.instanceCenter", "geometry.center"), default_intent="移动阵列实例", backend_operation="move_pattern_instance", post_check="target_pattern_instance_center", required_backend_command_groups=(("Move",),), roadmap_stage="S7.5", ), "shell.thickness": ScdmCapabilityDefinition( key="shell.thickness", display_name="壳体厚度", object_types=("shell", "thin_wall"), value_kind="number", current_fields=("geometry.thickness",), default_intent="固定一侧,移动另一侧", backend_operation="change_shell_thickness", post_check="target_shell_thickness", required_backend_command_groups=(("Move",),), roadmap_stage="S7.5", ), } def capability_definition(key: str) -> ScdmCapabilityDefinition | None: return CAPABILITY_DEFINITIONS.get(key) def productized_capability_keys(raw_object: Mapping[str, object]) -> tuple[str, ...]: return tuple( key for key in capability_keys_for_raw_object(raw_object, include_planned=False) if (definition := capability_definition(key)) is not None and definition.productized ) def planned_capability_keys(raw_object: Mapping[str, object]) -> tuple[str, ...]: return tuple( key for key in capability_keys_for_raw_object(raw_object, include_planned=True) if (definition := capability_definition(key)) is not None and not definition.productized ) def capability_keys_for_raw_object(raw_object: Mapping[str, object], *, include_planned: bool = False) -> tuple[str, ...]: object_type = str(raw_object.get("objectType") or "").strip().lower() geometry = _mapping(raw_object.get("geometry")) commands = tuple(_command_operations(raw_object.get("backendCommandCandidates"))) keys: list[str] = [] if object_type in {"hole", "cylindrical_hole", "cylindrical_face_group"}: if _has_any(geometry, ("diameter", "radius")) or _has_command_token(commands, ("diameter", "radius")): keys.append("hole.diameter") if _has_any(geometry, ("center", "axisCenter")) or _has_command_token(commands, ("move", "position", "translate")): keys.append("hole.position") if _has_command_token(commands, ("fill", "delete", "remove")): keys.append("feature.fill") surface_type = str(geometry.get("surfaceType") or geometry.get("surface") or "").strip().lower() if object_type in {"face", "planar_face"} and surface_type in {"plane", "planar", ""}: if _has_command_token(commands, ("pull", "offset", "move_face")) or _has_any(geometry, ("normal", "planeOffset")): keys.append("face.offset") if object_type in {"hole", "small_feature"} and _has_command_token(commands, ("fill", "delete", "remove")): keys.append("feature.fill") if object_type in {"slot", "obround_slot", "rectangular_slot"}: if _has_any(geometry, ("width",)) or _has_command_token(commands, ("slot_width", "width")): keys.append("slot.width") if _has_any(geometry, ("depth",)) or _has_command_token(commands, ("slot_depth", "depth")): keys.append("slot.depth") if _has_any(geometry, ("center", "axisCenter")) or _has_command_token(commands, ("move", "position", "translate")): keys.append("slot.position") if object_type in {"boss", "cylindrical_boss", "rectangular_boss"}: if _has_any(geometry, ("height",)) or _has_command_token(commands, ("boss_height", "height")): keys.append("boss.height") if object_type != "rectangular_boss" and (_has_any(geometry, ("diameter", "radius")) or _has_command_token(commands, ("diameter", "radius"))): keys.append("boss.diameter") if _has_any(geometry, ("center", "axisCenter")) or _has_command_token(commands, ("move", "position", "translate")): keys.append("boss.position") if object_type in {"round", "fillet"}: if _has_any(geometry, ("radius",)) or _has_command_token(commands, ("round_radius", "fillet_radius", "radius")): keys.append("round.radius") if _has_command_token(commands, ("fill", "delete", "remove")): keys.append("feature.delete_round_or_chamfer") if object_type == "chamfer": if _has_any(geometry, ("distance", "offset")) or _has_command_token(commands, ("chamfer_distance", "distance", "offset")): keys.append("chamfer.distance") if _has_command_token(commands, ("fill", "delete", "remove")): keys.append("feature.delete_round_or_chamfer") if object_type in {"pattern", "linear_pattern"}: if _has_any(geometry, ("spacing", "pitch")) or _has_command_token(commands, ("pattern_spacing", "spacing", "pitch")): keys.append("pattern.spacing") keys.append("pattern.segment_spacing") if _has_any(geometry, ("instanceCenter", "center")) or _has_command_token(commands, ("move_instance", "instance_position")): keys.append("pattern.instance_position") if object_type in {"shell", "thin_wall"}: if _has_any(geometry, ("thickness",)) or _has_command_token(commands, ("shell_thickness", "thickness")): keys.append("shell.thickness") result = [] for key in keys: definition = capability_definition(key) if definition is None: continue if definition.productized or include_planned: result.append(key) return tuple(dict.fromkeys(result)) def _mapping(value: object) -> Mapping[str, object]: return value if isinstance(value, Mapping) else {} def _has_any(mapping: Mapping[str, object], names: tuple[str, ...]) -> bool: return any(name in mapping and mapping.get(name) is not None for name in names) def _command_operations(value: object) -> list[str]: if not isinstance(value, list): return [] result: list[str] = [] for item in value: if not isinstance(item, Mapping): continue enabled = item.get("enabled") if enabled is False: continue text = " ".join( str(part or "") for part in ( item.get("key"), item.get("operation"), item.get("command"), item.get("type"), ) ) result.append(text.lower()) return result def _has_command_token(commands: tuple[str, ...], tokens: tuple[str, ...]) -> bool: return any(token in command for command in commands for token in tokens) __all__ = [ "CAPABILITY_DEFINITIONS", "ScdmCapabilityDefinition", "capability_keys_for_raw_object", "capability_definition", "planned_capability_keys", "productized_capability_keys", ]