Merge pull request #3585 from gwicke/unified_region_experiments
Path: Area based unified projection implementation
This commit is contained in:
@@ -189,7 +189,9 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
elif obj.BoundaryShape == 'Perimeter':
|
||||
if obj.ClearEdges:
|
||||
psZMin = planeshape.BoundBox.ZMin
|
||||
ofstShape = PathSurfaceSupport.extractFaceOffset(planeshape, self.radius * 1.25, planeshape)
|
||||
ofstShape = PathUtils.getOffsetArea(planeshape,
|
||||
self.radius * 1.25,
|
||||
plane=planeshape)
|
||||
ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin))
|
||||
env = PathUtils.getEnvelope(partshape=ofstShape, depthparams=self.depthparams)
|
||||
else:
|
||||
@@ -198,7 +200,9 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
import PathScripts.PathSurfaceSupport as PathSurfaceSupport
|
||||
baseShape = oneBase[0].Shape
|
||||
psZMin = planeshape.BoundBox.ZMin
|
||||
ofstShape = PathSurfaceSupport.extractFaceOffset(planeshape, self.tool.Diameter * 1.1, planeshape)
|
||||
ofstShape = PathUtils.getOffsetArea(planeshape,
|
||||
self.tool.Diameter * 1.1,
|
||||
plane=planeshape)
|
||||
ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin))
|
||||
|
||||
custDepthparams = self._customDepthParams(obj, obj.StartDepth.Value + 0.1, obj.FinalDepth.Value - 0.1) # only an envelope
|
||||
|
||||
@@ -1069,7 +1069,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
|
||||
cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv)
|
||||
|
||||
# Calculate offset shape, containing cut region
|
||||
ofstShp = self._extractFaceOffset(obj, cutShp, False)
|
||||
ofstShp = self._getOffsetArea(obj, cutShp, False)
|
||||
|
||||
# CHECK for ZERO area of offset shape
|
||||
try:
|
||||
@@ -1174,40 +1174,22 @@ class ObjectProfile(PathAreaOp.ObjectOp):
|
||||
|
||||
return rtnWIRES
|
||||
|
||||
def _extractFaceOffset(self, obj, fcShape, isHole):
|
||||
'''_extractFaceOffset(obj, fcShape, isHole) ... internal function.
|
||||
Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified.
|
||||
Adjustments made based on notes by @sliptonic - https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.'''
|
||||
PathLog.debug('_extractFaceOffset()')
|
||||
def _getOffsetArea(self, obj, fcShape, isHole):
|
||||
'''Get an offset area for a shape. Wrapper around
|
||||
PathUtils.getOffsetArea.'''
|
||||
PathLog.debug('_getOffsetArea()')
|
||||
|
||||
areaParams = {}
|
||||
# JOB = PathUtils.findParentJob(obj)
|
||||
# tolrnc = JOB.GeometryTolerance.Value
|
||||
# if self.useComp:
|
||||
# offset = self.ofstRadius # + tolrnc
|
||||
# else:
|
||||
# offset = self.offsetExtra # + tolrnc
|
||||
JOB = PathUtils.findParentJob(obj)
|
||||
tolerance = JOB.GeometryTolerance.Value
|
||||
offset = self.ofstRadius
|
||||
|
||||
if isHole is False:
|
||||
offset = 0 - offset
|
||||
|
||||
areaParams['Offset'] = offset
|
||||
areaParams['Fill'] = 1
|
||||
areaParams['Coplanar'] = 0
|
||||
areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections
|
||||
areaParams['Reorient'] = True
|
||||
areaParams['OpenMode'] = 0
|
||||
areaParams['MaxArcPoints'] = 400 # 400
|
||||
areaParams['Project'] = True
|
||||
# areaParams['JoinType'] = 1
|
||||
|
||||
area = Path.Area() # Create instance of Area() class object
|
||||
area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane
|
||||
area.add(fcShape) # obj.Shape to use for extracting offset
|
||||
area.setParams(**areaParams) # set parameters
|
||||
|
||||
return area.getShape()
|
||||
return PathUtils.getOffsetArea(fcShape,
|
||||
offset,
|
||||
plane=fcShape,
|
||||
tolerance=tolerance)
|
||||
|
||||
def _findNearestVertex(self, shape, point):
|
||||
PathLog.debug('_findNearestVertex()')
|
||||
@@ -1373,7 +1355,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
|
||||
return (wireIdxs[0], wireIdxs[1])
|
||||
|
||||
def _makeCrossSection(self, shape, sliceZ, zHghtTrgt=False):
|
||||
'''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)...
|
||||
'''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)...
|
||||
Creates cross-section objectc from shape. Translates cross-section to zHghtTrgt if available.
|
||||
Makes face shape from cross-section object. Returns face shape at zHghtTrgt.'''
|
||||
PathLog.debug('_makeCrossSection()')
|
||||
@@ -1502,7 +1484,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
|
||||
# 2 6
|
||||
# | |
|
||||
# | ----5----|
|
||||
# | 4
|
||||
# | 4
|
||||
# -----3-------|
|
||||
# positive dist in _makePerp2DVector() is CCW rotation
|
||||
p1 = E
|
||||
|
||||
@@ -383,60 +383,18 @@ class PathGeometryGenerator:
|
||||
def _extractOffsetFaces(self):
|
||||
PathLog.debug('_extractOffsetFaces()')
|
||||
wires = list()
|
||||
faces = list()
|
||||
ofst = 0.0 # - self.cutOut
|
||||
shape = self.shape
|
||||
cont = True
|
||||
cnt = 0
|
||||
while cont:
|
||||
ofstArea = self._getFaceOffset(shape, ofst)
|
||||
if not ofstArea:
|
||||
cont = False
|
||||
True if cont else False # cont used for LGTM
|
||||
offset = 0.0 # Start right at the edge of cut area
|
||||
while True:
|
||||
offsetArea = PathUtils.getOffsetArea(shape, offset, plane=self.wpc)
|
||||
if not offsetArea:
|
||||
# Area fully consumed
|
||||
break
|
||||
for F in ofstArea.Faces:
|
||||
faces.append(F)
|
||||
for w in F.Wires:
|
||||
for f in offsetArea.Faces:
|
||||
for w in f.Wires:
|
||||
wires.append(w)
|
||||
shape = ofstArea
|
||||
if cnt == 0:
|
||||
ofst = 0.0 - self.cutOut
|
||||
cnt += 1
|
||||
offset -= self.cutOut
|
||||
return wires
|
||||
|
||||
def _getFaceOffset(self, shape, offset):
|
||||
'''_getFaceOffset(shape, offset) ... internal function.
|
||||
Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified.
|
||||
Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.'''
|
||||
areaParams = {}
|
||||
|
||||
areaParams['Offset'] = offset
|
||||
areaParams['Fill'] = 1 # 1
|
||||
areaParams['Coplanar'] = 0
|
||||
areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections
|
||||
areaParams['Reorient'] = True
|
||||
areaParams['OpenMode'] = 0
|
||||
areaParams['MaxArcPoints'] = 400 # 400
|
||||
areaParams['Project'] = True
|
||||
|
||||
area = Path.Area() # Create instance of Area() class object
|
||||
area.setPlane(PathUtils.makeWorkplane(self.wpc)) # Set working plane to normal at Z=1
|
||||
area.add(shape)
|
||||
area.setParams(**areaParams) # set parameters
|
||||
|
||||
offsetShape = area.getShape()
|
||||
wCnt = len(offsetShape.Wires)
|
||||
if wCnt == 0:
|
||||
return False
|
||||
elif wCnt == 1:
|
||||
ofstFace = Part.Face(offsetShape.Wires[0])
|
||||
else:
|
||||
W = list()
|
||||
for wr in offsetShape.Wires:
|
||||
W.append(Part.Face(wr))
|
||||
ofstFace = Part.makeCompound(W)
|
||||
|
||||
return ofstFace
|
||||
# Eclass
|
||||
|
||||
|
||||
@@ -654,24 +612,17 @@ class ProcessSelectedFaces:
|
||||
isHole = False
|
||||
if self.obj.HandleMultipleFeatures == 'Collectively':
|
||||
cont = True
|
||||
fsL = list() # face shape list
|
||||
ifL = list() # avoid shape list
|
||||
outFCS = list()
|
||||
PathLog.debug(
|
||||
'Attempting to get cross-section of collective faces.')
|
||||
outFCS, ifL = self.findUnifiedRegions(FCS)
|
||||
if self.obj.InternalFeaturesCut and ifL:
|
||||
ifL = list() # clear avoid shape list
|
||||
|
||||
# Use new face-unifying class
|
||||
FUR = FindUnifiedRegions(FCS, self.JOB.GeometryTolerance.Value)
|
||||
if self.showDebugObjects:
|
||||
FUR.setTempGroup(self.tempGroup)
|
||||
outFCS = FUR.getUnifiedRegions()
|
||||
if not self.obj.InternalFeaturesCut:
|
||||
gIF = FUR.getInternalFeatures()
|
||||
if gIF:
|
||||
ifL.extend(gIF)
|
||||
|
||||
PathLog.debug('Attempting to get cross-section of collective faces.')
|
||||
if len(outFCS) == 0:
|
||||
msg = translate('PathSurfaceSupport',
|
||||
'Cannot process selected faces. Check horizontal surface exposure.')
|
||||
msg = translate(
|
||||
'PathSurfaceSupport',
|
||||
'Cannot process selected faces. Check horizontal '
|
||||
'surface exposure.')
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
cont = False
|
||||
else:
|
||||
@@ -681,7 +632,9 @@ class ProcessSelectedFaces:
|
||||
if cont and self.profileEdges != 'None':
|
||||
PathLog.debug('.. include Profile Edge')
|
||||
ofstVal = self._calculateOffsetValue(isHole)
|
||||
psOfst = extractFaceOffset(cfsL, ofstVal, self.wpc)
|
||||
psOfst = PathUtils.getOffsetArea(cfsL,
|
||||
ofstVal,
|
||||
plane=self.wpc)
|
||||
if psOfst:
|
||||
mPS = [psOfst]
|
||||
if self.profileEdges == 'Only':
|
||||
@@ -698,7 +651,8 @@ class ProcessSelectedFaces:
|
||||
self.tempGroup.addObject(T)
|
||||
|
||||
ofstVal = self._calculateOffsetValue(isHole)
|
||||
faceOfstShp = extractFaceOffset(cfsL, ofstVal, self.wpc)
|
||||
faceOfstShp = PathUtils.getOffsetArea(
|
||||
cfsL, ofstVal, plane=self.wpc)
|
||||
if not faceOfstShp:
|
||||
msg = translate('PathSurfaceSupport',
|
||||
'Failed to create offset face.') + '\n'
|
||||
@@ -721,7 +675,8 @@ class ProcessSelectedFaces:
|
||||
C.purgeTouched()
|
||||
self.tempGroup.addObject(C)
|
||||
ofstVal = self._calculateOffsetValue(isHole=True)
|
||||
intOfstShp = extractFaceOffset(casL, ofstVal, self.wpc)
|
||||
intOfstShp = PathUtils.getOffsetArea(
|
||||
casL, ofstVal, plane=self.wpc)
|
||||
mIFS.append(intOfstShp)
|
||||
|
||||
mFS = [faceOfstShp]
|
||||
@@ -730,28 +685,22 @@ class ProcessSelectedFaces:
|
||||
elif self.obj.HandleMultipleFeatures == 'Individually':
|
||||
for (fcshp, fcIdx) in FCS:
|
||||
cont = True
|
||||
ifL = list() # avoid shape list
|
||||
fNum = fcIdx + 1
|
||||
outerFace = False
|
||||
|
||||
# Use new face-unifying class
|
||||
FUR = FindUnifiedRegions([(fcshp, fcIdx)], self.JOB.GeometryTolerance.Value)
|
||||
if self.showDebugObjects:
|
||||
FUR.setTempGroup(self.tempGroup)
|
||||
gUR = FUR.getUnifiedRegions()
|
||||
gUR, ifL = self.findUnifiedRegions(FCS)
|
||||
if len(gUR) > 0:
|
||||
outerFace = gUR[0]
|
||||
if not self.obj.InternalFeaturesCut:
|
||||
gIF = FUR.getInternalFeatures()
|
||||
if gIF:
|
||||
ifL = gIF
|
||||
if self.obj.InternalFeaturesCut:
|
||||
ifL = list() # avoid shape list
|
||||
|
||||
if outerFace:
|
||||
PathLog.debug('Attempting to create offset face of Face{}'.format(fNum))
|
||||
|
||||
if self.profileEdges != 'None':
|
||||
ofstVal = self._calculateOffsetValue(isHole)
|
||||
psOfst = extractFaceOffset(outerFace, ofstVal, self.wpc)
|
||||
psOfst = PathUtils.getOffsetArea(
|
||||
outerFace, ofstVal, plane=self.wpc)
|
||||
if psOfst:
|
||||
if mPS is False:
|
||||
mPS = list()
|
||||
@@ -766,7 +715,8 @@ class ProcessSelectedFaces:
|
||||
|
||||
if cont:
|
||||
ofstVal = self._calculateOffsetValue(isHole)
|
||||
faceOfstShp = extractFaceOffset(outerFace, ofstVal, self.wpc)
|
||||
faceOfstShp = PathUtils.getOffsetArea(
|
||||
outerFace, ofstVal, plane=self.wpc)
|
||||
|
||||
lenIfl = len(ifL)
|
||||
if self.obj.InternalFeaturesCut is False and lenIfl > 0:
|
||||
@@ -776,7 +726,8 @@ class ProcessSelectedFaces:
|
||||
casL = Part.makeCompound(ifL)
|
||||
|
||||
ofstVal = self._calculateOffsetValue(isHole=True)
|
||||
intOfstShp = extractFaceOffset(casL, ofstVal, self.wpc)
|
||||
intOfstShp = PathUtils.getOffsetArea(
|
||||
casL, ofstVal, plane=self.wpc)
|
||||
mIFS.append(intOfstShp)
|
||||
# faceOfstShp = faceOfstShp.cut(intOfstShp)
|
||||
|
||||
@@ -801,20 +752,9 @@ class ProcessSelectedFaces:
|
||||
outFCS = list()
|
||||
intFEAT = list()
|
||||
|
||||
for (fcshp, fcIdx) in VDS:
|
||||
fNum = fcIdx + 1
|
||||
|
||||
# Use new face-unifying class
|
||||
FUR = FindUnifiedRegions([(fcshp, fcIdx)], self.JOB.GeometryTolerance.Value)
|
||||
if self.showDebugObjects:
|
||||
FUR.setTempGroup(self.tempGroup)
|
||||
gUR = FUR.getUnifiedRegions()
|
||||
if len(gUR) > 0:
|
||||
outFCS.extend(gUR)
|
||||
if not self.obj.InternalFeaturesCut:
|
||||
gIF = FUR.getInternalFeatures()
|
||||
if gIF:
|
||||
intFEAT.extend(gIF)
|
||||
outFCS, intFEAT = self.findUnifiedRegions(VDS)
|
||||
if self.obj.InternalFeaturesCut:
|
||||
intFEAT = list()
|
||||
|
||||
lenOtFcs = len(outFCS)
|
||||
if lenOtFcs == 0:
|
||||
@@ -838,7 +778,9 @@ class ProcessSelectedFaces:
|
||||
P.purgeTouched()
|
||||
self.tempGroup.addObject(P)
|
||||
ofstVal = self._calculateOffsetValue(isHole, isVoid=True)
|
||||
avdOfstShp = extractFaceOffset(avoid, ofstVal, self.wpc)
|
||||
avdOfstShp = PathUtils.getOffsetArea(avoid,
|
||||
ofstVal,
|
||||
plane=self.wpc)
|
||||
if avdOfstShp is False:
|
||||
msg = translate('PathSurfaceSupport',
|
||||
'Failed to create collective offset avoid face.')
|
||||
@@ -854,7 +796,9 @@ class ProcessSelectedFaces:
|
||||
else:
|
||||
ifc = intFEAT[0]
|
||||
ofstVal = self._calculateOffsetValue(isHole=True)
|
||||
ifOfstShp = extractFaceOffset(ifc, ofstVal, self.wpc)
|
||||
ifOfstShp = PathUtils.getOffsetArea(ifc,
|
||||
ofstVal,
|
||||
plane=self.wpc)
|
||||
if ifOfstShp is False:
|
||||
msg = translate('PathSurfaceSupport',
|
||||
'Failed to create collective offset avoid internal features.') + '\n'
|
||||
@@ -900,7 +844,9 @@ class ProcessSelectedFaces:
|
||||
if cont and self.profileEdges != 'None':
|
||||
PathLog.debug(' -Attempting profile geometry for model base.')
|
||||
ofstVal = self._calculateOffsetValue(isHole)
|
||||
psOfst = extractFaceOffset(csFaceShape, ofstVal, self.wpc)
|
||||
psOfst = PathUtils.getOffsetArea(csFaceShape,
|
||||
ofstVal,
|
||||
plane=self.wpc)
|
||||
if psOfst:
|
||||
if self.profileEdges == 'Only':
|
||||
return (True, psOfst)
|
||||
@@ -910,9 +856,10 @@ class ProcessSelectedFaces:
|
||||
|
||||
if cont:
|
||||
ofstVal = self._calculateOffsetValue(isHole)
|
||||
faceOffsetShape = extractFaceOffset(csFaceShape, ofstVal, self.wpc)
|
||||
faceOffsetShape = PathUtils.getOffsetArea(csFaceShape, ofstVal,
|
||||
plane=self.wpc)
|
||||
if faceOffsetShape is False:
|
||||
PathLog.debug('extractFaceOffset() failed for entire base.')
|
||||
PathLog.debug('getOffsetArea() failed for entire base.')
|
||||
else:
|
||||
faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin))
|
||||
return (faceOffsetShape, prflShp)
|
||||
@@ -944,6 +891,55 @@ class ProcessSelectedFaces:
|
||||
offset += self.radius + tolrnc
|
||||
|
||||
return offset
|
||||
|
||||
def findUnifiedRegions(
|
||||
self,
|
||||
shapeAndIndexTuples,
|
||||
useAreaImplementation=True):
|
||||
"""Wrapper around area and wire based region unification
|
||||
implementations."""
|
||||
PathLog.debug('findUnifiedRegions()')
|
||||
# Allow merging of faces within the LinearDeflection tolerance.
|
||||
tolerance = self.obj.LinearDeflection.Value
|
||||
# Default: normal to Z=1 (XY plane), at Z=0
|
||||
try:
|
||||
# Use Area based implementation
|
||||
shapes = Part.makeCompound([t[0] for t in shapeAndIndexTuples])
|
||||
outlineShape = PathUtils.getOffsetArea(
|
||||
shapes,
|
||||
# Make the outline very slightly smaller, to avoid creating
|
||||
# small edges in the cut with the hole-preserving projection.
|
||||
0.0 - tolerance / 10,
|
||||
removeHoles=True, # Outline has holes filled in
|
||||
tolerance=tolerance,
|
||||
plane=self.wpc)
|
||||
projectionShape = PathUtils.getOffsetArea(
|
||||
shapes,
|
||||
# Make the projection very slightly larger
|
||||
tolerance / 10,
|
||||
removeHoles=False, # Projection has holes preserved
|
||||
tolerance=tolerance,
|
||||
plane=self.wpc)
|
||||
internalShape = outlineShape.cut(projectionShape)
|
||||
# Filter out tiny faces, usually artifacts around the perimeter of
|
||||
# the cut.
|
||||
minArea = (10 * tolerance)**2
|
||||
internalFaces = [
|
||||
f for f in internalShape.Faces if f.Area > minArea
|
||||
]
|
||||
if internalFaces:
|
||||
internalFaces = Part.makeCompound(internalFaces)
|
||||
return ([outlineShape], [internalFaces])
|
||||
except Exception as e:
|
||||
PathLog.warning(
|
||||
"getOffsetArea failed: {}; Using FindUnifiedRegions.".format(
|
||||
e))
|
||||
# Use face-unifying class
|
||||
FUR = FindUnifiedRegions(shapeAndIndexTuples, tolerance)
|
||||
if self.showDebugObjects:
|
||||
FUR.setTempGroup(self.tempGroup)
|
||||
return (FUR.getUnifiedRegions(), FUR.getInternalFeatures)
|
||||
|
||||
# Eclass
|
||||
|
||||
|
||||
@@ -1097,50 +1093,6 @@ def getSliceFromEnvelope(env):
|
||||
return tf
|
||||
|
||||
|
||||
# Function to extract offset face from shape
|
||||
def extractFaceOffset(fcShape, offset, wpc, makeComp=True):
|
||||
'''extractFaceOffset(fcShape, offset) ... internal function.
|
||||
Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified.
|
||||
Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.'''
|
||||
PathLog.debug('extractFaceOffset()')
|
||||
|
||||
if fcShape.BoundBox.ZMin != 0.0:
|
||||
fcShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - fcShape.BoundBox.ZMin))
|
||||
|
||||
areaParams = {}
|
||||
areaParams['Offset'] = offset
|
||||
areaParams['Fill'] = 1 # 1
|
||||
areaParams['Coplanar'] = 0
|
||||
areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections
|
||||
areaParams['Reorient'] = True
|
||||
areaParams['OpenMode'] = 0
|
||||
areaParams['MaxArcPoints'] = 400 # 400
|
||||
areaParams['Project'] = True
|
||||
|
||||
area = Path.Area() # Create instance of Area() class object
|
||||
# area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane
|
||||
area.setPlane(PathUtils.makeWorkplane(wpc)) # Set working plane to normal at Z=1
|
||||
area.add(fcShape)
|
||||
area.setParams(**areaParams) # set parameters
|
||||
|
||||
offsetShape = area.getShape()
|
||||
wCnt = len(offsetShape.Wires)
|
||||
if wCnt == 0:
|
||||
return False
|
||||
elif wCnt == 1:
|
||||
ofstFace = Part.Face(offsetShape.Wires[0])
|
||||
if not makeComp:
|
||||
ofstFace = [ofstFace]
|
||||
else:
|
||||
W = list()
|
||||
for wr in offsetShape.Wires:
|
||||
W.append(Part.Face(wr))
|
||||
if makeComp:
|
||||
ofstFace = Part.makeCompound(W)
|
||||
else:
|
||||
ofstFace = W
|
||||
|
||||
return ofstFace # offsetShape
|
||||
|
||||
|
||||
def _prepareModelSTLs(self, JOB, obj, m, ocl):
|
||||
@@ -1159,7 +1111,6 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl):
|
||||
model, and avoided faces. Travel lines can be checked against this
|
||||
STL object to determine minimum travel height to clear stock and model.'''
|
||||
PathLog.debug('_makeSafeSTL()')
|
||||
import MeshPart
|
||||
|
||||
fuseShapes = list()
|
||||
Mdl = JOB.Model.Group[mdlIdx]
|
||||
@@ -1728,7 +1679,8 @@ def pathGeomToOffsetPointSet(obj, compGeoShp):
|
||||
optimize = obj.OptimizeLinearPaths
|
||||
ofstCnt = len(compGeoShp)
|
||||
|
||||
# Cycle through offeset loops
|
||||
# Cycle through offset loops
|
||||
iPOL = False
|
||||
for ei in range(0, ofstCnt):
|
||||
OS = compGeoShp[ei]
|
||||
lenOS = len(OS)
|
||||
@@ -2279,7 +2231,7 @@ class FindUnifiedRegions:
|
||||
face = Part.Face(wCS)
|
||||
return [face]
|
||||
else:
|
||||
(faceShp, fcIdx) = self.FACES[0]
|
||||
(faceShp, fcIdx) = self.FACES[0]
|
||||
msg = translate('PathSurfaceSupport',
|
||||
'Failed to identify a horizontal cross-section for Face')
|
||||
msg += '{}.\n'.format(fcIdx + 1)
|
||||
@@ -2535,8 +2487,8 @@ class OCL_Tool():
|
||||
if self.flatRadius == 0.0:
|
||||
self.flatRadius = self.diameter * 0.25
|
||||
elif self.flatRadius > 0.0:
|
||||
self.flatRadius = self.flatRadius * 1.25
|
||||
|
||||
self.flatRadius = self.flatRadius * 1.25
|
||||
|
||||
def _oclCylCutter(self):
|
||||
# Standard End Mill, Slot cutter, or Fly cutter
|
||||
# OCL -> CylCutter::CylCutter(diameter, length)
|
||||
@@ -2680,3 +2632,5 @@ def makeExtendedBoundBox(wBB, bbBfr, zDep):
|
||||
L4 = Part.makeLine(p4, p1)
|
||||
|
||||
return Part.Face(Part.Wire([L1, L2, L3, L4]))
|
||||
|
||||
|
||||
|
||||
@@ -336,6 +336,49 @@ def getEnvelope(partshape, subshape=None, depthparams=None):
|
||||
return envelopeshape
|
||||
|
||||
|
||||
# Function to extract offset face from shape
|
||||
def getOffsetArea(fcShape,
|
||||
offset,
|
||||
removeHoles=False,
|
||||
# Default: XY plane
|
||||
plane=Part.makeCircle(10),
|
||||
tolerance=1e-4):
|
||||
'''Make an offset area of a shape, projected onto a plane.
|
||||
Positive offsets expand the area, negative offsets shrink it.
|
||||
Inspired by _buildPathArea() from PathAreaOp.py module. Adjustments made
|
||||
based on notes by @sliptonic at this webpage:
|
||||
https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.'''
|
||||
PathLog.debug('getOffsetArea()')
|
||||
|
||||
areaParams = {}
|
||||
areaParams['Offset'] = offset
|
||||
areaParams['Fill'] = 1 # 1
|
||||
areaParams['Outline'] = removeHoles
|
||||
areaParams['Coplanar'] = 0
|
||||
areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections
|
||||
areaParams['Reorient'] = True
|
||||
areaParams['OpenMode'] = 0
|
||||
areaParams['MaxArcPoints'] = 400 # 400
|
||||
areaParams['Project'] = True
|
||||
areaParams['FitArcs'] = False # Can be buggy & expensive
|
||||
areaParams['Deflection'] = tolerance
|
||||
areaParams['Accuracy'] = tolerance
|
||||
areaParams['Tolerance'] = 1e-5 # Equal point tolerance
|
||||
areaParams['Simplify'] = True
|
||||
areaParams['CleanDistance'] = tolerance / 5
|
||||
|
||||
area = Path.Area() # Create instance of Area() class object
|
||||
# Set working plane normal to Z=1
|
||||
area.setPlane(makeWorkplane(plane))
|
||||
area.add(fcShape)
|
||||
area.setParams(**areaParams) # set parameters
|
||||
|
||||
offsetShape = area.getShape()
|
||||
if not offsetShape.Faces:
|
||||
return False
|
||||
return offsetShape
|
||||
|
||||
|
||||
def reverseEdge(e):
|
||||
if DraftGeomUtils.geomType(e) == "Circle":
|
||||
arcstpt = e.valueAt(e.FirstParameter)
|
||||
|
||||
@@ -951,7 +951,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
return commands
|
||||
|
||||
def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines):
|
||||
'''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ...
|
||||
'''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ...
|
||||
Perform OCL scan for waterline purpose.'''
|
||||
pdc = ocl.PathDropCutter() # create a pdc
|
||||
pdc.setSTL(stl)
|
||||
@@ -1299,7 +1299,10 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
activeArea = area.cut(trimFace)
|
||||
activeAreaWireCnt = len(activeArea.Wires) # first wire is boundFace wire
|
||||
self.showDebugObject(activeArea, 'ActiveArea_{}'.format(caCnt))
|
||||
ofstArea = PathSurfaceSupport.extractFaceOffset(activeArea, ofst, self.wpc, makeComp=False)
|
||||
ofstArea = PathUtils.getOffsetArea(activeArea,
|
||||
ofst,
|
||||
self.wpc,
|
||||
makeComp=False)
|
||||
if not ofstArea:
|
||||
data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString
|
||||
PathLog.debug('No offset area returned for cut area depth at {}.'.format(data))
|
||||
@@ -1359,7 +1362,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
CUTAREAS = list()
|
||||
isFirst = True
|
||||
lenDP = len(depthparams)
|
||||
|
||||
|
||||
# Cycle through layer depths
|
||||
for dp in range(0, lenDP):
|
||||
csHght = depthparams[dp]
|
||||
@@ -1472,7 +1475,10 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
cont = True
|
||||
cnt = 0
|
||||
while cont:
|
||||
ofstArea = PathSurfaceSupport.extractFaceOffset(shape, ofst, self.wpc, makeComp=True)
|
||||
ofstArea = PathUtils.getOffsetArea(shape,
|
||||
ofst,
|
||||
self.wpc,
|
||||
makeComp=True)
|
||||
if not ofstArea:
|
||||
break
|
||||
for F in ofstArea.Faces:
|
||||
@@ -1584,7 +1590,7 @@ class ObjectWaterline(PathOp.ObjectOp):
|
||||
li = fIds.pop()
|
||||
low = csFaces[li] # senior face
|
||||
pIds = self._idInternalFeature(csFaces, fIds, pIds, li, low)
|
||||
|
||||
|
||||
for af in range(lenCsF - 1, -1, -1): # cycle from last item toward first
|
||||
prnt = pIds[af]
|
||||
if prnt == -1:
|
||||
|
||||
Reference in New Issue
Block a user