Spaces:
Running
Running
| """Complete Visualization Graph Model. | |
| Distinguishes between Mathematical Geometry (exact coordinates, constraints) | |
| and Visualization Graph (topological visual entities, faces, edges, auxiliary | |
| constructions, visibility, and importance tiers). | |
| """ | |
| from __future__ import annotations | |
| import enum | |
| from typing import Any, Dict, List, Optional, Set, Tuple | |
| from pydantic import BaseModel, ConfigDict, Field | |
| class ImportanceTier(str, enum.Enum): | |
| """Necessity level of an entity for minimal sufficient visualization.""" | |
| REQUIRED = "REQUIRED" # Essential to understand the geometry or solution | |
| HELPFUL = "HELPFUL" # Clarifies spatial relations (rendered by default) | |
| OPTIONAL = "OPTIONAL" # Extra detail, hidden unless requested | |
| class EntityKind(str, enum.Enum): | |
| """Distinction between primary, auxiliary, and derived entities.""" | |
| PRIMARY = "PRIMARY" # Base geometry defined by the main problem | |
| AUXILIARY = "AUXILIARY" # Geometric construction (height, foot, median, etc.) | |
| DERIVED = "DERIVED" # Secondary solid, cross-section, or composite object | |
| class EdgeStyle(str, enum.Enum): | |
| """Visual style for rendering edges.""" | |
| SOLID = "solid" | |
| DASHED = "dashed" | |
| DOTTED = "dotted" | |
| class VisVertex(BaseModel): | |
| """A vertex or point in the Visualization Graph.""" | |
| model_config = ConfigDict(extra="ignore") | |
| id: str | |
| coordinates: List[float] = Field(default_factory=list) | |
| role: str = "vertex" # vertex, apex, foot, midpoint, center, auxiliary_point | |
| tier: ImportanceTier = ImportanceTier.REQUIRED | |
| kind: EntityKind = EntityKind.PRIMARY | |
| label: Optional[str] = None | |
| show_label: bool = True | |
| parent_solid: Optional[str] = None | |
| def to_dict(self) -> Dict[str, Any]: | |
| return self.model_dump(mode="json") | |
| class VisEdge(BaseModel): | |
| """An edge or segment in the Visualization Graph.""" | |
| model_config = ConfigDict(extra="ignore") | |
| id: str # Canonical edge ID e.g. "AB" | |
| source: str | |
| target: str | |
| role: str = "edge" # base_edge, lateral_edge, altitude, median, bisector, diagonal, projection | |
| tier: ImportanceTier = ImportanceTier.REQUIRED | |
| kind: EntityKind = EntityKind.PRIMARY | |
| style: EdgeStyle = EdgeStyle.SOLID | |
| is_hidden: bool = False | |
| parent_solid: Optional[str] = None | |
| label: Optional[str] = None | |
| def to_dict(self) -> Dict[str, Any]: | |
| return self.model_dump(mode="json") | |
| class VisFace(BaseModel): | |
| """A polygonal face or surface in the Visualization Graph.""" | |
| model_config = ConfigDict(extra="ignore") | |
| id: str # e.g. "face_ABCD" | |
| vertices: List[str] # Cyclic ordered vertices [A, B, C, D] | |
| role: str = "face" # base_face, lateral_face, top_face, cross_section | |
| parent_solid: Optional[str] = None | |
| plane_equation: Optional[List[float]] = None # [a, b, c, d] for ax + by + cz + d = 0 | |
| tier: ImportanceTier = ImportanceTier.HELPFUL | |
| kind: EntityKind = EntityKind.PRIMARY | |
| opacity: float = 0.2 | |
| fill: bool = True | |
| def to_dict(self) -> Dict[str, Any]: | |
| return self.model_dump(mode="json") | |
| class VisSolid(BaseModel): | |
| """A 3D solid with complete topology (vertices, edges, faces).""" | |
| model_config = ConfigDict(extra="ignore") | |
| id: str # e.g. "pyramid_S_ABCD" | |
| type: str # pyramid, prism, cube, cuboid, tetrahedron, etc. | |
| vertices: List[str] | |
| edges: List[str] # List of edge IDs | |
| faces: List[str] # List of face IDs | |
| apex: Optional[str] = None | |
| base_vertices: List[str] = Field(default_factory=list) | |
| top_vertices: List[str] = Field(default_factory=list) | |
| tier: ImportanceTier = ImportanceTier.REQUIRED | |
| kind: EntityKind = EntityKind.PRIMARY | |
| def to_dict(self) -> Dict[str, Any]: | |
| return self.model_dump(mode="json") | |
| class VisAuxiliaryConstruction(BaseModel): | |
| """A solution-dependent auxiliary construction entity.""" | |
| model_config = ConfigDict(extra="ignore") | |
| id: str | |
| type: str # height, foot, median, bisector, diagonal, center, midpoint, section, projection | |
| source_entity: str | |
| target_entity: str | |
| created_vertices: List[str] = Field(default_factory=list) | |
| created_edges: List[str] = Field(default_factory=list) | |
| perpendicular_marks: List[Dict[str, Any]] = Field(default_factory=list) | |
| angle_marks: List[Dict[str, Any]] = Field(default_factory=list) | |
| equal_ticks: List[Dict[str, Any]] = Field(default_factory=list) | |
| parallel_marks: List[Dict[str, Any]] = Field(default_factory=list) | |
| tier: ImportanceTier = ImportanceTier.REQUIRED | |
| def to_dict(self) -> Dict[str, Any]: | |
| return self.model_dump(mode="json") | |
| class VisualizationGraph(BaseModel): | |
| """ | |
| Complete Topological Visualization Graph representing all visual entities, | |
| hierarchies, faces, edges, and auxiliary constructions. | |
| """ | |
| model_config = ConfigDict(extra="ignore") | |
| vertices: Dict[str, VisVertex] = Field(default_factory=dict) | |
| edges: Dict[str, VisEdge] = Field(default_factory=dict) | |
| faces: Dict[str, VisFace] = Field(default_factory=dict) | |
| solids: Dict[str, VisSolid] = Field(default_factory=dict) | |
| auxiliary: List[VisAuxiliaryConstruction] = Field(default_factory=list) | |
| perpendicular_marks: List[Dict[str, Any]] = Field(default_factory=list) | |
| angle_marks: List[Dict[str, Any]] = Field(default_factory=list) | |
| equal_ticks: List[Dict[str, Any]] = Field(default_factory=list) | |
| parallel_marks: List[Dict[str, Any]] = Field(default_factory=list) | |
| drawing_phases: List[Dict[str, Any]] = Field(default_factory=list) | |
| is_3d: bool = False | |
| def add_vertex( | |
| self, | |
| pid: str, | |
| coords: List[float], | |
| role: str = "vertex", | |
| tier: ImportanceTier = ImportanceTier.REQUIRED, | |
| kind: EntityKind = EntityKind.PRIMARY, | |
| parent_solid: Optional[str] = None, | |
| ) -> VisVertex: | |
| if pid in self.vertices: | |
| existing = self.vertices[pid] | |
| if existing.role == "vertex" and role != "vertex": | |
| existing.role = role | |
| if kind != EntityKind.PRIMARY: | |
| existing.kind = kind | |
| if tier == ImportanceTier.REQUIRED: | |
| existing.tier = ImportanceTier.REQUIRED | |
| if parent_solid and not existing.parent_solid: | |
| existing.parent_solid = parent_solid | |
| return existing | |
| vertex = VisVertex( | |
| id=pid, | |
| coordinates=list(coords), | |
| role=role, | |
| tier=tier, | |
| kind=kind, | |
| label=pid, | |
| parent_solid=parent_solid, | |
| ) | |
| self.vertices[pid] = vertex | |
| return vertex | |
| def add_edge( | |
| self, | |
| p1: str, | |
| p2: str, | |
| role: str = "edge", | |
| tier: ImportanceTier = ImportanceTier.REQUIRED, | |
| kind: EntityKind = EntityKind.PRIMARY, | |
| style: EdgeStyle = EdgeStyle.SOLID, | |
| is_hidden: bool = False, | |
| parent_solid: Optional[str] = None, | |
| ) -> VisEdge: | |
| edge_id = f"{p1}{p2}" if p1 < p2 else f"{p2}{p1}" | |
| if edge_id in self.edges: | |
| # Upgrade tier/style/role if needed | |
| existing = self.edges[edge_id] | |
| if existing.role in ("edge", "segment") and role not in ("edge", "segment"): | |
| existing.role = role | |
| if tier == ImportanceTier.REQUIRED: | |
| existing.tier = ImportanceTier.REQUIRED | |
| if kind != EntityKind.PRIMARY: | |
| existing.kind = kind | |
| if style == EdgeStyle.DASHED: | |
| existing.style = style | |
| if is_hidden: | |
| existing.is_hidden = is_hidden | |
| if parent_solid and not existing.parent_solid: | |
| existing.parent_solid = parent_solid | |
| return existing | |
| edge = VisEdge( | |
| id=edge_id, | |
| source=p1, | |
| target=p2, | |
| role=role, | |
| tier=tier, | |
| kind=kind, | |
| style=style, | |
| is_hidden=is_hidden, | |
| parent_solid=parent_solid, | |
| ) | |
| self.edges[edge_id] = edge | |
| return edge | |
| def add_face( | |
| self, | |
| vertices: List[str], | |
| role: str = "face", | |
| tier: ImportanceTier = ImportanceTier.HELPFUL, | |
| kind: EntityKind = EntityKind.PRIMARY, | |
| parent_solid: Optional[str] = None, | |
| opacity: float = 0.2, | |
| ) -> VisFace: | |
| face_id = f"face_{'_'.join(vertices)}" | |
| if face_id in self.faces: | |
| return self.faces[face_id] | |
| face = VisFace( | |
| id=face_id, | |
| vertices=list(vertices), | |
| role=role, | |
| tier=tier, | |
| kind=kind, | |
| parent_solid=parent_solid, | |
| opacity=opacity, | |
| ) | |
| self.faces[face_id] = face | |
| return face | |
| def get_minimal_sufficient_graph( | |
| self, | |
| max_tier: ImportanceTier = ImportanceTier.HELPFUL, | |
| ) -> Dict[str, Any]: | |
| """ | |
| Filters graph down to a minimal sufficient visualization by excluding | |
| extraneous/cluttering OPTIONAL entities unless requested. | |
| """ | |
| allowed_tiers = {ImportanceTier.REQUIRED} | |
| if max_tier in (ImportanceTier.HELPFUL, ImportanceTier.OPTIONAL): | |
| allowed_tiers.add(ImportanceTier.HELPFUL) | |
| if max_tier == ImportanceTier.OPTIONAL: | |
| allowed_tiers.add(ImportanceTier.OPTIONAL) | |
| filtered_vertices = { | |
| k: v.to_dict() for k, v in self.vertices.items() if v.tier in allowed_tiers | |
| } | |
| filtered_edges = { | |
| k: v.to_dict() | |
| for k, v in self.edges.items() | |
| if v.tier in allowed_tiers | |
| and v.source in filtered_vertices | |
| and v.target in filtered_vertices | |
| } | |
| filtered_faces = { | |
| k: v.to_dict() | |
| for k, v in self.faces.items() | |
| if v.tier in allowed_tiers | |
| and all(pt in filtered_vertices for pt in v.vertices) | |
| } | |
| return { | |
| "vertices": filtered_vertices, | |
| "edges": filtered_edges, | |
| "faces": filtered_faces, | |
| "solids": {k: v.to_dict() for k, v in self.solids.items() if v.tier in allowed_tiers}, | |
| "auxiliary": [a.to_dict() for a in self.auxiliary if a.tier in allowed_tiers], | |
| "drawing_phases": self.drawing_phases, | |
| "is_3d": self.is_3d, | |
| } | |
| def to_geometry_objects_list(self) -> List[Dict[str, Any]]: | |
| """Converts graph into list of geometry object dicts for VisualizationSpec.""" | |
| objs = [] | |
| # Points | |
| for pid, v in self.vertices.items(): | |
| objs.append({ | |
| "type": "point_3d" if self.is_3d else "point_2d", | |
| "label": pid, | |
| "properties": { | |
| "coordinates": v.coordinates, | |
| "role": v.role, | |
| "tier": v.tier.value, | |
| "kind": v.kind.value, | |
| }, | |
| }) | |
| # Edges | |
| for eid, e in self.edges.items(): | |
| objs.append({ | |
| "type": "segment_3d" if self.is_3d else "segment_2d", | |
| "label": eid, | |
| "properties": { | |
| "start": e.source, | |
| "end": e.target, | |
| "role": e.role, | |
| "style": e.style.value, | |
| "is_hidden": e.is_hidden, | |
| "tier": e.tier.value, | |
| "kind": e.kind.value, | |
| "parent_solid": e.parent_solid, | |
| }, | |
| }) | |
| # Faces | |
| for fid, f in self.faces.items(): | |
| objs.append({ | |
| "type": "face_3d" if self.is_3d else "polygon_2d", | |
| "label": fid, | |
| "properties": { | |
| "vertices": f.vertices, | |
| "role": f.role, | |
| "opacity": f.opacity, | |
| "parent_solid": f.parent_solid, | |
| "tier": f.tier.value, | |
| }, | |
| }) | |
| # Solids | |
| for sid, s in self.solids.items(): | |
| objs.append({ | |
| "type": s.type, | |
| "label": sid, | |
| "properties": s.to_dict(), | |
| }) | |
| return objs | |