- removed cadquery deoendency
This commit is contained in:
Generated
+16
-7
@@ -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>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ classifiers = [
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"cadquery>=2.4",
|
||||
"pygfx>=0.1.0",
|
||||
"wgpu>=0.1.0",
|
||||
"PySide6>=6.4.0",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user