From 0367c28fbcdda2a914c3f20b4f9afa2564b7219d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 28 Dec 2018 03:42:47 -0800 Subject: [PATCH] Added support for global extend corners --- .../Resources/panels/PageOpPocketExtEdit.ui | 20 ++-- src/Mod/Path/PathScripts/PathPocketShape.py | 100 ++++++++++-------- .../Path/PathScripts/PathPocketShapeGui.py | 100 +++++++++++++----- 3 files changed, 141 insertions(+), 79 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpPocketExtEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpPocketExtEdit.ui index 828bbb3e91..8787ec7dae 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpPocketExtEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpPocketExtEdit.ui @@ -22,10 +22,7 @@ - - - QFormLayout::AllNonFixedFieldsGrow - + @@ -33,6 +30,16 @@ + + + + Extend Corners + + + true + + + @@ -41,16 +48,13 @@ 999999999.000000000000000 - - 1.000000000000000 - - + QAbstractItemView::NoEditTriggers diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index f1dc0b5ba9..51da1ca4b2 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -72,27 +72,25 @@ def includesPoint(p, pts): return True return False -def selectOffsetWire(wire, wires, offset): - '''selectOffsetWire(wire, wires, offset) ... returns the Wire in wires which most likely is wire offset by offset''' - startPoint = endPoints(wire)[0] + offset; +def selectOffsetWire(feature, wires): + '''selectOffsetWire(feature, wires) ... returns the Wire in wires which is does not intersect with feature''' closest = None for w in wires: - for ep in endPoints(w): - dist = (startPoint - ep).Length - if closest is None or dist < closest[0]: - closest = (dist, w) + dist = feature.distToShape(w)[0] + if closest is None or dist > closest[0]: + closest = (dist, w) if not closest is None: return closest[1] return None -def extendWire(wire, length, direction): - '''extendWire(wire, length, direction) ... return a closed Wire which extends wire by length into direction''' +def extendWire(feature, wire, length): + '''extendWire(wire, length) ... return a closed Wire which extends wire by length''' off2D = wire.makeOffset2D(length) endPts = endPoints(wire) edges = [e for e in off2D.Edges if Part.Circle != type(e.Curve) or not includesPoint(e.Curve.Center, endPts)] wires = [Part.Wire(e) for e in Part.sortEdges(edges)] - offset = selectOffsetWire(wire, wires, direction * length) + offset = selectOffsetWire(feature, wires) ePts = endPoints(offset) l0 = (ePts[0] - endPts[0]).Length l1 = (ePts[1] - endPts[0]).Length @@ -112,13 +110,17 @@ class Extension(object): DirectionX = 1 DirectionY = 2 - def __init__(self, obj, sub, length, direction): + def __init__(self, obj, feature, sub, length, direction): self.obj = obj + self.feature = feature self.sub = sub self.length = length self.direction = direction - def extendEdge(self, e0, direction): + def getSubLink(self): + return "%s:%s" % (self.feature, self.sub) + + def extendEdge(self, feature, e0, direction): if Part.Line == type(e0.Curve) or Part.LineSegment == type(e0.Curve): e2 = e0.copy() off = self.length.Value * direction @@ -129,26 +131,46 @@ class Extension(object): wire = Part.Wire([e0, e1, e2, e3]) self.wire = wire return wire - return extendWire(Part.Wire([e0]), self.length.Value, direction) + return extendWire(feature, Part.Wire([e0]), self.length.Value) + + def getEdgeNumbers(self): + if 'Wire' in self.sub: + return [nr for nr in self.sub[5:-1].split(',')] + return [self.sub[4:]] + + def getEdgeNames(self): + return ["Edge%s" % nr for nr in self.getEdgeNumbers()] + + def getEdges(self): + return [self.obj.Shape.getElement(sub) for sub in self.getEdgeNames()] + + def getDirection(self, wire): + e0 = wire.Edges[0] + midparam = e0.FirstParameter + 0.5 * (e0.LastParameter - e0.FirstParameter) + tangent = e0.tangentAt(midparam) + normal = tangent.cross(FreeCAD.Vector(0, 0, 1)).normalize() + poffPlus = e0.valueAt(midparam) + 0.01 * normal + poffMinus = e0.valueAt(midparam) - 0.01 * normal + if not self.obj.Shape.isInside(poffPlus, 0.005, True): + return normal + if not self.obj.Shape.isInside(poffMinus, 0.005, True): + return normal.negative() + return None def getWire(self): - if PathGeom.isRoughly(0, self.length.Value): + if PathGeom.isRoughly(0, self.length.Value) or not self.sub: return None - feature = self.obj.Shape.getElement(self.sub) - if Part.Edge == type(feature): - e0 = feature - midparam = e0.FirstParameter + 0.5 * (e0.LastParameter - e0.FirstParameter) - tangent = e0.tangentAt(midparam) - normal = tangent.cross(FreeCAD.Vector(0, 0, 1)).normalize() - poffPlus = e0.valueAt(midparam) + 0.01 * normal - poffMinus = e0.valueAt(midparam) - 0.01 * normal - if not self.obj.Shape.isInside(poffPlus, 0.005, True): - return self.extendEdge(e0, normal) - if not self.obj.Shape.isInside(poffMinus, 0.005, True): - return self.extendEdge(e0, normal.negative()) - else: - PathLog.warning("getWire does not support %s" % type(feature)) + feature = self.obj.Shape.getElement(self.feature) + edges = self.getEdges() + sub = Part.Wire(edges) + + if 1 == len(edges): + direction = self.getDirection(sub) + if direction is None: + return None + return self.extendEdge(feature, edges[0], direction) + return extendWire(feature, sub, self.length.Value) class ObjectPocket(PathPocketBase.ObjectPocket): @@ -166,14 +188,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): obj.addProperty('App::PropertyDistance', 'ExtensionLengthDefault', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Default length of extensions.')) if not hasattr(obj, 'ExtensionFeature'): obj.addProperty('App::PropertyLinkSubListGlobal', 'ExtensionFeature', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'List of features to extend.')) - if not hasattr(obj, 'ExtensionLength'): - obj.addProperty('App::PropertyFloatList', 'ExtensionLength', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'List of extension lenght of corresponding feature.')) - if not hasattr(obj, 'ExtensionDirection'): - obj.addProperty('App::PropertyIntegerList', 'ExtensionDirection', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'List of extension direction of corresponding feature.')) obj.setEditorMode('ExtensionFeature', 2) - obj.setEditorMode('ExtensionLength', 2) - obj.setEditorMode('ExtensionDirection', 2) def opOnDocumentRestored(self, obj): '''opOnDocumentRestored(obj) ... adds the UseOutline property if it doesn't exist.''' @@ -285,26 +301,22 @@ class ObjectPocket(PathPocketBase.ObjectPocket): obj.OpStartDepth = bb.ZMax obj.ExtensionLengthDefault = obj.OpToolDiameter / 2 - def createExtension(self, obj, extObj, extSub): - return Extension(extObj, extSub, obj.ExtensionLengthDefault, Extension.DirectionNormal) + def createExtension(self, obj, extObj, extFeature, extSub): + return Extension(extObj, extFeature, extSub, obj.ExtensionLengthDefault, Extension.DirectionNormal) def getExtensions(self, obj): extensions = [] i = 0 for extObj,features in obj.ExtensionFeature: - for extSub in features: - extensions.append(self.createExtension(obj, extObj, extSub)) + for sub in features: + extFeature, extSub = sub.split(':') + extensions.append(self.createExtension(obj, extObj, extFeature, extSub)) i = i + 1 return extensions def setExtensions(self, obj, extensions): PathLog.track(obj.Label, len(extensions)) - features = {} - for ext in extensions: - subs = features.get(ext.obj, []) - subs.append(ext.sub) - features[ext.obj] = subs - obj.ExtensionFeature = [(ext.obj, ext.sub) for ext in extensions] + obj.ExtensionFeature = [(ext.obj, ext.getSubLink()) for ext in extensions] def SetupProperties(): return PathPocketBase.SetupProperties() + [ 'UseOutline' ] diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index 5cb6457e09..40103e0c04 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -24,6 +24,8 @@ import FreeCAD import FreeCADGui +import Part +import PathScripts.PathGeom as PathGeom import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp @@ -55,13 +57,13 @@ def createExtensionSoSwitch(ext): mat = coin.SoMaterial() crd = coin.SoCoordinate3() fce = coin.SoFaceSet() + hnt = coin.SoShapeHints() if not ext is None: wire = ext.getWire() if wire: - polygon = [] - for p in wire.discretize(Deflection=0.01): - polygon.append((p.x, p.y, p.z)) + poly = [p for p in wire.discretize(Deflection=0.01)][:-1] + polygon = [(p.x, p.y, p.z) for p in poly] crd.point.setValues(polygon) else: return None @@ -69,8 +71,12 @@ def createExtensionSoSwitch(ext): mat.diffuseColor = (1.0, 0.0, 0.0) mat.transparency = 0.5 + hnt.faceType = coin.SoShapeHints.UNKNOWN_FACE_TYPE + hnt.vertexOrdering = coin.SoShapeHints.CLOCKWISE + sep.addChild(pos) sep.addChild(mat) + sep.addChild(hnt) sep.addChild(crd) sep.addChild(fce) @@ -82,19 +88,30 @@ def createExtensionSoSwitch(ext): class _Extension(object): def __init__(self, obj, base, face, edge): + self.obj = obj self.base = base self.face = face self.edge = edge if edge is None: self.ext = None else: - self.ext = obj.Proxy.createExtension(obj, base, edge) + self.ext = obj.Proxy.createExtension(obj, base, face, edge) self.switch = createExtensionSoSwitch(self.ext) self.root = self.switch def isValid(self): return not self.root is None + def getEdgeNumbers(self): + return self.ext.getEdgeNumbers() + + def getEdges(self): + return self.ext.getEdges() + + def isWire(self): + return 1 == len(self.getEdgeNumbers()) + + Page = None class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): @@ -113,14 +130,14 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.defaultLength = PathGui.QuantitySpinBox(self.form.defaultLength, obj, 'ExtensionLengthDefault') - self.form.extensions.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.form.extensions.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.form.extensionTree.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + self.form.extensionTree.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.switch = coin.SoSwitch() self.obj.ViewObject.RootNode.addChild(self.switch) self.switch.whichChild = coin.SO_SWITCH_ALL - self.model = QtGui.QStandardItemModel(self.form.extensions) + self.model = QtGui.QStandardItemModel(self.form.extensionTree) self.model.setHorizontalHeaderLabels(['Base', 'Extension']) global Page @@ -143,7 +160,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): def getFields(self, obj): PathLog.track(obj.Label, self.model.rowCount(), self.model.columnCount()) + self.defaultLength.updateProperty() + extensions = [] def extractExtension(item): @@ -158,7 +177,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): def setFields(self, obj): PathLog.track(obj.Label) + self.defaultLength.updateSpinBox() + self.extensions = obj.Proxy.getExtensions(obj) self.setExtensions(self.extensions) def createItemForBaseModel(self, base, sub, edges, extensions): @@ -169,28 +190,50 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): item.setData(ext, self.DataObject) item.setSelectable(False) + extendCorners = self.form.extendCorners.isChecked() + + def createSubItem(label, ext0): + self.switch.addChild(ext0.root) + item0 = QtGui.QStandardItem() + item0.setData(label, QtCore.Qt.EditRole) + item0.setData(ext0, self.DataObject) + item0.setCheckable(True) + for e in extensions: + if e.obj == base and e.sub == label: + item0.setCheckState(QtCore.Qt.Checked) + break + item.appendRow([item0]) + + extensionEdges = {} for edge in base.Shape.getElement(sub).Edges: for (e, label) in edges: if edge.isSame(e): ext0 = _Extension(self.obj, base, sub, label) if ext0.isValid(): - self.switch.addChild(ext0.root) - item0 = QtGui.QStandardItem() - item0.setData(label, QtCore.Qt.EditRole) - item0.setData(ext0, self.DataObject) - item0.setCheckable(True) - for e in extensions: - if e.obj == base and e.sub == label: - item0.setCheckState(QtCore.Qt.Checked) - break - item.appendRow([item0]) + extensionEdges[e] = label[4:] + if not extendCorners: + createSubItem(label, ext0) break + if extendCorners: + def edgesMatchShape(e0, e1): + return PathGeom.edgesMatch(e0, e1) or PathGeom.edgesMatch(e0, PathGeom.flipEdge(e1)) + + self.extensionEdges = extensionEdges + for edgeList in Part.sortEdges(extensionEdges.keys()): + self.edgeList = edgeList + if len(edgeList) == 1: + label = "Edge%s" % [extensionEdges[keyEdge] for keyEdge in extensionEdges.keys() if edgesMatchShape(keyEdge, edgeList[0])][0] + else: + label = "Wire(%s)" % ','.join(sorted([extensionEdges[keyEdge] for e in edgeList for keyEdge in extensionEdges.keys() if edgesMatchShape(e, keyEdge)], key=lambda s: int(s))) + ext0 = _Extension(self.obj, base, sub, label) + createSubItem(label, ext0) + return item def setExtensions(self, extensions): PathLog.track(len(extensions)) - self.form.extensions.blockSignals(True) + self.form.extensionTree.blockSignals(True) # remember current visual state if hasattr(self, 'selectionModel'): @@ -202,11 +245,11 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): for modelRow in range(self.model.rowCount()): model = self.model.item(modelRow, 0) modelName = model.data(QtCore.Qt.EditRole) - if not self.form.extensions.isExpanded(model.index()): + if not self.form.extensionTree.isExpanded(model.index()): collapsedModels.append(modelName) for featureRow in range(model.rowCount()): feature = model.child(featureRow, 0); - if not self.form.extensions.isExpanded(feature.index()): + if not self.form.extensionTree.isExpanded(feature.index()): collapsedFeatures.append("%s.%s" % (modelName, feature.data(QtCore.Qt.EditRole))) # remove current extensions and all their visuals @@ -227,21 +270,21 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): baseItem.appendRow(self.createItemForBaseModel(base[0], sub, edges, extensions)) self.model.appendRow(baseItem) - self.form.extensions.setModel(self.model) - self.form.extensions.expandAll() - self.form.extensions.resizeColumnToContents(0) + self.form.extensionTree.setModel(self.model) + self.form.extensionTree.expandAll() + self.form.extensionTree.resizeColumnToContents(0) # restore previous state - at least the parts that are still valid for modelRow in range(self.model.rowCount()): model = self.model.item(modelRow, 0) modelName = model.data(QtCore.Qt.EditRole) if modelName in collapsedModels: - self.form.extensions.setExpanded(model.index(), False) + self.form.extensionTree.setExpanded(model.index(), False) for featureRow in range(model.rowCount()): feature = model.child(featureRow, 0); featureName = "%s.%s" % (modelName, feature.data(QtCore.Qt.EditRole)) if featureName in collapsedFeatures: - self.form.extensions.setExpanded(feature.index(), False) + self.form.extensionTree.setExpanded(feature.index(), False) if hasattr(self, 'selectionModel') and selectedExtensions: self.restoreSelection(selectedExtensions) @@ -309,16 +352,19 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): def getSignalsForUpdate(self, obj): PathLog.track(obj.Label) - return [self.form.defaultLength.editingFinished] + signals = [] + signals.append(self.form.defaultLength.editingFinished) + return signals def registerSignalHandlers(self, obj): + self.form.extendCorners.clicked.connect(lambda : self.setExtensions(obj.Proxy.getExtensions(obj))) self.form.buttonClear.clicked.connect(self.extensionsClear) self.form.buttonDisable.clicked.connect(self.extensionsDisable) self.form.buttonEnable.clicked.connect(self.extensionsEnable) self.model.itemChanged.connect(lambda x: self.setDirty()) - self.selectionModel = self.form.extensions.selectionModel() + self.selectionModel = self.form.extensionTree.selectionModel() self.selectionModel.selectionChanged.connect(self.selectionChanged) class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage):