添加cadfs_to_cdsl代码,优化cad生成
This commit is contained in:
@@ -124,7 +124,7 @@ class ProfileCadRuntime:
|
||||
result["ref_" + sha256(f"{document_hash}|{feature_id}".encode("utf-8")).hexdigest()[:16]] = feature_id
|
||||
return result
|
||||
|
||||
def materialize_fragment(self, base_cdsl: dict[str, Any] | None, fragment: dict[str, Any], contract: dict[str, Any], selector_tokens: dict[str, dict[str, Any]], reference_tokens: dict[str, str], *, require_through: bool = False) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
def materialize_fragment(self, base_cdsl: dict[str, Any] | None, fragment: dict[str, Any], contract: dict[str, Any], selector_tokens: dict[str, dict[str, Any]], reference_tokens: dict[str, str], *, require_through: bool = False, depends_on_feature_ids: tuple[str, ...] | list[str] = ()) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
# ActionCommandHandler validates the exposed schema first, but this
|
||||
# adapter is also used during crash recovery. Keep the runtime boundary
|
||||
# self-contained so a corrupted/replayed staged payload cannot produce
|
||||
@@ -168,7 +168,24 @@ class ProfileCadRuntime:
|
||||
if not isinstance(supplied, list) or not all(token in reference_tokens for token in supplied):
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: reference token is absent or stale")
|
||||
materialized["feature"]["params"][slot] = [reference_tokens[token] for token in supplied]
|
||||
self._semantic_preflight(materialized, contract, selector_tokens, base_cdsl, require_through=require_through)
|
||||
through_normalizations = self._normalize_required_through_cut_depth(
|
||||
materialized,
|
||||
contract,
|
||||
selector_tokens,
|
||||
require_through=require_through,
|
||||
)
|
||||
cut_support_normal = self._semantic_preflight(
|
||||
materialized,
|
||||
contract,
|
||||
selector_tokens,
|
||||
base_cdsl,
|
||||
require_through=require_through,
|
||||
)
|
||||
direction_normalizations = self._normalize_extrude_cut_direction(
|
||||
materialized,
|
||||
contract,
|
||||
cut_support_normal,
|
||||
)
|
||||
document = deepcopy(base_cdsl) if isinstance(base_cdsl, dict) else {
|
||||
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "agent_preflight", "geometry": {"sketches": []}, "features": [],
|
||||
}
|
||||
@@ -177,9 +194,17 @@ class ProfileCadRuntime:
|
||||
features = document.setdefault("features", [])
|
||||
if not isinstance(sketches, list) or not isinstance(features, list):
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: base CDSL collections are invalid")
|
||||
existing_feature_ids = {
|
||||
str(item.get("id") or "")
|
||||
for item in features
|
||||
if isinstance(item, dict) and str(item.get("id") or "")
|
||||
}
|
||||
direct_dependencies = tuple(str(value) for value in depends_on_feature_ids)
|
||||
if len(direct_dependencies) != len(set(direct_dependencies)) or any(value not in existing_feature_ids for value in direct_dependencies):
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: plan dependency feature is absent from the active checkpoint")
|
||||
index = len(features) + 1
|
||||
feature = materialized["feature"]
|
||||
output = {"id": f"feature_{index:03d}", "atomic_id": contract["atomic_id"], "params": deepcopy(feature["params"]), "depends_on": [str(features[-1].get("id"))] if features else []}
|
||||
output = {"id": f"feature_{index:03d}", "atomic_id": contract["atomic_id"], "params": deepcopy(feature["params"]), "depends_on": list(direct_dependencies)}
|
||||
if contract["fragment_shape"]["sketch"] == "required":
|
||||
sketch_id = f"sketch_{len(sketches) + 1:03d}"
|
||||
sketch = materialized["sketch"]
|
||||
@@ -225,35 +250,69 @@ class ProfileCadRuntime:
|
||||
"RUNTIME_CONTRACT_INVALID: materialized fragment violates the engine CDSL schema: "
|
||||
+ message
|
||||
) from error
|
||||
return document, {"schema_version": "cad.v3.fragment-audit.v1", "atomic_id": contract["atomic_id"], "fragment_hash": canonical_hash(fragment), "contract_hash": contract["contract_hash"], "assigned_feature_ids": [output["id"]], "assigned_sketch_ids": [output["sketch_id"]] if output.get("sketch_id") else [], "selector_snapshot_id": next(iter(selector_tokens.values()), {}).get("snapshot_id", ""), "selector_tokens": list(feature.get("selector_tokens", [])), "reference_snapshot_id": canonical_hash(reference_tokens), "reference_tokens": list(fragment.get("feature", {}).get("params", {}).get(str(reference.get("slot") or "").removeprefix("params."), [])) if reference["mode"] == "snapshot_bound" else []}
|
||||
return document, {"schema_version": "cad.v3.2.fragment-audit.v1", "atomic_id": contract["atomic_id"], "fragment_hash": canonical_hash(fragment), "contract_hash": contract["contract_hash"], "assigned_feature_ids": [output["id"]], "depends_on_feature_ids": list(direct_dependencies), "assigned_sketch_ids": [output["sketch_id"]] if output.get("sketch_id") else [], "selector_snapshot_id": next(iter(selector_tokens.values()), {}).get("snapshot_id", ""), "selector_tokens": list(feature.get("selector_tokens", [])), "reference_snapshot_id": canonical_hash(reference_tokens), "reference_tokens": list(fragment.get("feature", {}).get("params", {}).get(str(reference.get("slot") or "").removeprefix("params."), [])) if reference["mode"] == "snapshot_bound" else [], "server_normalizations": [*through_normalizations, *direction_normalizations]}
|
||||
|
||||
def rebuild(self, cdsl: dict[str, Any], output_dir: str, task_id: str, revision_id: str) -> dict[str, Any]:
|
||||
def build_checkpoint(self, cdsl: dict[str, Any], output_dir: str, task_id: str, revision_id: str) -> dict[str, Any]:
|
||||
"""Build the exact geometry checkpoint required by every DAG node.
|
||||
|
||||
Rendering is deliberately absent here: a renderer outage must never
|
||||
make a geometrically valid atomic feature fail its local acceptance.
|
||||
"""
|
||||
root = Path(output_dir)
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
cdsl_copy = deepcopy(cdsl)
|
||||
cdsl_copy["part_id"] = task_id
|
||||
step_path = root / "model.step"
|
||||
glb_path = root / "model.glb"
|
||||
self._write_json(root / "model.cdsl.json", cdsl_copy)
|
||||
try:
|
||||
self._validate_finite_tree(cdsl_copy)
|
||||
validate_cdsl(cdsl_copy, self.engine)
|
||||
except RuntimeAdapterError:
|
||||
raise
|
||||
except Exception as error:
|
||||
raise RuntimeAdapterError(f"RUNTIME_PRECONDITION_FAILED: persisted CDSL failed rebuild preflight: {error}") from error
|
||||
try:
|
||||
engine_result = self.engine.run_cdsl_only(cdsl_copy, step_path)
|
||||
if str(engine_result.get("engine") or "") != "cdsl_only":
|
||||
raise RuntimeAdapterError("engine did not execute CDSL-only rebuild")
|
||||
preview = step_to_glb(step_path, glb_path)
|
||||
health = self._health(engine_result, step_path, glb_path)
|
||||
topology = topology_snapshot(engine_result, task_id=task_id, revision_id=revision_id, preview=preview)
|
||||
health = self._health(engine_result, step_path)
|
||||
topology = topology_snapshot(engine_result, task_id=task_id, revision_id=revision_id)
|
||||
self._write_json(root / "model.topology.json", topology)
|
||||
manifest = render_checkpoint(self.settings, step_path=step_path, output_dir=root / "renders")
|
||||
report = {"engine_result": engine_result, "preview": preview, "health": health, "render_manifest": manifest}
|
||||
report = {"engine_result": engine_result, "preview": {}, "health": health, "render_manifest": {}}
|
||||
self._write_json(root / "rebuild-report.json", report)
|
||||
return {"health": health, "topology": topology, "report": report, "render_manifest": manifest, "paths": {"cdsl": "model.cdsl.json", "step": "model.step", "glb": "model.glb", "topology": "model.topology.json", "report": "rebuild-report.json", "render_manifest": "renders/render-manifest.json"}}
|
||||
return {"health": health, "topology": topology, "report": report, "render_manifest": {}, "paths": {"cdsl": "model.cdsl.json", "step": "model.step", "topology": "model.topology.json", "report": "rebuild-report.json"}}
|
||||
except OSError:
|
||||
raise
|
||||
except RuntimeAdapterError:
|
||||
raise
|
||||
except Exception as error:
|
||||
raise RuntimeAdapterError(f"RUNTIME_EXECUTION_FAILURE: {error}") from error
|
||||
|
||||
def create_preview(self, output_dir: str) -> dict[str, Any]:
|
||||
"""Create an optional GLB preview for a completed checkpoint."""
|
||||
root = Path(output_dir)
|
||||
step_path = root / "model.step"
|
||||
glb_path = root / "model.glb"
|
||||
preview = step_to_glb(step_path, glb_path)
|
||||
report_path = root / "rebuild-report.json"
|
||||
report = json.loads(report_path.read_text(encoding="utf-8")) if report_path.is_file() else {}
|
||||
report["preview"] = preview
|
||||
self._write_json(report_path, report)
|
||||
return {"preview": preview, "path": "model.glb"}
|
||||
|
||||
def render_review_bundle(self, output_dir: str) -> dict[str, Any]:
|
||||
root = Path(output_dir)
|
||||
manifest = render_checkpoint(self.settings, step_path=root / "model.step", output_dir=root / "renders")
|
||||
report_path = root / "rebuild-report.json"
|
||||
report = json.loads(report_path.read_text(encoding="utf-8")) if report_path.is_file() else {}
|
||||
report["render_manifest"] = manifest
|
||||
self._write_json(report_path, report)
|
||||
return manifest
|
||||
|
||||
def rebuild(self, cdsl: dict[str, Any], output_dir: str, task_id: str, revision_id: str) -> dict[str, Any]:
|
||||
# Legacy v3.1 compatibility path. New DAG nodes use build_checkpoint.
|
||||
built = self.build_checkpoint(cdsl, output_dir, task_id, revision_id)
|
||||
root = Path(output_dir)
|
||||
try:
|
||||
preview = self.create_preview(output_dir)
|
||||
manifest = self.render_review_bundle(output_dir)
|
||||
report = json.loads((root / "rebuild-report.json").read_text(encoding="utf-8"))
|
||||
return {**built, "report": report, "preview": preview["preview"], "render_manifest": manifest, "paths": {**built["paths"], "glb": "model.glb", "render_manifest": "renders/render-manifest.json"}}
|
||||
except OSError:
|
||||
# Artifact writes are a recoverable infrastructure outage. Let the
|
||||
# application handler park the same candidate stage for replay.
|
||||
@@ -330,7 +389,7 @@ class ProfileCadRuntime:
|
||||
}
|
||||
return document
|
||||
|
||||
def _semantic_preflight(self, fragment: dict[str, Any], contract: dict[str, Any], selector_tokens: dict[str, dict[str, Any]], base_cdsl: dict[str, Any] | None, *, require_through: bool) -> None:
|
||||
def _semantic_preflight(self, fragment: dict[str, Any], contract: dict[str, Any], selector_tokens: dict[str, dict[str, Any]], base_cdsl: dict[str, Any] | None, *, require_through: bool) -> list[float] | None:
|
||||
if fragment.get("feature", {}).get("atomic_id") != contract.get("atomic_id"):
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: atomic_id does not match active contract")
|
||||
policy = contract["selector_policy"]
|
||||
@@ -340,6 +399,83 @@ class ProfileCadRuntime:
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: selector token is absent, stale, or has the wrong kind")
|
||||
for name in contract["semantic_preflight"]:
|
||||
self._semantic_preflight_handlers[name](fragment, selector_tokens, base_cdsl, require_through)
|
||||
if contract.get("atomic_id") == "extrude_cut_blind":
|
||||
return self._preflight_extrude_cut_contacts_material(fragment, selector_tokens)
|
||||
return None
|
||||
|
||||
def _normalize_extrude_cut_direction(
|
||||
self,
|
||||
fragment: dict[str, Any],
|
||||
contract: dict[str, Any],
|
||||
support_normal: list[float] | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Aim a surface-attached cut into the measured material half-space.
|
||||
|
||||
A sketch extrusion has no host selector. Once preflight proves that its
|
||||
profile lies on an oriented boundary face, the only executable blind
|
||||
cut direction is the material side of that face. This is a coordinate
|
||||
normalization, not a planning decision, and is recorded in the audit.
|
||||
"""
|
||||
if contract.get("atomic_id") != "extrude_cut_blind" or not self._valid_vector3(support_normal, require_nonzero=True):
|
||||
return []
|
||||
sketch = fragment.get("sketch") if isinstance(fragment, dict) else None
|
||||
workplane = sketch.get("workplane") if isinstance(sketch, dict) else None
|
||||
normal = workplane.get("normal") if isinstance(workplane, dict) else None
|
||||
params = fragment.get("feature", {}).get("params") if isinstance(fragment.get("feature"), dict) else None
|
||||
if not self._valid_vector3(normal, require_nonzero=True) or not isinstance(params, dict):
|
||||
return []
|
||||
unit_normal = [float(component) / self._norm(normal) for component in normal]
|
||||
unit_support = [float(component) / self._norm(support_normal) for component in support_normal]
|
||||
alignment = self._dot(unit_normal, unit_support)
|
||||
if abs(abs(alignment) - 1.0) > 1e-6:
|
||||
return []
|
||||
materialized_reverse = alignment > 0
|
||||
submitted_reverse = bool(params.get("reverse", False))
|
||||
params["reverse"] = materialized_reverse
|
||||
return [{
|
||||
"path": "feature.params.reverse",
|
||||
"submitted": submitted_reverse,
|
||||
"materialized": materialized_reverse,
|
||||
"reason": "surface-attached cut must travel into the measured material half-space",
|
||||
"support_normal": unit_support,
|
||||
}]
|
||||
|
||||
def _normalize_required_through_cut_depth(
|
||||
self,
|
||||
fragment: dict[str, Any],
|
||||
contract: dict[str, Any],
|
||||
selector_tokens: dict[str, dict[str, Any]],
|
||||
*,
|
||||
require_through: bool,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Add the minimum deterministic exit allowance for a through cut.
|
||||
|
||||
The author owns nominal feature geometry. For a through requirement,
|
||||
however, the runtime owns the executable end condition: this engine
|
||||
needs a strictly greater cut distance than the measured host span.
|
||||
Recording the adjustment makes the operational allowance visible
|
||||
without treating it as a user-specified blind-cut depth.
|
||||
"""
|
||||
if not require_through or contract.get("atomic_id") != "extrude_cut_blind":
|
||||
return []
|
||||
params = fragment.get("feature", {}).get("params", {})
|
||||
sketch = fragment.get("sketch") if isinstance(fragment.get("sketch"), dict) else {}
|
||||
workplane = sketch.get("workplane") if isinstance(sketch, dict) else {}
|
||||
normal = workplane.get("normal") if isinstance(workplane, dict) else None
|
||||
thickness = self._span_from_bbox(self._active_body_bbox(selector_tokens), normal)
|
||||
distance = params.get("distance_mm") if isinstance(params, dict) else None
|
||||
if not isinstance(distance, (int, float)) or thickness is None:
|
||||
return []
|
||||
required_distance = float(thickness) + 0.01
|
||||
if float(distance) > float(thickness) + 1e-6:
|
||||
return []
|
||||
params["distance_mm"] = required_distance
|
||||
return [{
|
||||
"path": "/feature/params/distance_mm",
|
||||
"submitted_mm": float(distance),
|
||||
"materialized_mm": required_distance,
|
||||
"reason": "required through-cut exit allowance",
|
||||
}]
|
||||
|
||||
def _preflight_sketch_workplane(self, fragment: dict[str, Any], _selectors: dict[str, dict[str, Any]], _base: dict[str, Any] | None, _require_through: bool) -> None:
|
||||
sketch = fragment.get("sketch")
|
||||
@@ -362,6 +498,123 @@ class ProfileCadRuntime:
|
||||
elif profile.get("type") == "analytic_contours":
|
||||
self._preflight_analytic_contours(profile)
|
||||
|
||||
def _preflight_extrude_cut_contacts_material(self, fragment: dict[str, Any], selectors: dict[str, dict[str, Any]]) -> list[float] | None:
|
||||
"""Reject an extrude cut whose start profile floats above the solid.
|
||||
|
||||
Sketch cuts do not carry a host-face selector, so a model can place a
|
||||
correct 2-D profile on the top of an unrelated boss. The engine then
|
||||
rebuilds successfully but leaves the body unchanged. Detect the common
|
||||
no-contact form using the current planar topology and return a useful
|
||||
coordinate diagnosis before allocating a stage or running the kernel.
|
||||
"""
|
||||
sketch = fragment.get("sketch") if isinstance(fragment, dict) else None
|
||||
workplane = sketch.get("workplane") if isinstance(sketch, dict) else None
|
||||
profile = sketch.get("profile") if isinstance(sketch, dict) else None
|
||||
origin = workplane.get("origin_mm") if isinstance(workplane, dict) else None
|
||||
normal = workplane.get("normal") if isinstance(workplane, dict) else None
|
||||
x_dir = workplane.get("x_dir") if isinstance(workplane, dict) else None
|
||||
if not self._valid_vector3(origin) or not self._valid_vector3(normal, require_nonzero=True) or not self._valid_vector3(x_dir, require_nonzero=True) or not isinstance(profile, dict):
|
||||
return None
|
||||
unit_normal = [float(component) / self._norm(normal) for component in normal]
|
||||
x_projection = self._dot(x_dir, unit_normal)
|
||||
raw_x = [float(x_dir[index]) - x_projection * unit_normal[index] for index in range(3)]
|
||||
if self._norm(raw_x) <= 1e-9:
|
||||
return None
|
||||
unit_x = [value / self._norm(raw_x) for value in raw_x]
|
||||
unit_y = self._cross(unit_normal, unit_x)
|
||||
local_points = self._profile_probe_points(profile)
|
||||
if not local_points:
|
||||
return None
|
||||
world_points = [
|
||||
[
|
||||
float(origin[index]) + local[0] * unit_x[index] + local[1] * unit_y[index]
|
||||
for index in range(3)
|
||||
]
|
||||
for local in local_points
|
||||
]
|
||||
tolerance = 1e-5
|
||||
matching_faces: list[dict[str, Any]] = []
|
||||
available_heights: list[float] = []
|
||||
for value in selectors.values():
|
||||
geometry = value.get("geometry") if isinstance(value, dict) else None
|
||||
face_normal = geometry.get("normal") if isinstance(geometry, dict) else None
|
||||
center = geometry.get("center_mm") if isinstance(geometry, dict) else None
|
||||
loops = geometry.get("boundary_loops_mm") if isinstance(geometry, dict) else None
|
||||
if (
|
||||
not isinstance(geometry, dict)
|
||||
or geometry.get("surface_type") != "plane"
|
||||
or not self._valid_vector3(face_normal, require_nonzero=True)
|
||||
or not self._valid_vector3(center)
|
||||
or not isinstance(loops, list)
|
||||
or not loops
|
||||
):
|
||||
continue
|
||||
unit_face_normal = [float(component) / self._norm(face_normal) for component in face_normal]
|
||||
if abs(abs(self._dot(unit_normal, unit_face_normal)) - 1.0) > 1e-6:
|
||||
continue
|
||||
available_heights.append(self._dot([float(center[index]) for index in range(3)], unit_normal))
|
||||
if abs(self._dot([float(origin[index]) - float(center[index]) for index in range(3)], unit_normal)) <= tolerance:
|
||||
matching_faces.append(geometry)
|
||||
if not matching_faces:
|
||||
return None
|
||||
for face in matching_faces:
|
||||
if any(
|
||||
self._point_in_planar_face(point, face["boundary_loops_mm"], unit_normal, tolerance)
|
||||
for point in world_points
|
||||
):
|
||||
face_normal = face.get("normal")
|
||||
return [float(component) for component in face_normal] if self._valid_vector3(face_normal, require_nonzero=True) else None
|
||||
plane_coordinate = self._dot([float(value) for value in origin], unit_normal)
|
||||
heights = ", ".join(f"{value:g}" for value in sorted(set(round(value, 6) for value in available_heights))[:8])
|
||||
raise RuntimeAdapterError(
|
||||
"RUNTIME_PRECONDITION_FAILED: extrude-cut profile does not contact material on its start plane "
|
||||
f"(plane coordinate {plane_coordinate:g}; available parallel planar faces: [{heights}]); "
|
||||
"place the sketch on the material face containing the intended cut profile"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _profile_probe_points(profile: dict[str, Any]) -> list[tuple[float, float]]:
|
||||
"""Return inexpensive local points sufficient for contact preflight."""
|
||||
points: list[tuple[float, float]] = []
|
||||
|
||||
def append(value: Any) -> None:
|
||||
point = ProfileCadRuntime._point2(value)
|
||||
if point is not None:
|
||||
points.append(point)
|
||||
|
||||
profile_type = profile.get("type")
|
||||
if profile_type == "circle":
|
||||
append(profile.get("center"))
|
||||
elif profile_type == "rectangle":
|
||||
append(profile.get("center"))
|
||||
elif profile_type == "polygon":
|
||||
vertices = profile.get("vertices")
|
||||
if isinstance(vertices, list):
|
||||
for vertex in vertices:
|
||||
append(vertex)
|
||||
elif profile_type == "analytic_contours":
|
||||
contours = profile.get("contours")
|
||||
if isinstance(contours, list):
|
||||
for contour in contours:
|
||||
segments = contour.get("segments") if isinstance(contour, dict) else None
|
||||
if not isinstance(segments, list):
|
||||
continue
|
||||
contour_points: list[tuple[float, float]] = []
|
||||
for segment in segments:
|
||||
if not isinstance(segment, dict):
|
||||
continue
|
||||
for key in ("start", "end", "center"):
|
||||
point = ProfileCadRuntime._point2(segment.get(key))
|
||||
if point is not None:
|
||||
points.append(point)
|
||||
contour_points.append(point)
|
||||
if contour_points:
|
||||
points.append((
|
||||
sum(point[0] for point in contour_points) / len(contour_points),
|
||||
sum(point[1] for point in contour_points) / len(contour_points),
|
||||
))
|
||||
return points
|
||||
|
||||
def _preflight_host_face_exists(self, fragment: dict[str, Any], selectors: dict[str, dict[str, Any]], _base: dict[str, Any] | None, _require_through: bool) -> None:
|
||||
supplied = fragment.get("feature", {}).get("selector_tokens", [])
|
||||
if len(supplied) != 1 or not isinstance(selectors.get(supplied[0]), dict) or selectors[supplied[0]].get("kind") != "face":
|
||||
@@ -439,7 +692,79 @@ class ProfileCadRuntime:
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: hole position is outside the selected host-face bounds")
|
||||
boundary_loops = geometry.get("boundary_loops_mm")
|
||||
if isinstance(boundary_loops, list) and boundary_loops and not self._point_in_planar_face(point, boundary_loops, unit_normal, tolerance):
|
||||
raise RuntimeAdapterError("RUNTIME_PRECONDITION_FAILED: hole position is outside the selected host face's effective boundary")
|
||||
if not self._counterbore_reuses_existing_pilot(fragment, selectors, point, unit_normal, tolerance):
|
||||
host_z = float(center[2]) if isinstance(center, list) and len(center) == 3 else float("nan")
|
||||
coordinate = ", ".join(f"{float(value):g}" for value in point)
|
||||
raise RuntimeAdapterError(
|
||||
"RUNTIME_PRECONDITION_FAILED: hole position "
|
||||
f"[{coordinate}] lies outside the selected host face material boundary (host center z={host_z:g}); "
|
||||
"select a planar host face that contains every requested hole center"
|
||||
)
|
||||
|
||||
def _counterbore_reuses_existing_pilot(
|
||||
self,
|
||||
fragment: dict[str, Any],
|
||||
selectors: dict[str, dict[str, Any]],
|
||||
point: list[float],
|
||||
host_normal: list[float],
|
||||
tolerance: float,
|
||||
) -> bool:
|
||||
"""Allow a counterbore to start from an existing coaxial pilot bore.
|
||||
|
||||
A top face becomes annular after a through bore, so the pilot centre
|
||||
is deliberately outside its material boundary. Counterboring that
|
||||
pilot is nevertheless a standard valid operation. The exception is
|
||||
deliberately narrow: it only applies to a matching inner cylindrical
|
||||
bore whose axis is normal to the selected host plane and whose open
|
||||
end contains the requested start point.
|
||||
"""
|
||||
feature = fragment.get("feature") if isinstance(fragment, dict) else None
|
||||
params = feature.get("params") if isinstance(feature, dict) and isinstance(feature.get("params"), dict) else {}
|
||||
pilot_diameter = params.get("diameter_mm")
|
||||
counterbore_diameter = params.get("counterbore_diameter_mm")
|
||||
if (
|
||||
not isinstance(feature, dict)
|
||||
or feature.get("atomic_id") != "hole_counterbore"
|
||||
or not isinstance(pilot_diameter, (int, float))
|
||||
or not isinstance(counterbore_diameter, (int, float))
|
||||
or float(pilot_diameter) <= 0
|
||||
or float(counterbore_diameter) <= float(pilot_diameter)
|
||||
):
|
||||
return False
|
||||
diameter_tolerance = max(tolerance, abs(float(pilot_diameter)) * 1e-6)
|
||||
for value in selectors.values():
|
||||
geometry = value.get("geometry") if isinstance(value, dict) else None
|
||||
if (
|
||||
not isinstance(geometry, dict)
|
||||
or geometry.get("surface_type") != "cylinder"
|
||||
or geometry.get("cylinder_role") != "inner"
|
||||
or not bool(geometry.get("through"))
|
||||
):
|
||||
continue
|
||||
radius = geometry.get("radius_mm")
|
||||
axis_origin = geometry.get("axis_origin_mm")
|
||||
axis_direction = geometry.get("axis_direction")
|
||||
bbox = geometry.get("bbox_mm")
|
||||
if (
|
||||
not isinstance(radius, (int, float))
|
||||
or abs(2 * float(radius) - float(pilot_diameter)) > diameter_tolerance
|
||||
or not self._valid_vector3(axis_origin)
|
||||
or not self._valid_vector3(axis_direction, require_nonzero=True)
|
||||
or not self._valid_bbox(bbox)
|
||||
):
|
||||
continue
|
||||
unit_axis = [float(component) / self._norm(axis_direction) for component in axis_direction]
|
||||
if abs(abs(self._dot(unit_axis, host_normal)) - 1.0) > 1e-6:
|
||||
continue
|
||||
offset = [float(point[index]) - float(axis_origin[index]) for index in range(3)]
|
||||
axial = self._dot(offset, unit_axis)
|
||||
radial = [offset[index] - axial * unit_axis[index] for index in range(3)]
|
||||
if self._norm(radial) > tolerance:
|
||||
continue
|
||||
if any(float(point[index]) < float(bbox[index]) - tolerance or float(point[index]) > float(bbox[index + 3]) + tolerance for index in range(3)):
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
def _preflight_analytic_contours(self, profile: dict[str, Any]) -> None:
|
||||
contours = profile.get("contours")
|
||||
@@ -750,9 +1075,9 @@ class ProfileCadRuntime:
|
||||
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
@staticmethod
|
||||
def _health(engine_result: dict[str, Any], step_path: Path, glb_path: Path) -> dict[str, Any]:
|
||||
def _health(engine_result: dict[str, Any], step_path: Path, glb_path: Path | None = None) -> dict[str, Any]:
|
||||
bbox = engine_result.get("bbox_mm") if isinstance(engine_result.get("bbox_mm"), dict) else {}
|
||||
minimum, maximum = bbox.get("min"), bbox.get("max")
|
||||
if not isinstance(minimum, list) or not isinstance(maximum, list) or len(minimum) != 3 or len(maximum) != 3 or not step_path.is_file() or not glb_path.is_file():
|
||||
if not isinstance(minimum, list) or not isinstance(maximum, list) or len(minimum) != 3 or len(maximum) != 3 or not step_path.is_file() or (glb_path is not None and not glb_path.is_file()):
|
||||
raise RuntimeAdapterError("rebuild did not produce complete deterministic artifacts")
|
||||
return {"bbox_mm": {"min": [float(item) for item in minimum], "max": [float(item) for item in maximum], "dimensions": [float(maximum[index]) - float(minimum[index]) for index in range(3)]}, "volume_mm3": float(engine_result["volume_mm3"]), "solid_count": int(engine_result["solid_count"]), "feature_count": len(engine_result.get("feature_results") or [])}
|
||||
|
||||
Reference in New Issue
Block a user