Skip to content

Commit

Permalink
Final shape / free func tweaks (#1731)
Browse files Browse the repository at this point in the history
* Add sample

* Mypy fix

* Isoline constructon

* Add isolines and return params when sampling

* Extend check

* Test extended check output

* Mypy fix

* Func API

* Use the official import in the docs

* Add face

* Handle trimming and all possible geometries

* Initial tests

* test _adaptor_curve_to_edge

* Fix test

* Be consistent with Sketch

* Punctuation

* Update cadquery/occ_impl/shapes.py

Co-authored-by: Jeremy Wright <[email protected]>

---------

Co-authored-by: Jeremy Wright <[email protected]>
  • Loading branch information
adam-urbanczyk and jmwright authored Dec 26, 2024
1 parent f1bf901 commit 1c0e747
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 14 deletions.
47 changes: 47 additions & 0 deletions cadquery/func.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from .occ_impl.geom import Vector, Plane, Location
from .occ_impl.shapes import (
Shape,
Vertex,
Edge,
Wire,
Face,
Shell,
Solid,
CompSolid,
Compound,
wire,
face,
shell,
solid,
compound,
vertex,
segment,
polyline,
polygon,
rect,
spline,
circle,
ellipse,
plane,
box,
cylinder,
sphere,
torus,
cone,
text,
fuse,
cut,
intersect,
split,
fill,
clean,
cap,
fillet,
chamfer,
extrude,
revolve,
offset,
sweep,
loft,
check,
)
6 changes: 5 additions & 1 deletion cadquery/occ_impl/geom.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from math import pi, radians, degrees

from typing import overload, Sequence, Union, Tuple, Type, Optional
from typing import overload, Sequence, Union, Tuple, Type, Optional, Iterator

from OCP.gp import (
gp_Vec,
Expand Down Expand Up @@ -236,6 +236,10 @@ def __eq__(self, other: "Vector") -> bool: # type: ignore[override]
else False
)

def __iter__(self) -> Iterator[float]:

yield from (self.x, self.y, self.z)

def toPnt(self) -> gp_Pnt:

return gp_Pnt(self.wrapped.XYZ())
Expand Down
129 changes: 123 additions & 6 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@
GeomAbs_C2,
GeomAbs_Intersection,
GeomAbs_JoinType,
GeomAbs_IsoType,
GeomAbs_CurveType,
)
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeFilling
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Mode
Expand All @@ -244,7 +246,11 @@
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds, ShapeAnalysis_Wire
from OCP.TopTools import TopTools_HSequenceOfShape

from OCP.GCPnts import GCPnts_AbscissaPoint
from OCP.GCPnts import (
GCPnts_AbscissaPoint,
GCPnts_QuasiUniformAbscissa,
GCPnts_QuasiUniformDeflection,
)

from OCP.GeomFill import (
GeomFill_Frenet,
Expand Down Expand Up @@ -281,6 +287,10 @@

from OCP.BinTools import BinTools

from OCP.Adaptor3d import Adaptor3d_IsoCurve, Adaptor3d_Curve

from OCP.GeomAdaptor import GeomAdaptor_Surface

from math import pi, sqrt, inf, radians, cos

import warnings
Expand Down Expand Up @@ -1900,7 +1910,8 @@ def _curve_and_param(
def positionAt(
self: Mixin1DProtocol, d: float, mode: ParamMode = "length",
) -> Vector:
"""Generate a position along the underlying curve.
"""
Generate a position along the underlying curve.
:param d: distance or parameter value
:param mode: position calculation mode (default: length)
Expand All @@ -1914,7 +1925,8 @@ def positionAt(
def positions(
self: Mixin1DProtocol, ds: Iterable[float], mode: ParamMode = "length",
) -> List[Vector]:
"""Generate positions along the underlying curve
"""
Generate positions along the underlying curve.
:param ds: distance or parameter values
:param mode: position calculation mode (default: length)
Expand All @@ -1923,14 +1935,44 @@ def positions(

return [self.positionAt(d, mode) for d in ds]

def sample(
self: Mixin1DProtocol, n: Union[int, float]
) -> Tuple[List[Vector], List[float]]:
"""
Sample a curve based on a number of points or deflection.
:param n: Number of positions or deflection
:return: A list of Vectors and a list of parameters.
"""

gcpnts: Union[GCPnts_QuasiUniformAbscissa, GCPnts_QuasiUniformDeflection]

if isinstance(n, int):
crv = self._geomAdaptor()
gcpnts = GCPnts_QuasiUniformAbscissa(crv, n + 1 if crv.IsClosed() else n)
else:
crv = self._geomAdaptor()
gcpnts = GCPnts_QuasiUniformDeflection(crv, n)

N_pts = gcpnts.NbPoints()

params = [
gcpnts.Parameter(i)
for i in range(1, N_pts if crv.IsClosed() else N_pts + 1)
]
pnts = [Vector(crv.Value(p)) for p in params]

return pnts, params

def locationAt(
self: Mixin1DProtocol,
d: float,
mode: ParamMode = "length",
frame: FrameMode = "frenet",
planar: bool = False,
) -> Location:
"""Generate a location along the underlying curve.
"""
Generate a location along the underlying curve.
:param d: distance or parameter value
:param mode: position calculation mode (default: length)
Expand Down Expand Up @@ -1973,7 +2015,8 @@ def locations(
frame: FrameMode = "frenet",
planar: bool = False,
) -> List[Location]:
"""Generate location along the curve
"""
Generate locations along the curve.
:param ds: distance or parameter values
:param mode: position calculation mode (default: length)
Expand Down Expand Up @@ -3188,6 +3231,35 @@ def trim(self, u0: Real, u1: Real, v0: Real, v1: Real, tol: Real = 1e-6) -> "Fac

return self.__class__(bldr.Shape())

def isoline(self, param: Real, direction: Literal["u", "v"] = "v") -> Edge:
"""
Construct an isoline.
"""

u1, u2, v1, v2 = self._uvBounds()

if direction == "u":
iso = GeomAbs_IsoType.GeomAbs_IsoU
p1, p2 = v1, v2
else:
iso = GeomAbs_IsoType.GeomAbs_IsoV
p1, p2 = u1, u2

adaptor = Adaptor3d_IsoCurve(
GeomAdaptor_Surface(self._geomAdaptor()), iso, param
)

return Edge(_adaptor_curve_to_edge(adaptor, p1, p2))

def isolines(
self, params: Iterable[Real], direction: Literal["u", "v"] = "v"
) -> List[Edge]:
"""
Construct multiple isolines.
"""

return [self.isoline(p, direction) for p in params]


class Shell(Shape):
"""
Expand Down Expand Up @@ -4622,6 +4694,14 @@ def _shapes_to_toptools_list(s: Iterable[Shape]) -> TopTools_ListOfShape:
return rv


def _toptools_list_to_shapes(tl: TopTools_ListOfShape) -> List[Shape]:
"""
Convert a TopTools list (OCCT specific) to a compound.
"""

return [_normalize(Shape.cast(el)) for el in tl]


_geomabsshape_dict = dict(
C0=GeomAbs_Shape.GeomAbs_C0,
C1=GeomAbs_Shape.GeomAbs_C1,
Expand Down Expand Up @@ -4656,6 +4736,34 @@ def _to_parametrization(name: str) -> Approx_ParametrizationType:
return _parametrization_dict[name.lower()]


def _adaptor_curve_to_edge(crv: Adaptor3d_Curve, p1: float, p2: float) -> TopoDS_Edge:

GCT = GeomAbs_CurveType

t = crv.GetType()

if t == GCT.GeomAbs_BSplineCurve:
bldr = BRepBuilderAPI_MakeEdge(crv.BSpline(), p1, p2)
elif t == GCT.GeomAbs_BezierCurve:
bldr = BRepBuilderAPI_MakeEdge(crv.Bezier(), p1, p2)
elif t == GCT.GeomAbs_Circle:
bldr = BRepBuilderAPI_MakeEdge(crv.Circle(), p1, p2)
elif t == GCT.GeomAbs_Line:
bldr = BRepBuilderAPI_MakeEdge(crv.Line(), p1, p2)
elif t == GCT.GeomAbs_Ellipse:
bldr = BRepBuilderAPI_MakeEdge(crv.Ellipse(), p1, p2)
elif t == GCT.GeomAbs_Hyperbola:
bldr = BRepBuilderAPI_MakeEdge(crv.Hyperbola(), p1, p2)
elif t == GCT.GeomAbs_Parabola:
bldr = BRepBuilderAPI_MakeEdge(crv.Parabola(), p1, p2)
elif t == GCT.GeomAbs_OffsetCurve:
bldr = BRepBuilderAPI_MakeEdge(crv.OffsetCurve(), p1, p2)
else:
raise ValueError(r"{t} is not a supported curve type")

return bldr.Edge()


#%% alternative constructors


Expand Down Expand Up @@ -5675,7 +5783,7 @@ def loft(
#%% diagnotics


def check(s: Shape) -> bool:
def check(s: Shape, results: Optional[List[Tuple[List[Shape], Any]]] = None) -> bool:
"""
Check if a shape is valid.
"""
Expand All @@ -5688,4 +5796,13 @@ def check(s: Shape) -> bool:

rv = analyzer.IsValid()

# output detailed results if requested
if results is not None:
results.clear()

for r in analyzer.Result():
results.append(
(_toptools_list_to_shapes(r.GetFaultyShapes1()), r.GetCheckStatus())
)

return rv
14 changes: 7 additions & 7 deletions doc/free-func.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The purpose of this section is to demonstrate how to construct Shape objects usi
.. cadquery::
:height: 600px

from cadquery.occ_impl.shapes import *
from cadquery.func import *

dh = 2
r = 1
Expand Down Expand Up @@ -97,7 +97,7 @@ Various 1D, 2D and 3D primitives are supported.

.. cadquery::

from cadquery.occ_impl.shapes import *
from cadquery.func import *

e = segment((0,0), (0,1))

Expand All @@ -119,7 +119,7 @@ One can for example union multiple solids at once by first combining them into a

.. cadquery::

from cadquery.occ_impl.shapes import *
from cadquery.func import *

c1 = cylinder(1, 2)
c2 = cylinder(0.5, 3)
Expand Down Expand Up @@ -158,7 +158,7 @@ Constructing complex shapes from simple shapes is possible in various contexts.

.. cadquery::

from cadquery.occ_impl.shapes import *
from cadquery.func import *

e1 = segment((0,0), (1,0))
e2 = segment((1,0), (1,1))
Expand Down Expand Up @@ -196,7 +196,7 @@ Free function API currently supports :meth:`~cadquery.occ_impl.shapes.extrude`,

.. cadquery::

from cadquery.occ_impl.shapes import *
from cadquery.func import *

r = rect(1,0.5)
f = face(r, circle(0.2).moved(0.2), rect(0.2, 0.4).moved(-0.2))
Expand Down Expand Up @@ -229,7 +229,7 @@ Placement and creation of arrays is possible using :meth:`~cadquery.Shape.move`

.. cadquery::

from cadquery.occ_impl.shapes import *
from cadquery.func import *

locs = [(0,-1,0), (0,1,0)]

Expand All @@ -246,7 +246,7 @@ The free function API has extensive text creation capabilities including text on

.. cadquery::

from cadquery.occ_impl.shapes import *
from cadquery.func import *

from math import pi

Expand Down
Loading

0 comments on commit 1c0e747

Please sign in to comment.