From e6c968cb7947cf7aebcc8338f5bd7582e18b8021 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 3 Jan 2017 18:01:16 -0800 Subject: [PATCH 01/14] Basic tag visualization and selection. --- .../PathScripts/PathDressupHoldingTags.py | 128 +++++++++++++++--- 1 file changed, 108 insertions(+), 20 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index d05292806..ef04ef5eb 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -36,6 +36,7 @@ from DraftGui import todo from PathScripts import PathUtils from PathScripts.PathGeom import * from PySide import QtCore, QtGui +from pivy import coin """Holding Tags Dressup object and FreeCAD command""" @@ -753,9 +754,10 @@ class TaskPanel: DataX = QtCore.Qt.ItemDataRole.UserRole DataY = QtCore.Qt.ItemDataRole.UserRole + 1 - def __init__(self, obj, jvoVisibility=None): + def __init__(self, obj, viewProvider, jvoVisibility=None): self.obj = obj self.obj.Proxy.obj = obj + self.viewProvider = viewProvider self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") self.jvo = PathUtils.findParentJob(obj).ViewObject if jvoVisibility is None: @@ -779,6 +781,7 @@ class TaskPanel: FreeCAD.ActiveDocument.recompute() def cleanup(self): + self.viewProvider.clearTaskPanel() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() @@ -790,7 +793,7 @@ class TaskPanel: print("closed") def open(self): - self.s = SelObserver() + self.s = SelObserver(self.viewProvider) # install the function mode resident FreeCADGui.Selection.addObserver(self.s) @@ -836,6 +839,7 @@ class TaskPanel: item.setFlags(flags) self.form.lwTags.addItem(item) self.form.lwTags.blockSignals(False) + self.whenTagSelectionChanged() def cleanupUI(self): print("cleanupUI") @@ -868,10 +872,14 @@ class TaskPanel: count = self.form.sbCount.value() self.form.pbGenerate.setEnabled(count) + def selectTagWithId(self, index): + self.form.lwTags.setCurrentRow(index) + def whenTagSelectionChanged(self): print('whenTagSelectionChanged') - item = self.form.lwTags.currentItem() - self.form.pbDelete.setEnabled(not item is None) + index = self.form.lwTags.currentRow() + self.form.pbDelete.setEnabled(index != -1) + self.viewProvider.selectTag(index) def deleteSelectedTag(self): self.obj.Proxy.setXyEnabled(self.getTags(False)) @@ -883,10 +891,8 @@ class TaskPanel: tags = self.tags tags.append((point.x, point.y, True)) self.obj.Proxy.setXyEnabled(tags) - panel = TaskPanel(self.obj, self.jvoVisible) - todo.delay(FreeCADGui.Control.closeDialog, None) - todo.delay(FreeCADGui.Control.showDialog, panel) - todo.delay(panel.setupUi, None) + panel = TaskPanel(self.obj, self.viewProvider, self.jvoVisible) + todo.delay(self.viewProvider.setupTaskPanel, panel) def addNewTag(self): self.tags = self.getTags(True) @@ -938,47 +944,94 @@ class TaskPanel: self.form.pbDelete.clicked.connect(self.deleteSelectedTag) self.form.pbAdd.clicked.connect(self.addNewTag) + self.viewProvider.turnMarkerDisplayOn(True) class SelObserver: - def __init__(self): - import PathScripts.PathSelection as PST - PST.eselect() + def __init__(self, viewProvider): + FreeCADGui.Selection.addSelectionGate(self) + self.viewProvider = viewProvider def __del__(self): - import PathScripts.PathSelection as PST - PST.clear() + FreeCADGui.Selection.removeSelectionGate() + + def allow(self, doc, obj, sub): + return self.viewProvider.allowSelection(obj, sub) def addSelection(self, doc, obj, sub, pnt): + self.viewProvider.addSelection(pnt) #FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') FreeCADGui.updateGui() +class HoldingTagMarker: + def __init__(self, p): + self.point = p + self.sep = coin.SoSeparator() + self.pos = coin.SoTranslation() + self.pos.translation = (p.x, p.y, p.z) + self.sphere = coin.SoSphere() + self.material = coin.SoMaterial() + self.sep.addChild(self.pos) + self.sep.addChild(self.material) + self.sep.addChild(self.sphere) + + def setSelected(self, select): + self.selected = select + self.sphere.radius = 1.5 if select else 1.0 + + def setEnabled(self, enabled): + self.enabled = enabled + if enabled: + print("green") + self.material.diffuseColor = coin.SbColor(0.0, 1.0, 0.0) + self.material.transparency = 0.0 + else: + print("gray") + self.material.diffuseColor = coin.SbColor(0.8, 0.8, 0.8) + self.material.transparency = 0.6 + class ViewProviderDressup: def __init__(self, vobj): vobj.Proxy = self def attach(self, vobj): - self.Object = vobj.Object - return + self.obj = vobj.Object + self.tags = [] + self.switch = coin.SoSwitch() + vobj.RootNode.addChild(self.switch) + self.turnMarkerDisplayOn(False) + + def turnMarkerDisplayOn(self, display): + sw = coin.SO_SWITCH_ALL if display else coin.SO_SWITCH_NONE + self.switch.whichChild = sw + def claimChildren(self): - for i in self.Object.Base.InList: + for i in self.obj.Base.InList: if hasattr(i, "Group"): group = i.Group for g in group: - if g.Name == self.Object.Base.Name: + if g.Name == self.obj.Base.Name: group.remove(g) i.Group = group print i.Group #FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False - return [self.Object.Base] + return [self.obj.Base] def setEdit(self, vobj, mode=0): + panel = TaskPanel(vobj.Object, self) + self.setupTaskPanel(panel) + return True + + def setupTaskPanel(self, panel): + self.panel = panel FreeCADGui.Control.closeDialog() - panel = TaskPanel(vobj.Object) FreeCADGui.Control.showDialog(panel) panel.setupUi() - return True + + def clearTaskPanel(self): + self.panel = None + self.turnMarkerDisplayOn(False) def __getstate__(self): return None @@ -992,6 +1045,41 @@ class ViewProviderDressup: PathUtils.addToJob(arg1.Object.Base) return True + def updateData(self, obj, propName): + if 'Disabled' == propName: + for tag in self.tags: + self.switch.removeChild(tag.sep) + tags = [] + for i, p in enumerate(obj.Positions): + tag = HoldingTagMarker(p) + tag.setEnabled(not i in obj.Disabled) + tags.append(tag) + self.switch.addChild(tag.sep) + self.tags = tags + + def selectTag(self, index): + print("selectTag(%s)" % index) + for i, tag in enumerate(self.tags): + tag.setSelected(i == index) + + def allowSelection(self, obj, sub): + if obj == self.obj: + print("allowSelection(%s, %s)" % (obj.Name, sub)) + return True + return False + + def tagAtPoint(self, point): + p = FreeCAD.Vector(point[0], point[1], point[2]) + for i, tag in enumerate(self.tags): + if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.1): + return i + return -1 + + def addSelection(self, point): + i = self.tagAtPoint(point) + if self.panel: + self.panel.selectTagWithId(i) + class CommandPathDressupHoldingTags: def GetResources(self): From 8f7102e2bfd455d9d627579da01af17788223c1d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 4 Jan 2017 22:37:25 -0800 Subject: [PATCH 02/14] Adding and modifying tags with mouse works - event and selection handlers are properly registered and removed again. --- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Path/Gui/Resources/panels/PointEdit.ui | 71 ++++++ .../PathScripts/PathDressupHoldingTags.py | 211 +++++++++++++----- 3 files changed, 229 insertions(+), 54 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/PointEdit.ui diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 5e701bc5c..7299f8d86 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -58,6 +58,7 @@ panels/JobEdit.ui panels/MillFaceEdit.ui panels/PocketEdit.ui + panels/PointEdit.ui panels/ProfileEdgesEdit.ui panels/ProfileEdit.ui panels/RemoteEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PointEdit.ui b/src/Mod/Path/Gui/Resources/panels/PointEdit.ui new file mode 100644 index 000000000..fe932984f --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PointEdit.ui @@ -0,0 +1,71 @@ + + + Form + + + + 0 + 0 + 362 + 182 + + + + Point Edit + + + + + + + + + Global X + + + + + + + + + + Global Y + + + + + + + + + + Global Z + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index ef04ef5eb..462f27323 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -23,7 +23,9 @@ # *************************************************************************** import FreeCAD import FreeCADGui +import Draft import DraftGeomUtils +import DraftGui import Path import Part import copy @@ -563,6 +565,12 @@ class PathData: raise ValueError("There's something really wrong here") return ordered + def pointIsOnPath(self, p): + for e in self.edges: + if DraftGeomUtils.isPtOnEdge(p, e): + return True + return False + class ObjectDressup: @@ -750,15 +758,32 @@ class ObjectDressup: self.obj.Disabled = disabled self.execute(self.obj) + def pointIsOnPath(self, obj, point): + if not hasattr(self, 'pathData'): + self.setup(obj) + return self.pathData.pointIsOnPath(point) + class TaskPanel: DataX = QtCore.Qt.ItemDataRole.UserRole DataY = QtCore.Qt.ItemDataRole.UserRole + 1 + DataID = QtCore.Qt.ItemDataRole.UserRole + 2 def __init__(self, obj, viewProvider, jvoVisibility=None): self.obj = obj self.obj.Proxy.obj = obj self.viewProvider = viewProvider - self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") + self.form = QtGui.QWidget() + self.formTags = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") + self.formPoint = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui") + self.layout = QtGui.QVBoxLayout(self.form) + self.form.setGeometry(self.formTags.geometry()) + self.form.setWindowTitle(self.formTags.windowTitle()) + self.form.setSizePolicy(self.formTags.sizePolicy()) + self.formTags.setParent(self.form) + self.formPoint.setParent(self.form) + self.layout.addWidget(self.formTags) + self.layout.addWidget(self.formPoint) + self.formPoint.hide() self.jvo = PathUtils.findParentJob(obj).ViewObject if jvoVisibility is None: FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) @@ -781,39 +806,35 @@ class TaskPanel: FreeCAD.ActiveDocument.recompute() def cleanup(self): + self.removeGlobalCallbacks() self.viewProvider.clearTaskPanel() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() - FreeCADGui.Selection.removeObserver(self.s) if self.jvoVisible: self.jvo.show() - def closeDialog(self): - print("closed") - def open(self): self.s = SelObserver(self.viewProvider) - # install the function mode resident FreeCADGui.Selection.addObserver(self.s) def getTags(self, includeCurrent): tags = [] - index = self.form.lwTags.currentRow() - for i in range(0, self.form.lwTags.count()): - item = self.form.lwTags.item(i) + index = self.formTags.lwTags.currentRow() + for i in range(0, self.formTags.lwTags.count()): + item = self.formTags.lwTags.item(i) enabled = item.checkState() == QtCore.Qt.CheckState.Checked x = item.data(self.DataX) y = item.data(self.DataY) - print("(%.2f, %.2f) i=%d/%s" % (x, y, i, index)) + #print("(%.2f, %.2f) i=%d/%s" % (x, y, i, index)) if includeCurrent or i != index: tags.append((x, y, enabled)) return tags def getTagParameters(self): - self.obj.Width = self.form.dsbWidth.value() - self.obj.Height = self.form.dsbHeight.value() - self.obj.Angle = self.form.dsbAngle.value() + self.obj.Width = self.formTags.dsbWidth.value() + self.obj.Height = self.formTags.dsbHeight.value() + self.obj.Angle = self.formTags.dsbAngle.value() def getFields(self): self.getTagParameters() @@ -822,13 +843,14 @@ class TaskPanel: def updateTagsView(self): print("updateTagsView") - self.form.lwTags.blockSignals(True) - self.form.lwTags.clear() + self.formTags.lwTags.blockSignals(True) + self.formTags.lwTags.clear() for i, pos in enumerate(self.obj.Positions): lbl = "%d: (%.2f, %.2f)" % (i, pos.x, pos.y) item = QtGui.QListWidgetItem(lbl) item.setData(self.DataX, pos.x) item.setData(self.DataY, pos.y) + item.setData(self.DataID, i) if i in self.obj.Disabled: item.setCheckState(QtCore.Qt.CheckState.Unchecked) else: @@ -837,8 +859,8 @@ class TaskPanel: flags |= QtCore.Qt.ItemFlag.ItemIsEnabled flags |= QtCore.Qt.ItemFlag.ItemIsUserCheckable item.setFlags(flags) - self.form.lwTags.addItem(item) - self.form.lwTags.blockSignals(False) + self.formTags.lwTags.addItem(item) + self.formTags.lwTags.blockSignals(False) self.whenTagSelectionChanged() def cleanupUI(self): @@ -852,7 +874,7 @@ class TaskPanel: print("generateNewTags") self.cleanupUI() - count = self.form.sbCount.value() + count = self.formTags.sbCount.value() if not self.obj.Proxy.generateTags(self.obj, count): self.obj.Proxy.execute(self.obj) @@ -869,34 +891,109 @@ class TaskPanel: def whenCountChanged(self): print("whenCountChanged") - count = self.form.sbCount.value() - self.form.pbGenerate.setEnabled(count) + count = self.formTags.sbCount.value() + self.formTags.pbGenerate.setEnabled(count) def selectTagWithId(self, index): - self.form.lwTags.setCurrentRow(index) + self.formTags.lwTags.setCurrentRow(index) def whenTagSelectionChanged(self): print('whenTagSelectionChanged') - index = self.form.lwTags.currentRow() - self.form.pbDelete.setEnabled(index != -1) + index = self.formTags.lwTags.currentRow() + self.formTags.pbDelete.setEnabled(index != -1) self.viewProvider.selectTag(index) def deleteSelectedTag(self): + print("deleteSelectedTag") self.obj.Proxy.setXyEnabled(self.getTags(False)) self.updateTagsView() - def addNewTagAt(self, point, what): - if what == self.obj: - print("%s '%s'" %( point, what.Name)) + def addNewTagAt(self, point, obj): + if obj == self.obj and obj.Proxy.pointIsOnPath(obj, point): + print("addNewTagAt(%s, %s)" % (point, obj.Name)) tags = self.tags tags.append((point.x, point.y, True)) self.obj.Proxy.setXyEnabled(tags) - panel = TaskPanel(self.obj, self.viewProvider, self.jvoVisible) - todo.delay(self.viewProvider.setupTaskPanel, panel) + self.updateTagsView() + else: + print("ignore new tag at %s (%s)" % (point, obj)) + self.formPoint.hide() + self.formTags.show() def addNewTag(self): self.tags = self.getTags(True) - FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt, extradlg=[self]) + self.getPoint(self.addNewTagAt) + + def editTagAt(self, point, obj): + if obj == self.obj: + tags = [] + for i, (x, y, enabled) in enumerate(self.tags): + if i == self.editItem: + tags.append((point.x, point.y, enabled)) + else: + tags.append((x, y, enabled)) + self.obj.Proxy.setXyEnabled(tags) + self.updateTagsView() + self.formPoint.hide() + self.formTags.show() + + def editTag(self, item): + self.tags = self.getTags(True) + self.editItem = item.data(self.DataID) + x = item.data(self.DataX) + y = item.data(self.DataY) + self.getPoint(self.editTagAt, FreeCAD.Vector(x, y, 0)) + + def removeGlobalCallbacks(self): + if hasattr(self, 'view') and self.view: + if self.callbackClick: + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) + self.callbackClick = None + if self.callbackMove: + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) + self.callbackMove = None + self.view = None + + def getPoint(self, whenDone, start=None): + + def mouseMove(cb): + event = cb.getEvent() + pos = event.getPosition() + cntrl = event.wasCtrlDown() + shift = event.wasShiftDown() + self.pt = FreeCADGui.Snapper.snap(pos, lastpoint=start, active=cntrl, constrain=shift) + if cntrl: + p = self.pt + else: + plane = FreeCAD.DraftWorkingPlane + p = plane.getLocalCoords(self.pt) + self.formPoint.ifValueX.setText(DraftGui.displayExternal(p.x)) + self.formPoint.ifValueY.setText(DraftGui.displayExternal(p.y)) + self.formPoint.ifValueZ.setText(DraftGui.displayExternal(p.z)) + + def click(cb): + event = cb.getEvent() + if event.getButton() == 1 and event.getState() == coin.SoMouseButtonEvent.DOWN: + accept() + + def finish(ok): + self.removeGlobalCallbacks(); + obj = FreeCADGui.Snapper.lastSnappedObject + FreeCADGui.Snapper.off() + whenDone(self.pt if ok else None, obj if ok else None) + + def accept(): + finish(True) + + def cancel(): + finish(False) + + self.formTags.hide() + self.formPoint.show() + + self.view = Draft.get3DView() + self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click) + self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove) def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -906,12 +1003,12 @@ class TaskPanel: def setFields(self): self.updateTagsView() - self.setupSpinBox(self.form.sbCount, len(self.obj.Positions), None) - self.setupSpinBox(self.form.dsbHeight, self.obj.Height) - self.setupSpinBox(self.form.dsbWidth, self.obj.Width) - self.setupSpinBox(self.form.dsbAngle, self.obj.Angle, 0) - self.form.dsbAngle.setMaximum(90) - self.form.dsbAngle.setSingleStep(5.) + self.setupSpinBox(self.formTags.sbCount, len(self.obj.Positions), None) + self.setupSpinBox(self.formTags.dsbHeight, self.obj.Height) + self.setupSpinBox(self.formTags.dsbWidth, self.obj.Width) + self.setupSpinBox(self.formTags.dsbAngle, self.obj.Angle, 0) + self.formTags.dsbAngle.setMaximum(90) + self.formTags.dsbAngle.setSingleStep(5.) def updateModelHeight(self): print('updateModelHeight') @@ -933,32 +1030,35 @@ class TaskPanel: self.setFields() self.whenCountChanged() - self.form.sbCount.valueChanged.connect(self.whenCountChanged) - self.form.pbGenerate.clicked.connect(self.generateNewTags) + self.formTags.sbCount.valueChanged.connect(self.whenCountChanged) + self.formTags.pbGenerate.clicked.connect(self.generateNewTags) - self.form.dsbHeight.editingFinished.connect(self.updateModelHeight) - self.form.dsbWidth.editingFinished.connect(self.updateModelWidth) - self.form.dsbAngle.editingFinished.connect(self.updateModelAngle) - self.form.lwTags.itemChanged.connect(self.updateModelTags) - self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) + self.formTags.dsbHeight.editingFinished.connect(self.updateModelHeight) + self.formTags.dsbWidth.editingFinished.connect(self.updateModelWidth) + self.formTags.dsbAngle.editingFinished.connect(self.updateModelAngle) + self.formTags.lwTags.itemChanged.connect(self.updateModelTags) + self.formTags.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) + self.formTags.lwTags.itemActivated.connect(self.editTag) - self.form.pbDelete.clicked.connect(self.deleteSelectedTag) - self.form.pbAdd.clicked.connect(self.addNewTag) + self.formTags.pbDelete.clicked.connect(self.deleteSelectedTag) + self.formTags.pbAdd.clicked.connect(self.addNewTag) self.viewProvider.turnMarkerDisplayOn(True) class SelObserver: def __init__(self, viewProvider): + print("register observer") FreeCADGui.Selection.addSelectionGate(self) self.viewProvider = viewProvider def __del__(self): + print("remove observer") FreeCADGui.Selection.removeSelectionGate() def allow(self, doc, obj, sub): return self.viewProvider.allowSelection(obj, sub) def addSelection(self, doc, obj, sub, pnt): - self.viewProvider.addSelection(pnt) + self.viewProvider.addSelection(doc, obj, sub, pnt) #FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') FreeCADGui.updateGui() @@ -981,11 +1081,9 @@ class HoldingTagMarker: def setEnabled(self, enabled): self.enabled = enabled if enabled: - print("green") self.material.diffuseColor = coin.SbColor(0.0, 1.0, 0.0) self.material.transparency = 0.0 else: - print("gray") self.material.diffuseColor = coin.SbColor(0.8, 0.8, 0.8) self.material.transparency = 0.6 @@ -1028,9 +1126,13 @@ class ViewProviderDressup: FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(panel) panel.setupUi() + FreeCADGui.Selection.addSelectionGate(self) + FreeCADGui.Selection.addObserver(self) def clearTaskPanel(self): self.panel = None + FreeCADGui.Selection.removeObserver(self) + FreeCADGui.Selection.removeSelectionGate() self.turnMarkerDisplayOn(False) def __getstate__(self): @@ -1062,12 +1164,6 @@ class ViewProviderDressup: for i, tag in enumerate(self.tags): tag.setSelected(i == index) - def allowSelection(self, obj, sub): - if obj == self.obj: - print("allowSelection(%s, %s)" % (obj.Name, sub)) - return True - return False - def tagAtPoint(self, point): p = FreeCAD.Vector(point[0], point[1], point[2]) for i, tag in enumerate(self.tags): @@ -1075,10 +1171,17 @@ class ViewProviderDressup: return i return -1 - def addSelection(self, point): + # SelectionObserver interface + def allow(self, doc, obj, sub): + if obj == self.obj: + return True + return False + + def addSelection(self, doc, obj, sub, point): i = self.tagAtPoint(point) if self.panel: self.panel.selectTagWithId(i) + FreeCADGui.updateGui() class CommandPathDressupHoldingTags: From 0781cf75d6b7d80025f366bc9387b1d7167caeda Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 5 Jan 2017 08:43:54 -0800 Subject: [PATCH 03/14] Added support for manual point input. --- .../Path/Gui/Resources/panels/PointEdit.ui | 11 ++- .../PathScripts/PathDressupHoldingTags.py | 80 ++++++++++--------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PointEdit.ui b/src/Mod/Path/Gui/Resources/panels/PointEdit.ui index fe932984f..454bcabaf 100644 --- a/src/Mod/Path/Gui/Resources/panels/PointEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PointEdit.ui @@ -39,13 +39,20 @@ + + false + Global Z - + + + false + + @@ -53,7 +60,7 @@ - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Save diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 462f27323..333b2fb14 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -792,6 +792,7 @@ class TaskPanel: self.jvo.hide() else: self.jvoVisible = jvoVisibility + self.pt = FreeCAD.Vector(0, 0, 0) def reject(self): print("reject") @@ -814,10 +815,6 @@ class TaskPanel: if self.jvoVisible: self.jvo.show() - def open(self): - self.s = SelObserver(self.viewProvider) - FreeCADGui.Selection.addObserver(self.s) - def getTags(self, includeCurrent): tags = [] index = self.formTags.lwTags.currentRow() @@ -909,14 +906,14 @@ class TaskPanel: self.updateTagsView() def addNewTagAt(self, point, obj): - if obj == self.obj and obj.Proxy.pointIsOnPath(obj, point): - print("addNewTagAt(%s, %s)" % (point, obj.Name)) + if (obj or point != FreeCAD.Vector()) and self.obj.Proxy.pointIsOnPath(self.obj, point): + print("addNewTagAt(%s)" % (point)) tags = self.tags tags.append((point.x, point.y, True)) self.obj.Proxy.setXyEnabled(tags) self.updateTagsView() else: - print("ignore new tag at %s (%s)" % (point, obj)) + print("ignore new tag at %s" % (point)) self.formPoint.hide() self.formTags.show() @@ -956,40 +953,41 @@ class TaskPanel: def getPoint(self, whenDone, start=None): + def displayPoint(p): + self.formPoint.ifValueX.setText(DraftGui.displayExternal(p.x)) + self.formPoint.ifValueY.setText(DraftGui.displayExternal(p.y)) + self.formPoint.ifValueZ.setText(DraftGui.displayExternal(p.z)) + self.formPoint.ifValueX.setFocus() + self.formPoint.ifValueX.selectAll() + def mouseMove(cb): event = cb.getEvent() pos = event.getPosition() cntrl = event.wasCtrlDown() shift = event.wasShiftDown() self.pt = FreeCADGui.Snapper.snap(pos, lastpoint=start, active=cntrl, constrain=shift) - if cntrl: - p = self.pt - else: - plane = FreeCAD.DraftWorkingPlane - p = plane.getLocalCoords(self.pt) - self.formPoint.ifValueX.setText(DraftGui.displayExternal(p.x)) - self.formPoint.ifValueY.setText(DraftGui.displayExternal(p.y)) - self.formPoint.ifValueZ.setText(DraftGui.displayExternal(p.z)) + plane = FreeCAD.DraftWorkingPlane + p = plane.getLocalCoords(self.pt) + displayPoint(p) def click(cb): event = cb.getEvent() if event.getButton() == 1 and event.getState() == coin.SoMouseButtonEvent.DOWN: accept() - def finish(ok): - self.removeGlobalCallbacks(); - obj = FreeCADGui.Snapper.lastSnappedObject - FreeCADGui.Snapper.off() - whenDone(self.pt if ok else None, obj if ok else None) - def accept(): - finish(True) + self.pointAccept() def cancel(): - finish(False) + self.pointCancel() + self.pointWhenDone = whenDone self.formTags.hide() self.formPoint.show() + if start: + displayPoint(start) + else: + displayPoint(FreeCAD.Vector(0,0,0)) self.view = Draft.get3DView() self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click) @@ -1042,25 +1040,33 @@ class TaskPanel: self.formTags.pbDelete.clicked.connect(self.deleteSelectedTag) self.formTags.pbAdd.clicked.connect(self.addNewTag) + + self.formPoint.buttonBox.accepted.connect(self.pointAccept) + QtCore.QObject.connect(self.formPoint.ifValueX, QtCore.SIGNAL("valueChanged(double)"), self.changeValueX) + QtCore.QObject.connect(self.formPoint.ifValueY, QtCore.SIGNAL("valueChanged(double)"), self.changeValueY) + QtCore.QObject.connect(self.formPoint.ifValueZ, QtCore.SIGNAL("valueChanged(double)"), self.changeValueZ) + self.viewProvider.turnMarkerDisplayOn(True) -class SelObserver: - def __init__(self, viewProvider): - print("register observer") - FreeCADGui.Selection.addSelectionGate(self) - self.viewProvider = viewProvider + def pointFinish(self, ok): + self.removeGlobalCallbacks(); + obj = FreeCADGui.Snapper.lastSnappedObject + FreeCADGui.Snapper.off() + self.pointWhenDone(self.pt if ok else None, obj if ok else None) - def __del__(self): - print("remove observer") - FreeCADGui.Selection.removeSelectionGate() + def pointReject(self): + self.pointFinish(False) - def allow(self, doc, obj, sub): - return self.viewProvider.allowSelection(obj, sub) + def pointAccept(self): + self.pointFinish(True) + + def changeValueX(self, double): + self.pt.x = double + def changeValueY(self, double): + self.pt.y = double + def changeValueZ(self, double): + self.pt.z = double - def addSelection(self, doc, obj, sub, pnt): - self.viewProvider.addSelection(doc, obj, sub, pnt) - #FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') - FreeCADGui.updateGui() class HoldingTagMarker: def __init__(self, p): From 6792e2c4b9d4b6c8411e79ef99e307eef99d9ebc Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 5 Jan 2017 08:53:10 -0800 Subject: [PATCH 04/14] Minor name cleanups. --- .../Path/PathScripts/PathDressupHoldingTags.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 333b2fb14..596e0b92a 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -943,12 +943,12 @@ class TaskPanel: def removeGlobalCallbacks(self): if hasattr(self, 'view') and self.view: - if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) - self.callbackClick = None - if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) - self.callbackMove = None + if self.pointCbClick: + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.pointCbClick) + self.pointCbClick = None + if self.pointCbMove: + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.pointCbMove) + self.pointCbMove = None self.view = None def getPoint(self, whenDone, start=None): @@ -990,8 +990,8 @@ class TaskPanel: displayPoint(FreeCAD.Vector(0,0,0)) self.view = Draft.get3DView() - self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click) - self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove) + self.pointCbClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click) + self.pointCbMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove) def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) From f89eea7b5902d8e83d73f467713332473fec1b66 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 5 Jan 2017 17:07:02 -0800 Subject: [PATCH 05/14] Using InputField for width and height and use proper properties. --- .../Gui/Resources/panels/HoldingTagsEdit.ui | 79 +++++++++++++------ .../PathScripts/PathDressupHoldingTags.py | 30 ++++--- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index d8289aca7..ad02f9819 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -6,8 +6,8 @@ 0 0 - 363 - 530 + 380 + 539 @@ -16,6 +16,12 @@ + + + 0 + 0 + + QFrame::NoFrame @@ -27,8 +33,8 @@ 0 0 - 345 - 476 + 362 + 485 @@ -48,13 +54,6 @@ - - - - <html><head/><body><p>Specify the resulting width of tags at the base.</p><p>The initial default width is based on the longest edge found in the base path.</p></body></html> - - - @@ -62,13 +61,6 @@ - - - - <html><head/><body><p>Height of holding tags.</p></body></html> - - - @@ -76,10 +68,36 @@ + + + + <html><head/><body><p>Width of the resulting holding tag.</p></body></html> + + + + + + + <html><head/><body><p>Height of holding tag.</p><p>Note that resulting tag might be smaller if the tag's width and angle result in a triangular shape.</p></body></html> + + + - <html><head/><body><p>Angle of ascend and descend of the tool for the holding tag cutout.</p><p><br/></p><p>If the angle is too flat for the given width to reach the specified height the resulting tag will have a triangular shape and not as high as specified.</p></body></html> + <html><head/><body><p>Plunge angle for ascent and descent of holding tag.</p></body></html> + + + 5.000000000000000 + + + 90.000000000000000 + + + 15.000000000000000 + + + 45.000000000000000 @@ -89,14 +107,14 @@ - <html><head/><body><p>List of current tags.</p><p>Edit coordinates to move tag or invoker snapper tool.</p></body></html> + <html><head/><body><p>List of current tags.</p><p>Edit coordinates by double click or Enter key.</p></body></html> - + false @@ -106,13 +124,23 @@ - + Add... + + + + false + + + Edit... + + + @@ -154,6 +182,13 @@ + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 596e0b92a..df005ee4c 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -577,9 +577,9 @@ class ObjectDressup: def __init__(self, obj): self.obj = obj obj.addProperty("App::PropertyLink", "Base","Base", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "The base path to modify")) - obj.addProperty("App::PropertyFloat", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) - obj.addProperty("App::PropertyFloat", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) - obj.addProperty("App::PropertyFloat", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) + obj.addProperty("App::PropertyLength", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) + obj.addProperty("App::PropertyLength", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) + obj.addProperty("App::PropertyAngle", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags")) obj.addProperty("App::PropertyIntegerList", "Disabled", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags")) obj.Proxy = self @@ -592,7 +592,7 @@ class ObjectDressup: def generateTags(self, obj, count): if hasattr(self, "pathData"): - self.tags = self.pathData.generateTags(obj, count, obj.Width, obj.Height, obj.Angle, None) + self.tags = self.pathData.generateTags(obj, count, obj.Width.Value, obj.Height.Value, obj.Angle, None) obj.Positions = [tag.originAt(0) for tag in self.tags] obj.Disabled = [] return False @@ -696,7 +696,7 @@ class ObjectDressup: self.tags = [] if hasattr(obj, "Positions"): for i, pos in enumerate(obj.Positions): - tag = Tag(pos.x, pos.y, obj.Width, obj.Height, obj.Angle, not i in obj.Disabled) + tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, not i in obj.Disabled) tag.createSolidsAt(pathData.minZ, self.toolRadius) self.tags.append(tag) @@ -829,8 +829,8 @@ class TaskPanel: return tags def getTagParameters(self): - self.obj.Width = self.formTags.dsbWidth.value() - self.obj.Height = self.formTags.dsbHeight.value() + self.obj.Width = FreeCAD.Units.Quantity(self.formTags.ifWidth.text()).Value + self.obj.Height = FreeCAD.Units.Quantity(self.formTags.ifHeight.text()).Value self.obj.Angle = self.formTags.dsbAngle.value() def getFields(self): @@ -954,9 +954,9 @@ class TaskPanel: def getPoint(self, whenDone, start=None): def displayPoint(p): - self.formPoint.ifValueX.setText(DraftGui.displayExternal(p.x)) - self.formPoint.ifValueY.setText(DraftGui.displayExternal(p.y)) - self.formPoint.ifValueZ.setText(DraftGui.displayExternal(p.z)) + self.formPoint.ifValueX.setText(FreeCAD.Units.Quantity(p.x, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueY.setText(FreeCAD.Units.Quantity(p.y, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueZ.setText(FreeCAD.Units.Quantity(p.z, FreeCAD.Units.Length).UserString) self.formPoint.ifValueX.setFocus() self.formPoint.ifValueX.selectAll() @@ -1002,11 +1002,9 @@ class TaskPanel: def setFields(self): self.updateTagsView() self.setupSpinBox(self.formTags.sbCount, len(self.obj.Positions), None) - self.setupSpinBox(self.formTags.dsbHeight, self.obj.Height) - self.setupSpinBox(self.formTags.dsbWidth, self.obj.Width) + self.formTags.ifHeight.setText(FreeCAD.Units.Quantity(self.obj.Height, FreeCAD.Units.Length).UserString) + self.formTags.ifWidth.setText(FreeCAD.Units.Quantity(self.obj.Width, FreeCAD.Units.Length).UserString) self.setupSpinBox(self.formTags.dsbAngle, self.obj.Angle, 0) - self.formTags.dsbAngle.setMaximum(90) - self.formTags.dsbAngle.setSingleStep(5.) def updateModelHeight(self): print('updateModelHeight') @@ -1031,8 +1029,8 @@ class TaskPanel: self.formTags.sbCount.valueChanged.connect(self.whenCountChanged) self.formTags.pbGenerate.clicked.connect(self.generateNewTags) - self.formTags.dsbHeight.editingFinished.connect(self.updateModelHeight) - self.formTags.dsbWidth.editingFinished.connect(self.updateModelWidth) + self.formTags.ifHeight.editingFinished.connect(self.updateModelHeight) + self.formTags.ifWidth.editingFinished.connect(self.updateModelWidth) self.formTags.dsbAngle.editingFinished.connect(self.updateModelAngle) self.formTags.lwTags.itemChanged.connect(self.updateModelTags) self.formTags.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) From 706875b3cc3cbac520ade10902d3472249cd5d19 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 5 Jan 2017 18:58:43 -0800 Subject: [PATCH 06/14] Fixed v-scrollbar issue and added edit button. --- .../Gui/Resources/panels/HoldingTagsEdit.ui | 12 +++--- .../PathScripts/PathDressupHoldingTags.py | 39 +++++++++++-------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index ad02f9819..85ea93435 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -6,8 +6,8 @@ 0 0 - 380 - 539 + 399 + 564 @@ -33,8 +33,8 @@ 0 0 - 362 - 485 + 381 + 510 @@ -107,7 +107,7 @@ - <html><head/><body><p>List of current tags.</p><p>Edit coordinates by double click or Enter key.</p></body></html> + <html><head/><body><p>List of current tags.</p><p>Edit coordinates by double click or Edit button.</p></body></html> @@ -132,7 +132,7 @@ - + false diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index df005ee4c..e4c6c2764 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -776,7 +776,7 @@ class TaskPanel: self.formTags = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") self.formPoint = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui") self.layout = QtGui.QVBoxLayout(self.form) - self.form.setGeometry(self.formTags.geometry()) + #self.form.setGeometry(self.formTags.geometry()) self.form.setWindowTitle(self.formTags.windowTitle()) self.form.setSizePolicy(self.formTags.sizePolicy()) self.formTags.setParent(self.form) @@ -898,6 +898,7 @@ class TaskPanel: print('whenTagSelectionChanged') index = self.formTags.lwTags.currentRow() self.formTags.pbDelete.setEnabled(index != -1) + self.formTags.pbEdit.setEnabled(index != -1) self.viewProvider.selectTag(index) def deleteSelectedTag(self): @@ -922,7 +923,7 @@ class TaskPanel: self.getPoint(self.addNewTagAt) def editTagAt(self, point, obj): - if obj == self.obj: + if (obj or point != FreeCAD.Vector()) and self.obj.Proxy.pointIsOnPath(self.obj, point): tags = [] for i, (x, y, enabled) in enumerate(self.tags): if i == self.editItem: @@ -935,11 +936,15 @@ class TaskPanel: self.formTags.show() def editTag(self, item): - self.tags = self.getTags(True) - self.editItem = item.data(self.DataID) - x = item.data(self.DataX) - y = item.data(self.DataY) - self.getPoint(self.editTagAt, FreeCAD.Vector(x, y, 0)) + if item: + self.tags = self.getTags(True) + self.editItem = item.data(self.DataID) + x = item.data(self.DataX) + y = item.data(self.DataY) + self.getPoint(self.editTagAt, FreeCAD.Vector(x, y, 0)) + + def editSelectedTag(self): + self.editTag(self.formTags.lwTags.currentItem()) def removeGlobalCallbacks(self): if hasattr(self, 'view') and self.view: @@ -1037,12 +1042,13 @@ class TaskPanel: self.formTags.lwTags.itemActivated.connect(self.editTag) self.formTags.pbDelete.clicked.connect(self.deleteSelectedTag) + self.formTags.pbEdit.clicked.connect(self.editSelectedTag) self.formTags.pbAdd.clicked.connect(self.addNewTag) self.formPoint.buttonBox.accepted.connect(self.pointAccept) - QtCore.QObject.connect(self.formPoint.ifValueX, QtCore.SIGNAL("valueChanged(double)"), self.changeValueX) - QtCore.QObject.connect(self.formPoint.ifValueY, QtCore.SIGNAL("valueChanged(double)"), self.changeValueY) - QtCore.QObject.connect(self.formPoint.ifValueZ, QtCore.SIGNAL("valueChanged(double)"), self.changeValueZ) + self.formPoint.ifValueX.editingFinished.connect(self.updatePoint) + self.formPoint.ifValueY.editingFinished.connect(self.updatePoint) + self.formPoint.ifValueZ.editingFinished.connect(self.updatePoint) self.viewProvider.turnMarkerDisplayOn(True) @@ -1056,15 +1062,14 @@ class TaskPanel: self.pointFinish(False) def pointAccept(self): + print("pointAccept") self.pointFinish(True) - def changeValueX(self, double): - self.pt.x = double - def changeValueY(self, double): - self.pt.y = double - def changeValueZ(self, double): - self.pt.z = double - + def updatePoint(self): + x = FreeCAD.Units.Quantity(self.formPoint.ifValueX.text()).Value + y = FreeCAD.Units.Quantity(self.formPoint.ifValueY.text()).Value + z = FreeCAD.Units.Quantity(self.formPoint.ifValueZ.text()).Value + self.pt = FreeCAD.Vector(x, y, z) class HoldingTagMarker: def __init__(self, p): From 45893d778393f6a27dda6c5df7d447c43f078a9c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 5 Jan 2017 20:14:53 -0800 Subject: [PATCH 07/14] Fixed adding new tags - sorting required. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index e4c6c2764..17ed01b13 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -143,7 +143,7 @@ class Tag: r1 = self.fullWidth() / 2 self.r1 = r1 self.r2 = r1 - height = self.height + height = self.height * 1.01 if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) debugPrint("Part.makeCone(%f, %f)" % (r1, height)) @@ -154,7 +154,7 @@ class Tag: r2 = r1 - dr else: r2 = 0 - height = r1 * tangens + height = r1 * tangens * 1.01 self.actualHeight = height self.r2 = r2 debugPrint("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) @@ -168,7 +168,7 @@ class Tag: debugPrint("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) debugPrint("solid.translate(%s)" % self.originAt(z)) - self.solid.translate(self.originAt(z)) + self.solid.translate(self.originAt(z - 0.01 * height)) def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: @@ -695,10 +695,12 @@ class ObjectDressup: self.tags = [] if hasattr(obj, "Positions"): + tags = [] for i, pos in enumerate(obj.Positions): tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, not i in obj.Disabled) tag.createSolidsAt(pathData.minZ, self.toolRadius) - self.tags.append(tag) + tags.append(tag) + self.tags = pathData.sortedTags(tags) if not self.tags: print("execute - no tags") From 190aa1fc742213787db5b071ca0a526b08e21a7e Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 5 Jan 2017 21:18:51 -0800 Subject: [PATCH 08/14] Retrieve colors from settings. --- .../PathScripts/PathDressupHoldingTags.py | 30 ++++++++++++--- src/Mod/Path/PathScripts/PathPreferences.py | 38 ++++++++++--------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 17ed01b13..7caa9eb70 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -37,6 +37,7 @@ import time from DraftGui import todo from PathScripts import PathUtils from PathScripts.PathGeom import * +from PathScripts.PathPreferences import * from PySide import QtCore, QtGui from pivy import coin @@ -1074,28 +1075,32 @@ class TaskPanel: self.pt = FreeCAD.Vector(x, y, z) class HoldingTagMarker: - def __init__(self, p): - self.point = p + def __init__(self, point, colors): + self.point = point + self.color = colors self.sep = coin.SoSeparator() self.pos = coin.SoTranslation() - self.pos.translation = (p.x, p.y, p.z) + self.pos.translation = (point.x, point.y, point.z) self.sphere = coin.SoSphere() self.material = coin.SoMaterial() self.sep.addChild(self.pos) self.sep.addChild(self.material) self.sep.addChild(self.sphere) + self.enabled = True + self.selected = False def setSelected(self, select): self.selected = select self.sphere.radius = 1.5 if select else 1.0 + self.setEnabled(self.enabled) def setEnabled(self, enabled): self.enabled = enabled if enabled: - self.material.diffuseColor = coin.SbColor(0.0, 1.0, 0.0) + self.material.diffuseColor = self.color[0] if not self.selected else self.color[2] self.material.transparency = 0.0 else: - self.material.diffuseColor = coin.SbColor(0.8, 0.8, 0.8) + self.material.diffuseColor = self.color[1] if not self.selected else self.color[2] self.material.transparency = 0.6 class ViewProviderDressup: @@ -1103,7 +1108,20 @@ class ViewProviderDressup: def __init__(self, vobj): vobj.Proxy = self + def setupColors(self): + def colorForColorValue(val): + v = [((val >> n) & 0xff) / 255. for n in [24, 16, 8, 0]] + return coin.SbColor(v[0], v[1], v[2]) + + pref = PathPreferences.preferences() + # R G B A + npc = pref.GetUnsigned("DefaultPathMarkerColor", (( 85*256 + 255)*256 + 0)*256 + 255) + hpc = pref.GetUnsigned("DefaultHighlightPathColor", ((255*256 + 125)*256 + 0)*256 + 255) + dpc = pref.GetUnsigned("DefaultDisabledPathColor", ((205*256 + 205)*256 + 205)*256 + 154) + self.colors = [colorForColorValue(npc), colorForColorValue(dpc), colorForColorValue(hpc)] + def attach(self, vobj): + self.setupColors() self.obj = vobj.Object self.tags = [] self.switch = coin.SoSwitch() @@ -1164,7 +1182,7 @@ class ViewProviderDressup: self.switch.removeChild(tag.sep) tags = [] for i, p in enumerate(obj.Positions): - tag = HoldingTagMarker(p) + tag = HoldingTagMarker(p, self.colors) tag.setEnabled(not i in obj.Disabled) tags.append(tag) self.switch.addChild(tag.sep) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index ab155ab2f..e1fe30912 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -31,6 +31,10 @@ class PathPreferences: PostProcessorDefaultArgs = "PostProcessorDefaultArgs" PostProcessorBlacklist = "PostProcessorBlacklist" + @classmethod + def preferences(cls): + return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") + @classmethod def allAvailablePostProcessors(cls): path = FreeCAD.getHomePath() + ("Mod/Path/PathScripts/") @@ -58,28 +62,28 @@ class PathPreferences: @classmethod def defaultPostProcessor(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.PostProcessorDefault, "") + pref = cls.preferences() + return pref.GetString(cls.PostProcessorDefault, "") @classmethod def defaultPostProcessorArgs(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.PostProcessorDefaultArgs, "") + pref = cls.preferences() + return pref.GetString(cls.PostProcessorDefaultArgs, "") @classmethod def postProcessorBlacklist(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - blacklist = preferences.GetString(cls.PostProcessorBlacklist, "") + pref = cls.preferences() + blacklist = pref.GetString(cls.PostProcessorBlacklist, "") if not blacklist: return [] return eval(blacklist) @classmethod def savePostProcessorDefaults(cls, processor, args, blacklist): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - preferences.SetString(cls.PostProcessorDefault, processor) - preferences.SetString(cls.PostProcessorDefaultArgs, args) - preferences.SetString(cls.PostProcessorBlacklist, "%s" % (blacklist)) + pref = cls.preferences() + pref.SetString(cls.PostProcessorDefault, processor) + pref.SetString(cls.PostProcessorDefaultArgs, args) + pref.SetString(cls.PostProcessorBlacklist, "%s" % (blacklist)) DefaultOutputFile = "DefaultOutputFile" @@ -87,16 +91,16 @@ class PathPreferences: @classmethod def saveOutputFileDefaults(cls, file, policy): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - preferences.SetString(cls.DefaultOutputFile, file) - preferences.SetString(cls.DefaultOutputPolicy, policy) + pref = cls.preferences() + pref.SetString(cls.DefaultOutputFile, file) + pref.SetString(cls.DefaultOutputPolicy, policy) @classmethod def defaultOutputFile(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.DefaultOutputFile, "") + pref = cls.preferences() + return pref.GetString(cls.DefaultOutputFile, "") @classmethod def defaultOutputPolicy(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.DefaultOutputPolicy, "") + pref = cls.preferences() + return pref.GetString(cls.DefaultOutputPolicy, "") From 240389096588af8a7bffc35594ed873832b9a23d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 10:26:00 -0800 Subject: [PATCH 09/14] Minimum # tags is 2, makes sense and avoids the endless loop. --- src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui | 6 +++++- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index 85ea93435..ba2fe037d 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -151,7 +151,11 @@ - + + + 2 + + diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 7caa9eb70..408d3cd0a 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -747,7 +747,7 @@ class ObjectDressup: obj.Height = self.pathData.defaultTagHeight() obj.Width = self.pathData.defaultTagWidth() obj.Angle = self.pathData.defaultTagAngle() - self.generateTags(obj, generate) + self.generateTags(obj, min(2, generate)) return self.pathData def setXyEnabled(self, triples): @@ -900,7 +900,8 @@ class TaskPanel: def whenTagSelectionChanged(self): print('whenTagSelectionChanged') index = self.formTags.lwTags.currentRow() - self.formTags.pbDelete.setEnabled(index != -1) + count = self.formTags.lwTags.count() + self.formTags.pbDelete.setEnabled(index != -1 and count > 2) self.formTags.pbEdit.setEnabled(index != -1) self.viewProvider.selectTag(index) @@ -1002,17 +1003,16 @@ class TaskPanel: self.pointCbMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove) def setupSpinBox(self, widget, val, decimals = 2): - widget.setMinimum(0) if decimals: widget.setDecimals(decimals) widget.setValue(val) def setFields(self): self.updateTagsView() - self.setupSpinBox(self.formTags.sbCount, len(self.obj.Positions), None) + self.formTags.sbCount.setValue(len(self.obj.Positions)) self.formTags.ifHeight.setText(FreeCAD.Units.Quantity(self.obj.Height, FreeCAD.Units.Length).UserString) self.formTags.ifWidth.setText(FreeCAD.Units.Quantity(self.obj.Width, FreeCAD.Units.Length).UserString) - self.setupSpinBox(self.formTags.dsbAngle, self.obj.Angle, 0) + self.formTags.dsbAngle.setValue(self.obj.Angle) def updateModelHeight(self): print('updateModelHeight') From 70c3fc8686ac521f8c4f675aff528ce9d394b9e9 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 13:13:22 -0800 Subject: [PATCH 10/14] Automatically disable tags if they overlap with previous tag and/or do not fall on the base wire. --- .../Gui/Resources/panels/HoldingTagsEdit.ui | 2 +- .../PathScripts/PathDressupHoldingTags.py | 85 ++++++++++++++----- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index ba2fe037d..93fc2de77 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -107,7 +107,7 @@ - <html><head/><body><p>List of current tags.</p><p>Edit coordinates by double click or Edit button.</p></body></html> + <html><head/><body><p>List of current tags. Edit coordinates by double click or Edit button.</p><p>Tags are automatically disabled if they overlap with the previous tag, or don't lie on the base wire.</p></body></html> diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 408d3cd0a..8841f4bca 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -399,7 +399,7 @@ class _RapidEdges: def __init__(self, rapid): self.rapid = rapid - def isRapid(self, edge, removeIfFound=True): + def isRapid(self, edge): if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: v0 = edge.Vertexes[0] v1 = edge.Vertexes[1] @@ -407,12 +407,10 @@ class _RapidEdges: r0 = r.Vertexes[0] r1 = r.Vertexes[1] if PathGeom.isRoughly(r0.X, v0.X) and PathGeom.isRoughly(r0.Y, v0.Y) and PathGeom.isRoughly(r0.Z, v0.Z) and PathGeom.isRoughly(r1.X, v1.X) and PathGeom.isRoughly(r1.Y, v1.Y) and PathGeom.isRoughly(r1.Z, v1.Z): - if removeIfFound: - self.rapid.remove(r) return True return False - def p(self): + def debugPrint(self): print('rapid:') for r in self.rapid: debugEdge(r, ' ', True) @@ -562,8 +560,10 @@ class PathData: for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.valueAt(edge.FirstParameter)).Length): tags.remove(t) ordered.append(t) - if tags: - raise ValueError("There's something really wrong here") + # disable all tags that are not on the base wire. + for tag in tags: + tag.enabled = False + ordered.append(tag) return ordered def pointIsOnPath(self, p): @@ -657,7 +657,7 @@ class ObjectDressup: # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') - if rapid.isRapid(edge, True): + if rapid.isRapid(edge): v = edge.Vertexes[1] commands.append(Path.Command('G0', {'X': v.X, 'Y': v.Y, 'Z': v.Z})) else: @@ -672,6 +672,43 @@ class ObjectDressup: def problems(self): return filter(lambda m: m.haveProblem, self.mappers) + def createTagsPositionDisabled(self, obj, positionsIn, disabledIn): + rawTags = [] + print + for i, pos in enumerate(positionsIn): + print("%d: (%.2f, %.2f)" % (i, pos.x, pos.y)) + tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, not i in disabledIn) + tag.createSolidsAt(self.pathData.minZ, self.toolRadius) + rawTags.append(tag) + # disable all tags that intersect with their previous tag + bb = None + tags = [] + positions = [] + disabled = [] + print + for i, tag in enumerate(self.pathData.sortedTags(rawTags)): + print("%d: (%.2f, %.2f) %d" % (i, tag.x, tag.y, tag.enabled)) + if tag.enabled: + if bb: + if bb.intersect(tag.solid.BoundBox): + tag.enabled = False + elif self.pathData.edges: + e = self.pathData.edges[0] + p0 = e.valueAt(e.FirstParameter) + p1 = e.valueAt(e.LastParameter) + if tag.solid.BoundBox.isInside(p0) or tag.solid.BoundBox.isInside(p1): + tag.enabled = False + if tag.enabled: + bb = tag.solid.BoundBox + else: + disabled.append(i) + tags.append(tag) + positions.append(tag.originAt(0)) + print + print + print + return (tags, positions, disabled) + def execute(self, obj): #pr = cProfile.Profile() #pr.enable() @@ -696,31 +733,33 @@ class ObjectDressup: self.tags = [] if hasattr(obj, "Positions"): - tags = [] - for i, pos in enumerate(obj.Positions): - tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, not i in obj.Disabled) - tag.createSolidsAt(pathData.minZ, self.toolRadius) - tags.append(tag) - self.tags = pathData.sortedTags(tags) + self.tags, positions, disabled = self.createTagsPositionDisabled(obj, obj.Positions, obj.Disabled) + if obj.Disabled != disabled: + print("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) + obj.Positions = positions + obj.Disabled = disabled if not self.tags: print("execute - no tags") obj.Path = obj.Base.Path return + self.processTags(obj) + + def processTags(self, obj): tagID = 0 if debugDressup: for tag in self.tags: tagID += 1 if tag.enabled: - print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) - debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + print("x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ)) + debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) if tag.angle != 90: - debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + debugCone(tag.originAt(self.pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) else: - debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) + debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) - obj.Path = self.createPath(pathData.edges, self.tags, pathData.rapid) + obj.Path = self.createPath(self.pathData.edges, self.tags, self.pathData.rapid) print("execute - done") def setup(self, obj, generate=None): @@ -751,15 +790,17 @@ class ObjectDressup: return self.pathData def setXyEnabled(self, triples): + if not hasattr(self, 'pathData'): + self.setup(self.obj) positions = [] disabled = [] for i, (x, y, enabled) in enumerate(triples): + print("%d: (%.2f, %.2f) %d" % (i, x, y, enabled)) positions.append(FreeCAD.Vector(x, y, 0)) if not enabled: disabled.append(i) - self.obj.Positions = positions - self.obj.Disabled = disabled - self.execute(self.obj) + self.tags, self.obj.Positions, self.obj.Disabled = self.createTagsPositionDisabled(self.obj, positions, disabled) + self.processTags(self.obj) def pointIsOnPath(self, obj, point): if not hasattr(self, 'pathData'): @@ -911,7 +952,7 @@ class TaskPanel: self.updateTagsView() def addNewTagAt(self, point, obj): - if (obj or point != FreeCAD.Vector()) and self.obj.Proxy.pointIsOnPath(self.obj, point): + if self.obj.Proxy.pointIsOnPath(self.obj, point): print("addNewTagAt(%s)" % (point)) tags = self.tags tags.append((point.x, point.y, True)) From 891add9bd3d27f4c5b035e53eb973e3e9ed02c17 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 18:39:27 -0800 Subject: [PATCH 11/14] Added preferences for holding tags. --- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../preferences/PathDressupHoldingTags.ui | 133 ++++++++++++++++++ src/Mod/Path/InitGui.py | 5 +- .../PathScripts/PathDressupHoldingTags.py | 95 ++++++++++--- .../PathScripts/PathPreferencesPathDressup.py | 59 ++++++++ .../PathScripts/PathPreferencesPathJob.py | 2 +- 7 files changed, 276 insertions(+), 20 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui create mode 100644 src/Mod/Path/PathScripts/PathPreferencesPathDressup.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index f01eb9b64..32ce98cae 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -45,6 +45,7 @@ SET(PathScripts_SRCS PathScripts/PathPost.py PathScripts/PathPostProcessor.py PathScripts/PathPreferences.py + PathScripts/PathPreferencesPathDressup.py PathScripts/PathPreferencesPathJob.py PathScripts/PathProfile.py PathScripts/PathProfileEdges.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 7299f8d86..b6800333c 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -66,6 +66,7 @@ panels/ToolControl.ui panels/ToolEdit.ui panels/ToolLibraryEditor.ui + preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm translations/Path_cs.qm diff --git a/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui b/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui new file mode 100644 index 000000000..c805d52a9 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui @@ -0,0 +1,133 @@ + + + Form + + + + 0 + 0 + 477 + 478 + + + + Form + + + + + + Tag Parameters + + + + + + Default Width + + + + + + + <html><head/><body><p>Set the default width of holding tags.</p><p>If the width is set to 0 the dressup will try to guess a reasonable value based on the path itself.</p></body></html> + + + + + + + Default Height + + + + + + + <html><head/><body><p>Default height of holding tags.</p><p>If the specified height is 0 the dressup will use half the height of the part. Should the height be bigger than the height of the part the dressup will reduce the height to the height of the part.</p></body></html> + + + + + + + Default Angle + + + + + + + <html><head/><body><p>Plunge angle for the holding tags ascent and descent.</p></body></html> + + + 5.000000000000000 + + + 90.000000000000000 + + + 15.000000000000000 + + + 45.000000000000000 + + + + + + + + + + Tag Generation + + + + + + Initial # Tags + + + + + + + <html><head/><body><p>Specify the number of tags generated when a new dressup is created.</p></body></html> + + + 2 + + + 4 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 22b13882b..dd0c1d6f3 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -31,8 +31,9 @@ class PathWorkbench (Workbench): def Initialize(self): # Add preferences pages - before loading PathGui to properly order pages of Path group - from PathScripts import PathPreferencesPathJob - FreeCADGui.addPreferencePage(PathPreferencesPathJob.Page, "Path") + from PathScripts import PathPreferencesPathJob, PathPreferencesPathDressup + FreeCADGui.addPreferencePage(PathPreferencesPathJob.JobPreferencesPage, "Path") + FreeCADGui.addPreferencePage(PathPreferencesPathDressup.DressupPreferencesPage, "Path") # load the builtin modules import Path diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 8841f4bca..d7f31bacd 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -27,6 +27,7 @@ import Draft import DraftGeomUtils import DraftGui import Path +import PathScripts.PathPreferencesPathDressup as PathPreferencesPathDressup import Part import copy import math @@ -103,19 +104,65 @@ def debugCone(vector, r1, r2, height, label, color = None): if color: obj.ViewObject.ShapeColor = color -class Tag: + +class HoldingTagsPreferences: + DefaultHoldingTagWidth = 'DefaultHoldingTagWidth' + DefaultHoldingTagHeight = 'DefaultHoldingTagHeight' + DefaultHoldingTagAngle = 'DefaultHoldingTagAngle' + DefaultHoldingTagCount = 'DefaultHoldingTagCount' @classmethod - def FromString(cls, string): - try: - t = eval(string) - return Tag(t[0], t[1], t[2], t[3], t[4], t[5]) - except: - return None + def defaultWidth(cls, ifNotSet): + value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagWidth, ifNotSet) + if value == 0.0: + return ifNotSet + return value - def toString(self): - return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + @classmethod + def defaultHeight(cls, ifNotSet): + value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagHeight, ifNotSet) + if value == 0.0: + return ifNotSet + return value + @classmethod + def defaultAngle(cls, ifNotSet = 45.0): + value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagAngle, ifNotSet) + if value < 10.0: + return ifNotSet + return value + + @classmethod + def defaultCount(cls, ifNotSet = 4): + value = PathPreferences.preferences().GetUnsigned(cls.DefaultHoldingTagCount, ifNotSet) + if value < 2: + return float(ifNotSet) + return float(value) + + def __init__(self): + self.form = FreeCADGui.PySideUic.loadUi(":/preferences/PathDressupHoldingTags.ui") + self.label = 'Holding Tags' + + def loadSettings(self): + print("holding tags - load settings") + self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.defaultWidth(0), FreeCAD.Units.Length).UserString) + self.form.ifHeight.setText(FreeCAD.Units.Quantity(self.defaultHeight(0), FreeCAD.Units.Length).UserString) + self.form.dsbAngle.setValue(self.defaultAngle()) + self.form.sbCount.setValue(self.defaultCount()) + + def saveSettings(self): + print("holding tags - save settings") + pref = PathPreferences.preferences() + pref.SetFloat(self.DefaultHoldingTagWidth, FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value) + pref.SetFloat(self.DefaultHoldingTagHeight, FreeCAD.Units.Quantity(self.form.ifHeight.text()).Value) + pref.SetFloat(self.DefaultHoldingTagAngle, self.form.dsbAngle.value()) + pref.SetUnsigned(self.DefaultHoldingTagCount, self.form.sbCount.value()) + + @classmethod + def preferencesPage(cls): + return HoldingTagsPreferences() + +class Tag: def __init__(self, x, y, width, height, angle, enabled=True): debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle/math.pi, enabled)) self.x = x @@ -456,7 +503,7 @@ class PathData: edges = sorted(self.base.Edges, key=lambda e: e.Length) return (edges[0], edges[-1]) - def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): + def generateTags(self, obj, count, width=None, height=None, angle=None, spacing=None): debugPrint("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -544,14 +591,20 @@ class PathData: def defaultTagHeight(self): if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): - return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value / 2 - return (self.maxZ - self.minZ) / 2 + pathHeight = (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value + else: + pathHeight = self.maxZ - self.minZ + height = HoldingTagsPreferences.defaultHeight(pathHeight / 2) + if height > pathHeight: + return pathHeight + return height def defaultTagWidth(self): - return self.shortestAndLongestPathEdge()[1].Length / 10 + width = self.shortestAndLongestPathEdge()[1].Length / 10 + return HoldingTagsPreferences.defaultWidth(width) def defaultTagAngle(self): - return 45 + return HoldingTagsPreferences.defaultAngle() def sortedTags(self, tags): ordered = [] @@ -762,7 +815,7 @@ class ObjectDressup: obj.Path = self.createPath(self.pathData.edges, self.tags, self.pathData.rapid) print("execute - done") - def setup(self, obj, generate=None): + def setup(self, obj, generate=False): print("setup") self.obj = obj try: @@ -786,7 +839,8 @@ class ObjectDressup: obj.Height = self.pathData.defaultTagHeight() obj.Width = self.pathData.defaultTagWidth() obj.Angle = self.pathData.defaultTagAngle() - self.generateTags(obj, min(2, generate)) + count = HoldingTagsPreferences.defaultCount() + self.generateTags(obj, count) return self.pathData def setXyEnabled(self, triples): @@ -807,6 +861,12 @@ class ObjectDressup: self.setup(obj) return self.pathData.pointIsOnPath(point) + @classmethod + def preferencesPage(cls): + return HoldingTagsPreferences() + +PathPreferencesPathDressup.RegisterDressup(ObjectDressup) + class TaskPanel: DataX = QtCore.Qt.ItemDataRole.UserRole DataY = QtCore.Qt.ItemDataRole.UserRole + 1 @@ -1292,10 +1352,11 @@ class CommandPathDressupHoldingTags: FreeCADGui.doCommand('PathScripts.PathDressupHoldingTags.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') - FreeCADGui.doCommand('dbo.setup(obj, 4.)') + FreeCADGui.doCommand('dbo.setup(obj, True)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('PathDressup_HoldingTags', CommandPathDressupHoldingTags()) diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py b/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py new file mode 100644 index 000000000..86fa52507 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +from PySide import QtCore, QtGui +from PathScripts.PathPreferences import PathPreferences + +_dressups = [] + +def RegisterDressup(dressup): + _dressups.append(dressup) + +class DressupPreferencesPage: + def __init__(self, parent=None): + print('dressup - __init__') + self.form = QtGui.QToolBox() + self.form.setWindowTitle('Dressups') + pages = [] + for dressup in _dressups: + page = dressup.preferencesPage() + if hasattr(page, 'icon') and page.icon: + self.form.addItem(page.form, page.icon, page.label) + else: + self.form.addItem(page.form, page.label) + pages.append(page) + self.pages = pages + + def saveSettings(self): + print('dressup - save settings') + for page in self.pages: + page.saveSettings() + + def loadSettings(self): + print('dressup - load settings') + for page in self.pages: + page.loadSettings() + diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py index 25e12f28a..cb374133d 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py @@ -29,7 +29,7 @@ from PathScripts.PathPreferences import PathPreferences from PathScripts.PathPostProcessor import PostProcessor -class Page: +class JobPreferencesPage: def __init__(self, parent=None): self.form = FreeCADGui.PySideUic.loadUi(":preferences/PathJob.ui") From 3cbf1c32b4a7203c4ae6cfdfb5684a0107260f5b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 18:56:13 -0800 Subject: [PATCH 12/14] Reduced debug logging. --- .../PathScripts/PathDressupHoldingTags.py | 53 +++++-------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index d7f31bacd..cc83f0aec 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -144,14 +144,12 @@ class HoldingTagsPreferences: self.label = 'Holding Tags' def loadSettings(self): - print("holding tags - load settings") self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.defaultWidth(0), FreeCAD.Units.Length).UserString) self.form.ifHeight.setText(FreeCAD.Units.Quantity(self.defaultHeight(0), FreeCAD.Units.Length).UserString) self.form.dsbAngle.setValue(self.defaultAngle()) self.form.sbCount.setValue(self.defaultCount()) def saveSettings(self): - print("holding tags - save settings") pref = PathPreferences.preferences() pref.SetFloat(self.DefaultHoldingTagWidth, FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value) pref.SetFloat(self.DefaultHoldingTagHeight, FreeCAD.Units.Quantity(self.form.ifHeight.text()).Value) @@ -458,7 +456,7 @@ class _RapidEdges: return False def debugPrint(self): - print('rapid:') + debugPrint('rapid:') for r in self.rapid: debugEdge(r, ' ', True) @@ -667,7 +665,7 @@ class ObjectDressup: return True def createPath(self, edges, tags, rapid): - print("createPath") + #print("createPath") commands = [] lastEdge = 0 lastTag = 0 @@ -727,9 +725,7 @@ class ObjectDressup: def createTagsPositionDisabled(self, obj, positionsIn, disabledIn): rawTags = [] - print for i, pos in enumerate(positionsIn): - print("%d: (%.2f, %.2f)" % (i, pos.x, pos.y)) tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, not i in disabledIn) tag.createSolidsAt(self.pathData.minZ, self.toolRadius) rawTags.append(tag) @@ -738,9 +734,7 @@ class ObjectDressup: tags = [] positions = [] disabled = [] - print for i, tag in enumerate(self.pathData.sortedTags(rawTags)): - print("%d: (%.2f, %.2f) %d" % (i, tag.x, tag.y, tag.enabled)) if tag.enabled: if bb: if bb.intersect(tag.solid.BoundBox): @@ -757,9 +751,6 @@ class ObjectDressup: disabled.append(i) tags.append(tag) positions.append(tag.originAt(0)) - print - print - print return (tags, positions, disabled) def execute(self, obj): @@ -788,7 +779,7 @@ class ObjectDressup: if hasattr(obj, "Positions"): self.tags, positions, disabled = self.createTagsPositionDisabled(obj, obj.Positions, obj.Disabled) if obj.Disabled != disabled: - print("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) + printDebug("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) obj.Positions = positions obj.Disabled = disabled @@ -813,10 +804,10 @@ class ObjectDressup: debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) obj.Path = self.createPath(self.pathData.edges, self.tags, self.pathData.rapid) - print("execute - done") + #print("execute - done") def setup(self, obj, generate=False): - print("setup") + #print("setup") self.obj = obj try: pathData = PathData(obj) @@ -849,7 +840,7 @@ class ObjectDressup: positions = [] disabled = [] for i, (x, y, enabled) in enumerate(triples): - print("%d: (%.2f, %.2f) %d" % (i, x, y, enabled)) + #print("%d: (%.2f, %.2f) %d" % (i, x, y, enabled)) positions.append(FreeCAD.Vector(x, y, 0)) if not enabled: disabled.append(i) @@ -899,12 +890,10 @@ class TaskPanel: self.pt = FreeCAD.Vector(0, 0, 0) def reject(self): - print("reject") FreeCAD.ActiveDocument.abortTransaction() self.cleanup() def accept(self): - print("accept") self.getFields() FreeCAD.ActiveDocument.commitTransaction() self.cleanup() @@ -943,7 +932,7 @@ class TaskPanel: self.obj.Proxy.setXyEnabled(tags) def updateTagsView(self): - print("updateTagsView") + #print("updateTagsView") self.formTags.lwTags.blockSignals(True) self.formTags.lwTags.clear() for i, pos in enumerate(self.obj.Positions): @@ -964,17 +953,7 @@ class TaskPanel: self.formTags.lwTags.blockSignals(False) self.whenTagSelectionChanged() - def cleanupUI(self): - print("cleanupUI") - if debugDressup: - for obj in FreeCAD.ActiveDocument.Objects: - if obj.Name.startswith('tag'): - FreeCAD.ActiveDocument.removeObject(obj.Name) - def generateNewTags(self): - print("generateNewTags") - self.cleanupUI() - count = self.formTags.sbCount.value() if not self.obj.Proxy.generateTags(self.obj, count): self.obj.Proxy.execute(self.obj) @@ -991,7 +970,6 @@ class TaskPanel: #FreeCAD.ActiveDocument.recompute() def whenCountChanged(self): - print("whenCountChanged") count = self.formTags.sbCount.value() self.formTags.pbGenerate.setEnabled(count) @@ -999,7 +977,6 @@ class TaskPanel: self.formTags.lwTags.setCurrentRow(index) def whenTagSelectionChanged(self): - print('whenTagSelectionChanged') index = self.formTags.lwTags.currentRow() count = self.formTags.lwTags.count() self.formTags.pbDelete.setEnabled(index != -1 and count > 2) @@ -1007,13 +984,12 @@ class TaskPanel: self.viewProvider.selectTag(index) def deleteSelectedTag(self): - print("deleteSelectedTag") self.obj.Proxy.setXyEnabled(self.getTags(False)) self.updateTagsView() def addNewTagAt(self, point, obj): if self.obj.Proxy.pointIsOnPath(self.obj, point): - print("addNewTagAt(%s)" % (point)) + #print("addNewTagAt(%s)" % (point)) tags = self.tags tags.append((point.x, point.y, True)) self.obj.Proxy.setXyEnabled(tags) @@ -1116,19 +1092,19 @@ class TaskPanel: self.formTags.dsbAngle.setValue(self.obj.Angle) def updateModelHeight(self): - print('updateModelHeight') + #print('updateModelHeight') self.updateModel() def updateModelWidth(self): - print('updateModelWidth') + #print('updateModelWidth') self.updateModel() def updateModelAngle(self): - print('updateModelAngle') + #print('updateModelAngle') self.updateModel() def updateModelTags(self): - print('updateModelTags') + #print('updateModelTags') self.updateModel() def setupUi(self): @@ -1166,7 +1142,6 @@ class TaskPanel: self.pointFinish(False) def pointAccept(self): - print("pointAccept") self.pointFinish(True) def updatePoint(self): @@ -1242,7 +1217,7 @@ class ViewProviderDressup: if g.Name == self.obj.Base.Name: group.remove(g) i.Group = group - print i.Group + #print i.Group #FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False return [self.obj.Base] @@ -1290,7 +1265,7 @@ class ViewProviderDressup: self.tags = tags def selectTag(self, index): - print("selectTag(%s)" % index) + #print("selectTag(%s)" % index) for i, tag in enumerate(self.tags): tag.setSelected(i == index) From 126fe816891bdf2182a8cf055cfc081f2b641273 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 21:09:21 -0800 Subject: [PATCH 13/14] Translation of strings. --- .../Path/PathScripts/PathDressupHoldingTags.py | 2 +- .../PathScripts/PathPreferencesPathDressup.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index cc83f0aec..fdd9a37f0 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -141,7 +141,7 @@ class HoldingTagsPreferences: def __init__(self): self.form = FreeCADGui.PySideUic.loadUi(":/preferences/PathDressupHoldingTags.ui") - self.label = 'Holding Tags' + self.label = translate('PathDressup_HoldingTags', 'Holding Tags') def loadSettings(self): self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.defaultWidth(0), FreeCAD.Units.Length).UserString) diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py b/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py index 86fa52507..a23b370e8 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py @@ -27,6 +27,18 @@ import FreeCADGui from PySide import QtCore, QtGui from PathScripts.PathPreferences import PathPreferences +# Qt tanslation handling +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) + +except AttributeError: + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) + _dressups = [] def RegisterDressup(dressup): @@ -34,9 +46,8 @@ def RegisterDressup(dressup): class DressupPreferencesPage: def __init__(self, parent=None): - print('dressup - __init__') self.form = QtGui.QToolBox() - self.form.setWindowTitle('Dressups') + self.form.setWindowTitle(translate('PathPreferencesPathDressup', 'Dressups')) pages = [] for dressup in _dressups: page = dressup.preferencesPage() @@ -48,12 +59,10 @@ class DressupPreferencesPage: self.pages = pages def saveSettings(self): - print('dressup - save settings') for page in self.pages: page.saveSettings() def loadSettings(self): - print('dressup - load settings') for page in self.pages: page.loadSettings() From 9d544209a8cbabbc82288ddeaa89fe5395186f14 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 10 Jan 2017 14:01:33 -0800 Subject: [PATCH 14/14] Added support for rounded tags. --- .../Gui/Resources/panels/HoldingTagsEdit.ui | 14 + .../preferences/PathDressupHoldingTags.ui | 62 +-- .../PathScripts/PathDressupHoldingTags.py | 355 +++++++++++------- src/Mod/Path/PathScripts/PathGeom.py | 52 +-- 4 files changed, 291 insertions(+), 192 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index 93fc2de77..0fca680a7 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -101,6 +101,20 @@
+ + + + Radius + + + + + + + <html><head/><body><p>Radius of the fillet at the top.</p><p>If the radius is too big for the tag shape it gets reduced to the maximum possible radius - resulting in a spherical shape.</p></body></html> + + +
diff --git a/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui b/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui index c805d52a9..c5085d5fa 100644 --- a/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui +++ b/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui @@ -20,13 +20,6 @@ Tag Parameters - - - - Default Width - - - @@ -34,24 +27,10 @@ - - + + - Default Height - - - - - - - <html><head/><body><p>Default height of holding tags.</p><p>If the specified height is 0 the dressup will use half the height of the part. Should the height be bigger than the height of the part the dressup will reduce the height to the height of the part.</p></body></html> - - - - - - - Default Angle + Default Width @@ -74,6 +53,41 @@ + + + + <html><head/><body><p>Default height of holding tags.</p><p>If the specified height is 0 the dressup will use half the height of the part. Should the height be bigger than the height of the part the dressup will reduce the height to the height of the part.</p></body></html> + + + + + + + Default Angle + + + + + + + Default Height + + + + + + + <html><head/><body><p>Radius of the fillet on the tag's top edge.</p><p>If the radius is bigger than than the tag shape itself supports the resulting shape will be that of a dome.</p></body></html> + + + + + + + Default Radius + + + diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index fdd9a37f0..34908fe57 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -45,16 +45,8 @@ from pivy import coin """Holding Tags Dressup object and FreeCAD command""" # Qt tanslation handling -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig, _encoding) - -except AttributeError: - - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig) +def translate(text, context = "PathDressup_HoldingTags", disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) debugDressup = False @@ -106,10 +98,11 @@ def debugCone(vector, r1, r2, height, label, color = None): class HoldingTagsPreferences: - DefaultHoldingTagWidth = 'DefaultHoldingTagWidth' - DefaultHoldingTagHeight = 'DefaultHoldingTagHeight' - DefaultHoldingTagAngle = 'DefaultHoldingTagAngle' - DefaultHoldingTagCount = 'DefaultHoldingTagCount' + DefaultHoldingTagWidth = 'DefaultHoldingTagWidth' + DefaultHoldingTagHeight = 'DefaultHoldingTagHeight' + DefaultHoldingTagAngle = 'DefaultHoldingTagAngle' + DefaultHoldingTagRadius = 'DefaultHoldingTagRadius' + DefaultHoldingTagCount = 'DefaultHoldingTagCount' @classmethod def defaultWidth(cls, ifNotSet): @@ -139,14 +132,20 @@ class HoldingTagsPreferences: return float(ifNotSet) return float(value) + @classmethod + def defaultRadius(cls, ifNotSet = 0.0): + return PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagRadius, ifNotSet) + + def __init__(self): self.form = FreeCADGui.PySideUic.loadUi(":/preferences/PathDressupHoldingTags.ui") - self.label = translate('PathDressup_HoldingTags', 'Holding Tags') + self.label = translate('Holding Tags') def loadSettings(self): self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.defaultWidth(0), FreeCAD.Units.Length).UserString) self.form.ifHeight.setText(FreeCAD.Units.Quantity(self.defaultHeight(0), FreeCAD.Units.Length).UserString) self.form.dsbAngle.setValue(self.defaultAngle()) + self.form.ifRadius.setText(FreeCAD.Units.Quantity(self.defaultRadius(), FreeCAD.Units.Length).UserString) self.form.sbCount.setValue(self.defaultCount()) def saveSettings(self): @@ -154,6 +153,7 @@ class HoldingTagsPreferences: pref.SetFloat(self.DefaultHoldingTagWidth, FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value) pref.SetFloat(self.DefaultHoldingTagHeight, FreeCAD.Units.Quantity(self.form.ifHeight.text()).Value) pref.SetFloat(self.DefaultHoldingTagAngle, self.form.dsbAngle.value()) + pref.SetFloat(self.DefaultHoldingTagRadius, FreeCAD.Units.Quantity(self.form.ifRadius.text())) pref.SetUnsigned(self.DefaultHoldingTagCount, self.form.sbCount.value()) @classmethod @@ -161,14 +161,15 @@ class HoldingTagsPreferences: return HoldingTagsPreferences() class Tag: - def __init__(self, x, y, width, height, angle, enabled=True): - debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle/math.pi, enabled)) + def __init__(self, x, y, width, height, angle, radius, enabled=True): + debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle, radius, enabled)) self.x = x self.y = y self.width = math.fabs(width) self.height = math.fabs(height) self.actualHeight = self.height self.angle = math.fabs(angle) + self.radius = radius self.enabled = enabled def fullWidth(self): @@ -190,15 +191,24 @@ class Tag: self.r1 = r1 self.r2 = r1 height = self.height * 1.01 + radius = 0 if self.angle == 90 and height > 0: + # cylinder self.solid = Part.makeCylinder(r1, height) + radius = min(min(self.radius, r1), self.height) debugPrint("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: - tangens = math.tan(math.radians(self.angle)) + # cone + rad = math.radians(self.angle) + tangens = math.tan(rad) dr = height / tangens if dr < r1: + # with top r2 = r1 - dr + s = height / math.sin(rad) + radius = min(r2, s) * math.tan((math.pi - rad)/2) * 0.95 else: + # triangular r2 = 0 height = r1 * tangens * 1.01 self.actualHeight = height @@ -215,9 +225,13 @@ class Tag: self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) debugPrint("solid.translate(%s)" % self.originAt(z)) self.solid.translate(self.originAt(z - 0.01 * height)) + self.realRadius = radius + if radius != 0: + debugPrint("makeFillet(%.4f)" % radius) + self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]]) def filterIntersections(self, pts, face): - if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: + if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder or type(face.Surface) == Part.Toroid: #print("it's a cone/cylinder, checking z") return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) if type(face.Surface) == Part.Plane: @@ -225,7 +239,7 @@ class Tag: c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius or PathGeom.isRoughly((pt - c.Center).Length, c.Radius), pts) - #print("==== we got a %s" % face.Surface) + print("==== we got a %s" % face.Surface) def isPointOnEdge(self, pt, edge): param = edge.Curve.parameter(pt) @@ -249,26 +263,9 @@ class Tag: el = edge.valueAt(edge.LastParameter) #print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) - pts = [] - for index, face in enumerate(solid.Faces): - i = edge.Curve.intersect(face.Surface)[0] - #print i - ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) - pts.extend(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)) - if len(ps) != len(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)): - filtered = filter(lambda pt: self.isPointOnEdge(pt, edge), ps) - #print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) - for p in ps: - included = '+' if p in filtered else '-' - #print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) - if pts: - closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - #for p in pts: - # print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) - #print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) - return closest - - #print("-------- -> None") + vertexes = edge.common(solid).Vertexes + if vertexes: + return sorted(vertexes, key=lambda v: (v.Point - refPt).Length)[0].Point return None def intersects(self, edge, param): @@ -277,6 +274,18 @@ class Tag: return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param)) return None + def bbEdges(self): + edges = [] + for i in range(12): + p1, p2 = self.solid.BoundBox.getEdge(i) + edges.append(Part.Edge(Part.LineSegment(p1, p2))) + return edges + + def bbShow(self): + for e in self.bbEdges(): + Part.show(e) + + class MapWireToTag: def __init__(self, edge, tag, i): debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z)) @@ -294,9 +303,14 @@ class MapWireToTag: debugEdge(e, '++++++++ .') self.commands = PathGeom.cmdsForEdge(e) debugEdge(tail, '.........-') + self.initialEdge = edge self.tail = tail self.edges = [] self.entry = i + if tail: + debugPrint("MapWireToTag(%s - %s)" % (i, tail.valueAt(tail.FirstParameter))) + else: + debugPrint("MapWireToTag(%s - )" % i) self.complete = False self.haveProblem = False @@ -322,63 +336,114 @@ class MapWireToTag: return 2 return 0 - def cleanupEdges(self, edges, baseEdge): + def cleanupEdges(self, edges): # want to remove all edges from the wire itself, and all internal struts #print("+cleanupEdges") - #print(" base:") - debugEdge(baseEdge, ' ') #print(" edges:") for e in edges: debugEdge(e, ' ') #print(":") + self.edgesCleanup = [copy.copy(edges)] - haveEntry = False + # remove any edge that has a point inside the tag solid + # and collect all edges that are connected to the entry and/or exit + self.entryEdges = [] + self.exitEdges = [] + self.edgePoints = [] for e in copy.copy(edges): - if PathGeom.edgesMatch(e, baseEdge): - debugEdge(e, '......... X0') + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + self.edgePoints.append(p1) + self.edgePoints.append(p2) + if self.tag.solid.isInside(p1, PathGeom.Tolerance, False) or self.tag.solid.isInside(p2, PathGeom.Tolerance, False): edges.remove(e) - elif self.isStrut(e): - typ = self.isEntryOrExitStrut(e) - debugEdge(e, '......... |%d' % typ) - if 0 == typ: # neither entry nor exit - debugEdge(e, '......... X1') - edges.remove(e) - elif 1 == typ: - haveEntry = True + debugEdge(e, '......... X0', False) + else: + if PathGeom.pointsCoincide(p1, self.entry) or PathGeom.pointsCoincide(p2, self.entry): + self.entryEdges.append(e) + if PathGeom.pointsCoincide(p1, self.exit) or PathGeom.pointsCoincide(p2, self.exit): + self.exitEdges.append(e) + self.edgesCleanup.append(copy.copy(edges)) + # if there are no edges connected to entry/exit, it means the plunge in/out is vertical + # we need to add in the missing segment and collect the new entry/exit edges. + if not self.entryEdges: + print("fill entryEdges ...") + self.realEntry = sorted(self.edgePoints, key=lambda p: (p - self.entry).Length)[0] + self.entryEdges = filter(lambda e: PathGeom.edgeConnectsTo(e, self.realEntry), edges) + edges.append(Part.Edge(Part.LineSegment(self.entry, self.realEntry))) + else: + self.realEntry = None + if not self.exitEdges: + print("fill exitEdges ...") + self.realExit = sorted(self.edgePoints, key=lambda p: (p - self.exit).Length)[0] + self.exitEdges = filter(lambda e: PathGeom.edgeConnectsTo(e, self.realExit), edges) + edges.append(Part.Edge(Part.LineSegment(self.realExit, self.exit))) + else: + self.realExit = None + self.edgesCleanup.append(copy.copy(edges)) + + # if there are 2 edges attached to entry/exit, throw away the one that is "lower" + if len(self.entryEdges) > 1: + debugEdge(self.entryEdges[0], ' entry[0]', False) + debugEdge(self.entryEdges[1], ' entry[1]', False) + if self.entryEdges[0].BoundBox.ZMax < self.entryEdges[1].BoundBox.ZMax: + edges.remove(self.entryEdges[0]) + debugEdge(e, '......... X1', False) + else: + edges.remove(self.entryEdges[1]) + debugEdge(e, '......... X2', False) + if len(self.exitEdges) > 1: + debugEdge(self.exitEdges[0], ' exit[0]', False) + debugEdge(self.exitEdges[1], ' exit[1]', False) + if self.exitEdges[0].BoundBox.ZMax < self.exitEdges[1].BoundBox.ZMax: + if self.exitEdges[0] in edges: + edges.remove(self.exitEdges[0]) + debugEdge(e, '......... X3', False) + else: + if self.exitEdges[1] in edges: + edges.remove(self.exitEdges[1]) + debugEdge(e, '......... X4', False) + + self.edgesCleanup.append(copy.copy(edges)) + return edges + + def orderAndFlipEdges(self, edges): #print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) - # the remaininng edges for the path from xy(baseEdge) along the tags surface + self.edgesOrder = [] outputEdges = [] - p0 = baseEdge.valueAt(baseEdge.FirstParameter) - ignoreZ = False - if not haveEntry: - ignoreZ = True - p0 = PathGeom.xy(p0) + p0 = self.entry lastP = p0 while edges: - #print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) + #print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z)) for e in edges: p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(PathGeom.xy(p1) if ignoreZ else p1, p0): + if PathGeom.pointsCoincide(p1, p0): outputEdges.append((e, False)) edges.remove(e) lastP = None - ignoreZ = False p0 = p2 debugEdge(e, ">>>>> no flip") break - elif PathGeom.pointsCoincide(PathGeom.xy(p2) if ignoreZ else p2, p0): + elif PathGeom.pointsCoincide(p2, p0): outputEdges.append((e, True)) edges.remove(e) lastP = None - ignoreZ = False p0 = p1 debugEdge(e, ">>>>> flip") break else: debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z)) if lastP == p0: + self.edgesOrder.append(outputEdges) + self.edgesOrder.append(edges) + print('ordered edges:') + for e, flip in outputEdges: + debugEdge(e, ' %c ' % ('<' if flip else '>'), False) + print('remaining edges:') + for e in edges: + debugEdge(e, ' ', False) raise ValueError("No connection to %s" % (p0)) elif lastP: debugPrint("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) @@ -393,40 +458,50 @@ class MapWireToTag: p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) return PathGeom.pointsCoincide(p1, p2) - def cmdsForEdge(self, edge): - cmds = [] - - # OCC doesn't like it very much if the shapes align with each other. So if we have a slightly - # extended edge for the last edge in list we'll use that instead for stable results. - if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.lastEdge.valueAt(self.lastEdge.FirstParameter)): - shell = self.lastEdge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + def shell(self): + if len(self.edges) > 1: + wire = Part.Wire(self.initialEdge) else: - shell = edge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) - shape = shell.common(self.tag.solid) + edge = self.edges[0] + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter)): + wire = Part.Wire(self.finalEdge) + elif hasattr(self, 'initialEdge') and PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.initialEdge.valueAt(self.initialEdge.FirstParameter)): + wire = Part.Wire(self.initialEdge) + else: + wire = Part.Wire(edge) - if not shape.Edges: - self.haveProblem = True + for edge in self.edges[1:]: + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter)): + wire.add(self.finalEdge) + else: + wire.add(edge) - for e,flip in self.cleanupEdges(shape.Edges, edge): - debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) - cmds.extend(PathGeom.cmdsForEdge(e, flip, False)) - return cmds + shell = wire.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + return shell.removeShape(filter(lambda f: PathGeom.isRoughly(f.Area, 0), shell.Faces)) def commandsForEdges(self): - commands = [] - for e in self.edges: - if self.isStrut(e): - continue - commands.extend(self.cmdsForEdge(e)) - return commands + if self.edges: + shape = self.shell().common(self.tag.solid) + commands = [] + for e,flip in self.orderAndFlipEdges(self.cleanupEdges(shape.Edges)): + debugEdge(e, '++++++++ %s' % ('<' if flip else '>'), False) + commands.extend(PathGeom.cmdsForEdge(e, flip, False)) + return commands + return [] def add(self, edge): self.tail = None - self.lastEdge = edge # see cmdsForEdge - if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): + self.finalEdge = edge + if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), PathGeom.Tolerance, True): self.addEdge(edge) else: i = self.tag.intersects(edge, edge.LastParameter) + if not i: + self.offendingEdge = edge + debugEdge(edge, 'offending Edge:', False) + o = self.tag.originAt(self.tag.z) + print('originAt: (%.2f, %.2f, %.2f)' % (o.x, o.y, o.z)) + i = edge.valueAt(edge.FirstParameter) if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): self.tail = edge else: @@ -458,7 +533,7 @@ class _RapidEdges: def debugPrint(self): debugPrint('rapid:') for r in self.rapid: - debugEdge(r, ' ', True) + debugEdge(r, ' ') class PathData: def __init__(self, obj): @@ -501,7 +576,7 @@ class PathData: edges = sorted(self.base.Edges, key=lambda e: e.Length) return (edges[0], edges[-1]) - def generateTags(self, obj, count, width=None, height=None, angle=None, spacing=None): + def generateTags(self, obj, count, width=None, height=None, angle=None, radius=None, spacing=None): debugPrint("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -509,18 +584,12 @@ class PathData: if spacing: tagDistance = spacing else: - if count: - tagDistance = self.base.Length / count - else: - tagDistance = self.base.Length / 4 - if width: - W = width - else: - W = self.defaultTagWidth() - if height: - H = height - else: - H = self.defaultTagHeight() + tagDistance = self.base.Length / (count if count else 4) + + W = width if width else self.defaultTagWidth() + H = height if height else self.defaultTagHeight() + A = angle if angle else self.defaultTagAngle() + R = radius if radius else self.defaultTagRadius() # start assigning tags on the longest segment @@ -568,7 +637,7 @@ class PathData: distance = (edge.LastParameter - edge.FirstParameter) / count for j in range(0, count): tag = edge.Curve.value((j+0.5) * distance) - tags.append(Tag(tag.x, tag.y, W, H, angle, True)) + tags.append(Tag(tag.x, tag.y, W, H, A, R, True)) return tags @@ -604,6 +673,9 @@ class PathData: def defaultTagAngle(self): return HoldingTagsPreferences.defaultAngle() + def defaultTagRadius(self): + return HoldingTagsPreferences.defaultRadius() + def sortedTags(self, tags): ordered = [] for edge in self.base.Edges: @@ -613,6 +685,7 @@ class PathData: ordered.append(t) # disable all tags that are not on the base wire. for tag in tags: + FreeCAD.Console.PrintMessage("Tag #%d not on base wire - disabling\n" % len(ordered)) tag.enabled = False ordered.append(tag) return ordered @@ -632,6 +705,7 @@ class ObjectDressup: obj.addProperty("App::PropertyLength", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) obj.addProperty("App::PropertyLength", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) obj.addProperty("App::PropertyAngle", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) + obj.addProperty("App::PropertyLength", "Radius", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Radius of the fillet on the top the tag.")) obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags")) obj.addProperty("App::PropertyIntegerList", "Disabled", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags")) obj.Proxy = self @@ -644,7 +718,7 @@ class ObjectDressup: def generateTags(self, obj, count): if hasattr(self, "pathData"): - self.tags = self.pathData.generateTags(obj, count, obj.Width.Value, obj.Height.Value, obj.Angle, None) + self.tags = self.pathData.generateTags(obj, count, obj.Width.Value, obj.Height.Value, obj.Angle, obj.Radius.Value, None) obj.Positions = [tag.originAt(0) for tag in self.tags] obj.Disabled = [] return False @@ -726,27 +800,31 @@ class ObjectDressup: def createTagsPositionDisabled(self, obj, positionsIn, disabledIn): rawTags = [] for i, pos in enumerate(positionsIn): - tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, not i in disabledIn) + tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, obj.Radius, not i in disabledIn) tag.createSolidsAt(self.pathData.minZ, self.toolRadius) rawTags.append(tag) # disable all tags that intersect with their previous tag - bb = None + prev = None tags = [] positions = [] disabled = [] for i, tag in enumerate(self.pathData.sortedTags(rawTags)): if tag.enabled: - if bb: - if bb.intersect(tag.solid.BoundBox): + if prev: + if prev.solid.common(tag.solid).Faces: + FreeCAD.Console.PrintMessage("Tag #%d intersects with previous tag - disabling\n" % i) + debugPrint("this tag = %d [%s]" % (i, tag.solid.BoundBox)) tag.enabled = False elif self.pathData.edges: e = self.pathData.edges[0] p0 = e.valueAt(e.FirstParameter) p1 = e.valueAt(e.LastParameter) - if tag.solid.BoundBox.isInside(p0) or tag.solid.BoundBox.isInside(p1): + if tag.solid.isInside(p0, PathGeom.Tolerance, True) or tag.solid.isInside(p1, PathGeom.Tolerance, True): + FreeCAD.Console.PrintMessage("Tag #%d intersects with starting point - disabling\n" % i) tag.enabled = False if tag.enabled: - bb = tag.solid.BoundBox + prev = tag + debugPrint("previousTag = %d [%s]" % (i, prev)) else: disabled.append(i) tags.append(tag) @@ -779,7 +857,7 @@ class ObjectDressup: if hasattr(obj, "Positions"): self.tags, positions, disabled = self.createTagsPositionDisabled(obj, obj.Positions, obj.Disabled) if obj.Disabled != disabled: - printDebug("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) + debugPrint("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) obj.Positions = positions obj.Disabled = disabled @@ -796,23 +874,23 @@ class ObjectDressup: for tag in self.tags: tagID += 1 if tag.enabled: - print("x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ)) - debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - if tag.angle != 90: - debugCone(tag.originAt(self.pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) - else: - debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) + debugPrint("x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ)) + #debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + #if tag.angle != 90: + # debugCone(tag.originAt(self.pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + #else: + # debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) obj.Path = self.createPath(self.pathData.edges, self.tags, self.pathData.rapid) #print("execute - done") def setup(self, obj, generate=False): - #print("setup") + debugPrint("setup") self.obj = obj try: pathData = PathData(obj) except ValueError: - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) + FreeCAD.Console.PrintError(translate("Cannot insert holding tags for this path - please select a Profile path\n")) return None self.toolRadius = 5 @@ -830,11 +908,13 @@ class ObjectDressup: obj.Height = self.pathData.defaultTagHeight() obj.Width = self.pathData.defaultTagWidth() obj.Angle = self.pathData.defaultTagAngle() + obj.Radius = self.pathData.defaultTagRadius() count = HoldingTagsPreferences.defaultCount() self.generateTags(obj, count) return self.pathData def setXyEnabled(self, triples): + debugPrint("setXyEnabled") if not hasattr(self, 'pathData'): self.setup(self.obj) positions = [] @@ -881,7 +961,7 @@ class TaskPanel: self.formPoint.hide() self.jvo = PathUtils.findParentJob(obj).ViewObject if jvoVisibility is None: - FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + FreeCAD.ActiveDocument.openTransaction(translate("Edit HoldingTags Dress-up")) self.jvoVisible = self.jvo.isVisible() if self.jvoVisible: self.jvo.hide() @@ -925,6 +1005,7 @@ class TaskPanel: self.obj.Width = FreeCAD.Units.Quantity(self.formTags.ifWidth.text()).Value self.obj.Height = FreeCAD.Units.Quantity(self.formTags.ifHeight.text()).Value self.obj.Angle = self.formTags.dsbAngle.value() + self.obj.Radius = FreeCAD.Units.Quantity(self.formTags.ifRadius.text()).Value def getFields(self): self.getTagParameters() @@ -1090,22 +1171,7 @@ class TaskPanel: self.formTags.ifHeight.setText(FreeCAD.Units.Quantity(self.obj.Height, FreeCAD.Units.Length).UserString) self.formTags.ifWidth.setText(FreeCAD.Units.Quantity(self.obj.Width, FreeCAD.Units.Length).UserString) self.formTags.dsbAngle.setValue(self.obj.Angle) - - def updateModelHeight(self): - #print('updateModelHeight') - self.updateModel() - - def updateModelWidth(self): - #print('updateModelWidth') - self.updateModel() - - def updateModelAngle(self): - #print('updateModelAngle') - self.updateModel() - - def updateModelTags(self): - #print('updateModelTags') - self.updateModel() + self.formTags.ifRadius.setText(FreeCAD.Units.Quantity(self.obj.Radius, FreeCAD.Units.Length).UserString) def setupUi(self): self.setFields() @@ -1114,10 +1180,11 @@ class TaskPanel: self.formTags.sbCount.valueChanged.connect(self.whenCountChanged) self.formTags.pbGenerate.clicked.connect(self.generateNewTags) - self.formTags.ifHeight.editingFinished.connect(self.updateModelHeight) - self.formTags.ifWidth.editingFinished.connect(self.updateModelWidth) - self.formTags.dsbAngle.editingFinished.connect(self.updateModelAngle) - self.formTags.lwTags.itemChanged.connect(self.updateModelTags) + self.formTags.ifHeight.editingFinished.connect(self.updateModel) + self.formTags.ifWidth.editingFinished.connect(self.updateModel) + self.formTags.dsbAngle.editingFinished.connect(self.updateModel) + self.formTags.ifRadius.editingFinished.connect(self.updateModel) + self.formTags.lwTags.itemChanged.connect(self.updateModel) self.formTags.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) self.formTags.lwTags.itemActivated.connect(self.editTag) @@ -1307,18 +1374,18 @@ class CommandPathDressupHoldingTags: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select one path object\n")) + FreeCAD.Console.PrintError(translate("Please select one path object\n")) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "The selected object is not a path\n")) + FreeCAD.Console.PrintError(translate("The selected object is not a path\n")) return if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select a Profile object")) + FreeCAD.Console.PrintError(translate("Please select a Profile object")) return # everything ok! - FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Create HoldingTags Dress-up")) + FreeCAD.ActiveDocument.openTransaction(translate("Create HoldingTags Dress-up")) FreeCADGui.addModule("PathScripts.PathDressupHoldingTags") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "HoldingTagsDressup")') diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 388b10952..ed8539873 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -29,6 +29,8 @@ import Path from FreeCAD import Vector +PathGeomTolerance = 0.000001 + class Side: """Class to determine and define the side a Path is on, or Vectors are in relation to each other.""" Left = +1 @@ -70,22 +72,24 @@ class PathGeom: CmdMoveArc = CmdMoveCW + CmdMoveCCW CmdMove = CmdMoveStraight + CmdMoveArc + Tolerance = PathGeomTolerance + @classmethod - def isRoughly(cls, float1, float2, error=0.0000001): - """(float1, float2, [error=0.0000001]) - Returns true if the two values are the same within a given error.""" + def isRoughly(cls, float1, float2, error=PathGeomTolerance): + """(float1, float2, [error=%s]) + Returns true if the two values are the same within a given error.""" % PathGeomTolerance return math.fabs(float1 - float2) <= error @classmethod - def pointsCoincide(cls, p1, p2, error=0.0000001): - """(p1, p2, [error=0.0000001]) - Return True if two points are roughly identical (see also isRoughly).""" + def pointsCoincide(cls, p1, p2, error=PathGeomTolerance): + """(p1, p2, [error=%s]) + Return True if two points are roughly identical (see also isRoughly).""" % PathGeomTolerance return cls.isRoughly(p1.x, p2.x, error) and cls.isRoughly(p1.y, p2.y, error) and cls.isRoughly(p1.z, p2.z, error) @classmethod - def edgesMatch(cls, e0, e1, error=0.0000001): - """(e0, e1, [error=0.0000001] - Return true if the edges start and end at the same point and have the same type of curve.""" + def edgesMatch(cls, e0, e1, error=PathGeomTolerance): + """(e0, e1, [error=%s] + Return true if the edges start and end at the same point and have the same type of curve.""" % PathGeomTolerance if type(e0.Curve) != type(e1.Curve): return False if not cls.pointsCoincide(e0.valueAt(e0.FirstParameter), e1.valueAt(e1.FirstParameter)): @@ -95,9 +99,9 @@ class PathGeom: return True @classmethod - def edgeConnectsTo(cls, edge, vector): - """(edge, vector) - Returns True if edge connects to given vector.""" + def edgeConnectsTo(cls, edge, vector, error=PathGeomTolerance): + """(edge, vector, error=%f) + Returns True if edge connects to given vector.""" % PathGeomTolerance return cls.pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or cls.pointsCoincide(edge.valueAt(edge.LastParameter), vector) @classmethod @@ -106,7 +110,7 @@ class PathGeom: Returns the angle [-pi,pi] of a vector using the X-axis as the reference. Positive angles for vertexes in the upper hemishpere (positive y values) and negative angles for the lower hemishpere.""" - a = vector.getAngle(FreeCAD.Vector(1,0,0)) + a = vector.getAngle(Vector(1,0,0)) if vector.y < 0: return -a return a @@ -132,7 +136,7 @@ class PathGeom: x = cmd.Parameters.get(X, defaultPoint.x) y = cmd.Parameters.get(Y, defaultPoint.y) z = cmd.Parameters.get(Z, defaultPoint.z) - return FreeCAD.Vector(x, y, z) + return Vector(x, y, z) @classmethod def xy(cls, point): @@ -162,12 +166,12 @@ class PathGeom: p1 = pt p3 = edge.valueAt(edge.LastParameter) p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - if type(edge.Curve) == Part.Circle or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve): + if (type(edge.Curve) == Part.Circle and cls.pointsCoincide(edge.Curve.Axis, Vector(0, 0, 1))) or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve): if Side.Left == Side.of(p2 - p1, p3 - p2): cmd = 'G3' else: cmd = 'G2' - #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center pa = PathGeom.xy(p1) @@ -232,7 +236,7 @@ class PathGeom: #print("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d)) #print("arc: R=%.2f angle=%.2f" % (R, angle/math.pi)) if startPoint.z == endPoint.z: - midPoint = center + FreeCAD.Vector(math.cos(angle), math.sin(angle), 0) * R + midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) # It's a Helix @@ -255,7 +259,7 @@ class PathGeom: return None @classmethod - def wireForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)): + def wireForPath(cls, path, startPoint = Vector(0, 0, 0)): """(path, [startPoint=Vector(0,0,0)]) Returns a wire representing all move commands found in the given path.""" edges = [] @@ -271,7 +275,7 @@ class PathGeom: return (Part.Wire(edges), rapid) @classmethod - def wiresForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)): + def wiresForPath(cls, path, startPoint = Vector(0, 0, 0)): """(path, [startPoint=Vector(0,0,0)]) Returns a collection of wires, each representing a continuous cutting Path in path.""" wires = [] @@ -306,7 +310,7 @@ class PathGeom: #print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) #print("- %s -> %s" % (cmd, command)) - return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) + return cls.edgeForCmd(command, Vector(p1.x, p1.y, z0)) @classmethod @@ -316,9 +320,9 @@ class PathGeom: p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) p3 = edge.valueAt(edge.LastParameter) - p01 = FreeCAD.Vector(p1.x, p1.y, z) - p02 = FreeCAD.Vector(p2.x, p2.y, z) - p03 = FreeCAD.Vector(p3.x, p3.y, z) + p01 = Vector(p1.x, p1.y, z) + p02 = Vector(p2.x, p2.y, z) + p03 = Vector(p3.x, p3.y, z) return Part.Edge(Part.Arc(p01, p02, p03)) @classmethod @@ -364,6 +368,6 @@ class PathGeom: else: # it's a helix arc = cls.helixToArc(edge, 0) - aes = cls.splitArcAt(arc, FreeCAD.Vector(pt.x, pt.y, 0)) + aes = cls.splitArcAt(arc, Vector(pt.x, pt.y, 0)) return [cls.arcToHelix(aes[0], p1.z, p2.z), cls.arcToHelix(aes[1], p2.z, p3.z)]