Added support for global extend corners
This commit is contained in:
@@ -22,10 +22,7 @@
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -33,6 +30,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="extendCorners">
|
||||
<property name="text">
|
||||
<string>Extend Corners</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="defaultLength">
|
||||
<property name="minimum">
|
||||
@@ -41,16 +48,13 @@
|
||||
<property name="maximum">
|
||||
<double>999999999.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="extensions">
|
||||
<widget class="QTreeView" name="extensionTree">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
|
||||
@@ -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' ]
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user