- removed cadquery deoendency

This commit is contained in:
bklronin
2026-07-01 20:03:00 +02:00
parent 9938f4ddd4
commit f860ff3e77
6 changed files with 165 additions and 1854 deletions
+16 -7
View File
@@ -4,13 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- Tons of addtions">
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- Basic operations">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/geometry_occ/kernel.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/geometry_occ/kernel.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/geometry_occ/sketch.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/geometry_occ/sketch.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/main.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/test_geometry.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/test_geometry.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -280,7 +280,15 @@
<option name="project" value="LOCAL" />
<updated>1782679912834</updated>
</task>
<option name="localTasksCounter" value="22" />
<task id="LOCAL-00022" summary="- Basic operations">
<option name="closed" value="true" />
<created>1782768610475</created>
<option name="number" value="00022" />
<option name="presentableId" value="LOCAL-00022" />
<option name="project" value="LOCAL" />
<updated>1782768610475</updated>
</task>
<option name="localTasksCounter" value="23" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -319,6 +327,7 @@
<MESSAGE value="- added screenshot" />
<MESSAGE value="- added sdf folder ( doesnt work via pip or git=)" />
<MESSAGE value="- Tons of addtions" />
<option name="LAST_COMMIT_MESSAGE" value="- Tons of addtions" />
<MESSAGE value="- Basic operations" />
<option name="LAST_COMMIT_MESSAGE" value="- Basic operations" />
</component>
</project>
+3 -4
View File
@@ -6,7 +6,7 @@ A parametric CAD application built on OpenCASCADE Technology (OCCT) with a moder
- **OpenCASCADE Geometry Kernel**: Industry-standard BRep geometry with exact precision
- **STEP/IGES Import/Export**: Full support for industry-standard CAD file formats
- **Parametric Sketching**: 2D sketching with constraint solving using CadQuery
- **Parametric Sketching**: 2D sketching with constraint solving using SolveSpace
- **Boolean Operations**: Union, difference, and intersection
- **Fillet & Chamfer**: Apply edge treatments to solid bodies
- **Modern Renderer**: WebGPU-based rendering with pygfx (smaller footprint than VTK)
@@ -47,8 +47,7 @@ pip install -e ".[dev]"
| Package | Purpose |
|---------|---------|
| cadquery | High-level OpenCASCADE Python bindings |
| ocp | Low-level OpenCASCADE Python bindings |
| cadquery-ocp | OpenCASCADE Python bindings (OCP) |
| pygfx | WebGPU-based 3D renderer |
| wgpu | WebGPU Python bindings |
| PySide6 | Qt GUI framework |
@@ -105,7 +104,7 @@ kernel.export_stl(body, "part.stl")
| File Size | Large (mesh) | Small (BRep) |
| Fillet/Chamfer | Approximate | Exact |
| Dependency Size | ~200MB (VTK) | ~30MB (pygfx) |
| Constraint Solver | SolveSpace (separate) | CadQuery (integrated) |
| Constraint Solver | SolveSpace (separate) | SolveSpace (integrated) |
## License
-1
View File
@@ -26,7 +26,6 @@ classifiers = [
]
dependencies = [
"cadquery>=2.4",
"pygfx>=0.1.0",
"wgpu>=0.1.0",
"PySide6>=6.4.0",
+1 -1
View File
@@ -2,7 +2,7 @@
Fluency CAD - Parametric CAD Application
A modern parametric CAD application built on OpenCASCADE Technology (OCCT)
with a clean Python API using CadQuery.
with a clean Python API using OCP (OpenCASCADE Python bindings).
"""
__version__ = "2.0.0"
+145 -261
View File
@@ -2,7 +2,7 @@
OpenCASCADE-based geometry kernel for Fluency CAD.
This module provides a concrete implementation of the geometry kernel
using CadQuery and OCP (OpenCASCADE Python bindings).
using OCP (OpenCASCADE Python bindings).
"""
from typing import List, Tuple, Optional, Any, Dict
@@ -21,24 +21,14 @@ class OCCGeometryObject(GeometryObject):
def __init__(self, shape: Any = None, metadata: Optional[Dict] = None):
super().__init__(shape, metadata)
self._cadquery_obj: Any = None
@property
def cq_obj(self) -> Any:
"""Get the CadQuery object if available."""
return self._cadquery_obj
@cq_obj.setter
def cq_obj(self, value: Any) -> None:
self._cadquery_obj = value
class OCGeometryKernel(GeometryKernel):
"""
OpenCASCADE-based geometry kernel implementation.
This kernel uses CadQuery for high-level operations and
OCP for direct OpenCASCADE access when needed.
This kernel uses OCP (OpenCASCADE Python bindings) for all geometry
operations.
"""
def __init__(self) -> None:
@@ -51,114 +41,93 @@ class OCGeometryKernel(GeometryKernel):
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):
if obj._cadquery_obj is not None:
shape = obj._cadquery_obj.val()
if hasattr(shape, "wrapped"):
return shape.wrapped
return shape
if obj.shape is not None:
if isinstance(obj.shape, cq.Workplane):
shape = obj.shape.val()
if hasattr(shape, "wrapped"):
return shape.wrapped
return shape
if hasattr(obj.shape, "wrapped"):
return obj.shape.wrapped
return obj.shape
# No cadquery obj and no raw shape → genuinely empty.
return None
if obj.shape is not None and hasattr(obj.shape, "wrapped"):
return obj.shape.wrapped
return obj.shape
# 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."""
if isinstance(obj, OCCGeometryObject) and obj._cadquery_obj is not None:
return obj._cadquery_obj
return obj.shape
def create_point(self, x: float, y: float) -> GeometryObject:
"""Create a 2D point."""
import cadquery as cq
point = cq.Vector(x, y, 0)
return OCCGeometryObject(point)
from OCP.gp import gp_Pnt
return OCCGeometryObject(gp_Pnt(x, y, 0))
def create_line(self, start: Point2D, end: Point2D) -> GeometryObject:
"""Create a 2D line segment."""
import cadquery as cq
from OCP.gp import gp_Pnt
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
wp = cq.Workplane("XY").moveTo(start.x, start.y).lineTo(end.x, end.y)
obj = OCCGeometryObject(wp.val(), {"type": "line"})
obj._cadquery_obj = wp
return obj
edge = BRepBuilderAPI_MakeEdge(
gp_Pnt(start.x, start.y, 0), gp_Pnt(end.x, end.y, 0)
).Edge()
return OCCGeometryObject(edge, {"type": "line"})
def create_circle(self, center: Point2D, radius: float) -> GeometryObject:
"""Create a 2D circle."""
import cadquery as cq
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2, gp_Circ
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
wp = cq.Workplane("XY").center(center.x, center.y).circle(radius)
obj = OCCGeometryObject(wp.val(), {"type": "circle"})
obj._cadquery_obj = wp
return obj
circ = gp_Circ(
gp_Ax2(gp_Pnt(center.x, center.y, 0), gp_Dir(0, 0, 1)), radius
)
edge = BRepBuilderAPI_MakeEdge(circ).Edge()
return OCCGeometryObject(edge, {"type": "circle"})
def create_arc(
self, center: Point2D, radius: float, start_angle: float, end_angle: float
) -> GeometryObject:
"""Create a 2D arc."""
import cadquery as cq
import math
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2, gp_Circ
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
start_rad = math.radians(start_angle)
end_rad = math.radians(end_angle)
start_x = center.x + radius * math.cos(start_rad)
start_y = center.y + radius * math.sin(start_rad)
wire = (
cq.Workplane("XY")
.moveTo(start_x, start_y)
.radiusArc(
(center.x + radius * math.cos(end_rad), center.y + radius * math.sin(end_rad)),
radius,
)
circ = gp_Circ(
gp_Ax2(gp_Pnt(center.x, center.y, 0), gp_Dir(0, 0, 1)), radius
)
return OCCGeometryObject(wire.val(), {"type": "arc"})
edge = BRepBuilderAPI_MakeEdge(circ, start_rad, end_rad).Edge()
return OCCGeometryObject(edge, {"type": "arc"})
def create_polygon(self, points: List[Point2D]) -> GeometryObject:
"""Create a closed polygon from points."""
import cadquery as cq
from OCP.gp import gp_Pnt
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakePolygon
if len(points) < 3:
raise ValueError("Polygon requires at least 3 points")
wp = cq.Workplane("XY").moveTo(points[0].x, points[0].y)
for pt in points[1:]:
wp = wp.lineTo(pt.x, pt.y)
wp = wp.close()
obj = OCCGeometryObject(wp.val(), {"type": "polygon"})
obj._cadquery_obj = wp
return obj
mp = BRepBuilderAPI_MakePolygon()
for pt in points:
mp.Add(gp_Pnt(pt.x, pt.y, 0))
mp.Close()
return OCCGeometryObject(mp.Wire(), {"type": "polygon"})
def create_rectangle(
self, width: float, height: float, center: Optional[Point2D] = None
) -> GeometryObject:
"""Create a rectangle."""
import cadquery as cq
from OCP.gp import gp_Pnt
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakePolygon
cx = center.x if center else 0
cy = center.y if center else 0
wp = cq.Workplane("XY").center(cx, cy).rect(width, height)
obj = OCCGeometryObject(wp.val(), {"type": "rectangle"})
obj._cadquery_obj = wp
return obj
hw = width / 2.0
hh = height / 2.0
mp = BRepBuilderAPI_MakePolygon()
mp.Add(gp_Pnt(cx - hw, cy - hh, 0))
mp.Add(gp_Pnt(cx + hw, cy - hh, 0))
mp.Add(gp_Pnt(cx + hw, cy + hh, 0))
mp.Add(gp_Pnt(cx - hw, cy + hh, 0))
mp.Close()
return OCCGeometryObject(mp.Wire(), {"type": "rectangle"})
def extrude(
self,
@@ -182,10 +151,6 @@ class OCGeometryKernel(GeometryKernel):
# Defensive: figure out the actual shape from whatever the caller
# hands us, and surface a clear error if we can't get one.
# - If it's an OCCGeometryObject wrapper, unwrap via _get_shape.
# - If it's already a TopoDS_Shape (raw face/wire/etc.), use it.
# - If it's a cadquery Workplane, unwrap that too.
# - If it's a cadquery Shape (cq.Shape), unwrap to TopoDS_Shape.
if isinstance(sketch, OCCGeometryObject):
face = self._get_shape(sketch)
elif isinstance(sketch, TopoDS_Shape):
@@ -205,8 +170,8 @@ class OCGeometryKernel(GeometryKernel):
"Cannot extrude: sketch geometry is a wrapper, not a shape. "
"This is a bug — please report it."
)
# ``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`` may be a TopoDS_Face (new path) or a compound/wire.
# If it's not already a face, build one.
face = self._ensure_face(face)
if face is None:
raise ValueError(
@@ -303,48 +268,58 @@ class OCGeometryKernel(GeometryKernel):
def loft(self, profiles: List[GeometryObject], ruled: bool = False) -> GeometryObject:
"""Create a loft between multiple profiles."""
import cadquery as cq
from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_WIRE
from OCP.TopoDS import TopoDS
if len(profiles) < 2:
raise ValueError("Loft requires at least 2 profiles")
wires = []
loft_maker = BRepOffsetAPI_ThruSections(True, ruled)
for profile in profiles:
cq_obj = self._get_cq_obj(profile)
if isinstance(cq_obj, cq.Workplane):
wires.append(cq_obj.val())
else:
wires.append(cq_obj)
shape = self._get_shape(profile)
explorer = TopExp_Explorer(shape, TopAbs_WIRE)
while explorer.More():
wire = TopoDS.Wire_s(explorer.Current())
loft_maker.AddWire(wire)
explorer.Next()
loft = cq.Solid.loft(wires, ruled)
return OCCGeometryObject(loft, {"type": "loft"})
loft_maker.Build()
solid = loft_maker.Shape()
return OCCGeometryObject(solid, {"type": "loft"})
def sweep(
self, profile: GeometryObject, path: GeometryObject, is_frenet: bool = False
) -> GeometryObject:
"""Sweep a profile along a path."""
import cadquery as cq
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakePipeShell
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_WIRE
from OCP.TopoDS import TopoDS
profile_obj = self._get_cq_obj(profile)
path_obj = self._get_cq_obj(path)
profile_shape = self._get_shape(profile)
path_shape = self._get_shape(path)
if isinstance(profile_obj, cq.Workplane):
profile_wire = profile_obj.val()
else:
profile_wire = profile_obj
def _first_wire(shape):
exp = TopExp_Explorer(shape, TopAbs_WIRE)
if exp.More():
return TopoDS.Wire_s(exp.Current())
raise ValueError("No wire found in shape for sweep")
if isinstance(path_obj, cq.Workplane):
path_wire = path_obj.val()
else:
path_wire = path_obj
profile_wire = _first_wire(profile_shape)
path_wire = _first_wire(path_shape)
solid = cq.Solid.sweep(profile_wire, path_wire, is_frenet)
pipe = BRepOffsetAPI_MakePipeShell(path_wire)
pipe.Add(profile_wire, False, False)
if is_frenet:
pipe.SetMode(True)
pipe.Build()
solid = pipe.Shape()
return OCCGeometryObject(solid, {"type": "sweep"})
def boolean_union(self, *bodies: GeometryObject) -> GeometryObject:
"""Union multiple bodies."""
import cadquery as cq
if len(bodies) < 2:
return bodies[0] if bodies else OCCGeometryObject(None)
@@ -357,12 +332,10 @@ class OCGeometryKernel(GeometryKernel):
fuse.Build()
result = fuse.Shape()
return OCCGeometryObject(cq.Shape(result), {"type": "union"})
return OCCGeometryObject(result, {"type": "union"})
def boolean_difference(self, base: GeometryObject, tool: GeometryObject) -> GeometryObject:
"""Subtract tool from base."""
import cadquery as cq
base_shape = self._get_shape(base)
tool_shape = self._get_shape(tool)
@@ -371,12 +344,10 @@ class OCGeometryKernel(GeometryKernel):
cut = BRepAlgoAPI_Cut(base_shape, tool_shape)
cut.Build()
return OCCGeometryObject(cq.Shape(cut.Shape()), {"type": "difference"})
return OCCGeometryObject(cut.Shape(), {"type": "difference"})
def boolean_intersection(self, body1: GeometryObject, body2: GeometryObject) -> GeometryObject:
"""Intersect two bodies."""
import cadquery as cq
shape1 = self._get_shape(body1)
shape2 = self._get_shape(body2)
@@ -385,27 +356,21 @@ class OCGeometryKernel(GeometryKernel):
common = BRepAlgoAPI_Common(shape1, shape2)
common.Build()
return OCCGeometryObject(cq.Shape(common.Shape()), {"type": "intersection"})
return OCCGeometryObject(common.Shape(), {"type": "intersection"})
def fillet(
self, body: GeometryObject, radius: float, edges: Optional[List[Any]] = None
) -> GeometryObject:
"""Apply fillet to edges."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.BRepFilletAPI import BRepFilletAPI_MakeFillet
cq_obj = self._get_cq_obj(body)
fillet = BRepFilletAPI_MakeFillet(shape)
if isinstance(cq_obj, cq.Workplane):
if edges:
result = cq_obj.edges(edges).fillet(radius)
else:
result = cq_obj.edges().fillet(radius)
if edges:
for edge in edges:
fillet.Add(radius, edge)
else:
shape = self._get_shape(body)
from OCP.BRepFilletAPI import BRepFilletAPI_MakeFillet
fillet = BRepFilletAPI_MakeFillet(shape)
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_EDGE
@@ -414,29 +379,22 @@ class OCGeometryKernel(GeometryKernel):
fillet.Add(radius, explorer.Current())
explorer.Next()
result = cq.Shape(fillet.Shape())
return OCCGeometryObject(result, {"type": "fillet"})
fillet.Build()
return OCCGeometryObject(fillet.Shape(), {"type": "fillet"})
def chamfer(
self, body: GeometryObject, size: float, edges: Optional[List[Any]] = None
) -> GeometryObject:
"""Apply chamfer to edges."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer
cq_obj = self._get_cq_obj(body)
chamfer = BRepFilletAPI_MakeChamfer(shape)
if isinstance(cq_obj, cq.Workplane):
if edges:
result = cq_obj.edges(edges).chamfer(size)
else:
result = cq_obj.edges().chamfer(size)
if edges:
for edge in edges:
chamfer.Add(size, edge)
else:
shape = self._get_shape(body)
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer
chamfer = BRepFilletAPI_MakeChamfer(shape)
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_EDGE
@@ -445,71 +403,47 @@ class OCGeometryKernel(GeometryKernel):
chamfer.Add(size, explorer.Current())
explorer.Next()
result = cq.Shape(chamfer.Shape())
return OCCGeometryObject(result, {"type": "chamfer"})
chamfer.Build()
return OCCGeometryObject(chamfer.Shape(), {"type": "chamfer"})
def shell(
self, body: GeometryObject, thickness: float, faces_to_remove: Optional[List[Any]] = None
) -> GeometryObject:
"""Create a shell (hollow body)."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeThickSolid
from OCP.TopTools import TopTools_ListOfShape
cq_obj = self._get_cq_obj(body)
faces_list = TopTools_ListOfShape()
if faces_to_remove:
for face in faces_to_remove:
faces_list.Append(face)
if isinstance(cq_obj, cq.Workplane):
if faces_to_remove:
result = cq_obj.faces(faces_to_remove).shell(thickness)
else:
result = cq_obj.shell(thickness)
else:
shape = self._get_shape(body)
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeThickSolid
from OCP.TopTools import TopTools_ListOfShape
faces_list = TopTools_ListOfShape()
if faces_to_remove:
for face in faces_to_remove:
faces_list.Append(face)
shell_maker = BRepOffsetAPI_MakeThickSolid()
shell_maker.MakeThickSolidByJoin(shape, faces_list, thickness, 0.001)
shell_maker.Build()
result = cq.Shape(shell_maker.Shape())
return OCCGeometryObject(result, {"type": "shell"})
shell_maker = BRepOffsetAPI_MakeThickSolid()
shell_maker.MakeThickSolidByJoin(shape, faces_list, thickness, 0.001)
shell_maker.Build()
return OCCGeometryObject(shell_maker.Shape(), {"type": "shell"})
def offset(self, face: GeometryObject, distance: float) -> GeometryObject:
"""Offset a face or surface."""
import cadquery as cq
shape = self._get_shape(face)
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeOffset
offset_maker = BRepOffsetAPI_MakeOffset(shape, False)
offset_maker.Perform(distance)
return OCCGeometryObject(cq.Shape(offset_maker.Shape()), {"type": "offset"})
return OCCGeometryObject(offset_maker.Shape(), {"type": "offset"})
def translate(self, body: GeometryObject, vector: Tuple[float, float, float]) -> GeometryObject:
"""Translate a body."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf, gp_Vec
cq_obj = self._get_cq_obj(body)
if isinstance(cq_obj, cq.Workplane):
result = cq_obj.translate(vector)
else:
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf, gp_Vec
transform = gp_Trsf()
transform.SetTranslation(gp_Vec(*vector))
transformer = BRepBuilderAPI_Transform(shape, transform)
result = cq.Shape(transformer.Shape())
return OCCGeometryObject(result, {"type": "translated"})
transform = gp_Trsf()
transform.SetTranslation(gp_Vec(*vector))
transformer = BRepBuilderAPI_Transform(shape, transform)
return OCCGeometryObject(transformer.Shape(), {"type": "translated"})
def rotate(
self,
@@ -519,30 +453,19 @@ class OCGeometryKernel(GeometryKernel):
origin: Tuple[float, float, float] = (0, 0, 0),
) -> GeometryObject:
"""Rotate a body around an axis."""
import cadquery as cq
import math
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf, gp_Ax1, gp_Pnt, gp_Dir
cq_obj = self._get_cq_obj(body)
if isinstance(cq_obj, cq.Workplane):
result = cq_obj.rotate(origin, axis, math.degrees(angle))
else:
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf, gp_Ax1, gp_Pnt, gp_Dir, gp_Vec
ax1 = gp_Ax1(gp_Pnt(*origin), gp_Dir(*axis))
transform = gp_Trsf()
transform.SetRotation(ax1, angle)
transformer = BRepBuilderAPI_Transform(shape, transform)
result = cq.Shape(transformer.Shape())
return OCCGeometryObject(result, {"type": "rotated"})
ax1 = gp_Ax1(gp_Pnt(*origin), gp_Dir(*axis))
transform = gp_Trsf()
transform.SetRotation(ax1, angle)
transformer = BRepBuilderAPI_Transform(shape, transform)
return OCCGeometryObject(transformer.Shape(), {"type": "rotated"})
def scale(self, body: GeometryObject, factor: float) -> GeometryObject:
"""Scale a body uniformly."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf
@@ -551,7 +474,7 @@ class OCGeometryKernel(GeometryKernel):
transform.SetScale(factor)
transformer = BRepBuilderAPI_Transform(shape, transform)
return OCCGeometryObject(cq.Shape(transformer.Shape()), {"type": "scaled"})
return OCCGeometryObject(transformer.Shape(), {"type": "scaled"})
def mirror(
self,
@@ -560,8 +483,6 @@ class OCGeometryKernel(GeometryKernel):
plane_origin: Tuple[float, float, float] = (0, 0, 0),
) -> GeometryObject:
"""Mirror a body across a plane."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf, gp_Ax2, gp_Pnt, gp_Dir
@@ -571,19 +492,12 @@ class OCGeometryKernel(GeometryKernel):
transform.SetMirror(ax2)
transformer = BRepBuilderAPI_Transform(shape, transform)
return OCCGeometryObject(cq.Shape(transformer.Shape()), {"type": "mirrored"})
return OCCGeometryObject(transformer.Shape(), {"type": "mirrored"})
def export_step(self, body: GeometryObject, filepath: str, schema: str = "AP214") -> bool:
"""Export to STEP format."""
try:
import cadquery as cq
shape = self._get_shape(body)
if hasattr(shape, "exportStep"):
shape.exportStep(filepath)
return True
from OCP.STEPControl import STEPControl_Writer, STEPControl_AsIs
from OCP.Interface import Interface_Static
@@ -594,8 +508,7 @@ class OCGeometryKernel(GeometryKernel):
Interface_Static.SetCVal_s("write.step.schema", "AP203")
writer.Transfer(shape, STEPControl_AsIs)
writer.Write(filepath)
return True
return writer.Write(filepath)
except Exception as e:
print(f"STEP export error: {e}")
return False
@@ -603,18 +516,14 @@ class OCGeometryKernel(GeometryKernel):
def export_iges(self, body: GeometryObject, filepath: str) -> bool:
"""Export to IGES format."""
try:
import cadquery as cq
shape = self._get_shape(body)
from OCP.IGESControl import IGESControl_Writer
from OCP.Interface import Interface_Static
Interface_Static.SetCVal_s("write.iges.schema", "5.3")
writer = IGESControl_Writer()
writer.AddShape(shape)
writer.Write(filepath)
return True
return writer.Write(filepath)
except Exception as e:
print(f"IGES export error: {e}")
return False
@@ -624,14 +533,7 @@ class OCGeometryKernel(GeometryKernel):
) -> bool:
"""Export to STL format."""
try:
import cadquery as cq
shape = self._get_shape(body)
if hasattr(shape, "exportStl"):
shape.exportStl(filepath, tolerance)
return True
from OCP.StlAPI import StlAPI_Writer
from OCP.BRepMesh import BRepMesh_IncrementalMesh
@@ -640,23 +542,28 @@ class OCGeometryKernel(GeometryKernel):
writer = StlAPI_Writer()
writer.ASCIIMode = ascii_mode
writer.Write(shape, filepath)
return True
return writer.Write(shape, filepath)
except Exception as e:
print(f"STL export error: {e}")
return False
def import_step(self, filepath: str) -> GeometryObject:
"""Import from STEP format."""
import cadquery as cq
from OCP.STEPControl import STEPControl_Reader
from OCP.IFSelect import IFSelect_RetDone
result = cq.importers.importStep(filepath)
return OCCGeometryObject(result, {"type": "imported_step"})
reader = STEPControl_Reader()
status = reader.ReadFile(filepath)
if status != IFSelect_RetDone:
raise ValueError(f"Failed to read STEP file: {filepath}")
reader.TransferRoots()
shape = reader.OneShape()
return OCCGeometryObject(shape, {"type": "imported_step"})
def import_iges(self, filepath: str) -> GeometryObject:
"""Import from IGES format."""
import cadquery as cq
from OCP.IGESControl import IGESControl_Reader
from OCP.IFSelect import IFSelect_RetDone
@@ -669,26 +576,13 @@ class OCGeometryKernel(GeometryKernel):
reader.TransferRoots()
shape = reader.OneShape()
return OCCGeometryObject(cq.Shape(shape), {"type": "imported_iges"})
return OCCGeometryObject(shape, {"type": "imported_iges"})
def get_mesh(
self, body: GeometryObject, tolerance: float = 0.1
) -> Tuple[np.ndarray, np.ndarray]:
"""Get triangulated mesh for rendering."""
import cadquery as cq
cq_obj = self._get_cq_obj(body)
if isinstance(cq_obj, cq.Workplane):
shape = cq_obj.val()
if hasattr(shape, "wrapped"):
shape = shape.wrapped
else:
shape = self._get_shape(body)
if hasattr(shape, "tessellate"):
vertices, faces = shape.tessellate(tolerance)
return np.array(vertices), np.array(faces)
shape = self._get_shape(body)
from OCP.BRepMesh import BRepMesh_IncrementalMesh
from OCP.TopExp import TopExp_Explorer
@@ -745,8 +639,6 @@ class OCGeometryKernel(GeometryKernel):
def get_edges(self, body: GeometryObject) -> Tuple[np.ndarray, np.ndarray]:
"""Get edge wireframe for rendering."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.TopExp import TopExp_Explorer
@@ -801,8 +693,6 @@ class OCGeometryKernel(GeometryKernel):
def get_bounding_box(self, body: GeometryObject) -> Tuple[Point3D, Point3D]:
"""Get the bounding box of a body."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.Bnd import Bnd_Box
@@ -817,8 +707,6 @@ class OCGeometryKernel(GeometryKernel):
def get_volume(self, body: GeometryObject) -> float:
"""Calculate the volume of a solid body."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.GProp import GProp_GProps
@@ -831,8 +719,6 @@ class OCGeometryKernel(GeometryKernel):
def get_surface_area(self, body: GeometryObject) -> float:
"""Calculate the surface area of a body."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.GProp import GProp_GProps
@@ -845,8 +731,6 @@ class OCGeometryKernel(GeometryKernel):
def get_center_of_mass(self, body: GeometryObject) -> Point3D:
"""Calculate the center of mass of a solid body."""
import cadquery as cq
shape = self._get_shape(body)
from OCP.GProp import GProp_GProps
Generated
-1580
View File
File diff suppressed because it is too large Load Diff