feat: 完善 SCDM-first 参数化编辑交付版
This commit is contained in:
@@ -8,7 +8,7 @@ from .scdm_capabilities import capability_definition, planned_capability_keys, p
|
||||
from .scdm_schema import SCDM_CACHE_SCHEMA_VERSION, payload_backend_version, payload_model_fingerprint, read_json, utc_now, write_json
|
||||
|
||||
|
||||
SCDM_FEATURE_CACHE_REVISION = 4
|
||||
SCDM_FEATURE_CACHE_REVISION = 8
|
||||
|
||||
|
||||
def map_scdm_raw_features(raw_payload: Mapping[str, object]) -> dict[str, object]:
|
||||
@@ -400,6 +400,8 @@ def _cylindrical_face_group_objects(raw_objects: list[object]) -> list[dict[str,
|
||||
for raw_object in raw_objects:
|
||||
if not isinstance(raw_object, Mapping):
|
||||
continue
|
||||
if not _can_derive_cylindrical_hole_group(raw_object):
|
||||
continue
|
||||
geometry = _mapping(raw_object.get("geometry"))
|
||||
if _surface_key(geometry.get("surfaceType") or geometry.get("surface")) != "cylinder":
|
||||
continue
|
||||
@@ -453,6 +455,56 @@ def _cylindrical_face_group_objects(raw_objects: list[object]) -> list[dict[str,
|
||||
return result
|
||||
|
||||
|
||||
def _can_derive_cylindrical_hole_group(raw_object: Mapping[str, object]) -> bool:
|
||||
object_type = str(raw_object.get("objectType") or "").strip().lower()
|
||||
if object_type in {
|
||||
"round",
|
||||
"fillet",
|
||||
"chamfer",
|
||||
"slot",
|
||||
"obround_slot",
|
||||
"rectangular_slot",
|
||||
"boss",
|
||||
"cylindrical_boss",
|
||||
"rectangular_boss",
|
||||
"shell",
|
||||
"thin_wall",
|
||||
}:
|
||||
return False
|
||||
if object_type not in {"", "face", "planar_face", "hole", "cylindrical_hole"}:
|
||||
return False
|
||||
geometry = _mapping(raw_object.get("geometry"))
|
||||
if _mapping(geometry.get("roundInfo")) or _mapping(geometry.get("chamferInfo")) or _mapping(geometry.get("slotInfo")):
|
||||
return object_type in {"hole", "cylindrical_hole"}
|
||||
commands = tuple(_raw_command_tokens(raw_object.get("backendCommandCandidates")))
|
||||
if any(token in command for command in commands for token in ("round", "fillet", "chamfer", "slot", "boss")):
|
||||
return object_type in {"hole", "cylindrical_hole"}
|
||||
return True
|
||||
|
||||
|
||||
def _raw_command_tokens(value: object) -> list[str]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
tokens: list[str] = []
|
||||
for item in value:
|
||||
if not isinstance(item, Mapping):
|
||||
continue
|
||||
if item.get("enabled") is False:
|
||||
continue
|
||||
tokens.append(
|
||||
" ".join(
|
||||
str(part or "")
|
||||
for part in (
|
||||
item.get("key"),
|
||||
item.get("operation"),
|
||||
item.get("command"),
|
||||
item.get("type"),
|
||||
)
|
||||
).lower()
|
||||
)
|
||||
return tokens
|
||||
|
||||
|
||||
def _derived_feature_objects(source_objects: list[object]) -> list[dict[str, object]]:
|
||||
return [
|
||||
*_derived_linear_pattern_objects(source_objects),
|
||||
@@ -952,7 +1004,7 @@ def _derived_thin_wall_objects(source_objects: list[object]) -> list[dict[str, o
|
||||
{"operation": "change_shell_thickness", "enabled": True, "parameterFields": {"thickness": round(float(candidate["thickness"]), 6)}},
|
||||
],
|
||||
"rawLimitations": [
|
||||
"Derived from paired planar SCDM faces; shell.thickness remains non-productized until real SCDM edit samples pass.",
|
||||
"Derived from paired planar SCDM faces; shell.thickness is productized only when both wall faces and thickness axis are locatable.",
|
||||
],
|
||||
}
|
||||
)
|
||||
@@ -1153,6 +1205,8 @@ def attach_local_face_ids_to_scdm_cache(
|
||||
item = dict(raw_object)
|
||||
signature = dict(item.get("geometrySignature") if isinstance(item.get("geometrySignature"), Mapping) else {})
|
||||
if not _int_list(signature.get("faceIds")):
|
||||
# SCDM 和本软件的 Face 编号不是同一套 ID。优先用 ordinal locator 精确映射;
|
||||
# locator 不完整时才退到几何签名打分,避免修改后 ID 变化把公式续接错。
|
||||
ordinal_face_ids = _face_ids_from_signature_ordinals(signature, local_signatures)
|
||||
if ordinal_face_ids:
|
||||
signature["faceIds"] = ordinal_face_ids
|
||||
@@ -1195,6 +1249,8 @@ def attach_local_face_ids_to_scdm_cache(
|
||||
support_face_ids = _pattern_support_face_ids(signature, local_signatures)
|
||||
if support_face_ids:
|
||||
signature["supportFaceIds"] = support_face_ids
|
||||
# 阵列间距不能只看实例中心距,还要知道这些实例是否仍落在支撑面上。
|
||||
# supportPatternFit 会给 UI 和 preflight 提供最大安全间距。
|
||||
support_fit = _pattern_support_fit(signature, local_signatures, support_face_ids, unit_scale or 1.0)
|
||||
if support_fit:
|
||||
signature["supportPatternFit"] = support_fit
|
||||
@@ -1420,6 +1476,8 @@ def _pattern_support_face_ids(
|
||||
if distance > tolerance:
|
||||
continue
|
||||
area = _number(local.get("area")) or _box_area_estimate(face_box)
|
||||
# 支撑面候选要求和阵列包围盒投影重叠,并且沿面法向几乎贴合;
|
||||
# 面积越大、贴合越近,越像承载该阵列的底面。
|
||||
candidates.append((overlap * max(area, 1.0) - distance, int(face_id)))
|
||||
candidates.sort(reverse=True)
|
||||
return [face_id for _score, face_id in candidates[:4]]
|
||||
@@ -1469,6 +1527,8 @@ def _pattern_support_fit(
|
||||
max_spacing_local = (2.0 * min(left_capacity, right_capacity)) / (len(center_projections) - 1)
|
||||
if max_spacing_local <= 0:
|
||||
continue
|
||||
# 这里按“保持整列中心不变”等距重排”估算最大间距。
|
||||
# 局部段间距会在 property spec 里按具体移动语义再计算范围。
|
||||
support_candidates.append(
|
||||
{
|
||||
"faceId": int(face_id),
|
||||
@@ -1742,20 +1802,58 @@ def _current_value(fields: tuple[str, ...], raw_object: Mapping[str, object]) ->
|
||||
return 1
|
||||
if field == "geometry.radius*2":
|
||||
radius = _number(geometry.get("radius"))
|
||||
if radius is not None:
|
||||
if radius is not None and radius > 0:
|
||||
return radius * 2.0
|
||||
continue
|
||||
if field.startswith("geometry."):
|
||||
value = _path_value(geometry, field.split(".", 1)[1])
|
||||
if value is not None:
|
||||
if _invalid_dimension_current_value(field, value):
|
||||
continue
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _invalid_dimension_current_value(field: str, value: object) -> bool:
|
||||
if not field.startswith("geometry."):
|
||||
return False
|
||||
path = field.split(".", 1)[1]
|
||||
if path in {"offset", "planeOffset"} or path.endswith(".offset"):
|
||||
return False
|
||||
dimension_suffixes = (
|
||||
"radius",
|
||||
"diameter",
|
||||
"width",
|
||||
"depth",
|
||||
"height",
|
||||
"distance",
|
||||
"thickness",
|
||||
"spacing",
|
||||
"pitch",
|
||||
)
|
||||
if not path.endswith(dimension_suffixes):
|
||||
return False
|
||||
number = _number(value)
|
||||
return number is not None and number <= 0
|
||||
|
||||
|
||||
def _missing_geometry_reason(key: str, raw_object: Mapping[str, object]) -> str:
|
||||
if key not in {"slot.depth", "boss.height", "boss.diameter", "round.radius", "chamfer.distance", "pattern.spacing"}:
|
||||
if key not in {"slot.depth", "boss.height", "boss.diameter", "round.radius", "chamfer.distance", "pattern.spacing", "shell.thickness"}:
|
||||
return ""
|
||||
signature = geometry_signature(raw_object)
|
||||
if key == "shell.thickness":
|
||||
thickness = _rounded_number(signature.get("thickness"))
|
||||
if thickness is None or thickness <= 0:
|
||||
return "SCDM 已识别薄壁候选,但没有返回可用于编辑的当前壳体厚度。"
|
||||
axis = _rounded_vector(signature.get("thicknessAxis") or signature.get("axis"))
|
||||
if len(axis) != 3:
|
||||
return "SCDM 已识别薄壁厚度,但没有返回稳定的厚度方向,暂不能修改。"
|
||||
wall_locators = _face_locators(signature.get("wallFaceLocators") or signature.get("scdmFaceLocators"))
|
||||
wall_ordinals = _int_list(signature.get("wallFaceOrdinals") or signature.get("faceOrdinals"))
|
||||
wall_global_ordinals = _int_list(signature.get("globalWallFaceOrdinals") or signature.get("globalFaceOrdinals"))
|
||||
if len(wall_locators) < 2 and len(wall_ordinals) < 2 and len(wall_global_ordinals) < 2:
|
||||
return "SCDM 已识别薄壁厚度,但没有返回两侧墙面的定位信息,暂不能修改。"
|
||||
return ""
|
||||
if key == "pattern.spacing":
|
||||
spacing = _rounded_number(_first_present(signature.get("spacing"), signature.get("pitch")))
|
||||
if spacing is None or spacing <= 0:
|
||||
|
||||
Reference in New Issue
Block a user