- sketch enhacements

This commit is contained in:
bklronin
2026-07-04 12:10:58 +02:00
parent 01833e4af2
commit 6ba742ddf5
5 changed files with 1652 additions and 104 deletions
+92
View File
@@ -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: