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) newCenter = p + Vector(xDist, yDist, 0)
return self.newObject([self.plane.toWorldCoords(newCenter)]) 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. Create a spline interpolated through the provided points.
:param listOfXYTuple: points to interpolate through :param listOfXYTuple: points to interpolate through
:type listOfXYTuple: list of 2-tuple :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 :return: a Workplane object with the current point at the end of the spline
The spline will begin at the current point, and The spline will begin at the current point, and
@ -1344,12 +1347,16 @@ class Workplane(CQ):
* provide access to control points * provide access to control points
""" """
gstartPoint = self._findFromPoint(False) gstartPoint = self._findFromPoint(False)
gEndPoint = self.plane.toWorldCoords(listOfXYTuple[-1])
vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple] vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple]
allPoints = [gstartPoint] + vecs allPoints = [gstartPoint] + vecs
if tangents:
t1, t2 = tangents
tangents = (self.plane.toWorldCoords(t1),
self.plane.toWorldCoords(t2))
e = Edge.makeSpline(allPoints) e = Edge.makeSpline(allPoints, tangents=tangents, periodic=periodic)
if not forConstruction: if not forConstruction:
self._addPendingEdge(e) self._addPendingEdge(e)
@ -2133,7 +2140,7 @@ class Workplane(CQ):
newS = newS.clean() newS = newS.clean()
return newS 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. 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 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 clean: call :py:meth:`clean` afterwards to have a clean shape
:param boolean both: extrude in both directions symmetrically :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. :return: a CQ object with the resulting solid selected.
extrude always *adds* material to a part. extrude always *adds* material to a part.
@ -2159,7 +2167,7 @@ class Workplane(CQ):
selected may not be planar selected may not be planar
""" """
r = self._extrude( 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: if combine:
newS = self._combineWithBase(r) newS = self._combineWithBase(r)
@ -2396,7 +2404,7 @@ class Workplane(CQ):
return self.newObject([newS]) 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. 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, :type distanceToCut: float, >0 means in the positive direction of the workplane normal,
<0 means in the negative direction <0 means in the negative direction
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape :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 :raises: ValueError if there is no solid to subtract from in the chain
:return: a CQ object with the resulting object selected :return: a CQ object with the resulting object selected
@ -2416,7 +2425,7 @@ class Workplane(CQ):
Cut Up to Surface Cut Up to Surface
""" """
# first, make the object # first, make the object
toCut = self._extrude(distanceToCut) toCut = self._extrude(distanceToCut, taper=taper)
# now find a solid in the chain # now find a solid in the chain
@ -2468,7 +2477,7 @@ class Workplane(CQ):
return self.newObject([r]) 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. Make a prismatic solid from the existing set of pending wires.
@ -2518,14 +2527,20 @@ class Workplane(CQ):
# return r # return r
toFuse = [] toFuse = []
for ws in wireSets:
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir) if taper:
toFuse.append(thisObj) for ws in wireSets:
thisObj = Solid.extrudeLinear(ws[0], [], eDir, taper)
if both: toFuse.append(thisObj)
thisObj = Solid.extrudeLinear( else:
ws[0], ws[1:], eDir.multiply(-1.)) for ws in wireSets:
toFuse.append(thisObj) thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
toFuse.append(thisObj)
if both:
thisObj = Solid.extrudeLinear(
ws[0], ws[1:], eDir.multiply(-1.))
toFuse.append(thisObj)
return Compound.makeCompound(toFuse) return Compound.makeCompound(toFuse)

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) gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d)
# collection of pints (used for spline construction) # 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.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex, from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
BRepBuilderAPI_MakeEdge, 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.GC import GC_MakeArcOfCircle # geometry construction
from OCC.Core.GCE2d import GCE2d_MakeSegment from OCC.Core.GCE2d import GCE2d_MakeSegment
from OCC.Core.GeomAPI import (GeomAPI_PointsToBSpline, from OCC.Core.GeomAPI import (GeomAPI_Interpolate,
GeomAPI_ProjectPointOnSurf) GeomAPI_ProjectPointOnSurf)
from OCC.Core.BRepFill import brepfill_Shell, brepfill_Face 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.Core.Visualization import Tesselator
from OCC.LocOpe import LocOpe_DPrism
from OCC.BRepCheck import BRepCheck_Analyzer
from math import pi, sqrt from math import pi, sqrt
TOLERANCE = 1e-6 TOLERANCE = 1e-6
@ -313,7 +317,7 @@ class Shape(object):
return self.wrapped.IsEqual(other.wrapped) return self.wrapped.IsEqual(other.wrapped)
def isValid(self): # seems to be not used in the codebase -- remove? 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 def BoundingBox(self, tolerance=0.1): # need to implement that in GEOM
return BoundBox._fromTopoDS(self.wrapped) return BoundBox._fromTopoDS(self.wrapped)
@ -462,7 +466,11 @@ class Shape(object):
return [Solid(i) for i in self._entities('Solid')] return [Solid(i) for i in self._entities('Solid')]
def Area(self): def Area(self):
raise NotImplementedError Properties = GProp_GProps()
brepgprop_SurfaceProperties(self.wrapped,
Properties)
return Properties.Mass()
def Volume(self): def Volume(self):
# when density == 1, mass == volume # when density == 1, mass == volume
@ -631,6 +639,10 @@ class Mixin1D(object):
brepgprop_LinearProperties(self.wrapped, Properties) brepgprop_LinearProperties(self.wrapped, Properties)
return Properties.Mass() return Properties.Mass()
def IsClosed(self):
return BRep_Tool.IsClosed(self.wrapped)
class Edge(Shape, Mixin1D): class Edge(Shape, Mixin1D):
@ -671,20 +683,17 @@ class Edge(Shape, Mixin1D):
return Vector(curve.Value(umax)) return Vector(curve.Value(umax))
def tangentAt(self, locationVector=None): def tangentAt(self, locationParam=0.5):
""" """
Compute tangent vector at the specified location. 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 :return: tangent vector
""" """
curve = self._geomAdaptor() curve = self._geomAdaptor()
if locationVector: umin, umax = curve.FirstParameter(), curve.LastParameter()
raise NotImplementedError umid = (1-locationParam)*umin + locationParam*umax
else:
umin, umax = curve.FirstParameter(), curve.LastParameter()
umid = 0.5 * (umin + umax)
# TODO what are good parameters for those? # TODO what are good parameters for those?
curve_props = BRepLProp_CLProps(curve, 2, curve.Tolerance()) curve_props = BRepLProp_CLProps(curve, 2, curve.Tolerance())
@ -726,18 +735,28 @@ class Edge(Shape, Mixin1D):
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())
@classmethod @classmethod
def makeSpline(cls, listOfVector): def makeSpline(cls, listOfVector, tangents=None, periodic=False,
tol = 1e-6):
""" """
Interpolate a spline through the provided points. Interpolate a spline through the provided points.
:param cls: :param cls:
:param listOfVector: a list of Vectors that represent the points :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 :return: an Edge
""" """
pnts = TColgp_Array1OfPnt(0, len(listOfVector) - 1) pnts = TColgp_HArray1OfPnt(1, len(listOfVector))
for ix, v in enumerate(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()) return cls(BRepBuilderAPI_MakeEdge(spline_geom).Edge())
@ -1280,13 +1299,14 @@ class Solid(Shape, Mixin3D):
return cls(BRepAlgoAPI_Cut(outer_solid, inner_comp).Shape()) return cls(BRepAlgoAPI_Cut(outer_solid, inner_comp).Shape())
@classmethod @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 Attempt to extrude the list of wires into a prismatic solid in the provided direction
:param outerWire: the outermost wire :param outerWire: the outermost wire
:param innerWires: a list of inner wires :param innerWires: a list of inner wires
:param vecNormal: a vector along which to extrude the wires :param vecNormal: a vector along which to extrude the wires
:param taper: taper angle, default=0
:return: a Solid object :return: a Solid object
The wires must not intersect The wires must not intersect
@ -1303,18 +1323,20 @@ class Solid(Shape, Mixin3D):
reliable. reliable.
""" """
# one would think that fusing faces into a compound and then extruding would work, if taper==0:
# but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc), face = Face.makeFromWires(outerWire, innerWires)
# but then cutting it from the main solid fails with BRep_NotDone. prism_builder = BRepPrimAPI_MakePrism(
# the work around is to extrude each and then join the resulting solids, which seems to work face.wrapped, vecNormal.wrapped, True)
else:
# FreeCAD allows this in one operation, but others might not face = Face.makeFromWires(outerWire)
faceNormal = face.normalAt()
face = Face.makeFromWires(outerWire, innerWires) d = 1 if vecNormal.getAngle(faceNormal)<90 * DEG2RAD else -1
prism_builder = BRepPrimAPI_MakePrism( prism_builder = LocOpe_DPrism(face.wrapped,
face.wrapped, vecNormal.wrapped, True) d * vecNormal.Length,
d * taper * DEG2RAD)
return cls(prism_builder.Shape()) return cls(prism_builder.Shape())
@classmethod @classmethod
def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd): def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd):

View File

@ -170,7 +170,7 @@ class BaseDirSelector(Selector):
r.append(o) r.append(o)
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'): 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 # 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): if self.test(tangent):
r.append(o) r.append(o)

View File

@ -362,6 +362,38 @@ class TestCadQuery(BaseTest):
self.assertEqual(2, result.faces().size()) self.assertEqual(2, result.faces().size())
self.assertEqual(2, result.vertices().size()) self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().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): def testSweep(self):
""" """
@ -1668,11 +1700,24 @@ class TestCadQuery(BaseTest):
def testExtrude(self): def testExtrude(self):
""" """
Test symmetric extrude Test extrude
""" """
r = 1. r = 1.
h = 1. h = 1.
decimal_places = 9. 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 # extrude symmetrically
s = Workplane("XY").circle(r).extrude(h, both=True) s = Workplane("XY").circle(r).extrude(h, both=True)
@ -1686,6 +1731,42 @@ class TestCadQuery(BaseTest):
self.assertTupleAlmostEquals(delta.toTuple(), self.assertTupleAlmostEquals(delta.toTuple(),
(0., 0., 2. * h), (0., 0., 2. * h),
decimal_places) 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): def testClose(self):
# Close without endPoint and startPoint coincide. # Close without endPoint and startPoint coincide.