Spaces:
Running
Running
| """Semantic Constraint Compiler for Geometry Engine. | |
| Audits high-level geometric primitives and expands them into complete, | |
| unambiguous low-level mathematical and topological constraints (P0). | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from typing import Any, Dict, List, Optional, Set, Tuple | |
| from .models import Point, Constraint | |
| logger = logging.getLogger(__name__) | |
| class ConstraintCompiler: | |
| """ | |
| Compiles and expands high-level semantic geometry DSL into complete, | |
| rigorous low-level geometric invariants and topological constraints. | |
| """ | |
| def compile( | |
| self, | |
| points: Dict[str, Point], | |
| raw_constraints: List[Constraint], | |
| is_3d: bool = False, | |
| ) -> Tuple[List[Point], List[Constraint], bool]: | |
| """ | |
| Takes raw parsed points and constraints and returns expanded points | |
| and complete low-level mathematical constraints. | |
| """ | |
| expanded_constraints: List[Constraint] = [] | |
| derived_segments: List[List[str]] = [] | |
| solids_meta: List[Dict[str, Any]] = [] | |
| def ensure_point(pid: str) -> Point: | |
| if pid not in points: | |
| points[pid] = Point(id=pid) | |
| return points[pid] | |
| def add_segment(p1: str, p2: str): | |
| if p1 != p2: | |
| ensure_point(p1) | |
| ensure_point(p2) | |
| pair = [p1, p2] | |
| if pair not in derived_segments and [p2, p1] not in derived_segments: | |
| derived_segments.append(pair) | |
| expanded_constraints.append(Constraint(type="segment", targets=pair, value=0)) | |
| # 1. First pass: scan and expand high-level semantic constraints | |
| for c in raw_constraints: | |
| c_type = c.type | |
| targets = [t.strip() for t in c.targets if isinstance(t, str)] | |
| val = c.value | |
| # ------------------------------------------------------------- | |
| # HEIGHT / ALTITUDE: HEIGHT(S, O, ABCD) or HEIGHT(S, O, ABC) | |
| # Semantics: O in plane(Base), SO perp to plane(Base) | |
| # ------------------------------------------------------------- | |
| if c_type in ("height", "altitude") and len(targets) >= 3: | |
| is_3d = True | |
| s_apex = targets[0] | |
| o_foot = targets[1] | |
| base_pts = targets[2:] | |
| ensure_point(s_apex) | |
| ensure_point(o_foot) | |
| for bp in base_pts: | |
| ensure_point(bp) | |
| add_segment(s_apex, o_foot) | |
| # 1. Foot O lies on the base plane | |
| expanded_constraints.append( | |
| Constraint(type="point_on_plane", targets=[o_foot] + base_pts[:3], value=0) | |
| ) | |
| # 2. SO is perpendicular to base plane | |
| expanded_constraints.append( | |
| Constraint(type="perp_plane", targets=[s_apex, o_foot] + base_pts, value=0) | |
| ) | |
| # 3. Explicit height length if provided as value > 0 | |
| if isinstance(val, (int, float)) and float(val) > 0: | |
| expanded_constraints.append( | |
| Constraint(type="length", targets=[s_apex, o_foot], value=float(val)) | |
| ) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded HEIGHT: {s_apex}{o_foot} _|_ plane({base_pts})") | |
| # ------------------------------------------------------------- | |
| # MIDPOINT: MIDPOINT(M, A, B) | |
| # Semantics: M on AB, MA = MB, 2M - A - B = 0 | |
| # ------------------------------------------------------------- | |
| elif c_type == "midpoint" and len(targets) == 3: | |
| pM, pA, pB = targets[0], targets[1], targets[2] | |
| for p in [pM, pA, pB]: | |
| ensure_point(p) | |
| add_segment(pA, pM) | |
| add_segment(pM, pB) | |
| expanded_constraints.append(Constraint(type="point_on", targets=[pM, pA, pB], value=0)) | |
| expanded_constraints.append(Constraint(type="length_equal", targets=[pM, pA, pM, pB], value=0)) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded MIDPOINT: {pM} = mid({pA}, {pB})") | |
| # ------------------------------------------------------------- | |
| # CENTER / CENTROID: CENTER(O, A, B, C, ...) | |
| # Semantics: O in plane(Poly), O = mean(vertices) | |
| # ------------------------------------------------------------- | |
| elif c_type in ("center", "centroid") and len(targets) >= 3: | |
| pO = targets[0] | |
| poly_pts = targets[1:] | |
| ensure_point(pO) | |
| for p in poly_pts: | |
| ensure_point(p) | |
| if is_3d or len(poly_pts) >= 3: | |
| expanded_constraints.append( | |
| Constraint(type="point_on_plane", targets=[pO] + poly_pts[:3], value=0) | |
| ) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded CENTER: {pO} center of {poly_pts}") | |
| # ------------------------------------------------------------- | |
| # FOOT OF PERPENDICULAR: FOOT(H, P, A, B) | |
| # Semantics: H on line(AB), PH perp AB | |
| # ------------------------------------------------------------- | |
| elif c_type in ("foot", "foot_perp") and len(targets) >= 4: | |
| pH, pP, pA, pB = targets[0], targets[1], targets[2], targets[3] | |
| for p in [pH, pP, pA, pB]: | |
| ensure_point(p) | |
| add_segment(pP, pH) | |
| expanded_constraints.append(Constraint(type="point_on", targets=[pH, pA, pB], value=0)) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pP, pH, pA, pB], value=0) | |
| ) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded FOOT: {pH} foot of {pP} on {pA}{pB}") | |
| # ------------------------------------------------------------- | |
| # FOOT ON PLANE: FOOT_PLANE(H, P, A, B, C) | |
| # Semantics: H in plane(ABC), PH perp plane(ABC) | |
| # ------------------------------------------------------------- | |
| elif c_type in ("foot_plane", "perp_foot_plane") and len(targets) >= 4: | |
| is_3d = True | |
| pH, pP = targets[0], targets[1] | |
| plane_pts = targets[2:] | |
| ensure_point(pH) | |
| ensure_point(pP) | |
| for p in plane_pts: | |
| ensure_point(p) | |
| add_segment(pP, pH) | |
| expanded_constraints.append( | |
| Constraint(type="point_on_plane", targets=[pH] + plane_pts[:3], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="perp_plane", targets=[pP, pH] + plane_pts, value=0) | |
| ) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded FOOT_PLANE: {pH} on plane({plane_pts})") | |
| # ------------------------------------------------------------- | |
| # MEDIAN: MEDIAN(A, M, B, C) | |
| # Semantics: M is midpoint of BC, segment AM | |
| # ------------------------------------------------------------- | |
| elif c_type == "median" and len(targets) >= 4: | |
| pA, pM, pB, pC = targets[0], targets[1], targets[2], targets[3] | |
| for p in [pA, pM, pB, pC]: | |
| ensure_point(p) | |
| add_segment(pA, pM) | |
| expanded_constraints.append(Constraint(type="midpoint", targets=[pM, pB, pC], value=0)) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded MEDIAN: {pA}{pM} to {pB}{pC}") | |
| # ------------------------------------------------------------- | |
| # BISECTOR: BISECTOR(A, D, B, C) | |
| # ------------------------------------------------------------- | |
| elif c_type == "bisector" and len(targets) >= 4: | |
| pA, pD, pB, pC = targets[0], targets[1], targets[2], targets[3] | |
| for p in [pA, pD, pB, pC]: | |
| ensure_point(p) | |
| add_segment(pA, pD) | |
| expanded_constraints.append(Constraint(type="point_on", targets=[pD, pB, pC], value=0)) | |
| expanded_constraints.append(c) | |
| logger.debug(f"[ConstraintCompiler] Expanded BISECTOR: {pA}{pD} to {pB}{pC}") | |
| # ------------------------------------------------------------- | |
| # SQUARE: SQUARE(ABCD) | |
| # Semantics: 4 equal sides, 4 right angles, 2 equal & orthogonal diagonals, coplanar | |
| # ------------------------------------------------------------- | |
| elif c_type == "square" and len(targets) >= 4: | |
| pA, pB, pC, pD = targets[:4] | |
| for p in [pA, pB, pC, pD]: | |
| ensure_point(p) | |
| add_segment(pA, pB) | |
| add_segment(pB, pC) | |
| add_segment(pC, pD) | |
| add_segment(pD, pA) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pA, pB, pA, pD], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pB, pA, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pB, pD, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pD, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pB, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pB, pC, pC, pD], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pC, pD, pD, pA], value=0) | |
| ) | |
| # Diagonals | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pC, pB, pD], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pA, pC, pB, pD], value=0) | |
| ) | |
| if is_3d: | |
| expanded_constraints.append( | |
| Constraint(type="coplanar", targets=[pA, pB, pC, pD], value=0) | |
| ) | |
| logger.debug(f"[ConstraintCompiler] Expanded SQUARE: {targets[:4]}") | |
| # ------------------------------------------------------------- | |
| # RECTANGLE: RECTANGLE(ABCD) | |
| # Semantics: Opposite sides parallel & equal, right angles, diagonals equal, coplanar | |
| # ------------------------------------------------------------- | |
| elif c_type == "rectangle" and len(targets) >= 4: | |
| pA, pB, pC, pD = targets[:4] | |
| for p in [pA, pB, pC, pD]: | |
| ensure_point(p) | |
| add_segment(pA, pB) | |
| add_segment(pB, pC) | |
| add_segment(pC, pD) | |
| add_segment(pD, pA) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pA, pB, pA, pD], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pB, pA, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pB, pD, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pD, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pB, pD, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pD, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pC, pB, pD], value=0) | |
| ) | |
| if is_3d: | |
| expanded_constraints.append( | |
| Constraint(type="coplanar", targets=[pA, pB, pC, pD], value=0) | |
| ) | |
| logger.debug(f"[ConstraintCompiler] Expanded RECTANGLE: {targets[:4]}") | |
| # ------------------------------------------------------------- | |
| # PARALLELOGRAM / RHOMBUS | |
| # ------------------------------------------------------------- | |
| elif c_type in ("parallelogram", "rhombus") and len(targets) >= 4: | |
| pA, pB, pC, pD = targets[:4] | |
| for p in [pA, pB, pC, pD]: | |
| ensure_point(p) | |
| add_segment(pA, pB) | |
| add_segment(pB, pC) | |
| add_segment(pC, pD) | |
| add_segment(pD, pA) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pB, pD, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pD, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pB, pD, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pD, pB, pC], value=0) | |
| ) | |
| if c_type == "rhombus": | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pB, pB, pC], value=0) | |
| ) | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pA, pC, pB, pD], value=0) | |
| ) | |
| if is_3d: | |
| expanded_constraints.append( | |
| Constraint(type="coplanar", targets=[pA, pB, pC, pD], value=0) | |
| ) | |
| # ------------------------------------------------------------- | |
| # TRAPEZOID: TRAPEZOID(ABCD) (AB || CD) | |
| # ------------------------------------------------------------- | |
| elif c_type in ("trapezoid", "isosceles_trapezoid", "right_trapezoid") and len(targets) >= 4: | |
| pA, pB, pC, pD = targets[:4] | |
| for p in [pA, pB, pC, pD]: | |
| ensure_point(p) | |
| add_segment(pA, pB) | |
| add_segment(pB, pC) | |
| add_segment(pC, pD) | |
| add_segment(pD, pA) | |
| expanded_constraints.append( | |
| Constraint(type="parallel", targets=[pA, pB, pD, pC], value=0) | |
| ) | |
| if c_type == "isosceles_trapezoid": | |
| expanded_constraints.append( | |
| Constraint(type="length_equal", targets=[pA, pD, pB, pC], value=0) | |
| ) | |
| elif c_type == "right_trapezoid": | |
| expanded_constraints.append( | |
| Constraint(type="perpendicular", targets=[pA, pD, pA, pB], value=0) | |
| ) | |
| if is_3d: | |
| expanded_constraints.append( | |
| Constraint(type="coplanar", targets=[pA, pB, pC, pD], value=0) | |
| ) | |
| # ------------------------------------------------------------- | |
| # Metadata constraints (pass-through without treating targets as point IDs) | |
| # ------------------------------------------------------------- | |
| elif c_type in ("solids_metadata", "lines_metadata", "rays_metadata", "polygon_order", "explicit_points"): | |
| expanded_constraints.append(c) | |
| # ------------------------------------------------------------- | |
| # Standard Constraints (pass-through with point validation) | |
| # ------------------------------------------------------------- | |
| else: | |
| for t in targets: | |
| ensure_point(t) | |
| expanded_constraints.append(c) | |
| # 2. Add derived segments to constraints | |
| for seg in derived_segments: | |
| if not any( | |
| c.type == "segment" and set(c.targets[:2]) == set(seg) | |
| for c in expanded_constraints | |
| ): | |
| expanded_constraints.append(Constraint(type="segment", targets=seg, value=0)) | |
| # 3. Final sanity check: all points referenced in constraints must exist | |
| for c in expanded_constraints: | |
| if c.type in ("solids_metadata", "lines_metadata", "rays_metadata", "polygon_order", "explicit_points"): | |
| continue | |
| for pid in c.targets: | |
| if isinstance(pid, str) and pid not in points and not pid.replace(".", "", 1).isdigit() and not pid.startswith("{"): | |
| points[pid] = Point(id=pid) | |
| logger.info( | |
| f"[ConstraintCompiler] Compiled {len(raw_constraints)} raw constraints into " | |
| f"{len(expanded_constraints)} low-level constraints for {len(points)} points (is_3d={is_3d})." | |
| ) | |
| return list(points.values()), expanded_constraints, is_3d | |