443 lines
12 KiB
Python
443 lines
12 KiB
Python
"""
|
|
Geometry abstraction layer for Fluency CAD.
|
|
|
|
This module defines abstract interfaces for geometry operations,
|
|
allowing different geometry kernels to be used interchangeably.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass
|
|
from typing import List, Tuple, Optional, Any, Dict
|
|
import numpy as np
|
|
|
|
|
|
@dataclass
|
|
class Point2D:
|
|
"""2D point representation."""
|
|
|
|
x: float
|
|
y: float
|
|
|
|
def to_tuple(self) -> Tuple[float, float]:
|
|
return (self.x, self.y)
|
|
|
|
def to_array(self) -> np.ndarray:
|
|
return np.array([self.x, self.y])
|
|
|
|
def distance_to(self, other: "Point2D") -> float:
|
|
return np.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if not isinstance(other, Point2D):
|
|
return False
|
|
return abs(self.x - other.x) < 1e-6 and abs(self.y - other.y) < 1e-6
|
|
|
|
|
|
@dataclass
|
|
class Point3D:
|
|
"""3D point representation."""
|
|
|
|
x: float
|
|
y: float
|
|
z: float
|
|
|
|
def to_tuple(self) -> Tuple[float, float, float]:
|
|
return (self.x, self.y, self.z)
|
|
|
|
def to_array(self) -> np.ndarray:
|
|
return np.array([self.x, self.y, self.z])
|
|
|
|
def distance_to(self, other: "Point3D") -> float:
|
|
return np.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2 + (self.z - other.z) ** 2)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if not isinstance(other, Point3D):
|
|
return False
|
|
return (
|
|
abs(self.x - other.x) < 1e-6
|
|
and abs(self.y - other.y) < 1e-6
|
|
and abs(self.z - other.z) < 1e-6
|
|
)
|
|
|
|
|
|
class GeometryObject:
|
|
"""Base class for geometry objects."""
|
|
|
|
def __init__(self, shape: Any = None, metadata: Optional[Dict] = None):
|
|
self.shape = shape
|
|
self.metadata = metadata or {}
|
|
self._mesh_cache: Optional[Tuple[np.ndarray, np.ndarray]] = None
|
|
|
|
def invalidate_cache(self) -> None:
|
|
"""Invalidate any cached data."""
|
|
self._mesh_cache = None
|
|
|
|
|
|
class SketchEntity:
|
|
"""Base class for sketch entities (points, lines, circles)."""
|
|
|
|
def __init__(self, entity_id: int, entity_type: str):
|
|
self.id = entity_id
|
|
self.entity_type = entity_type
|
|
self.constraints: List[str] = []
|
|
self.is_construction: bool = False
|
|
|
|
def add_constraint(self, constraint_type: str) -> None:
|
|
self.constraints.append(constraint_type)
|
|
|
|
|
|
class GeometryKernel(ABC):
|
|
"""
|
|
Abstract base class for geometry kernels.
|
|
|
|
A geometry kernel provides primitives, operations, and export capabilities
|
|
for CAD geometry.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def create_point(self, x: float, y: float) -> GeometryObject:
|
|
"""Create a 2D point."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def create_line(self, start: Point2D, end: Point2D) -> GeometryObject:
|
|
"""Create a 2D line segment."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def create_circle(self, center: Point2D, radius: float) -> GeometryObject:
|
|
"""Create a 2D circle."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def create_arc(
|
|
self, center: Point2D, radius: float, start_angle: float, end_angle: float
|
|
) -> GeometryObject:
|
|
"""Create a 2D arc."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def create_polygon(self, points: List[Point2D]) -> GeometryObject:
|
|
"""Create a closed polygon from points."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def create_rectangle(
|
|
self, width: float, height: float, center: Optional[Point2D] = None
|
|
) -> GeometryObject:
|
|
"""Create a rectangle."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def extrude(
|
|
self,
|
|
sketch: GeometryObject,
|
|
height: float,
|
|
direction: Tuple[float, float, float] = (0, 0, 1),
|
|
symmetric: bool = False,
|
|
) -> GeometryObject:
|
|
"""Extrude a 2D sketch into a 3D solid."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def revolve(
|
|
self,
|
|
sketch: GeometryObject,
|
|
angle: float = 360.0,
|
|
axis: Tuple[float, float, float] = (0, 0, 1),
|
|
origin: Tuple[float, float, float] = (0, 0, 0),
|
|
) -> GeometryObject:
|
|
"""Revolve a 2D sketch around an axis."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def loft(self, profiles: List[GeometryObject], ruled: bool = False) -> GeometryObject:
|
|
"""Create a loft between multiple profiles."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def sweep(
|
|
self, profile: GeometryObject, path: GeometryObject, is_frenet: bool = False
|
|
) -> GeometryObject:
|
|
"""Sweep a profile along a path."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def boolean_union(self, *bodies: GeometryObject) -> GeometryObject:
|
|
"""Union multiple bodies."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def boolean_difference(self, base: GeometryObject, tool: GeometryObject) -> GeometryObject:
|
|
"""Subtract tool from base."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def boolean_intersection(self, body1: GeometryObject, body2: GeometryObject) -> GeometryObject:
|
|
"""Intersect two bodies."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def fillet(
|
|
self, body: GeometryObject, radius: float, edges: Optional[List[Any]] = None
|
|
) -> GeometryObject:
|
|
"""Apply fillet to edges."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def chamfer(
|
|
self, body: GeometryObject, size: float, edges: Optional[List[Any]] = None
|
|
) -> GeometryObject:
|
|
"""Apply chamfer to edges."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def shell(
|
|
self, body: GeometryObject, thickness: float, faces_to_remove: Optional[List[Any]] = None
|
|
) -> GeometryObject:
|
|
"""Create a shell (hollow body)."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def offset(self, face: GeometryObject, distance: float) -> GeometryObject:
|
|
"""Offset a face or surface."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def translate(self, body: GeometryObject, vector: Tuple[float, float, float]) -> GeometryObject:
|
|
"""Translate a body."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def rotate(
|
|
self,
|
|
body: GeometryObject,
|
|
axis: Tuple[float, float, float],
|
|
angle: float,
|
|
origin: Tuple[float, float, float] = (0, 0, 0),
|
|
) -> GeometryObject:
|
|
"""Rotate a body around an axis."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def scale(self, body: GeometryObject, factor: float) -> GeometryObject:
|
|
"""Scale a body uniformly."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def mirror(
|
|
self,
|
|
body: GeometryObject,
|
|
plane_normal: Tuple[float, float, float],
|
|
plane_origin: Tuple[float, float, float] = (0, 0, 0),
|
|
) -> GeometryObject:
|
|
"""Mirror a body across a plane."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def export_step(self, body: GeometryObject, filepath: str, schema: str = "AP214") -> bool:
|
|
"""Export to STEP format."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def export_iges(self, body: GeometryObject, filepath: str) -> bool:
|
|
"""Export to IGES format."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def export_stl(
|
|
self, body: GeometryObject, filepath: str, tolerance: float = 0.1, ascii_mode: bool = False
|
|
) -> bool:
|
|
"""Export to STL format."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def import_step(self, filepath: str) -> GeometryObject:
|
|
"""Import from STEP format."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def import_iges(self, filepath: str) -> GeometryObject:
|
|
"""Import from IGES format."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_mesh(
|
|
self, body: GeometryObject, tolerance: float = 0.1
|
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
"""
|
|
Get triangulated mesh for rendering.
|
|
|
|
Returns:
|
|
Tuple of (vertices, faces) where:
|
|
- vertices: Nx3 numpy array of vertex positions
|
|
- faces: Mx3 numpy array of triangle indices
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_edges(self, body: GeometryObject) -> Tuple[np.ndarray, np.ndarray]:
|
|
"""
|
|
Get edge wireframe for rendering.
|
|
|
|
Returns:
|
|
Tuple of (vertices, edges) where:
|
|
- vertices: Nx3 numpy array of vertex positions
|
|
- edges: Mx2 numpy array of edge vertex indices
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_bounding_box(self, body: GeometryObject) -> Tuple[Point3D, Point3D]:
|
|
"""Get the bounding box of a body."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_volume(self, body: GeometryObject) -> float:
|
|
"""Calculate the volume of a solid body."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_surface_area(self, body: GeometryObject) -> float:
|
|
"""Calculate the surface area of a body."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_center_of_mass(self, body: GeometryObject) -> Point3D:
|
|
"""Calculate the center of mass of a solid body."""
|
|
pass
|
|
|
|
|
|
class SketchInterface(ABC):
|
|
"""
|
|
Abstract interface for 2D sketching with constraints.
|
|
|
|
A sketch provides 2D geometry creation and constraint solving
|
|
capabilities for parametric CAD.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def add_point(self, x: float, y: float) -> SketchEntity:
|
|
"""Add a point to the sketch."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def add_line(self, start: SketchEntity, end: SketchEntity) -> SketchEntity:
|
|
"""Add a line between two points."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def add_circle(self, center: SketchEntity, radius: float) -> SketchEntity:
|
|
"""Add a circle."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def add_arc(
|
|
self,
|
|
center: SketchEntity,
|
|
radius: float,
|
|
start_point: SketchEntity,
|
|
end_point: SketchEntity,
|
|
) -> SketchEntity:
|
|
"""Add an arc."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def add_rectangle(
|
|
self, corner1: Tuple[float, float], corner2: Tuple[float, float]
|
|
) -> List[SketchEntity]:
|
|
"""Add a rectangle, returning the created entities."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_coincident(self, *entities: SketchEntity) -> bool:
|
|
"""Make entities coincident."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_horizontal(self, line: SketchEntity) -> bool:
|
|
"""Constrain a line to be horizontal."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_vertical(self, line: SketchEntity) -> bool:
|
|
"""Constrain a line to be vertical."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_distance(
|
|
self, entity1: SketchEntity, entity2: SketchEntity, distance: float
|
|
) -> bool:
|
|
"""Constrain distance between two entities."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_angle(self, line1: SketchEntity, line2: SketchEntity, angle: float) -> bool:
|
|
"""Constrain angle between two lines."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_parallel(self, line1: SketchEntity, line2: SketchEntity) -> bool:
|
|
"""Constrain two lines to be parallel."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_perpendicular(self, line1: SketchEntity, line2: SketchEntity) -> bool:
|
|
"""Constrain two lines to be perpendicular."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_midpoint(self, point: SketchEntity, line: SketchEntity) -> bool:
|
|
"""Constrain a point to be at the midpoint of a line."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_tangent(self, entity1: SketchEntity, entity2: SketchEntity) -> bool:
|
|
"""Constrain two entities to be tangent."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_equal_length(self, line1: SketchEntity, line2: SketchEntity) -> bool:
|
|
"""Constrain two lines to have equal length."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_equal_radius(self, circle1: SketchEntity, circle2: SketchEntity) -> bool:
|
|
"""Constrain two circles to have equal radius."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_diameter(self, circle: SketchEntity, diameter: float) -> bool:
|
|
"""Set the diameter of a circle."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def constrain_fixed(self, entity: SketchEntity) -> bool:
|
|
"""Fix an entity in place."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def solve(self) -> bool:
|
|
"""Solve all constraints."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_geometry(self) -> GeometryObject:
|
|
"""Get the solved geometry for operations."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_points(self) -> List[Point2D]:
|
|
"""Get all point positions."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def clear(self) -> None:
|
|
"""Clear all geometry and constraints."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def delete_entity(self, entity: SketchEntity) -> bool:
|
|
"""Delete an entity and its constraints."""
|
|
pass
|