更新功能
This commit is contained in:
@@ -8,9 +8,10 @@ from typing import Any
|
||||
|
||||
QUALITY_RULE_TYPES = frozenset({
|
||||
"bbox", "solid_count", "feature_count", "hole_count", "hole_diameter",
|
||||
"hole_center", "overall_length", "overall_diameter", "through_condition",
|
||||
"hole_center", "overall_length", "overall_width", "overall_height",
|
||||
"overall_diameter", "through_condition",
|
||||
})
|
||||
_FEATURE_RULE_TYPES = {"hole_count", "hole_diameter", "hole_center", "through_condition"}
|
||||
FEATURE_RULE_TYPES = frozenset({"hole_count", "hole_diameter", "hole_center", "through_condition"})
|
||||
_SEVERITIES = {"blocking", "warning", "informational"}
|
||||
|
||||
|
||||
@@ -22,7 +23,12 @@ def _bbox_bounds(engine_result: dict[str, Any], feature_id: str | None = None) -
|
||||
if not isinstance(record, dict):
|
||||
continue
|
||||
owners = record.get("owner_feature_ids") or []
|
||||
if record.get("feature_id") == feature_id or feature_id in owners:
|
||||
# Runtime records may carry the current operation in feature_id
|
||||
# while owner_feature_ids identify the actual geometry owner. Use
|
||||
# ownership when present so inherited faces do not pollute a
|
||||
# feature-local measurement.
|
||||
matches = feature_id in owners if owners else record.get("feature_id") == feature_id
|
||||
if matches:
|
||||
geometry = record.get("geometry")
|
||||
if isinstance(geometry, dict):
|
||||
candidates.append(geometry.get("bbox_mm"))
|
||||
@@ -88,12 +94,64 @@ def _circles(profile: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
]
|
||||
|
||||
|
||||
def _circle_feature_bbox(feature: dict[str, Any] | None, sketch: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
"""Derive a stable world-space bbox for a circular extrude feature."""
|
||||
if not isinstance(feature, dict) or not isinstance(sketch, dict):
|
||||
return None
|
||||
if str(feature.get("atomic_id") or "") != "extrude_add_blind":
|
||||
return None
|
||||
profile = sketch.get("profile")
|
||||
workplane = sketch.get("workplane")
|
||||
params = feature.get("params")
|
||||
if not isinstance(profile, dict) or profile.get("type") != "circle" or not isinstance(workplane, dict):
|
||||
return None
|
||||
center_local = profile.get("center")
|
||||
radius = profile.get("radius_mm")
|
||||
origin = workplane.get("origin_mm")
|
||||
x_dir = workplane.get("x_dir")
|
||||
y_dir = workplane.get("y_dir")
|
||||
normal = workplane.get("normal")
|
||||
distance = (params or {}).get("distance_mm") if isinstance(params, dict) else None
|
||||
if not isinstance(center_local, list) or len(center_local) < 2 or not _finite_number(radius):
|
||||
return None
|
||||
if not all(isinstance(value, list) and len(value) >= 3 for value in (origin, x_dir, y_dir, normal)) or not _finite_number(distance):
|
||||
return None
|
||||
center = [
|
||||
float(origin[index]) + float(center_local[0]) * float(x_dir[index]) + float(center_local[1]) * float(y_dir[index])
|
||||
for index in range(3)
|
||||
]
|
||||
points = []
|
||||
for axis_x in (-1.0, 1.0):
|
||||
for axis_y in (-1.0, 1.0):
|
||||
cross_section = [
|
||||
center[index]
|
||||
+ axis_x * float(radius) * float(x_dir[index])
|
||||
+ axis_y * float(radius) * float(y_dir[index])
|
||||
for index in range(3)
|
||||
]
|
||||
points.append(cross_section)
|
||||
points.append([
|
||||
cross_section[index] + float(distance) * float(normal[index])
|
||||
for index in range(3)
|
||||
])
|
||||
minimum = [min(point[index] for point in points) for index in range(3)]
|
||||
maximum = [max(point[index] for point in points) for index in range(3)]
|
||||
return {
|
||||
"min": minimum,
|
||||
"max": maximum,
|
||||
"dimensions": [maximum[index] - minimum[index] for index in range(3)],
|
||||
}
|
||||
|
||||
|
||||
def _finite_number(value: Any) -> bool:
|
||||
return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(float(value))
|
||||
|
||||
|
||||
def _validate_expected(kind: str, expected: Any, index: int) -> None:
|
||||
scalar_types = {"solid_count", "feature_count", "hole_count", "hole_diameter", "overall_length", "overall_diameter"}
|
||||
scalar_types = {
|
||||
"solid_count", "feature_count", "hole_count", "hole_diameter",
|
||||
"overall_length", "overall_width", "overall_height", "overall_diameter",
|
||||
}
|
||||
if kind in scalar_types and not _finite_number(expected):
|
||||
raise ValueError(f"verification.rules[{index}].expected must be a finite number for {kind}")
|
||||
if kind == "bbox":
|
||||
@@ -157,7 +215,7 @@ def validate_verification(verification: Any, cdsl: dict[str, Any]) -> list[dict[
|
||||
if not math.isfinite(tolerance) or tolerance < 0:
|
||||
raise ValueError(f"verification.rules[{index}].tolerance must be finite and non-negative")
|
||||
feature = str(raw.get("feature") or "").strip()
|
||||
if kind in _FEATURE_RULE_TYPES and not feature:
|
||||
if kind in FEATURE_RULE_TYPES and not feature:
|
||||
raise ValueError(f"verification.rules[{index}].feature is required for {kind}")
|
||||
if feature and feature not in feature_ids:
|
||||
raise ValueError(f"verification.rules[{index}].feature must reference a CDSL feature ID")
|
||||
@@ -175,6 +233,9 @@ def _actual(rule: dict[str, Any], cdsl: dict[str, Any], engine_result: dict[str,
|
||||
circles = _circles(profile) if isinstance(profile, dict) else []
|
||||
dimensions = _dimensions(engine_result)
|
||||
if kind == "bbox":
|
||||
derived = _circle_feature_bbox(feature, sketch)
|
||||
if target and derived is not None:
|
||||
return derived, f"cdsl.features.{target}.sketch + params"
|
||||
value = _bbox_value(engine_result, target)
|
||||
source = "runtime.bbox_mm" if not target else f"runtime.topology_records[{target}].bbox_mm"
|
||||
return value, source
|
||||
@@ -194,7 +255,13 @@ def _actual(rule: dict[str, Any], cdsl: dict[str, Any], engine_result: dict[str,
|
||||
return (centers[0] if len(centers) == 1 else centers), f"cdsl.features.{target}.sketch"
|
||||
if kind == "overall_length":
|
||||
return (max(dimensions) if dimensions else None), "runtime.bbox_mm"
|
||||
if kind == "overall_width":
|
||||
return (dimensions[1] if dimensions and len(dimensions) > 1 else None), "runtime.bbox_mm[y]"
|
||||
if kind == "overall_height":
|
||||
return (dimensions[2] if dimensions and len(dimensions) > 2 else None), "runtime.bbox_mm[z]"
|
||||
if kind == "overall_diameter":
|
||||
if target and circles:
|
||||
return max(circle["radius_mm"] for circle in circles) * 2.0, f"cdsl.features.{target}.sketch"
|
||||
return (min(dimensions) if dimensions else None), "runtime.bbox_mm"
|
||||
if kind == "through_condition":
|
||||
end = params.get("end_condition") if isinstance(params.get("end_condition"), dict) else {}
|
||||
|
||||
Reference in New Issue
Block a user