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