Path: Improve open-edges, messaging, Final Depth initial guess and more

Open-edges will now work in simple cases where user selects bottom edge, without requiring adjustment to Final Depth.  This will speed up procedural usage of the operation for open edges by re-enabling the `select-edge_create_OK` work flow.
The guessing procedure for Final Depth based on Base Geometry selected is fixed in `UpdateDepths()` method.
Improvements to user messages.
Remove unnecessary code and comments.
Improve debugging feedback.
This commit is contained in:
Russell Johnson
2020-05-21 22:03:12 -05:00
parent 6fc6a62df2
commit edda808c3c

View File

@@ -188,7 +188,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# ml = 0 if obj.JoinType == 'Miter' else 2
rotation = 2 if obj.EnableRotation == 'Off' else 0
side = 0 if obj.UseComp else 2
opType = self.getOperationType(obj)
opType = self._getOperationType(obj)
if opType == 'Contour':
side = 2
@@ -211,7 +211,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
obj.setEditorMode('AttemptInverseAngle', rotation)
obj.setEditorMode('LimitDepthToFace', rotation)
def getOperationType(self, obj):
def _getOperationType(self, obj):
if len(obj.Base) == 0:
return 'Contour'
@@ -286,23 +286,29 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return True
def opUpdateDepths(self, obj):
obj.OpStartDepth = obj.OpStockZMax
obj.OpFinalDepth = obj.OpStockZMin
if hasattr(obj, 'Base') and obj.Base.__len__() == 0:
obj.OpStartDepth = obj.OpStockZMax
obj.OpFinalDepth = obj.OpStockZMin
def areaOpShapes(self, obj):
'''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.'''
PathLog.track()
shapes = []
inaccessible = translate('PathProfileEdges', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.')
baseSubsTuples = list()
allTuples = list()
edgeFaces = list()
subCount = 0
self.inaccessibleMsg = translate('PathProfile', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.')
self.profileshape = list() # pylint: disable=attribute-defined-outside-init
self.offsetExtra = abs(obj.OffsetExtra.Value)
self.offsetExtra = obj.OffsetExtra.Value # abs(obj.OffsetExtra.Value)
if PathLog.getLevel(PathLog.thisModule()) == 4:
for grpNm in ['tmpDebugGrp', 'tmpDebugGrp001']:
if hasattr(FreeCAD.ActiveDocument, grpNm):
for go in FreeCAD.ActiveDocument.getObject(grpNm).Group:
FreeCAD.ActiveDocument.removeObject(go.Name)
FreeCAD.ActiveDocument.removeObject(grpNm)
self.tmpGrp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', 'tmpDebugGrp')
tmpGrpNm = self.tmpGrp.Name
self.JOB = PathUtils.findParentJob(obj)
@@ -321,8 +327,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
shapes.extend(self._processEdges(obj))
if obj.Base and len(obj.Base) > 0: # The user has selected subobjects from the base. Process each.
isFace = False
isEdge = False
if obj.EnableRotation != 'Off':
for p in range(0, len(obj.Base)):
(base, subsList) = obj.Base[p]
@@ -332,12 +336,11 @@ class ObjectProfile(PathAreaOp.ObjectOp):
if isinstance(shape, Part.Face):
tup = self._analyzeFace(obj, base, sub, shape, subCount)
allTuples.append(tup)
# Eif
# Efor
if subCount > 1:
msg = translate('PathProfile', "Multiple faces in Base Geometry.") + " "
msg += translate('PathProfile', "Depth settings will be applied to all faces.")
PathLog.warning(msg)
FreeCAD.Console.PrintWarning(msg)
(Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
subList = []
@@ -366,6 +369,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
for sub in subsList:
shape = getattr(base.Shape, sub)
# only process faces here
if isinstance(shape, Part.Face):
faces.append(shape)
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
@@ -382,8 +386,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Set initial Start and Final Depths and recalculate depthparams
finDep = obj.FinalDepth.Value
strDep = obj.StartDepth.Value
# if strDep > stock.Shape.BoundBox.ZMax:
# strDep = stock.Shape.BoundBox.ZMax
startDepths.append(strDep)
self.depthparams = self._customDepthParams(obj, strDep, finDep)
@@ -409,18 +411,17 @@ class ObjectProfile(PathAreaOp.ObjectOp):
finDep = profileshape.BoundBox.ZMin
envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope
try:
# env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams)
env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams)
except Exception as ee: # pylint: disable=broad-except
# PathUtils.getEnvelope() failed to return an object.
PathLog.error(translate('Path', 'Unable to create path for face(s).') + '\n{}'.format(ee))
msg = translate('Path', 'Unable to create path for face(s).')
PathLog.error(msg + '\n{}'.format(ee))
else:
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
elif obj.HandleMultipleFeatures == 'Individually':
for shape in faces:
# profShape = Part.makeCompound([shape])
finalDep = obj.FinalDepth.Value
custDepthparams = self.depthparams
if obj.Side == 'Inside':
@@ -429,7 +430,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
finalDep = shape.BoundBox.ZMin
custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep)
# env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
env = PathUtils.getEnvelope(shape, depthparams=custDepthparams)
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep
shapes.append(tup)
@@ -572,15 +572,15 @@ class ObjectProfile(PathAreaOp.ObjectOp):
f.Placement = newPlace
env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams)
# shapes.append((env, False))
tup = env, False, 'ProfileEdges', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
tup = env, False, 'Profile', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
PathLog.error(inaccessible)
PathLog.error(self.inaccessibleMsg)
else:
# Attempt open-edges profile
if self.JOB.GeometryTolerance.Value == 0.0:
msg = self.JOB.Label + '.GeometryTolerance = 0.0.'
msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.')
msg += translate('PathProfile', 'Please set to an acceptable value greater than zero.')
PathLog.error(msg)
else:
cutWireObjs = False
@@ -592,6 +592,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
os.Shape = flatWire
os.purgeTouched()
self.tmpGrp.addObject(os)
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
if cutShp:
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
@@ -603,9 +604,9 @@ class ObjectProfile(PathAreaOp.ObjectOp):
tup = cW, False, 'OpenEdge', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
PathLog.error(inaccessible)
PathLog.error(self.inaccessibleMsg)
else:
PathLog.error(inaccessible)
PathLog.error(self.inaccessibleMsg)
# Eif
# Eif
# Efor
@@ -664,9 +665,10 @@ class ObjectProfile(PathAreaOp.ObjectOp):
extLenFwd = sdv - fdv
if extLenFwd <= 0.0:
msg = translate('PathProfile',
'For open edges, select top edge and set Final Depth manually.')
'For open edges, verify Final Depth for this operation.')
FreeCAD.Console.PrintError(msg + '\n')
return False
# return False
extLenFwd = 0.1
WIRE = flatWire.Wires[0]
numEdges = len(WIRE.Edges)
@@ -686,10 +688,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
cntr = vectDist.multiply(0.5).add(pb)
R = diam / 2
pl = FreeCAD.Placement()
pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
pl.Base = FreeCAD.Vector(0, 0, 0)
# Obtain beginning point perpendicular points
if blen > 0.1:
bcp = begE.valueAt(begE.getParameterByLength(0.1)) # point returned 0.1 mm along edge
@@ -738,7 +736,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc])
topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth
if len(botFc) > 1:
PathLog.debug('len(botFc) > 1')
# PathLog.debug('len(botFc) > 1')
bndboxFace = Part.Face(extBndbox.Wires[0])
tmpFace = Part.Face(extBndbox.Wires[0])
for f in botFc:
@@ -840,9 +838,17 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Add path stops at ends of wire
cutShp = workShp.cut(pathStops)
if PathLog.getLevel(PathLog.thisModule()) == 4:
cs = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCutShape')
cs.Shape = cutShp
cs.recompute()
cs.purgeTouched()
self.tmpGrp.addObject(cs)
return cutShp
def _checkTagIntersection(self, iTAG, eTAG, cutSide, tstObj):
PathLog.debug('_checkTagIntersection()')
# Identify intersection of Common area and Interior Tags
intCmn = tstObj.common(iTAG)
@@ -880,10 +886,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
cent0 = FreeCAD.Vector(frstVrt.X, frstVrt.Y, fdv)
cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv)
pl = FreeCAD.Placement()
pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
pl.Base = FreeCAD.Vector(0, 0, 0)
# Calculate offset shape, containing cut region
ofstShp = self._extractFaceOffset(obj, cutShp, False)
@@ -891,7 +893,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
try:
osArea = ofstShp.Area
except Exception as ee:
PathLog.error('No area to offset shape returned.')
PathLog.error('No area to offset shape returned.\n{}'.format(ee))
return False
if PathLog.getLevel(PathLog.thisModule()) == 4:
@@ -941,7 +943,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
if w0 != w1:
PathLog.warning('Offset wire endpoint indexes are not equal - w0, w1: {}, {}'.format(w0, w1))
if PathLog.getLevel(PathLog.thisModule()) == 4:
if False and PathLog.getLevel(PathLog.thisModule()) == 4:
PathLog.debug('min0i is {}.'.format(min0i))
PathLog.debug('min1i is {}.'.format(min1i))
PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0]))
@@ -978,7 +980,11 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Eif
# Break offset loop into two wires - one of which is the desired profile path wire.
(edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1])
try:
(edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1])
except Exception as ee:
PathLog.error('Failed to identify offset edge.\n{}'.format(ee))
return False
edgs0 = list()
edgs1 = list()
for e in edgeIdxs0:
@@ -1105,7 +1111,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
chk4 = True
FLGS[e] += v
# Efor
PathLog.debug('_separateWireAtVertexes() FLGS: \n{}'.format(FLGS))
# PathLog.debug('_separateWireAtVertexes() FLGS: {}'.format(FLGS))
PRE = list()
POST = list()
@@ -1152,7 +1159,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
for e in range(0, lenFULL):
f = PRE[e]
if f == 1:
if begFlg is False:
if not begFlg:
begFlg = True
else:
begIdx = e
@@ -1184,7 +1191,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Efor
# Eif
if PathLog.getLevel(PathLog.thisModule()) != 4:
if False and PathLog.getLevel(PathLog.thisModule()) == 4:
PathLog.debug('grps[0]: {}'.format(grps[0]))
PathLog.debug('grps[1]: {}'.format(grps[1]))
PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0]))
@@ -1198,6 +1205,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
'''_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()')
# Create cross-section of shape and translate
wires = list()
slcs = shape.slice(FreeCAD.Vector(0, 0, 1), sliceZ)
@@ -1212,6 +1220,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return False
def _makeExtendedBoundBox(self, wBB, bbBfr, zDep):
PathLog.debug('_makeExtendedBoundBox()')
p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep)
p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep)
p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep)
@@ -1225,6 +1234,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return Part.Face(Part.Wire([L1, L2, L3, L4]))
def _makeIntersectionTags(self, useWire, numOrigEdges, fdv):
PathLog.debug('_makeIntersectionTags()')
# Create circular probe tags around perimiter of wire
extTags = list()
intTags = list()
@@ -1270,6 +1280,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return (begInt, begExt, iTAG, eTAG)
def _makeOffsetCircleTag(self, p1, p2, cutterRad, depth, lbl, reverse=False):
# PathLog.debug('_makeOffsetCircleTag()')
pb = FreeCAD.Vector(p1.x, p1.y, 0.0)
pe = FreeCAD.Vector(p2.x, p2.y, 0.0)
@@ -1283,8 +1294,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
perpE = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(-1 * cutFactor) # exterior tag
extPnt = pb.add(toMid.add(perpE))
pl = FreeCAD.Placement()
pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
# make exterior tag
eCntr = extPnt.add(FreeCAD.Vector(0, 0, depth))
ecw = Part.Wire(Part.makeCircle((cutterRad / 2), eCntr).Edges[0])
@@ -1300,20 +1309,17 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return (intTag, extTag)
def _makeStop(self, sType, pA, pB, lbl):
# PathLog.debug('_makeStop()')
rad = self.radius
ofstRad = self.ofstRadius
extra = self.radius / 10
pl = FreeCAD.Placement()
pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
pl.Base = FreeCAD.Vector(0, 0, 0)
E = FreeCAD.Vector(pB.x, pB.y, 0) # endpoint
C = FreeCAD.Vector(pA.x, pA.y, 0) # checkpoint
lenEC = E.sub(C).Length
if self.useComp is True or (self.useComp is False and self.offsetExtra != 0):
# 'L' stop shape and edge legend
# 'L' stop shape and edge map
# --1--
# | |
# 2 6
@@ -1325,15 +1331,15 @@ class ObjectProfile(PathAreaOp.ObjectOp):
p1 = E
if sType == 'BEG':
p2 = self._makePerp2DVector(C, E, -0.25) # E1
p3 = self._makePerp2DVector(p1, p2, ofstRad + 1 + extra) # E2
p3 = self._makePerp2DVector(p1, p2, ofstRad + 1.0 + extra) # E2
p4 = self._makePerp2DVector(p2, p3, 0.25 + ofstRad + extra) # E3
p5 = self._makePerp2DVector(p3, p4, 1 + extra) # E4
p5 = self._makePerp2DVector(p3, p4, 1.0 + extra) # E4
p6 = self._makePerp2DVector(p4, p5, ofstRad + extra) # E5
elif sType == 'END':
p2 = self._makePerp2DVector(C, E, 0.25) # E1
p3 = self._makePerp2DVector(p1, p2, -1 * (ofstRad + 1 + extra)) # E2
p3 = self._makePerp2DVector(p1, p2, -1 * (ofstRad + 1.0 + extra)) # E2
p4 = self._makePerp2DVector(p2, p3, -1 * (0.25 + ofstRad + extra)) # E3
p5 = self._makePerp2DVector(p3, p4, -1 * (1 + extra)) # E4
p5 = self._makePerp2DVector(p3, p4, -1 * (1.0 + extra)) # E4
p6 = self._makePerp2DVector(p4, p5, -1 * (ofstRad + extra)) # E5
p7 = E # E6
L1 = Part.makeLine(p1, p2)
@@ -1344,7 +1350,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
L6 = Part.makeLine(p6, p7)
wire = Part.Wire([L1, L2, L3, L4, L5, L6])
else:
# 'L' stop shape and edge legend
# 'L' stop shape and edge map
# :
# |----2-------|
# 3 1