- sketch enhacements
This commit is contained in:
@@ -687,6 +687,141 @@ class OCCRenderer(Renderer):
|
||||
qcol = Quantity_Color(*color, Quantity_TOC_RGB)
|
||||
self._view.SetBackgroundColor(qcol)
|
||||
|
||||
# ─── Workplane visualization ────────────────────────────────────────
|
||||
|
||||
_WORKPLANE_BASE_ID: str = "__workplane__"
|
||||
_WORKPLANE_GRID_SIZE: float = 200.0 # total extent of the visual plane
|
||||
|
||||
def show_workplane_plane(
|
||||
self,
|
||||
origin: Tuple[float, float, float] = (0, 0, 0),
|
||||
normal: Tuple[float, float, float] = (0, 0, 1),
|
||||
x_dir: Tuple[float, float, float] = (1, 0, 0),
|
||||
size: float = 200.0,
|
||||
name: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Display a semi-transparent planar grid at the workplane location.
|
||||
|
||||
The plane is a single large rectangular face with a grid of lines
|
||||
drawn on it, rendered at low opacity so it does not obscure the
|
||||
existing bodies. Returns an object ID that can be passed to
|
||||
:meth:`remove_workplane_plane`.
|
||||
|
||||
If *name* is provided it is used as the object ID; otherwise a
|
||||
unique one is generated. Passing an existing workplane ID will
|
||||
replace that workplane visual in place.
|
||||
"""
|
||||
import numpy as np
|
||||
from OCP.gp import gp_Pnt, gp_Dir
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
from OCP.gp import gp_Pln
|
||||
from OCP.AIS import AIS_Shape
|
||||
from OCP.Quantity import Quantity_Color, Quantity_TOC_RGB
|
||||
from OCP.Graphic3d import Graphic3d_MaterialAspect, Graphic3d_NameOfMaterial
|
||||
|
||||
obj_id = name or f"{self._WORKPLANE_BASE_ID}_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
# Compute orthonormal basis.
|
||||
n = np.asarray(normal, dtype=float)
|
||||
n = n / np.linalg.norm(n)
|
||||
x = np.asarray(x_dir, dtype=float)
|
||||
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)
|
||||
|
||||
hs = size / 2.0
|
||||
# Four corners of the plane in local coords.
|
||||
corners_3d = [
|
||||
(
|
||||
origin[0] + (-hs) * x[0] + (-hs) * y[0],
|
||||
origin[1] + (-hs) * x[1] + (-hs) * y[1],
|
||||
origin[2] + (-hs) * x[2] + (-hs) * y[2],
|
||||
),
|
||||
(
|
||||
origin[0] + (hs) * x[0] + (-hs) * y[0],
|
||||
origin[1] + (hs) * x[1] + (-hs) * y[1],
|
||||
origin[2] + (hs) * x[2] + (-hs) * y[2],
|
||||
),
|
||||
(
|
||||
origin[0] + (hs) * x[0] + (hs) * y[0],
|
||||
origin[1] + (hs) * x[1] + (hs) * y[1],
|
||||
origin[2] + (hs) * x[2] + (hs) * y[2],
|
||||
),
|
||||
(
|
||||
origin[0] + (-hs) * x[0] + (hs) * y[0],
|
||||
origin[1] + (-hs) * x[1] + (hs) * y[1],
|
||||
origin[2] + (-hs) * x[2] + (hs) * y[2],
|
||||
),
|
||||
]
|
||||
|
||||
# Build the planar face: point + normal direction.
|
||||
pln = gp_Pln(
|
||||
gp_Pnt(*origin),
|
||||
gp_Dir(n[0], n[1], n[2]),
|
||||
)
|
||||
# Use gp_Pnt for the corners to make a bounded face.
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakePolygon
|
||||
mp = BRepBuilderAPI_MakePolygon()
|
||||
for c in corners_3d:
|
||||
mp.Add(gp_Pnt(*c))
|
||||
mp.Close()
|
||||
wire = mp.Wire()
|
||||
face_maker = BRepBuilderAPI_MakeFace(pln, wire)
|
||||
face = face_maker.Face()
|
||||
|
||||
# Check if a workplane with this id already exists and remove it.
|
||||
existing = self._objects.get(obj_id)
|
||||
if existing is not None:
|
||||
self.remove_object(existing)
|
||||
|
||||
ais = AIS_Shape(face)
|
||||
# Semi-transparent blue-ish tint.
|
||||
ais.SetColor(Quantity_Color(0.55, 0.75, 0.95, Quantity_TOC_RGB)) # light blue
|
||||
try:
|
||||
ais.SetTransparency(0.75)
|
||||
except Exception:
|
||||
pass
|
||||
ais.SetDisplayMode(1) # shaded
|
||||
|
||||
# Draw the plane with a slight polygon offset so it doesn't z-fight.
|
||||
try:
|
||||
ais.SetPolygonOffsets(3, 1.0, -1.0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._context.Display(ais, True)
|
||||
# Deactivate selection on the workplane plane so that face-picking
|
||||
# (``pick_planar_face``) does NOT intercept the workplane's face
|
||||
# when the user clicks through it to select a body face underneath.
|
||||
# The workplane is decorative reference geometry — it must never
|
||||
# be a target of face-pick mode.
|
||||
try:
|
||||
self._context.Deactivate(ais)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
robj = OCCRenderObject(
|
||||
obj_id=obj_id,
|
||||
ais_shape=ais,
|
||||
ais_type="workplane",
|
||||
color=RenderColor(0.55, 0.75, 0.95),
|
||||
)
|
||||
self._objects[obj_id] = robj
|
||||
self._view.Redraw()
|
||||
return obj_id
|
||||
|
||||
def remove_workplane_plane(self, obj_id: str) -> bool:
|
||||
"""Remove a workplane plane visual by its object ID."""
|
||||
robj = self._objects.get(obj_id)
|
||||
if robj is not None:
|
||||
return self.remove_object(robj)
|
||||
return False
|
||||
|
||||
def take_screenshot(self) -> bytes:
|
||||
return b""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user