from __future__ import annotations from collections.abc import Iterable, Mapping def property_specs_from_scdm_cache( cache: Mapping[str, object], *, selected_face_ids: Iterable[int] = (), selected_edge_ids: Iterable[int] = (), execution_ready: bool | Iterable[str] = False, ) -> list[dict[str, object]]: face_ids = {int(item) for item in selected_face_ids} edge_ids = {int(item) for item in selected_edge_ids} if not face_ids and not edge_ids: return [] objects = cache.get("objects") if not isinstance(objects, list): return [] specs: list[dict[str, object]] = [] for item in objects: if not isinstance(item, Mapping) or not _object_matches(item, face_ids=face_ids, edge_ids=edge_ids): continue capabilities = item.get("capabilities") if not isinstance(capabilities, list): continue for capability in capabilities: if isinstance(capability, Mapping): spec = _capability_spec(item, capability, execution_ready=execution_ready) if spec is not None: specs.append(spec) return specs def _object_matches(raw_object: Mapping[str, object], *, face_ids: set[int], edge_ids: set[int]) -> bool: signature = raw_object.get("geometrySignature") if not isinstance(signature, Mapping): return False object_faces = set(_int_values(signature.get("faceIds"))) object_edges = set(_int_values(signature.get("edgeIds"))) return bool((face_ids and object_faces & face_ids) or (edge_ids and object_edges & edge_ids)) def _capability_spec( raw_object: Mapping[str, object], capability: Mapping[str, object], *, execution_ready: bool | Iterable[str], ) -> dict[str, object] | None: key = str(capability.get("key") or "").strip() label = str(capability.get("displayName") or key).strip() if not key or not label: return None value_kind = str(capability.get("valueKind") or "number") current = capability.get("currentValue") value_type = _value_type(value_kind, key) command_value = value_type == "command" current_text = "可执行" if command_value else _format_value(current, value_type=value_type) target_text = "执行" if command_value else _format_value(current, value_type=value_type) capability_block = str(capability.get("blockReason") or "").strip() object_block = str(raw_object.get("blockReason") or "").strip() block_reason = capability_block or object_block backend_operation = str(capability.get("backendOperation") or "") post_check = str(capability.get("postCheck") or "") can_execute = bool(_capability_execution_ready(key, execution_ready) and capability.get("editable", True) and not block_reason) if can_execute: disabled_tip = "" enabled_tip = ( f"SCDM 已识别“{label}”可由 {backend_operation or '后端命令'} 修改;" f"执行后会用 {post_check or '结果回测'} 校验。" ) elif block_reason: enabled_tip = "" disabled_tip = f"SCDM 已识别该对象,但当前能力被阻止:{block_reason}" else: enabled_tip = "" disabled_tip = "SCDM 已识别该参数,但 S5 修改执行器还没有接入;当前只作为后端识别结果缓存,不开放执行。" return { "key": f"scdm:{key}", "label": label, "current_raw": current if current is not None else "", "current_text": current_text, "target_text": target_text, "editable": True, "enabled": can_execute, "status_text": "可修改" if can_execute else "暂未接入", "scope_text": str(capability.get("defaultIntent") or "SCDM"), "action": "apply_scdm_property_edit", "value_type": value_type, "enabled_tip": enabled_tip, "disabled_tip": disabled_tip, "range_hint": "来源:SCDM 结构化识别结果。执行前仍需生成 edit job,并在结果 STEP 上做 OCCT 校验和目标值回测。", "min_value": 0.0 if value_type == "positive" else None, "min_exclusive": True if value_type == "positive" else False, "scdm_object_id": raw_object.get("objectId"), "scdm_source_backend_id": raw_object.get("sourceBackendId"), "scdm_capability_key": key, "scdm_backend_operation": backend_operation, "scdm_post_check": post_check, "scdm_geometry_signature": raw_object.get("geometrySignature") if isinstance(raw_object.get("geometrySignature"), Mapping) else {}, } def _value_type(value_kind: str, key: str) -> str: if value_kind == "vector3": return "vector3" if value_kind == "command": return "command" if key.endswith(".diameter") or key.endswith(".radius"): return "positive" return "number" def _capability_execution_ready(key: str, execution_ready: bool | Iterable[str]) -> bool: if isinstance(execution_ready, bool): return execution_ready try: return key in {str(item) for item in execution_ready} except TypeError: return False def _format_value(value: object, *, value_type: str) -> str: if value is None: return "" if value_type == "vector3": values = _float_values(value) return f"({values[0]:g}, {values[1]:g}, {values[2]:g})" if len(values) == 3 else "" if isinstance(value, float): return f"{value:g}" return str(value) def _float_values(value: object) -> list[float]: if isinstance(value, (str, bytes)) or value is None: return [] try: values = list(value) # type: ignore[arg-type] except TypeError: return [] result: list[float] = [] for item in values[:3]: try: result.append(float(item)) except (TypeError, ValueError): return [] return result def _int_values(value: object) -> list[int]: if isinstance(value, (str, bytes)) or value is None: return [] try: values = list(value) # type: ignore[arg-type] except TypeError: return [] result: list[int] = [] for item in values: try: result.append(int(item)) except (TypeError, ValueError): continue return result __all__ = ["property_specs_from_scdm_cache"]