- Tons of addtions
This commit is contained in:
@@ -46,7 +46,11 @@ class OCGeometryKernel(GeometryKernel):
|
||||
self._mesh_tolerance: float = 0.1
|
||||
|
||||
def _get_shape(self, obj: GeometryObject) -> Any:
|
||||
"""Extract the underlying OCC shape from a GeometryObject."""
|
||||
"""Extract the underlying OCC shape from a GeometryObject.
|
||||
|
||||
Returns *None* if the object carries no shape (e.g. an empty sketch) —
|
||||
callers should check for None before using the result.
|
||||
"""
|
||||
import cadquery as cq
|
||||
|
||||
if isinstance(obj, OCCGeometryObject):
|
||||
@@ -64,7 +68,13 @@ class OCGeometryKernel(GeometryKernel):
|
||||
if hasattr(obj.shape, "wrapped"):
|
||||
return obj.shape.wrapped
|
||||
return obj.shape
|
||||
return obj.shape if obj.shape else obj
|
||||
# No cadquery obj and no raw shape → genuinely empty.
|
||||
return None
|
||||
# Non-OCCGeometryObject: return its shape if present, else None.
|
||||
# (Use explicit identity/truth checks — some OCP TopoDS objects have a
|
||||
# falsy __bool__, so ``obj.shape if obj.shape`` is unsafe.)
|
||||
shape = getattr(obj, "shape", None)
|
||||
return shape if shape is not None else None
|
||||
|
||||
def _get_cq_obj(self, obj: GeometryObject) -> Any:
|
||||
"""Get CadQuery object from GeometryObject."""
|
||||
@@ -157,30 +167,94 @@ class OCGeometryKernel(GeometryKernel):
|
||||
direction: Tuple[float, float, float] = (0, 0, 1),
|
||||
symmetric: bool = False,
|
||||
) -> GeometryObject:
|
||||
"""Extrude a 2D sketch into a 3D solid.
|
||||
"""Extrude a sketch face into a 3D solid along the sketch plane normal.
|
||||
|
||||
*height* is extruded along *direction* (default +Z). A negative *height*
|
||||
extrudes in the opposite direction. The *direction* argument is accepted
|
||||
for API compatibility; currently only the sign of *height* is used for
|
||||
direction (positive → +Z, negative → -Z).
|
||||
The sketch's plane normal is read from ``sketch.metadata["normal"]``
|
||||
(set by ``OCCSketch.build_face_geometry``); it defaults to +Z for
|
||||
legacy objects that don't carry one. *direction* is accepted for API
|
||||
compatibility but ignored — the plane normal is authoritative. A
|
||||
negative *height* extrudes against the normal.
|
||||
"""
|
||||
import cadquery as cq
|
||||
from OCP.gp import gp_Vec
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakePrism
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse
|
||||
from OCP.TopoDS import TopoDS_Shape
|
||||
|
||||
cq_obj = self._get_cq_obj(sketch)
|
||||
face = self._get_shape(sketch)
|
||||
if face is None:
|
||||
raise ValueError(
|
||||
"Cannot extrude: sketch has no geometry. "
|
||||
"Draw a closed profile before extruding."
|
||||
)
|
||||
# ``face`` may be a TopoDS_Face (new path) or a compound/wire from
|
||||
# legacy cadquery objects. If it's not already a face, build one.
|
||||
face = self._ensure_face(face)
|
||||
if face is None:
|
||||
raise ValueError(
|
||||
"Cannot extrude: sketch geometry is not a valid face. "
|
||||
"Ensure the profile is closed (no open ends)."
|
||||
)
|
||||
|
||||
if isinstance(cq_obj, cq.Workplane):
|
||||
if symmetric:
|
||||
solid = cq_obj.extrude(height / 2, both=True)
|
||||
else:
|
||||
solid = cq_obj.extrude(height)
|
||||
normal = self._sketch_normal(sketch)
|
||||
nx, ny, nz = normal
|
||||
|
||||
def _prism(h: float):
|
||||
vec = gp_Vec(nx * h, ny * h, nz * h)
|
||||
maker = BRepPrimAPI_MakePrism(face, vec, False, True)
|
||||
maker.Build()
|
||||
return maker.Shape()
|
||||
|
||||
if symmetric:
|
||||
half = height / 2.0
|
||||
pos = _prism(half)
|
||||
neg = _prism(-half)
|
||||
fuse = BRepAlgoAPI_Fuse(pos, neg)
|
||||
fuse.Build()
|
||||
solid = fuse.Shape()
|
||||
else:
|
||||
wp = cq.Workplane("XY").add(cq_obj)
|
||||
if symmetric:
|
||||
solid = wp.extrude(height / 2, both=True)
|
||||
else:
|
||||
solid = wp.extrude(height)
|
||||
solid = _prism(height)
|
||||
|
||||
return OCCGeometryObject(solid, {"type": "extrusion"})
|
||||
return OCCGeometryObject(solid, {"type": "extrusion", "normal": normal})
|
||||
|
||||
@staticmethod
|
||||
def _sketch_normal(obj: GeometryObject) -> Tuple[float, float, float]:
|
||||
"""Return the normal stored on a sketch-derived geometry object, else +Z."""
|
||||
import numpy as np
|
||||
meta = getattr(obj, "metadata", None) or {}
|
||||
n = meta.get("normal")
|
||||
if n is None:
|
||||
return (0.0, 0.0, 1.0)
|
||||
arr = np.asarray(n, dtype=float)
|
||||
norm = float(np.linalg.norm(arr))
|
||||
if norm < 1e-12:
|
||||
return (0.0, 0.0, 1.0)
|
||||
arr = arr / norm
|
||||
return (float(arr[0]), float(arr[1]), float(arr[2]))
|
||||
|
||||
@staticmethod
|
||||
def _ensure_face(shape: Any) -> Any:
|
||||
"""Return a ``TopoDS_Face`` from *shape*, or *None* if impossible.
|
||||
|
||||
If *shape* is already a face, return it unchanged; otherwise try to
|
||||
build a planar face from it (wire/edge/compound). Returns *None* for
|
||||
empty/invalid input so callers can surface a clear error instead of
|
||||
feeding a non-face to ``BRepPrimAPI_MakePrism``.
|
||||
"""
|
||||
from OCP.TopoDS import TopoDS_Face
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
|
||||
if shape is None:
|
||||
return None
|
||||
if isinstance(shape, TopoDS_Face):
|
||||
return shape
|
||||
try:
|
||||
maker = BRepBuilderAPI_MakeFace(shape, True)
|
||||
maker.Build()
|
||||
if maker.IsDone():
|
||||
return maker.Face()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def revolve(
|
||||
self,
|
||||
@@ -189,22 +263,16 @@ class OCGeometryKernel(GeometryKernel):
|
||||
axis: Tuple[float, float, float] = (0, 0, 1),
|
||||
origin: Tuple[float, float, float] = (0, 0, 0),
|
||||
) -> GeometryObject:
|
||||
"""Revolve a 2D sketch around an axis."""
|
||||
import cadquery as cq
|
||||
"""Revolve a sketch face around an axis."""
|
||||
import math
|
||||
|
||||
# Get the OCC shape directly
|
||||
# Get the OCC shape directly (a TopoDS_Face for new sketch geometry).
|
||||
shape = self._get_shape(sketch)
|
||||
face = self._ensure_face(shape)
|
||||
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
from OCP.gp import gp_Ax1, gp_Pnt, gp_Dir
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeRevol
|
||||
|
||||
# Build a face from the wire/shape
|
||||
face_maker = BRepBuilderAPI_MakeFace(shape, False)
|
||||
face_maker.Build()
|
||||
face = face_maker.Face()
|
||||
|
||||
# Revolve the face around the axis
|
||||
revolve_axis = gp_Ax1(gp_Pnt(*origin), gp_Dir(*axis))
|
||||
angle_rad = math.radians(angle)
|
||||
@@ -212,8 +280,7 @@ class OCGeometryKernel(GeometryKernel):
|
||||
revolver.Build()
|
||||
solid_shape = revolver.Shape()
|
||||
|
||||
solid = cq.Shape(solid_shape)
|
||||
return OCCGeometryObject(solid, {"type": "revolution"})
|
||||
return OCCGeometryObject(solid_shape, {"type": "revolution"})
|
||||
|
||||
def loft(self, profiles: List[GeometryObject], ruled: bool = False) -> GeometryObject:
|
||||
"""Create a loft between multiple profiles."""
|
||||
|
||||
Reference in New Issue
Block a user