feat: 完善 SCDM-first 参数化编辑交付版
This commit is contained in:
+381
-33
@@ -50,6 +50,8 @@ def prepare_scdm_edit_job(
|
||||
"roadmap_stage": definition.roadmap_stage,
|
||||
}
|
||||
converted_target = _target_value_for_job(target_value, value_kind=definition.value_kind)
|
||||
# 先在本软件侧做快速守门,避免为了明显非法或证据不足的输入启动 SCDM,
|
||||
# 否则用户会等很久才得到一个本可提前说明的失败。
|
||||
preflight = _preflight_scdm_edit_job(
|
||||
definition.key,
|
||||
converted_target,
|
||||
@@ -74,8 +76,11 @@ def prepare_scdm_edit_job(
|
||||
script_path = work_dir / "scdm_edit.py"
|
||||
operation = backend_operation or definition.backend_operation
|
||||
|
||||
# 旧 result.step/error.json 不能留着复用;否则 SCDM 启动失败时可能被误判为成功。
|
||||
_clear_stale_outputs((output_path, result_path, error_path))
|
||||
|
||||
# 这是本软件和 SCDM 的后台协议。UI 不直接调用 SCDM API,
|
||||
# 而是写 job JSON + 临时脚本,再用 SpaceClaim.exe /RunScript 执行。
|
||||
job = {
|
||||
"schemaVersion": SCDM_EDIT_SCHEMA_VERSION,
|
||||
"adapter": SCDM_EDIT_ADAPTER,
|
||||
@@ -210,6 +215,8 @@ def run_prepared_scdm_edit_job(
|
||||
command = scdm_run_script_command(backend.path, script_path)
|
||||
run = runner or subprocess.run
|
||||
try:
|
||||
# SpaceClaim 是外部 GUI/几何进程,必须独立启动并加超时;
|
||||
# Python/Qt 主进程只等待结果文件,避免 SCDM 卡死时拖垮界面。
|
||||
completed = run(
|
||||
command,
|
||||
cwd=str(script_path.parent),
|
||||
@@ -286,9 +293,17 @@ def _preflight_scdm_edit_job(
|
||||
target_value: object,
|
||||
object_signature: Mapping[str, object],
|
||||
) -> dict[str, object]:
|
||||
basic = _preflight_basic_target(capability_key, target_value, object_signature)
|
||||
if basic.get("ok") is False:
|
||||
return basic
|
||||
required = _preflight_required_geometry(capability_key, object_signature)
|
||||
if required.get("ok") is False:
|
||||
return required
|
||||
if capability_key not in {"pattern.spacing", "pattern.segment_spacing"}:
|
||||
return {"ok": True, "reason": "ok"}
|
||||
if _is_body_pattern_signature(object_signature):
|
||||
# 实体/组件阵列必须有 occurrence locator。只有 bodyIndex 时移动的可能是共享定义,
|
||||
# 不是用户眼里那个独立阵列成员。
|
||||
if not _body_pattern_has_component_locators(object_signature):
|
||||
return {
|
||||
"ok": False,
|
||||
@@ -345,6 +360,203 @@ def _preflight_scdm_edit_job(
|
||||
}
|
||||
|
||||
|
||||
def _preflight_basic_target(
|
||||
capability_key: str,
|
||||
target_value: object,
|
||||
object_signature: Mapping[str, object],
|
||||
) -> dict[str, object]:
|
||||
if capability_key in _positive_number_capabilities():
|
||||
target = _float_or_none(target_value)
|
||||
if target is not None and target <= 0:
|
||||
return {
|
||||
"ok": False,
|
||||
"reason": "target-value-illegal",
|
||||
"message": "目标值必须大于 0。",
|
||||
}
|
||||
current_number = _current_number_for_capability(capability_key, object_signature)
|
||||
target_number = _float_or_none(target_value)
|
||||
if current_number is not None and target_number is not None:
|
||||
tolerance = max(abs(current_number), abs(target_number), 1.0) * 1.0e-9
|
||||
if abs(current_number - target_number) <= tolerance:
|
||||
return {
|
||||
"ok": False,
|
||||
"reason": "target-already-current",
|
||||
"message": "目标值与当前值相同,不需要修改。",
|
||||
"current": current_number,
|
||||
"target": target_number,
|
||||
}
|
||||
current_vector = _current_vector_for_capability(capability_key, object_signature)
|
||||
target_vector = _parse_vector3(target_value)
|
||||
if len(current_vector) == 3 and len(target_vector) == 3:
|
||||
error = sum((current_vector[index] - target_vector[index]) ** 2 for index in range(3)) ** 0.5
|
||||
scale = max(max(abs(item) for item in current_vector + target_vector), 1.0)
|
||||
if error <= scale * 1.0e-9:
|
||||
return {
|
||||
"ok": False,
|
||||
"reason": "target-already-current",
|
||||
"message": "目标位置与当前位置相同,不需要修改。",
|
||||
"current": current_vector,
|
||||
"target": target_vector,
|
||||
}
|
||||
return {"ok": True, "reason": "ok"}
|
||||
|
||||
|
||||
def _preflight_required_geometry(capability_key: str, signature: Mapping[str, object]) -> dict[str, object]:
|
||||
if capability_key == "slot.depth":
|
||||
if _positive_number(signature.get("depth")) is None:
|
||||
return _missing_signature("object-signature-missing-depth", "SCDM 已识别槽深,但缓存里没有当前槽深,不能稳定执行。")
|
||||
if not _has_vector3(signature.get("depthAxis") or signature.get("depthDirection")):
|
||||
return _missing_signature("object-signature-missing-depth-axis", "SCDM 已识别槽深,但缓存里没有槽深方向,不能稳定执行。")
|
||||
if not _has_face_locator_evidence(signature, ("depthFaceLocators", "bottomFaceLocators"), ("depthFaceOrdinals", "bottomFaceOrdinals"), ("globalDepthFaceOrdinals", "globalBottomFaceOrdinals")):
|
||||
return _missing_signature("object-signature-missing-depth-face-locator", "SCDM 已识别槽深,但缓存里没有可推动的槽底面定位,不能稳定执行。")
|
||||
elif capability_key == "boss.height":
|
||||
if _positive_number(signature.get("height")) is None:
|
||||
return _missing_signature("object-signature-missing-height", "SCDM 已识别凸台高度,但缓存里没有当前高度,不能稳定执行。")
|
||||
if not _has_vector3(signature.get("axis")):
|
||||
return _missing_signature("object-signature-missing-axis", "SCDM 已识别凸台高度,但缓存里没有高度方向,不能稳定执行。")
|
||||
if not _has_face_locator_evidence(signature, ("heightFaceLocators", "topFaceLocators"), ("heightFaceOrdinals", "topFaceOrdinals"), ("globalHeightFaceOrdinals", "globalTopFaceOrdinals")):
|
||||
return _missing_signature("object-signature-missing-height-face-locator", "SCDM 已识别凸台高度,但缓存里没有可推动的顶面定位,不能稳定执行。")
|
||||
elif capability_key == "boss.diameter":
|
||||
if _positive_number(signature.get("diameter"), _twice(signature.get("radius"))) is None:
|
||||
return _missing_signature("object-signature-missing-diameter", "SCDM 已识别凸台直径,但缓存里没有当前直径,不能稳定执行。")
|
||||
if not _has_face_locator_evidence(signature, ("diameterFaceLocators", "sideFaceLocators"), ("diameterFaceOrdinals", "sideFaceOrdinals"), ("globalDiameterFaceOrdinals", "globalSideFaceOrdinals")):
|
||||
return _missing_signature("object-signature-missing-diameter-face-locator", "SCDM 已识别凸台直径,但缓存里没有可偏移的侧壁定位,不能稳定执行。")
|
||||
elif capability_key == "round.radius":
|
||||
if signature.get("isConstantRound") is not True:
|
||||
return _missing_signature("object-signature-missing-constant-round-evidence", "SCDM 没有返回等半径圆角证据,不能稳定修改圆角半径。")
|
||||
if _positive_number(signature.get("radius")) is None:
|
||||
return _missing_signature("object-signature-missing-round-radius", "SCDM 已识别圆角,但缓存里没有当前半径,不能稳定执行。")
|
||||
if not _has_face_locator_evidence(signature, ("scdmFaceLocators",), ("faceOrdinals",), ("globalFaceOrdinals",), allow_scalar_face=True):
|
||||
return _missing_signature("object-signature-missing-round-face-locator", "SCDM 已识别圆角,但缓存里没有可定位的圆角面,不能稳定执行。")
|
||||
elif capability_key == "chamfer.distance":
|
||||
if signature.get("isEqualDistanceChamfer") is not True:
|
||||
return _missing_signature("object-signature-missing-equal-distance-chamfer-evidence", "SCDM 没有返回等距倒角证据,不能稳定修改倒角距离。")
|
||||
if _positive_number(signature.get("distance"), signature.get("offset")) is None:
|
||||
return _missing_signature("object-signature-missing-chamfer-distance", "SCDM 已识别倒角,但缓存里没有当前距离,不能稳定执行。")
|
||||
if not _has_face_locator_evidence(signature, ("scdmFaceLocators",), ("faceOrdinals",), ("globalFaceOrdinals",), allow_scalar_face=True):
|
||||
return _missing_signature("object-signature-missing-chamfer-face-locator", "SCDM 已识别倒角,但缓存里没有可定位的倒角面,不能稳定执行。")
|
||||
elif capability_key == "shell.thickness":
|
||||
if _positive_number(signature.get("thickness")) is None:
|
||||
return _missing_signature("object-signature-missing-shell-thickness", "SCDM 已识别壳体厚度,但缓存里没有当前厚度,不能稳定执行。")
|
||||
if not _has_vector3(signature.get("thicknessAxis") or signature.get("axis")):
|
||||
return _missing_signature("object-signature-missing-shell-thickness-axis", "SCDM 已识别壳体厚度,但缓存里没有厚度方向,不能稳定执行。")
|
||||
if not _has_face_locator_evidence(signature, ("wallFaceLocators", "scdmFaceLocators"), ("wallFaceOrdinals", "faceOrdinals"), ("globalWallFaceOrdinals", "globalFaceOrdinals"), minimum=2):
|
||||
return _missing_signature("object-signature-missing-wall-face-locator", "SCDM 已识别壳体厚度,但缓存里没有两侧墙面定位,不能稳定执行。")
|
||||
elif capability_key == "pattern.instance_position":
|
||||
if not _has_vector3(signature.get("center") or signature.get("instanceCenter")):
|
||||
return _missing_signature("object-signature-missing-pattern-instance-center", "SCDM 已识别阵列实例,但缓存里没有当前实例位置,不能稳定执行。")
|
||||
if not _has_pattern_instance_locator(signature):
|
||||
return _missing_signature("object-signature-missing-pattern-instance-locator", "SCDM 已识别阵列实例,但缓存里没有可定位的 Face / Body / Component,不能稳定执行。")
|
||||
return {"ok": True, "reason": "ok"}
|
||||
|
||||
|
||||
def _missing_signature(reason: str, message: str) -> dict[str, object]:
|
||||
return {"ok": False, "reason": reason, "message": message}
|
||||
|
||||
|
||||
def _has_vector3(value: object) -> bool:
|
||||
return len(_parse_vector3(value)) == 3
|
||||
|
||||
|
||||
def _has_face_locator_evidence(
|
||||
signature: Mapping[str, object],
|
||||
locator_keys: tuple[str, ...],
|
||||
ordinal_keys: tuple[str, ...],
|
||||
global_ordinal_keys: tuple[str, ...],
|
||||
*,
|
||||
minimum: int = 1,
|
||||
allow_scalar_face: bool = False,
|
||||
) -> bool:
|
||||
count = 0
|
||||
for key in locator_keys:
|
||||
value = signature.get(key)
|
||||
if isinstance(value, (list, tuple)):
|
||||
count += sum(1 for item in value if isinstance(item, Mapping))
|
||||
for key in ordinal_keys + global_ordinal_keys:
|
||||
value = signature.get(key)
|
||||
if isinstance(value, (list, tuple)):
|
||||
count += sum(1 for item in value if _int_or_none_value(item) is not None)
|
||||
if count >= minimum:
|
||||
return True
|
||||
if allow_scalar_face and minimum <= 1:
|
||||
return any(_int_or_none_value(signature.get(key)) is not None for key in ("faceOrdinal", "globalFaceOrdinal"))
|
||||
return False
|
||||
|
||||
|
||||
def _has_pattern_instance_locator(signature: Mapping[str, object]) -> bool:
|
||||
if signature.get("componentLocators") or signature.get("bodyLocators") or signature.get("scdmFaceLocators"):
|
||||
return True
|
||||
if _has_face_locator_evidence(signature, (), ("faceOrdinals",), ("globalFaceOrdinals",), allow_scalar_face=True):
|
||||
return True
|
||||
instance_kind = str(signature.get("instanceKind") or "").strip().lower()
|
||||
return instance_kind in {"body", "part", "component"} and _int_or_none_value(signature.get("bodyIndex")) is not None
|
||||
|
||||
|
||||
def _positive_number_capabilities() -> set[str]:
|
||||
return {
|
||||
"hole.diameter",
|
||||
"slot.width",
|
||||
"slot.depth",
|
||||
"boss.height",
|
||||
"boss.diameter",
|
||||
"round.radius",
|
||||
"chamfer.distance",
|
||||
"pattern.spacing",
|
||||
"pattern.segment_spacing",
|
||||
"shell.thickness",
|
||||
}
|
||||
|
||||
|
||||
def _current_number_for_capability(capability_key: str, signature: Mapping[str, object]) -> float | None:
|
||||
if capability_key == "hole.diameter":
|
||||
return _first_number(signature.get("diameter"), _twice(signature.get("radius")))
|
||||
if capability_key == "face.offset":
|
||||
return _first_number(signature.get("planeOffset"), signature.get("offset"), 0.0)
|
||||
if capability_key == "slot.width":
|
||||
return _first_number(signature.get("width"), signature.get("diameter"), _twice(signature.get("radius")))
|
||||
if capability_key == "slot.depth":
|
||||
return _first_number(signature.get("depth"))
|
||||
if capability_key == "boss.height":
|
||||
return _first_number(signature.get("height"))
|
||||
if capability_key == "boss.diameter":
|
||||
return _first_number(signature.get("diameter"), _twice(signature.get("radius")))
|
||||
if capability_key == "round.radius":
|
||||
return _first_number(signature.get("radius"))
|
||||
if capability_key == "chamfer.distance":
|
||||
return _first_number(signature.get("distance"), signature.get("offset"))
|
||||
if capability_key == "pattern.spacing":
|
||||
return _first_number(signature.get("spacing"), signature.get("pitch"))
|
||||
if capability_key == "pattern.segment_spacing":
|
||||
return _first_number(signature.get("segmentSpacing"))
|
||||
if capability_key == "shell.thickness":
|
||||
return _first_number(signature.get("thickness"))
|
||||
return None
|
||||
|
||||
|
||||
def _current_vector_for_capability(capability_key: str, signature: Mapping[str, object]) -> list[float]:
|
||||
if capability_key in {"hole.position", "slot.position", "boss.position", "pattern.instance_position"}:
|
||||
return _parse_vector3(signature.get("center") or signature.get("axisCenter") or signature.get("instanceCenter"))
|
||||
return []
|
||||
|
||||
|
||||
def _first_number(*values: object) -> float | None:
|
||||
for value in values:
|
||||
number = _float_or_none(value)
|
||||
if number is not None:
|
||||
return number
|
||||
return None
|
||||
|
||||
|
||||
def _positive_number(*values: object) -> float | None:
|
||||
number = _first_number(*values)
|
||||
return number if number is not None and number > 0 else None
|
||||
|
||||
|
||||
def _twice(value: object) -> float | None:
|
||||
number = _float_or_none(value)
|
||||
return None if number is None else number * 2.0
|
||||
|
||||
|
||||
def _float_or_none(value: object) -> float | None:
|
||||
try:
|
||||
return float(str(value).strip())
|
||||
@@ -935,6 +1147,18 @@ def _locate_depth_faces(signature):
|
||||
raise Exception('object_signature_missing_depth_face_locator')
|
||||
|
||||
|
||||
def _locate_wall_faces(signature):
|
||||
faces = _locate_named_faces(
|
||||
signature,
|
||||
('wallFaceLocators', 'scdmFaceLocators'),
|
||||
('wallFaceOrdinals', 'faceOrdinals'),
|
||||
('globalWallFaceOrdinals', 'globalFaceOrdinals'),
|
||||
)
|
||||
if len(faces) >= 2:
|
||||
return faces
|
||||
raise Exception('object_signature_missing_wall_face_locator')
|
||||
|
||||
|
||||
def _locate_pattern_instance_faces(instance, fallback_body_index):
|
||||
signature = dict(instance)
|
||||
if signature.get('bodyIndex') is None and fallback_body_index is not None:
|
||||
@@ -1246,41 +1470,53 @@ def _translate_selection(selection, delta):
|
||||
raise Exception('capability_not_implemented: Move.Translate failed; ' + '; '.join(result.get('errors') or []))
|
||||
|
||||
|
||||
def _delete_selection(selection, fill_errors=None):
|
||||
delete = _command_type('Delete')
|
||||
if delete is None:
|
||||
errors = list(fill_errors or [])
|
||||
errors.append('Delete command not available')
|
||||
raise Exception('capability_not_implemented: Fill/Delete command not available; ' + '; '.join(errors))
|
||||
variants = ((selection,), (selection, None))
|
||||
fallback = _call_variants('Delete.Execute', delete.Execute, variants)
|
||||
if fallback.get('ok'):
|
||||
return {'command': 'Delete.Execute', 'apiSignature': fallback.get('signature'), 'fillErrors': list(fill_errors or [])}
|
||||
errors = list(fill_errors or [])
|
||||
errors.extend(fallback.get('errors') or [])
|
||||
raise Exception('capability_not_implemented: Delete.Execute failed; ' + '; '.join(errors))
|
||||
|
||||
|
||||
def _fill_selection(selection):
|
||||
fill = _command_type('Fill')
|
||||
if fill is None:
|
||||
raise Exception('capability_not_implemented: Fill command not available')
|
||||
options = _new_options('FillOptions')
|
||||
for attr, value in (('AutoExtendFillArea', True), ('PatchBlend', True), ('ZipLaminarEdges', True)):
|
||||
try:
|
||||
if options is not None and hasattr(options, attr):
|
||||
setattr(options, attr, value)
|
||||
except Exception:
|
||||
pass
|
||||
secondary = _empty_selection()
|
||||
mode = _fill_mode_three_d()
|
||||
variants = []
|
||||
if mode is not None:
|
||||
variants.append((selection, secondary, options, mode, None))
|
||||
variants.append((selection, secondary, options, mode))
|
||||
variants.append((selection, None, options, mode, None))
|
||||
variants.append((selection, None, options, mode))
|
||||
variants.append((selection, secondary, options, None, None))
|
||||
variants.append((selection, secondary, options, None))
|
||||
variants.append((selection, options, None))
|
||||
variants.append((selection, options))
|
||||
variants.append((selection, None))
|
||||
variants.append((selection,))
|
||||
result = _call_variants('Fill.Execute', fill.Execute, variants)
|
||||
if result.get('ok'):
|
||||
return {'command': 'Fill.Execute', 'apiSignature': result.get('signature')}
|
||||
delete = _command_type('Delete')
|
||||
if delete is not None:
|
||||
fallback = _call_variants('Delete.Execute', delete.Execute, ((selection,),))
|
||||
if fallback.get('ok'):
|
||||
return {'command': 'Delete.Execute', 'apiSignature': fallback.get('signature'), 'fillErrors': result.get('errors') or []}
|
||||
result['errors'].extend(fallback.get('errors') or [])
|
||||
raise Exception('capability_not_implemented: Fill.Execute failed; ' + '; '.join(result.get('errors') or []))
|
||||
fill_errors = []
|
||||
if fill is not None:
|
||||
options = _new_options('FillOptions')
|
||||
for attr, value in (('AutoExtendFillArea', True), ('PatchBlend', True), ('ZipLaminarEdges', True)):
|
||||
try:
|
||||
if options is not None and hasattr(options, attr):
|
||||
setattr(options, attr, value)
|
||||
except Exception:
|
||||
pass
|
||||
secondary = _empty_selection()
|
||||
mode = _fill_mode_three_d()
|
||||
variants = []
|
||||
if mode is not None:
|
||||
variants.append((selection, secondary, options, mode, None))
|
||||
variants.append((selection, secondary, options, mode))
|
||||
variants.append((selection, None, options, mode, None))
|
||||
variants.append((selection, None, options, mode))
|
||||
variants.append((selection, secondary, options, None, None))
|
||||
variants.append((selection, secondary, options, None))
|
||||
variants.append((selection, options, None))
|
||||
variants.append((selection, options))
|
||||
variants.append((selection, None))
|
||||
variants.append((selection,))
|
||||
result = _call_variants('Fill.Execute', fill.Execute, variants)
|
||||
if result.get('ok'):
|
||||
return {'command': 'Fill.Execute', 'apiSignature': result.get('signature')}
|
||||
fill_errors.extend(result.get('errors') or [])
|
||||
else:
|
||||
fill_errors.append('Fill command not available')
|
||||
return _delete_selection(selection, fill_errors)
|
||||
|
||||
|
||||
def _float_value(value):
|
||||
@@ -1794,6 +2030,18 @@ def change_pattern_segment_spacing(job):
|
||||
for item in located[segment_index + 1:]
|
||||
)
|
||||
spacing_mode = 'segment_split'
|
||||
elif moving_side in ('single_left', 'only_left'):
|
||||
delta = [axis_unit[index] * -delta_distance for index in range(3)]
|
||||
moves = [
|
||||
{'items': located[segment_index]['items'], 'kind': located[segment_index]['kind'], 'delta': delta, 'itemCount': len(located[segment_index]['items'])}
|
||||
]
|
||||
spacing_mode = 'segment_single_left'
|
||||
elif moving_side in ('single_right', 'only_right'):
|
||||
delta = [axis_unit[index] * delta_distance for index in range(3)]
|
||||
moves = [
|
||||
{'items': located[segment_index + 1]['items'], 'kind': located[segment_index + 1]['kind'], 'delta': delta, 'itemCount': len(located[segment_index + 1]['items'])}
|
||||
]
|
||||
spacing_mode = 'segment_single_right'
|
||||
else:
|
||||
raise Exception('capability_not_implemented: unsupported pattern segment spacing motion semantics: ' + moving_side)
|
||||
if not moves:
|
||||
@@ -1833,6 +2081,37 @@ def change_pattern_segment_spacing(job):
|
||||
}
|
||||
|
||||
|
||||
def move_pattern_instance(job):
|
||||
target = _vector3(job['target']['value'])
|
||||
signature = job['object'].get('geometrySignature') or {}
|
||||
center = signature.get('center') or signature.get('instanceCenter') or []
|
||||
if not isinstance(center, list) or len(center) != 3:
|
||||
raise Exception('object_signature_missing_pattern_instance_center')
|
||||
delta = [float(target[index]) - float(center[index]) for index in range(3)]
|
||||
if _vector_length(delta) <= 1e-12:
|
||||
return {'command': 'noop', 'delta': delta, 'reason': 'target already reached'}
|
||||
body_index = _int_or_none(signature.get('bodyIndex'))
|
||||
try:
|
||||
located = _locate_pattern_instance_items(signature, body_index)
|
||||
items = located.get('items') or []
|
||||
if not items:
|
||||
raise Exception('object_signature_missing_pattern_instance_locator')
|
||||
if located.get('kind') == 'component':
|
||||
if len(items) != 1:
|
||||
raise Exception('component_pattern_instance_not_unique')
|
||||
applied = _translate_component_occurrence(items[0], delta)
|
||||
else:
|
||||
applied = _translate_selection(_selection(items), delta)
|
||||
applied['targetCenter'] = target
|
||||
applied['previousCenter'] = center
|
||||
applied['delta'] = delta
|
||||
applied['targetKind'] = located.get('kind') or 'face'
|
||||
applied['itemCount'] = len(items)
|
||||
return applied
|
||||
except Exception as exc:
|
||||
raise Exception('capability_not_implemented: pattern.instance_position adapter failed; ' + str(exc))
|
||||
|
||||
|
||||
def change_boss_height(job):
|
||||
target = _float_value(job['target']['value'])
|
||||
if target <= 0:
|
||||
@@ -2038,6 +2317,71 @@ def change_chamfer_distance(job):
|
||||
raise Exception('capability_not_implemented: chamfer.distance adapter failed; ' + str(exc))
|
||||
|
||||
|
||||
def change_shell_thickness(job):
|
||||
target = _float_value(job['target']['value'])
|
||||
if target <= 0:
|
||||
raise Exception('target_value_illegal: shell thickness must be positive')
|
||||
signature = job['object'].get('geometrySignature') or {}
|
||||
current = None
|
||||
try:
|
||||
current = float(signature.get('thickness'))
|
||||
except Exception:
|
||||
current = None
|
||||
if current is None or current <= 0:
|
||||
raise Exception('object_signature_missing_shell_thickness')
|
||||
axis = signature.get('thicknessAxis') or signature.get('axis') or []
|
||||
if not isinstance(axis, list) or len(axis) != 3:
|
||||
raise Exception('object_signature_missing_shell_thickness_axis')
|
||||
axis_length = _vector_length([float(axis[0]), float(axis[1]), float(axis[2])])
|
||||
if axis_length <= 1e-12:
|
||||
raise Exception('object_signature_missing_shell_thickness_axis')
|
||||
delta_distance = target - current
|
||||
if abs(delta_distance) <= 1e-12:
|
||||
return {'command': 'noop', 'targetThickness': target, 'reason': 'target already reached'}
|
||||
faces = _locate_wall_faces(signature)
|
||||
centers = signature.get('wallFaceCenters') or []
|
||||
sign = 1.0
|
||||
if isinstance(centers, list) and len(centers) >= 2:
|
||||
left = centers[0]
|
||||
right = centers[1]
|
||||
if isinstance(left, list) and len(left) == 3 and isinstance(right, list) and len(right) == 3:
|
||||
projection_delta = sum((float(right[index]) - float(left[index])) * float(axis[index]) for index in range(3))
|
||||
if projection_delta < 0:
|
||||
sign = -1.0
|
||||
moving_side = str(signature.get('movingSide') or 'after').strip().lower()
|
||||
axis_unit = [float(axis[index]) / axis_length for index in range(3)]
|
||||
if moving_side in ('before', 'left'):
|
||||
moving_faces = [faces[0]]
|
||||
delta = [axis_unit[index] * -sign * delta_distance for index in range(3)]
|
||||
elif moving_side in ('split', 'both', 'center'):
|
||||
left_delta = [axis_unit[index] * -sign * delta_distance * 0.5 for index in range(3)]
|
||||
right_delta = [axis_unit[index] * sign * delta_distance * 0.5 for index in range(3)]
|
||||
left_applied = _translate_selection(_selection([faces[0]]), left_delta)
|
||||
right_applied = _translate_selection(_selection([faces[-1]]), right_delta)
|
||||
return {
|
||||
'command': 'Move.Translate shell wall pair',
|
||||
'targetThickness': target,
|
||||
'previousThickness': current,
|
||||
'thicknessDelta': delta_distance,
|
||||
'movingSide': moving_side,
|
||||
'wallFaceCount': len(faces),
|
||||
'moves': [left_applied, right_applied],
|
||||
}
|
||||
else:
|
||||
moving_faces = [faces[-1]]
|
||||
delta = [axis_unit[index] * sign * delta_distance for index in range(3)]
|
||||
try:
|
||||
applied = _translate_selection(_selection(moving_faces), delta)
|
||||
applied['targetThickness'] = target
|
||||
applied['previousThickness'] = current
|
||||
applied['thicknessDelta'] = delta_distance
|
||||
applied['movingSide'] = moving_side
|
||||
applied['wallFaceCount'] = len(faces)
|
||||
return applied
|
||||
except Exception as exc:
|
||||
raise Exception('capability_not_implemented: shell.thickness adapter failed; ' + str(exc))
|
||||
|
||||
|
||||
def pull_face_offset(job):
|
||||
target = _float_value(job['target']['value'])
|
||||
signature = job['object'].get('geometrySignature') or {}
|
||||
@@ -2097,6 +2441,8 @@ def _apply_edit(job):
|
||||
return change_pattern_spacing(job)
|
||||
if operation == 'change_pattern_segment_spacing':
|
||||
return change_pattern_segment_spacing(job)
|
||||
if operation == 'move_pattern_instance':
|
||||
return move_pattern_instance(job)
|
||||
if operation == 'change_boss_height':
|
||||
return change_boss_height(job)
|
||||
if operation == 'change_boss_diameter':
|
||||
@@ -2105,6 +2451,8 @@ def _apply_edit(job):
|
||||
return change_round_radius(job)
|
||||
if operation == 'change_chamfer_distance':
|
||||
return change_chamfer_distance(job)
|
||||
if operation == 'change_shell_thickness':
|
||||
return change_shell_thickness(job)
|
||||
if operation == 'pull_face_offset':
|
||||
return pull_face_offset(job)
|
||||
if operation == 'fill_feature':
|
||||
|
||||
Reference in New Issue
Block a user