|
132 | 132 | from OCP.GeomAPI import (
|
133 | 133 | GeomAPI_Interpolate,
|
134 | 134 | GeomAPI_ProjectPointOnSurf,
|
| 135 | + GeomAPI_ProjectPointOnCurve, |
135 | 136 | GeomAPI_PointsToBSpline,
|
136 | 137 | GeomAPI_PointsToBSplineSurface,
|
137 | 138 | )
|
|
144 | 145 | BRepAlgoAPI_Cut,
|
145 | 146 | BRepAlgoAPI_BooleanOperation,
|
146 | 147 | BRepAlgoAPI_Splitter,
|
| 148 | + BRepAlgoAPI_Check, |
147 | 149 | )
|
148 | 150 |
|
149 | 151 | from OCP.Geom import (
|
|
152 | 154 | Geom_CylindricalSurface,
|
153 | 155 | Geom_Surface,
|
154 | 156 | Geom_Plane,
|
| 157 | + Geom_BSplineCurve, |
155 | 158 | )
|
156 | 159 | from OCP.Geom2d import Geom2d_Line
|
157 | 160 |
|
|
211 | 214 | Graphic3d_VTA_TOP,
|
212 | 215 | )
|
213 | 216 |
|
214 |
| -from OCP.Graphic3d import ( |
215 |
| - Graphic3d_HTA_LEFT, |
216 |
| - Graphic3d_HTA_CENTER, |
217 |
| - Graphic3d_HTA_RIGHT, |
218 |
| - Graphic3d_VTA_BOTTOM, |
219 |
| - Graphic3d_VTA_CENTER, |
220 |
| - Graphic3d_VTA_TOP, |
221 |
| -) |
222 |
| - |
223 | 217 | from OCP.NCollection import NCollection_Utf8String
|
224 | 218 |
|
225 | 219 | from OCP.BRepFeat import BRepFeat_MakeDPrism
|
226 | 220 |
|
227 |
| -from OCP.BRepClass3d import BRepClass3d_SolidClassifier |
| 221 | +from OCP.BRepClass3d import BRepClass3d_SolidClassifier, BRepClass3d |
228 | 222 |
|
229 | 223 | from OCP.TCollection import TCollection_AsciiString
|
230 | 224 |
|
|
233 | 227 | from OCP.GeomAbs import (
|
234 | 228 | GeomAbs_Shape,
|
235 | 229 | GeomAbs_C0,
|
236 |
| - GeomAbs_C1, |
237 |
| - GeomAbs_C2, |
238 | 230 | GeomAbs_G2,
|
239 |
| - GeomAbs_G1, |
| 231 | + GeomAbs_C2, |
240 | 232 | GeomAbs_Intersection,
|
241 | 233 | GeomAbs_JoinType,
|
242 | 234 | )
|
|
249 | 241 |
|
250 | 242 | from OCP.TopAbs import TopAbs_ShapeEnum, TopAbs_Orientation
|
251 | 243 |
|
252 |
| -from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds |
| 244 | +from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds, ShapeAnalysis_Wire |
253 | 245 | from OCP.TopTools import TopTools_HSequenceOfShape
|
254 | 246 |
|
255 | 247 | from OCP.GCPnts import GCPnts_AbscissaPoint
|
|
281 | 273 |
|
282 | 274 | from OCP.ChFi2d import ChFi2d_FilletAPI # For Wire.Fillet()
|
283 | 275 |
|
| 276 | +from OCP.GeomConvert import GeomConvert_ApproxCurve |
| 277 | + |
284 | 278 | from math import pi, sqrt, inf, radians, cos
|
285 | 279 |
|
286 | 280 | import warnings
|
@@ -1643,6 +1637,9 @@ def makeVertex(cls, x: float, y: float, z: float) -> "Vertex":
|
1643 | 1637 |
|
1644 | 1638 |
|
1645 | 1639 | class Mixin1DProtocol(ShapeProtocol, Protocol):
|
| 1640 | + def _approxCurve(self) -> Geom_BSplineCurve: |
| 1641 | + ... |
| 1642 | + |
1646 | 1643 | def _geomAdaptor(self) -> Union[BRepAdaptor_Curve, BRepAdaptor_CompCurve]:
|
1647 | 1644 | ...
|
1648 | 1645 |
|
@@ -1699,18 +1696,44 @@ def endPoint(self: Mixin1DProtocol) -> Vector:
|
1699 | 1696 |
|
1700 | 1697 | return Vector(curve.Value(umax))
|
1701 | 1698 |
|
1702 |
| - def paramAt(self: Mixin1DProtocol, d: float) -> float: |
| 1699 | + def _approxCurve(self: Mixin1DProtocol) -> Geom_BSplineCurve: |
1703 | 1700 | """
|
1704 |
| - Compute parameter value at the specified normalized distance. |
| 1701 | + Approximate curve adaptor into a real b-spline. Meant for handling of |
| 1702 | + BRepAdaptor_CompCurve. |
| 1703 | + """ |
| 1704 | + |
| 1705 | + rv = GeomConvert_ApproxCurve( |
| 1706 | + self._geomAdaptor(), TOLERANCE, GeomAbs_C2, MaxSegments=100, MaxDegree=3 |
| 1707 | + ).Curve() |
1705 | 1708 |
|
1706 |
| - :param d: normalized distance [0, 1] |
| 1709 | + return rv |
| 1710 | + |
| 1711 | + def paramAt(self: Mixin1DProtocol, d: Union[Real, Vector]) -> float: |
| 1712 | + """ |
| 1713 | + Compute parameter value at the specified normalized distance or a point. |
| 1714 | +
|
| 1715 | + :param d: normalized distance [0, 1] or a point |
1707 | 1716 | :return: parameter value
|
1708 | 1717 | """
|
1709 | 1718 |
|
1710 | 1719 | curve = self._geomAdaptor()
|
1711 | 1720 |
|
1712 |
| - l = GCPnts_AbscissaPoint.Length_s(curve) |
1713 |
| - return GCPnts_AbscissaPoint(curve, l * d, curve.FirstParameter()).Parameter() |
| 1721 | + if isinstance(d, Vector): |
| 1722 | + # handle comp curves (i.e. wire adaptors) |
| 1723 | + if isinstance(curve, BRepAdaptor_Curve): |
| 1724 | + curve_ = curve.Curve().Curve() # get the underlying curve object |
| 1725 | + else: |
| 1726 | + curve_ = self._approxCurve() # approximate the adaptor as a real curve |
| 1727 | + |
| 1728 | + rv = GeomAPI_ProjectPointOnCurve( |
| 1729 | + d.toPnt(), curve_, curve.FirstParameter(), curve.LastParameter(), |
| 1730 | + ).LowerDistanceParameter() |
| 1731 | + |
| 1732 | + else: |
| 1733 | + l = GCPnts_AbscissaPoint.Length_s(curve) |
| 1734 | + rv = GCPnts_AbscissaPoint(curve, l * d, curve.FirstParameter()).Parameter() |
| 1735 | + |
| 1736 | + return rv |
1714 | 1737 |
|
1715 | 1738 | def tangentAt(
|
1716 | 1739 | self: Mixin1DProtocol,
|
@@ -2253,12 +2276,29 @@ class Wire(Shape, Mixin1D):
|
2253 | 2276 |
|
2254 | 2277 | wrapped: TopoDS_Wire
|
2255 | 2278 |
|
2256 |
| - def _geomAdaptor(self) -> BRepAdaptor_CompCurve: |
| 2279 | + def _nbEdges(self) -> int: |
2257 | 2280 | """
|
2258 |
| - Return the underlying geometry |
| 2281 | + Number of edges. |
2259 | 2282 | """
|
2260 | 2283 |
|
2261 |
| - return BRepAdaptor_CompCurve(self.wrapped) |
| 2284 | + sa = ShapeAnalysis_Wire() |
| 2285 | + sa.Load(self.wrapped) |
| 2286 | + |
| 2287 | + return sa.NbEdges() |
| 2288 | + |
| 2289 | + def _geomAdaptor(self) -> Union[BRepAdaptor_Curve, BRepAdaptor_CompCurve]: |
| 2290 | + """ |
| 2291 | + Return the underlying geometry. |
| 2292 | + """ |
| 2293 | + |
| 2294 | + rv: Union[BRepAdaptor_Curve, BRepAdaptor_CompCurve] |
| 2295 | + |
| 2296 | + if self._nbEdges() == 1: |
| 2297 | + rv = self.Edges()[-1]._geomAdaptor() |
| 2298 | + else: |
| 2299 | + rv = BRepAdaptor_CompCurve(self.wrapped) |
| 2300 | + |
| 2301 | + return rv |
2262 | 2302 |
|
2263 | 2303 | def close(self) -> "Wire":
|
2264 | 2304 | """
|
@@ -2518,6 +2558,7 @@ def fillet(
|
2518 | 2558 | """
|
2519 | 2559 | Apply 2D or 3D fillet to a wire
|
2520 | 2560 |
|
| 2561 | + :param wire: The input wire to fillet. Currently only open wires are supported |
2521 | 2562 | :param radius: the radius of the fillet, must be > zero
|
2522 | 2563 | :param vertices: Optional list of vertices to fillet. By default all vertices are fillet.
|
2523 | 2564 | :return: A wire with filleted corners
|
@@ -2948,7 +2989,7 @@ def thicken(self, thickness: float) -> "Solid":
|
2948 | 2989 | False,
|
2949 | 2990 | GeomAbs_Intersection,
|
2950 | 2991 | True,
|
2951 |
| - ) # The last True is important to make solid |
| 2992 | + ) # The last True is important to make a solid |
2952 | 2993 |
|
2953 | 2994 | builder.MakeOffsetShape()
|
2954 | 2995 |
|
@@ -3300,8 +3341,8 @@ def isSolid(obj: Shape) -> bool:
|
3300 | 3341 | Returns true if the object is a solid, false otherwise
|
3301 | 3342 | """
|
3302 | 3343 | if hasattr(obj, "ShapeType"):
|
3303 |
| - if obj.ShapeType == "Solid" or ( |
3304 |
| - obj.ShapeType == "Compound" and len(obj.Solids()) > 0 |
| 3344 | + if obj.ShapeType() == "Solid" or ( |
| 3345 | + obj.ShapeType() == "Compound" and len(obj.Solids()) > 0 |
3305 | 3346 | ):
|
3306 | 3347 | return True
|
3307 | 3348 | return False
|
@@ -3837,6 +3878,22 @@ def sweep_multi(
|
3837 | 3878 |
|
3838 | 3879 | return cls(builder.Shape())
|
3839 | 3880 |
|
| 3881 | + def outerShell(self) -> Shell: |
| 3882 | + """ |
| 3883 | + Returns outer shell. |
| 3884 | + """ |
| 3885 | + |
| 3886 | + return Shell(BRepClass3d.OuterShell_s(self.wrapped)) |
| 3887 | + |
| 3888 | + def innerShells(self) -> List[Shell]: |
| 3889 | + """ |
| 3890 | + Returns inner shells. |
| 3891 | + """ |
| 3892 | + |
| 3893 | + outer = self.outerShell() |
| 3894 | + |
| 3895 | + return [s for s in self.Shells() if not s.isSame(outer)] |
| 3896 | + |
3840 | 3897 |
|
3841 | 3898 | class CompSolid(Shape, Mixin3D):
|
3842 | 3899 | """
|
@@ -4419,9 +4476,20 @@ def solid(*s: Shape) -> Shape:
|
4419 | 4476 |
|
4420 | 4477 |
|
4421 | 4478 | @solid.register
|
4422 |
| -def solid(s: Sequence[Shape]) -> Shape: |
| 4479 | +def solid(s: Sequence[Shape], inner: Optional[Sequence[Shape]] = None) -> Shape: |
4423 | 4480 |
|
4424 |
| - return solid(*s) |
| 4481 | + builder = BRepBuilderAPI_MakeSolid() |
| 4482 | + builder.Add(shell(*s).wrapped) |
| 4483 | + |
| 4484 | + if inner: |
| 4485 | + for sh in _get(shell(*inner), "Shell"): |
| 4486 | + builder.Add(sh.wrapped) |
| 4487 | + |
| 4488 | + # fix orientations |
| 4489 | + sf = ShapeFix_Solid(builder.Solid()) |
| 4490 | + sf.Perform() |
| 4491 | + |
| 4492 | + return _compound_or_shape(sf.Solid()) |
4425 | 4493 |
|
4426 | 4494 |
|
4427 | 4495 | @multimethod
|
@@ -5111,3 +5179,22 @@ def loft(s: Sequence[Shape], cap: bool = False, ruled: bool = False) -> Shape:
|
5111 | 5179 | def loft(*s: Shape, cap: bool = False, ruled: bool = False) -> Shape:
|
5112 | 5180 |
|
5113 | 5181 | return loft(s, cap, ruled)
|
| 5182 | + |
| 5183 | + |
| 5184 | +#%% diagnotics |
| 5185 | + |
| 5186 | + |
| 5187 | +def check(s: Shape) -> bool: |
| 5188 | + """ |
| 5189 | + Check if a shape is valid. |
| 5190 | + """ |
| 5191 | + |
| 5192 | + analyzer = BRepAlgoAPI_Check(s.wrapped) |
| 5193 | + analyzer.SetRunParallel(True) |
| 5194 | + analyzer.SetUseOBB(True) |
| 5195 | + |
| 5196 | + analyzer.Perform() |
| 5197 | + |
| 5198 | + rv = analyzer.IsValid() |
| 5199 | + |
| 5200 | + return rv |
0 commit comments