- sketch enhacements
This commit is contained in:
@@ -21,6 +21,76 @@ from fluency.geometry_occ.kernel import OCGeometryKernel, OCCGeometryObject
|
||||
from fluency.geometry_occ.sketch import OCCSketch
|
||||
|
||||
|
||||
@dataclass
|
||||
class Workplane:
|
||||
"""
|
||||
An independent working plane (datum plane) not tied to a face.
|
||||
|
||||
Workplanes can be created at any time and serve as the foundation for
|
||||
sketching and subsequent 3D operations (extrude, cut, revolve, etc.).
|
||||
They are visible in the 3D view as a semi-transparent reference grid.
|
||||
"""
|
||||
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
name: str = "Untitled Workplane"
|
||||
|
||||
origin: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||
normal: Tuple[float, float, float] = (0.0, 0.0, 1.0)
|
||||
x_dir: Tuple[float, float, float] = (1.0, 0.0, 0.0)
|
||||
|
||||
# OCC AIS shape (visual plane) object id in the renderer
|
||||
render_object: Any = None
|
||||
visible: bool = True
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
def __post_init__(self):
|
||||
# Normalise normal and x_dir on construction.
|
||||
import numpy as np
|
||||
n = np.asarray(self.normal, dtype=float)
|
||||
n = n / np.linalg.norm(n)
|
||||
x = np.asarray(self.x_dir, dtype=float)
|
||||
# Remove any component of x along n, then renormalise.
|
||||
x = x - np.dot(x, n) * n
|
||||
x_norm = np.linalg.norm(x)
|
||||
if x_norm < 1e-9:
|
||||
fallback = np.array([1.0, 0.0, 0.0]) if abs(n[0]) < 0.9 else np.array([0.0, 1.0, 0.0])
|
||||
x = fallback - np.dot(fallback, n) * n
|
||||
x_norm = np.linalg.norm(x)
|
||||
x = x / x_norm
|
||||
y = np.cross(n, x)
|
||||
y = y / np.linalg.norm(y)
|
||||
self.normal = tuple(float(v) for v in n)
|
||||
self.x_dir = tuple(float(v) for v in x)
|
||||
self._y_dir = tuple(float(v) for v in y)
|
||||
|
||||
@property
|
||||
def y_dir(self) -> Tuple[float, float, float]:
|
||||
"""Derived in-plane Y axis (normal × x_dir)."""
|
||||
return self._y_dir
|
||||
|
||||
def uv_to_world(self, u: float, v: float) -> Tuple[float, float, float]:
|
||||
"""Map a UV point to 3D world coordinates on this plane."""
|
||||
ox, oy, oz = self.origin
|
||||
xx, xy, xz = self.x_dir
|
||||
yx, yy, yz = self._y_dir
|
||||
return (
|
||||
ox + u * xx + v * yx,
|
||||
oy + u * xy + v * yy,
|
||||
oz + u * xz + v * yz,
|
||||
)
|
||||
|
||||
def world_to_uv(self, p: Tuple[float, float, float]) -> Tuple[float, float]:
|
||||
"""Map a 3D world point to UV coordinates on this plane."""
|
||||
import numpy as np
|
||||
ox, oy, oz = self.origin
|
||||
v = np.array([p[0] - ox, p[1] - oy, p[2] - oz])
|
||||
xd = np.array(self.x_dir, dtype=float)
|
||||
yd = np.array(self._y_dir, dtype=float)
|
||||
return (float(np.dot(v, xd)), float(np.dot(v, yd)))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sketch:
|
||||
"""
|
||||
@@ -192,12 +262,34 @@ class Component:
|
||||
|
||||
sketches: Dict[str, Sketch] = field(default_factory=dict)
|
||||
bodies: Dict[str, Body] = field(default_factory=dict)
|
||||
workplanes: Dict[str, Workplane] = field(default_factory=dict)
|
||||
|
||||
active_sketch: Optional[str] = None
|
||||
active_workplane: Optional[str] = None
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
def add_workplane(self, workplane: Optional[Workplane] = None) -> Workplane:
|
||||
"""Add an independent workplane to the component."""
|
||||
if workplane is None:
|
||||
workplane = Workplane(name=f"Workplane {len(self.workplanes) + 1}")
|
||||
self.workplanes[workplane.id] = workplane
|
||||
if self.active_workplane is None:
|
||||
self.active_workplane = workplane.id
|
||||
self.modified_at = datetime.now()
|
||||
return workplane
|
||||
|
||||
def remove_workplane(self, wp_id: str) -> bool:
|
||||
"""Remove a workplane from the component."""
|
||||
if wp_id in self.workplanes:
|
||||
del self.workplanes[wp_id]
|
||||
if self.active_workplane == wp_id:
|
||||
self.active_workplane = next(iter(self.workplanes.keys()), None)
|
||||
self.modified_at = datetime.now()
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_sketch(self, sketch: Optional[Sketch] = None) -> Sketch:
|
||||
"""Add a sketch to the component."""
|
||||
if sketch is None:
|
||||
|
||||
Reference in New Issue
Block a user