Merge branch 'master' into adam-urbanczyk-OCC-version-update
This commit is contained in:
@ -1311,12 +1311,15 @@ class Workplane(CQ):
|
||||
newCenter = p + Vector(xDist, yDist, 0)
|
||||
return self.newObject([self.plane.toWorldCoords(newCenter)])
|
||||
|
||||
def spline(self, listOfXYTuple, forConstruction=False):
|
||||
def spline(self, listOfXYTuple, tangents=None, periodic=False,
|
||||
forConstruction=False):
|
||||
"""
|
||||
Create a spline interpolated through the provided points.
|
||||
|
||||
:param listOfXYTuple: points to interpolate through
|
||||
:type listOfXYTuple: list of 2-tuple
|
||||
:param tangents: tuple of Vectors specifying start and finish tangent
|
||||
:param periodic: creation of peridic curves
|
||||
:return: a Workplane object with the current point at the end of the spline
|
||||
|
||||
The spline will begin at the current point, and
|
||||
@ -1344,12 +1347,16 @@ class Workplane(CQ):
|
||||
* provide access to control points
|
||||
"""
|
||||
gstartPoint = self._findFromPoint(False)
|
||||
gEndPoint = self.plane.toWorldCoords(listOfXYTuple[-1])
|
||||
|
||||
vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple]
|
||||
allPoints = [gstartPoint] + vecs
|
||||
|
||||
e = Edge.makeSpline(allPoints)
|
||||
if tangents:
|
||||
t1, t2 = tangents
|
||||
tangents = (self.plane.toWorldCoords(t1),
|
||||
self.plane.toWorldCoords(t2))
|
||||
|
||||
e = Edge.makeSpline(allPoints, tangents=tangents, periodic=periodic)
|
||||
|
||||
if not forConstruction:
|
||||
self._addPendingEdge(e)
|
||||
@ -2133,7 +2140,7 @@ class Workplane(CQ):
|
||||
newS = newS.clean()
|
||||
return newS
|
||||
|
||||
def extrude(self, distance, combine=True, clean=True, both=False):
|
||||
def extrude(self, distance, combine=True, clean=True, both=False, taper=None):
|
||||
"""
|
||||
Use all un-extruded wires in the parent chain to create a prismatic solid.
|
||||
|
||||
@ -2142,6 +2149,7 @@ class Workplane(CQ):
|
||||
:param boolean combine: True to combine the resulting solid with parent solids if found.
|
||||
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
|
||||
:param boolean both: extrude in both directions symmetrically
|
||||
:param float taper: angle for optional tapered extrusion
|
||||
:return: a CQ object with the resulting solid selected.
|
||||
|
||||
extrude always *adds* material to a part.
|
||||
@ -2159,7 +2167,7 @@ class Workplane(CQ):
|
||||
selected may not be planar
|
||||
"""
|
||||
r = self._extrude(
|
||||
distance, both=both) # returns a Solid (or a compound if there were multiple)
|
||||
distance, both=both, taper=taper) # returns a Solid (or a compound if there were multiple)
|
||||
|
||||
if combine:
|
||||
newS = self._combineWithBase(r)
|
||||
@ -2396,7 +2404,7 @@ class Workplane(CQ):
|
||||
|
||||
return self.newObject([newS])
|
||||
|
||||
def cutBlind(self, distanceToCut, clean=True):
|
||||
def cutBlind(self, distanceToCut, clean=True, taper=None):
|
||||
"""
|
||||
Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid.
|
||||
|
||||
@ -2407,6 +2415,7 @@ class Workplane(CQ):
|
||||
:type distanceToCut: float, >0 means in the positive direction of the workplane normal,
|
||||
<0 means in the negative direction
|
||||
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
|
||||
:param float taper: angle for optional tapered extrusion
|
||||
:raises: ValueError if there is no solid to subtract from in the chain
|
||||
:return: a CQ object with the resulting object selected
|
||||
|
||||
@ -2416,7 +2425,7 @@ class Workplane(CQ):
|
||||
Cut Up to Surface
|
||||
"""
|
||||
# first, make the object
|
||||
toCut = self._extrude(distanceToCut)
|
||||
toCut = self._extrude(distanceToCut, taper=taper)
|
||||
|
||||
# now find a solid in the chain
|
||||
|
||||
@ -2468,7 +2477,7 @@ class Workplane(CQ):
|
||||
|
||||
return self.newObject([r])
|
||||
|
||||
def _extrude(self, distance, both=False):
|
||||
def _extrude(self, distance, both=False, taper=None):
|
||||
"""
|
||||
Make a prismatic solid from the existing set of pending wires.
|
||||
|
||||
@ -2518,6 +2527,12 @@ class Workplane(CQ):
|
||||
# return r
|
||||
|
||||
toFuse = []
|
||||
|
||||
if taper:
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.extrudeLinear(ws[0], [], eDir, taper)
|
||||
toFuse.append(thisObj)
|
||||
else:
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
|
||||
toFuse.append(thisObj)
|
||||
|
@ -7,7 +7,7 @@ from OCC.Core.gp import (gp_Vec, gp_Pnt, gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Circ
|
||||
gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d)
|
||||
|
||||
# collection of pints (used for spline construction)
|
||||
from OCC.Core.TColgp import TColgp_Array1OfPnt
|
||||
from OCC.Core.TColgp import TColgp_HArray1OfPnt
|
||||
from OCC.Core.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
|
||||
from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
||||
BRepBuilderAPI_MakeEdge,
|
||||
@ -54,7 +54,7 @@ from OCC.Core.TopoDS import (TopoDS_Shell,
|
||||
|
||||
from OCC.Core.GC import GC_MakeArcOfCircle # geometry construction
|
||||
from OCC.Core.GCE2d import GCE2d_MakeSegment
|
||||
from OCC.Core.GeomAPI import (GeomAPI_PointsToBSpline,
|
||||
from OCC.Core.GeomAPI import (GeomAPI_Interpolate,
|
||||
GeomAPI_ProjectPointOnSurf)
|
||||
|
||||
from OCC.Core.BRepFill import brepfill_Shell, brepfill_Face
|
||||
@ -102,6 +102,10 @@ from OCC.Core.BRepTools import breptools_Write
|
||||
|
||||
from OCC.Core.Visualization import Tesselator
|
||||
|
||||
from OCC.LocOpe import LocOpe_DPrism
|
||||
|
||||
from OCC.BRepCheck import BRepCheck_Analyzer
|
||||
|
||||
from math import pi, sqrt
|
||||
|
||||
TOLERANCE = 1e-6
|
||||
@ -313,7 +317,7 @@ class Shape(object):
|
||||
return self.wrapped.IsEqual(other.wrapped)
|
||||
|
||||
def isValid(self): # seems to be not used in the codebase -- remove?
|
||||
raise NotImplemented
|
||||
return BRepCheck_Analyzer(self.wrapped).IsValid()
|
||||
|
||||
def BoundingBox(self, tolerance=0.1): # need to implement that in GEOM
|
||||
return BoundBox._fromTopoDS(self.wrapped)
|
||||
@ -462,7 +466,11 @@ class Shape(object):
|
||||
return [Solid(i) for i in self._entities('Solid')]
|
||||
|
||||
def Area(self):
|
||||
raise NotImplementedError
|
||||
Properties = GProp_GProps()
|
||||
brepgprop_SurfaceProperties(self.wrapped,
|
||||
Properties)
|
||||
|
||||
return Properties.Mass()
|
||||
|
||||
def Volume(self):
|
||||
# when density == 1, mass == volume
|
||||
@ -632,6 +640,10 @@ class Mixin1D(object):
|
||||
|
||||
return Properties.Mass()
|
||||
|
||||
def IsClosed(self):
|
||||
|
||||
return BRep_Tool.IsClosed(self.wrapped)
|
||||
|
||||
|
||||
class Edge(Shape, Mixin1D):
|
||||
"""
|
||||
@ -671,20 +683,17 @@ class Edge(Shape, Mixin1D):
|
||||
|
||||
return Vector(curve.Value(umax))
|
||||
|
||||
def tangentAt(self, locationVector=None):
|
||||
def tangentAt(self, locationParam=0.5):
|
||||
"""
|
||||
Compute tangent vector at the specified location.
|
||||
:param locationVector: location to use. Use the center point if None
|
||||
:param locationParam: location to use in [0,1]
|
||||
:return: tangent vector
|
||||
"""
|
||||
|
||||
curve = self._geomAdaptor()
|
||||
|
||||
if locationVector:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
umin, umax = curve.FirstParameter(), curve.LastParameter()
|
||||
umid = 0.5 * (umin + umax)
|
||||
umid = (1-locationParam)*umin + locationParam*umax
|
||||
|
||||
# TODO what are good parameters for those?
|
||||
curve_props = BRepLProp_CLProps(curve, 2, curve.Tolerance())
|
||||
@ -726,18 +735,28 @@ class Edge(Shape, Mixin1D):
|
||||
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())
|
||||
|
||||
@classmethod
|
||||
def makeSpline(cls, listOfVector):
|
||||
def makeSpline(cls, listOfVector, tangents=None, periodic=False,
|
||||
tol = 1e-6):
|
||||
"""
|
||||
Interpolate a spline through the provided points.
|
||||
:param cls:
|
||||
:param listOfVector: a list of Vectors that represent the points
|
||||
:param tangents: tuple of Vectors specifying start and finish tangent
|
||||
:param periodic: creation of peridic curves
|
||||
:param tol: tolerance of the algorithm (consult OCC documentation)
|
||||
:return: an Edge
|
||||
"""
|
||||
pnts = TColgp_Array1OfPnt(0, len(listOfVector) - 1)
|
||||
pnts = TColgp_HArray1OfPnt(1, len(listOfVector))
|
||||
for ix, v in enumerate(listOfVector):
|
||||
pnts.SetValue(ix, v.toPnt())
|
||||
pnts.SetValue(ix+1, v.toPnt())
|
||||
|
||||
spline_geom = GeomAPI_PointsToBSpline(pnts).Curve()
|
||||
spline_builder = GeomAPI_Interpolate(pnts.GetHandle(), periodic, tol)
|
||||
if tangents:
|
||||
v1,v2 = tangents
|
||||
spline_builder.Load(v1.wrapped,v2.wrapped)
|
||||
|
||||
spline_builder.Perform()
|
||||
spline_geom = spline_builder.Curve()
|
||||
|
||||
return cls(BRepBuilderAPI_MakeEdge(spline_geom).Edge())
|
||||
|
||||
@ -1280,13 +1299,14 @@ class Solid(Shape, Mixin3D):
|
||||
return cls(BRepAlgoAPI_Cut(outer_solid, inner_comp).Shape())
|
||||
|
||||
@classmethod
|
||||
def extrudeLinear(cls, outerWire, innerWires, vecNormal):
|
||||
def extrudeLinear(cls, outerWire, innerWires, vecNormal, taper=0):
|
||||
"""
|
||||
Attempt to extrude the list of wires into a prismatic solid in the provided direction
|
||||
|
||||
:param outerWire: the outermost wire
|
||||
:param innerWires: a list of inner wires
|
||||
:param vecNormal: a vector along which to extrude the wires
|
||||
:param taper: taper angle, default=0
|
||||
:return: a Solid object
|
||||
|
||||
The wires must not intersect
|
||||
@ -1303,19 +1323,21 @@ class Solid(Shape, Mixin3D):
|
||||
reliable.
|
||||
"""
|
||||
|
||||
# one would think that fusing faces into a compound and then extruding would work,
|
||||
# but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc),
|
||||
# but then cutting it from the main solid fails with BRep_NotDone.
|
||||
# the work around is to extrude each and then join the resulting solids, which seems to work
|
||||
|
||||
# FreeCAD allows this in one operation, but others might not
|
||||
|
||||
if taper==0:
|
||||
face = Face.makeFromWires(outerWire, innerWires)
|
||||
prism_builder = BRepPrimAPI_MakePrism(
|
||||
face.wrapped, vecNormal.wrapped, True)
|
||||
else:
|
||||
face = Face.makeFromWires(outerWire)
|
||||
faceNormal = face.normalAt()
|
||||
d = 1 if vecNormal.getAngle(faceNormal)<90 * DEG2RAD else -1
|
||||
prism_builder = LocOpe_DPrism(face.wrapped,
|
||||
d * vecNormal.Length,
|
||||
d * taper * DEG2RAD)
|
||||
|
||||
return cls(prism_builder.Shape())
|
||||
|
||||
|
||||
@classmethod
|
||||
def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd):
|
||||
"""
|
||||
|
@ -170,7 +170,7 @@ class BaseDirSelector(Selector):
|
||||
r.append(o)
|
||||
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
||||
# an edge is parallel to a direction if its underlying geometry is plane or line
|
||||
tangent = o.tangentAt(None)
|
||||
tangent = o.tangentAt()
|
||||
if self.test(tangent):
|
||||
r.append(o)
|
||||
|
||||
|
@ -363,6 +363,38 @@ class TestCadQuery(BaseTest):
|
||||
self.assertEqual(2, result.vertices().size())
|
||||
self.assertEqual(3, result.edges().size())
|
||||
|
||||
def testSpline(self):
|
||||
"""
|
||||
Tests construction of splines
|
||||
"""
|
||||
pts = [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(2, 4)
|
||||
]
|
||||
|
||||
# Spline path - just a smoke test
|
||||
path = Workplane("XZ").spline(pts).val()
|
||||
|
||||
# Closed spline
|
||||
path_closed = Workplane("XZ").spline(pts,periodic=True).val()
|
||||
self.assertTrue(path_closed.IsClosed())
|
||||
|
||||
# attempt to build a valid face
|
||||
w = Wire.assembleEdges([path_closed,])
|
||||
f = Face.makeFromWires(w)
|
||||
self.assertTrue(f.isValid())
|
||||
|
||||
# attempt to build an invalid face
|
||||
w = Wire.assembleEdges([path,])
|
||||
f = Face.makeFromWires(w)
|
||||
self.assertFalse(f.isValid())
|
||||
|
||||
# Spline with explicit tangents
|
||||
path_const = Workplane("XZ").spline(pts,tangents=((0,1),(1,0))).val()
|
||||
self.assertFalse(path.tangentAt(0) == path_const.tangentAt(0))
|
||||
self.assertFalse(path.tangentAt(1) == path_const.tangentAt(1))
|
||||
|
||||
def testSweep(self):
|
||||
"""
|
||||
Tests the operation of sweeping a wire(s) along a path
|
||||
@ -1668,12 +1700,25 @@ class TestCadQuery(BaseTest):
|
||||
|
||||
def testExtrude(self):
|
||||
"""
|
||||
Test symmetric extrude
|
||||
Test extrude
|
||||
"""
|
||||
r = 1.
|
||||
h = 1.
|
||||
decimal_places = 9.
|
||||
|
||||
# extrude in one direction
|
||||
s = Workplane("XY").circle(r).extrude(h, both=False)
|
||||
|
||||
top_face = s.faces(">Z")
|
||||
bottom_face = s.faces("<Z")
|
||||
|
||||
# calculate the distance between the top and the bottom face
|
||||
delta = top_face.val().Center().sub(bottom_face.val().Center())
|
||||
|
||||
self.assertTupleAlmostEquals(delta.toTuple(),
|
||||
(0., 0., h),
|
||||
decimal_places)
|
||||
|
||||
# extrude symmetrically
|
||||
s = Workplane("XY").circle(r).extrude(h, both=True)
|
||||
|
||||
@ -1687,6 +1732,42 @@ class TestCadQuery(BaseTest):
|
||||
(0., 0., 2. * h),
|
||||
decimal_places)
|
||||
|
||||
def testTaperedExtrudeCutBlind(self):
|
||||
|
||||
h = 1.
|
||||
r = 1.
|
||||
t = 5
|
||||
|
||||
# extrude with a positive taper
|
||||
s = Workplane("XY").circle(r).extrude(h, taper=t)
|
||||
|
||||
top_face = s.faces(">Z")
|
||||
bottom_face = s.faces("<Z")
|
||||
|
||||
# top and bottom face area
|
||||
delta = top_face.val().Area() - bottom_face.val().Area()
|
||||
|
||||
self.assertTrue(delta < 0)
|
||||
|
||||
# extrude with a negative taper
|
||||
s = Workplane("XY").circle(r).extrude(h, taper=-t)
|
||||
|
||||
top_face = s.faces(">Z")
|
||||
bottom_face = s.faces("<Z")
|
||||
|
||||
# top and bottom face area
|
||||
delta = top_face.val().Area() - bottom_face.val().Area()
|
||||
|
||||
self.assertTrue(delta > 0)
|
||||
|
||||
# cut a tapered hole
|
||||
s = Workplane("XY").rect(2*r,2*r).extrude(2*h).faces('>Z').workplane()\
|
||||
.rect(r,r).cutBlind(-h, taper=t)
|
||||
|
||||
middle_face = s.faces('>Z[-2]')
|
||||
|
||||
self.assertTrue(middle_face.val().Area() < 1)
|
||||
|
||||
def testClose(self):
|
||||
# Close without endPoint and startPoint coincide.
|
||||
# Create a half-circle
|
||||
|
Reference in New Issue
Block a user