Merge branch 'master' into adam-urbanczyk-OCC-version-update

This commit is contained in:
Adam Urbańczyk
2019-02-13 20:59:28 +01:00
committed by GitHub
4 changed files with 164 additions and 46 deletions

View File

@ -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)

View File

@ -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):
"""

View File

@ -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)

View File

@ -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