Merge branch 'FreeCAD:master' into patharray_multibase

This commit is contained in:
jimzim111
2021-05-24 21:04:37 -07:00
6 changed files with 229 additions and 110 deletions

View File

@@ -171,7 +171,7 @@ class ObjectJob:
obj.Stock.ViewObject.Visibility = False
def setupSetupSheet(self, obj):
if not hasattr(obj, 'SetupSheet'):
if not getattr(obj, 'SetupSheet', None):
obj.addProperty('App::PropertyLink', 'SetupSheet', 'Base', QtCore.QT_TRANSLATE_NOOP('PathJob', 'SetupSheet holding the settings for this job'))
obj.SetupSheet = PathSetupSheet.Create()
if obj.SetupSheet.ViewObject:
@@ -223,53 +223,58 @@ class ObjectJob:
PathLog.track(obj.Label, arg2)
doc = obj.Document
# the first to tear down are the ops, they depend on other resources
PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()])
while obj.Operations.Group:
op = obj.Operations.Group[0]
if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()):
PathUtil.clearExpressionEngine(op)
doc.removeObject(op.Name)
obj.Operations.Group = []
doc.removeObject(obj.Operations.Name)
obj.Operations = None
if getattr(obj, 'Operations', None):
# the first to tear down are the ops, they depend on other resources
PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()])
while obj.Operations.Group:
op = obj.Operations.Group[0]
if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()):
PathUtil.clearExpressionEngine(op)
doc.removeObject(op.Name)
obj.Operations.Group = []
doc.removeObject(obj.Operations.Name)
obj.Operations = None
# stock could depend on Model, so delete it first
if obj.Stock:
if getattr(obj, 'Stock', None):
PathLog.debug('taking down stock')
PathUtil.clearExpressionEngine(obj.Stock)
doc.removeObject(obj.Stock.Name)
obj.Stock = None
# base doesn't depend on anything inside job
for base in obj.Model.Group:
PathLog.debug("taking down base %s" % base.Label)
self.removeBase(obj, base, False)
obj.Model.Group = []
doc.removeObject(obj.Model.Name)
obj.Model = None
if getattr(obj, 'Model', None):
for base in obj.Model.Group:
PathLog.debug("taking down base %s" % base.Label)
self.removeBase(obj, base, False)
obj.Model.Group = []
doc.removeObject(obj.Model.Name)
obj.Model = None
# Tool controllers might refer to either legacy tool or toolbit
PathLog.debug('taking down tool controller')
for tc in obj.Tools.Group:
if hasattr(tc.Tool, "Proxy"):
PathUtil.clearExpressionEngine(tc.Tool)
doc.removeObject(tc.Tool.Name)
PathUtil.clearExpressionEngine(tc)
tc.Proxy.onDelete(tc)
doc.removeObject(tc.Name)
obj.Tools.Group = []
doc.removeObject(obj.Tools.Name)
obj.Tools = None
if getattr(obj, 'Tools', None):
PathLog.debug('taking down tool controller')
for tc in obj.Tools.Group:
if hasattr(tc.Tool, "Proxy"):
PathUtil.clearExpressionEngine(tc.Tool)
doc.removeObject(tc.Tool.Name)
PathUtil.clearExpressionEngine(tc)
tc.Proxy.onDelete(tc)
doc.removeObject(tc.Name)
obj.Tools.Group = []
doc.removeObject(obj.Tools.Name)
obj.Tools = None
# SetupSheet
PathUtil.clearExpressionEngine(obj.SetupSheet)
doc.removeObject(obj.SetupSheet.Name)
obj.SetupSheet = None
if getattr(obj, 'SetupSheet', None):
PathUtil.clearExpressionEngine(obj.SetupSheet)
doc.removeObject(obj.SetupSheet.Name)
obj.SetupSheet = None
return True
def fixupOperations(self, obj):
if obj.Operations.ViewObject:
if getattr(obj.Operations, 'ViewObject', None):
try:
obj.Operations.ViewObject.DisplayMode
except Exception: # pylint: disable=broad-except
@@ -409,7 +414,7 @@ class ObjectJob:
return None
def execute(self, obj):
if hasattr(obj, 'Operations'):
if getattr(obj, 'Operations', None):
obj.Path = obj.Operations.Path
self.getCycleTime()
@@ -457,6 +462,11 @@ class ObjectJob:
self.obj.Operations.Group = group
op.Path.Center = self.obj.Operations.Path.Center
def nextToolNumber(self):
# returns the next available toolnumber in the job
group = self.obj.Tools.Group
return sorted([t.ToolNumber for t in group])[-1] + 1
def addToolController(self, tc):
group = self.obj.Tools.Group
PathLog.debug("addToolController(%s): %s" % (tc.Label, [t.Label for t in group]))
@@ -479,8 +489,11 @@ class ObjectJob:
ops.append(op)
for sub in op.Group:
collectBaseOps(sub)
for op in self.obj.Operations.Group:
collectBaseOps(op)
if getattr(self.obj, 'Operations', None) and getattr(self.obj.Operations, 'Group', None):
for op in self.obj.Operations.Group:
collectBaseOps(op)
return ops
def setCenterOfRotation(self, center):

View File

@@ -27,11 +27,11 @@ import math
import traceback
from pivy import coin
from PySide import QtCore, QtGui
import json
import FreeCAD
import FreeCADGui
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathJob as PathJob
import PathScripts.PathJobCmd as PathJobCmd
import PathScripts.PathJobDlg as PathJobDlg
@@ -520,7 +520,7 @@ class StockFromExistingEdit(StockEdit):
stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex())
if not (hasattr(obj.Stock, 'Objects') and len(obj.Stock.Objects) == 1 and obj.Stock.Objects[0] == stock):
if stock:
stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix , 'Stock')
stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix, 'Stock')
stock.ViewObject.Visibility = True
PathStock.SetupStockObject(stock, PathStock.StockType.Unknown)
stock.Proxy.execute(stock)
@@ -546,7 +546,7 @@ class StockFromExistingEdit(StockEdit):
index = -1
for i, solid in enumerate(self.candidates(obj)):
self.form.stockExisting.addItem(solid.Label, solid)
label="{}-{}".format(self.StockLabelPrefix, solid.Label)
label = "{}-{}".format(self.StockLabelPrefix, solid.Label)
if label == stockName:
index = i
@@ -864,13 +864,33 @@ class TaskPanel:
self.toolControllerSelect()
def toolControllerAdd(self):
# adding a TC from a toolbit directly.
# Try to find a tool number from the currently selected lib. Otherwise
# use next available number
if PathPreferences.toolsUseLegacyTools():
PathToolLibraryEditor.CommandToolLibraryEdit().edit(self.obj, self.updateToolController)
else:
tools = PathToolBitGui.LoadTools()
curLib = PathPreferences.lastFileToolLibrary()
library = None
if curLib is not None:
with open(curLib) as fp:
library = json.load(fp)
for tool in tools:
tc = PathToolControllerGui.Create(name=tool.Label, tool=tool)
toolNum = self.obj.Proxy.nextToolNumber()
if library is not None:
for toolBit in library['tools']:
if toolBit['path'] == tool.File:
toolNum = toolBit['nr']
tc = PathToolControllerGui.Create(name=tool.Label, tool=tool, toolNumber=toolNum)
self.obj.Proxy.addToolController(tc)
FreeCAD.ActiveDocument.recompute()
self.updateToolController()

View File

@@ -139,6 +139,7 @@ class Extension(object):
self.sub = sub
self.length = length
self.direction = direction
self.extFaces = list()
self.wire = None
@@ -197,7 +198,22 @@ class Extension(object):
return self._getDirectedNormal(e0.valueAt(midparam), normal.normalize())
def getExtensionFaces(self, extensionWire):
'''getExtensionFace(extensionWire)...
A public helper method to retrieve the requested extension as a face,
rather than a wire becuase some extensions require a face shape
for definition that allows for two wires for boundary definition.
'''
if self.extFaces:
return self.extFaces
return [Part.Face(extensionWire)]
def getWire(self):
'''getWire()... Public method to retrieve the extension area, pertaining to the feature
and sub element provided at class instantiation, as a closed wire. If no closed wire
is possible, a `None` value is returned.'''
PathLog.track()
if PathGeom.isRoughly(0, self.length.Value) or not self.sub:
PathLog.debug("no extension, length=%.2f, sub=%s" % (self.length.Value, self.sub))
@@ -233,7 +249,9 @@ class Extension(object):
e2 = Part.makeLine(edge.valueAt(edge.LastParameter), e3.valueAt(e3.LastParameter))
return Part.Wire([e0, edge, e2, e3])
return Part.Wire([e3])
extWire = Part.Wire([e3])
self.extFaces = [self._makeCircularExtFace(edge, extWire)]
return extWire
# the extension is bigger than the hole - so let's just cover the whole hole
if endPoints(edge):
@@ -258,6 +276,25 @@ class Extension(object):
return extendWire(feature, sub, self.length.Value)
def _makeCircularExtFace(self, edge, extWire):
'''_makeCircularExtensionFace(edge, extWire)...
Create proper circular extension face shape. Incoming edge is expected to be a circle.
'''
# Add original outer wire to cut faces if necessary
edgeFace = Part.Face(Part.Wire([edge]))
edgeFace.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - edgeFace.BoundBox.ZMin))
extWireFace = Part.Face(extWire)
extWireFace.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - extWireFace.BoundBox.ZMin))
if extWireFace.Area >= edgeFace.Area:
extensionFace = extWireFace.cut(edgeFace)
else:
extensionFace = edgeFace.cut(extWireFace)
extensionFace.translate(FreeCAD.Vector(0.0, 0.0, edge.BoundBox.ZMin))
return extensionFace
# Eclass
class ObjectPocket(PathPocketBase.ObjectPocket):
'''Proxy object for Pocket operation.'''
@@ -385,9 +422,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
for ext in self.getExtensions(obj):
wire = ext.getWire()
if wire:
face = Part.Face(wire)
self.horiz.append(face)
self.exts.append(face)
for face in ext.getExtensionFaces(wire):
self.horiz.append(face)
self.exts.append(face)
# Place all self.horiz faces into same working plane
for h in self.horiz:

View File

@@ -75,6 +75,7 @@ class _Extension(object):
hnt = coin.SoShapeHints()
if not ext is None:
numVert = list() # track number of verticies in each polygon face
try:
wire = ext.getWire()
except FreeCAD.Base.FreeCADError:
@@ -86,8 +87,22 @@ class _Extension(object):
p2 = list(reversed(p1))
polygon = [(p.x, p.y, p.z) for p in (p0 + p2)]
else:
poly = [p for p in wire.discretize(Deflection=0.02)][:-1]
polygon = [(p.x, p.y, p.z) for p in poly]
if ext.extFaces:
# Create polygon for each extension face in compound extensions
allPolys = list()
extFaces = ext.getExtensionFaces(wire)
for f in extFaces:
pCnt = 0
for w in f.Wires:
poly = [p for p in w.discretize(Deflection=0.01)]
pCnt += len(poly)
allPolys.extend(poly)
numVert.append(pCnt)
polygon = [(p.x, p.y, p.z) for p in allPolys]
else:
# poly = [p for p in wire.discretize(Deflection=0.02)][:-1]
poly = [p for p in wire.discretize(Deflection=0.02)]
polygon = [(p.x, p.y, p.z) for p in poly]
crd.point.setValues(polygon)
else:
return None
@@ -98,6 +113,10 @@ class _Extension(object):
hnt.faceType = coin.SoShapeHints.UNKNOWN_FACE_TYPE
hnt.vertexOrdering = coin.SoShapeHints.CLOCKWISE
if numVert:
# Transfer vertex counts for polygon faces
fce.numVertices.setValues(tuple(numVert))
sep.addChild(pos)
sep.addChild(mat)
sep.addChild(hnt)
@@ -222,7 +241,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
if obj.ExtensionCorners != self.form.extendCorners.isChecked():
self.form.extendCorners.toggle()
self.defaultLength.updateSpinBox()
self.updateQuantitySpinBoxes()
self.extensions = obj.Proxy.getExtensions(obj) # pylint: disable=attribute-defined-outside-init
self.setExtensions(self.extensions)
@@ -341,11 +360,15 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.form.extensionTree.blockSignals(False)
def updateQuantitySpinBoxes(self, index = None):
self.defaultLength.updateSpinBox()
def updateData(self, obj, prop):
PathLog.track(obj.Label, prop, self.blockUpdateData)
if not self.blockUpdateData:
if prop in ['Base', 'ExtensionLengthDefault']:
self.setExtensions(obj.Proxy.getExtensions(obj))
self.updateQuantitySpinBoxes()
def restoreSelection(self, selection):
PathLog.track()
@@ -458,6 +481,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.form.buttonClear.clicked.connect(self.extensionsClear)
self.form.buttonDisable.clicked.connect(self.extensionsDisable)
self.form.buttonEnable.clicked.connect(self.extensionsEnable)
self.form.defaultLength.editingFinished.connect(self.updateQuantitySpinBoxes)
self.model.itemChanged.connect(self.updateItemEnabled)

View File

@@ -41,6 +41,7 @@ import math
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader('Part', globals(), 'Part')
Arcs = LazyLoader('draftgeoutils.arcs', globals(), 'draftgeoutils.arcs')
PathGeom = LazyLoader('PathScripts.PathGeom', globals(), 'PathScripts.PathGeom')
if FreeCAD.GuiUp:
FreeCADGui = LazyLoader('FreeCADGui', globals(), 'FreeCADGui')
@@ -159,7 +160,7 @@ class ObjectSlot(PathOp.ObjectOp):
'CustomPoint1': FreeCAD.Vector(0.0, 0.0, 0.0),
'ExtendPathStart': 0.0,
'Reference1': 'Center of Mass',
'CustomPoint2': FreeCAD.Vector(10.0, 10.0, 0.0),
'CustomPoint2': FreeCAD.Vector(0.0, 0.0, 0.0),
'ExtendPathEnd': 0.0,
'Reference2': 'Center of Mass',
'LayerMode': 'Multi-pass',
@@ -353,6 +354,7 @@ class ObjectSlot(PathOp.ObjectOp):
self.arcMidPnt = None
self.arcRadius = 0.0
self.newRadius = 0.0
self.featureDetails = ["", ""]
self.isDebug = False if PathLog.getLevel(PathLog.thisModule()) != 4 else True
self.showDebugObjects = False
self.stockZMin = self.job.Stock.Shape.BoundBox.ZMin
@@ -433,35 +435,41 @@ class ObjectSlot(PathOp.ObjectOp):
# Use custom inputs here
p1 = obj.CustomPoint1
p2 = obj.CustomPoint2
if p1.z == p2.z:
if p1 == p2:
msg = translate('PathSlot',
'Custom points are identical.')
FreeCAD.Console.PrintError(msg + '\n')
return False
elif p1.z == p2.z:
pnts = (p1, p2)
featureCount = 2
else:
msg = translate('PathSlot',
'Custom points not at same Z height.')
FreeCAD.Console.PrintError(msg + '\n')
return False
baseGeom = obj.Base[0]
base, subsList = baseGeom
self.base = base
featureCount = len(subsList)
if featureCount == 1:
PathLog.debug('Reference 1: {}'.format(obj.Reference1))
sub1 = subsList[0]
shape_1 = getattr(base.Shape, sub1)
self.shape1 = shape_1
pnts = self._processSingle(obj, shape_1, sub1)
else:
PathLog.debug('Reference 1: {}'.format(obj.Reference1))
PathLog.debug('Reference 2: {}'.format(obj.Reference2))
sub1 = subsList[0]
sub2 = subsList[1]
shape_1 = getattr(base.Shape, sub1)
shape_2 = getattr(base.Shape, sub2)
self.shape1 = shape_1
self.shape2 = shape_2
pnts = self._processDouble(obj, shape_1, sub1, shape_2, sub2)
baseGeom = obj.Base[0]
base, subsList = baseGeom
self.base = base
featureCount = len(subsList)
if featureCount == 1:
PathLog.debug('Reference 1: {}'.format(obj.Reference1))
sub1 = subsList[0]
shape_1 = getattr(base.Shape, sub1)
self.shape1 = shape_1
pnts = self._processSingle(obj, shape_1, sub1)
else:
PathLog.debug('Reference 1: {}'.format(obj.Reference1))
PathLog.debug('Reference 2: {}'.format(obj.Reference2))
sub1 = subsList[0]
sub2 = subsList[1]
shape_1 = getattr(base.Shape, sub1)
shape_2 = getattr(base.Shape, sub2)
self.shape1 = shape_1
self.shape2 = shape_2
pnts = self._processDouble(obj, shape_1, sub1, shape_2, sub2)
if not pnts:
return False
@@ -608,21 +616,26 @@ class ObjectSlot(PathOp.ObjectOp):
PathLog.debug('_finishLine() Perp, featureCnt == 2')
if perpZero:
(p1, p2) = pnts
pnts = self._makePerpendicular(p1, p2, 10.0) # 10.0 offset below
initPerpDist = p1.sub(p2).Length
pnts = self._makePerpendicular(p1, p2, initPerpDist) # 10.0 offset below
else:
# Modify path points if user selected two parallel edges
if (featureCnt == 2 and self.shapeType1 == 'Edge' and
self.shapeType2 == 'Edge' and self._isParallel(self.dYdX1, self.dYdX2)):
(p1, p2) = pnts
edg1_len = self.shape1.Length
edg2_len = self.shape2.Length
set_length = max(edg1_len, edg2_len)
pnts = self._makePerpendicular(p1, p2, 10.0 + set_length) # 10.0 offset below
if edg1_len != edg2_len:
msg = obj.Label + ' '
msg += translate('PathSlot',
'Verify slot path start and end points.')
FreeCAD.Console.PrintWarning(msg + '\n')
self.shapeType2 == 'Edge'):
if self.featureDetails[0] == "arc" and self.featureDetails[1] == "arc":
perpZero = False
elif self._isParallel(self.dYdX1, self.dYdX2):
PathLog.debug('_finishLine() StE, featureCnt == 2 // edges')
(p1, p2) = pnts
edg1_len = self.shape1.Length
edg2_len = self.shape2.Length
set_length = max(edg1_len, edg2_len)
pnts = self._makePerpendicular(p1, p2, 10.0 + set_length) # 10.0 offset below
if edg1_len != edg2_len:
msg = obj.Label + ' '
msg += translate('PathSlot',
'Verify slot path start and end points.')
FreeCAD.Console.PrintWarning(msg + '\n')
else:
perpZero = False
@@ -712,21 +725,30 @@ class ObjectSlot(PathOp.ObjectOp):
pnts = False
norm = shape_1.normalAt(0.0, 0.0)
PathLog.debug('{}.normalAt(): {}'.format(sub1, norm))
if norm.z == 1 or norm.z == -1:
pnts = self._processSingleHorizFace(obj, shape_1)
elif norm.z == 0:
faceType = self._getVertFaceType(shape_1)
if faceType:
(geo, shp) = faceType
if geo == 'Face':
pnts = self._processSingleComplexFace(obj, shp)
if geo == 'Wire':
pnts = self._processSingleVertFace(obj, shp)
if geo == 'Edge':
pnts = self._processSingleVertFace(obj, shp)
if PathGeom.isRoughly(shape_1.BoundBox.ZMax, shape_1.BoundBox.ZMin):
# Horizontal face
if norm.z == 1 or norm.z == -1:
pnts = self._processSingleHorizFace(obj, shape_1)
elif norm.z == 0:
faceType = self._getVertFaceType(shape_1)
if faceType:
(geo, shp) = faceType
if geo == 'Face':
pnts = self._processSingleComplexFace(obj, shp)
if geo == 'Wire':
pnts = self._processSingleVertFace(obj, shp)
if geo == 'Edge':
pnts = self._processSingleVertFace(obj, shp)
else:
if len(shape_1.Edges) == 4:
pnts = self._processSingleHorizFace(obj, shape_1)
else:
pnts = self._processSingleComplexFace(obj, shape_1)
if not pnts:
msg = translate('PathSlot',
'The selected face is not oriented horizontally or vertically.')
'The selected face is inaccessible.')
FreeCAD.Console.PrintError(msg + '\n')
return False
@@ -862,16 +884,16 @@ class ObjectSlot(PathOp.ObjectOp):
def _processSingleComplexFace(self, obj, shape):
"""Determine slot path endpoints from a single complex face."""
PathLog.debug('_processSingleComplexFace()')
PNTS = list()
pnts = list()
def zVal(V):
return V.z
def zVal(p):
return p.z
for E in shape.Wires[0].Edges:
p = self._findLowestEdgePoint(E)
PNTS.append(p)
PNTS.sort(key=zVal)
return (PNTS[0], PNTS[1])
pnts.append(p)
pnts.sort(key=zVal)
return (pnts[0], pnts[1])
def _processSingleVertFace(self, obj, shape):
"""Determine slot path endpoints from a single vertically oriented face
@@ -925,7 +947,7 @@ class ObjectSlot(PathOp.ObjectOp):
# Check that all Z values are equal (isRoughly same)
if (abs(z1 - z2) > tolrnc or
abs(z1 - z3) > tolrnc ):
# abs(z2 - z3) > tolrnc): 3rd test redundant.
# abs(z2 - z3) > tolrnc): 3rd test redundant.
return False
return True
@@ -1182,6 +1204,9 @@ class ObjectSlot(PathOp.ObjectOp):
p = self._getHighestPoint(shape)
elif cat == 'Edge':
featDetIdx = pNum - 1
if shape.Curve.TypeId == 'Part::GeomCircle':
self.featureDetails[featDetIdx] = "arc"
# calculate slope between end vertexes
v0 = shape.Edges[0].Vertexes[0]
v1 = shape.Edges[0].Vertexes[1]
@@ -1309,13 +1334,13 @@ class ObjectSlot(PathOp.ObjectOp):
def _isParallel(self, dYdX1, dYdX2):
"""Determine if two orientation vectors are parallel."""
# if dYdX1.add(dYdX2).Length == 0:
# return True
# if ((dYdX1.x + dYdX2.x) / 2.0 == dYdX1.x and
# (dYdX1.y + dYdX2.y) / 2.0 == dYdX1.y):
# return True
# return False
return (dYdX1.cross(dYdX2) == FreeCAD.Vector(0,0,0) )
# if dYdX1.add(dYdX2).Length == 0:
# return True
# if ((dYdX1.x + dYdX2.x) / 2.0 == dYdX1.x and
# (dYdX1.y + dYdX2.y) / 2.0 == dYdX1.y):
# return True
# return False
def _makePerpendicular(self, p1, p2, length):
"""_makePerpendicular(p1, p2, length)...
@@ -1367,7 +1392,7 @@ class ObjectSlot(PathOp.ObjectOp):
def _findLowestEdgePoint(self, E):
zMin = E.BoundBox.ZMin
eLen = E.Length
L0 = 0
L0 = 0.0
L1 = eLen
p0 = None
p1 = None

View File

@@ -186,7 +186,7 @@ class _Geometry(object):
def _calculate_depth(MIC, geom):
# given a maximum inscribed circle (MIC) and tool angle,
# return depth of cut relative to zStart.
depth = geom.start - round(MIC / geom.scale, 4)
depth = geom.start - round(MIC * geom.scale, 4)
PathLog.debug('zStart value: {} depth: {}'.format(geom.start, depth))
return max(depth, geom.stop)