Decoupled UI from model - can add and delete tags without recomputation.

This commit is contained in:
Markus Lampert
2017-07-20 12:17:42 -07:00
committed by wmayer
parent a2e9911e20
commit c200fb1bc6
2 changed files with 420 additions and 19 deletions

View File

@@ -22,6 +22,7 @@
# * *
# ***************************************************************************
import FreeCAD
import DraftGeomUtils
import Part
import Path
import PathScripts
@@ -190,8 +191,13 @@ class ObjectDressup(QtCore.QObject):
self.ptMax = FreeCAD.Vector(maxX, maxY, maxZ)
self.masterSolid = TagSolid(self, minZ, self.toolRadius())
self.solids = [self.masterSolid.cloneAt(pos) for pos in self.obj.Positions]
self.changed.emit()
self.wire, rapid = PathGeom.wireForPath(obj.Base.Path)
self.edges = self.wire.Edges
obj.Path = obj.Base.Path
self.changed.emit()
PathLog.track()
def toolRadius(self):
@@ -202,6 +208,15 @@ class ObjectDressup(QtCore.QObject):
obj = FreeCAD.ActiveDocument.addObject('Part::Compound', "tag_%02d" % i)
obj.Shape = solid
def supportsTagGeneration(self, obj):
return False
def pointIsOnPath(self, obj, p):
for e in self.edges:
if DraftGeomUtils.isPtOnEdge(p, e):
return True
return False
def Create(baseObject, name = 'DressupTag'):
'''
Create(basePath, name = 'DressupTag') ... create tag dressup object for the given base path.

View File

@@ -21,6 +21,7 @@
# * USA *
# * *
# ***************************************************************************
import Draft
import FreeCAD
import FreeCADGui
import Path
@@ -29,8 +30,9 @@ import PathScripts.PathDressupTag as PathDressupTag
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
from PathScripts.PathGeom import PathGeom
from PathScripts.PathPreferences import PathPreferences
from PySide import QtCore
from PySide import QtCore, QtGui
from pivy import coin
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
@@ -40,6 +42,357 @@ PathLog.trackModule()
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class PathDressupTagTaskPanel:
DataX = QtCore.Qt.ItemDataRole.UserRole
DataY = QtCore.Qt.ItemDataRole.UserRole + 1
DataZ = QtCore.Qt.ItemDataRole.UserRole + 2
DataID = QtCore.Qt.ItemDataRole.UserRole + 3
def __init__(self, obj, viewProvider, jvoVisibility=None):
self.obj = obj
self.obj.Proxy.obj = obj
self.viewProvider = viewProvider
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"))
self.jvoVisible = self.jvo.isVisible()
if self.jvoVisible:
self.jvo.hide()
else:
self.jvoVisible = jvoVisibility
self.pt = FreeCAD.Vector(0, 0, 0)
closeButton = self.formPoint.buttonBox.button(QtGui.QDialogButtonBox.StandardButton.Close)
closeButton.setText(translate("PathDressup_HoldingTags", "Done"))
self.isDirty = True
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel)
def clicked(self, button):
if button == QtGui.QDialogButtonBox.Apply:
self.getFields()
self.obj.Proxy.execute(self.obj)
self.isDirty = False
def addEscapeShortcut(self):
# The only way I could get to intercept the escape key, or really any key was
# by creating an action with a shortcut .....
self.escape = QtGui.QAction(self.formPoint)
self.escape.setText('Done')
self.escape.setShortcut(QtGui.QKeySequence.fromString('Esc'))
QtCore.QObject.connect(self.escape, QtCore.SIGNAL('triggered()'), self.pointDone)
self.formPoint.addAction(self.escape)
def removeEscapeShortcut(self):
if self.escape:
self.formPoint.removeAction(self.escape)
self.escape = None
def modifyStandardButtons(self, buttonBox):
self.buttonBox = buttonBox
def abort(self):
FreeCAD.ActiveDocument.abortTransaction()
self.cleanup(False)
def reject(self):
FreeCAD.ActiveDocument.abortTransaction()
self.cleanup(True)
def accept(self):
FreeCAD.ActiveDocument.commitTransaction()
self.cleanup(True)
if self.isDirty:
self.getFields()
FreeCAD.ActiveDocument.recompute()
def cleanup(self, gui):
self.removeGlobalCallbacks()
self.viewProvider.clearTaskPanel()
if gui:
FreeCADGui.ActiveDocument.resetEdit()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
if self.jvoVisible:
self.jvo.show()
def getTags(self, includeCurrent):
tags = []
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))
if includeCurrent or i != index:
tags.append((x, y, enabled))
return tags
def getTagParameters(self):
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()
tags = self.getTags(True)
self.obj.Proxy.setXyEnabled(tags)
self.isDirty = True
def updateTagsView(self):
PathLog.track()
self.formTags.lwTags.blockSignals(True)
self.formTags.lwTags.clear()
for i, pos in enumerate(self.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.DataZ, pos.z)
item.setData(self.DataID, i)
if i in self.Disabled:
item.setCheckState(QtCore.Qt.CheckState.Unchecked)
else:
item.setCheckState(QtCore.Qt.CheckState.Checked)
flags = QtCore.Qt.ItemFlag.ItemIsSelectable
flags |= QtCore.Qt.ItemFlag.ItemIsEnabled
flags |= QtCore.Qt.ItemFlag.ItemIsUserCheckable
item.setFlags(flags)
self.formTags.lwTags.addItem(item)
self.formTags.lwTags.blockSignals(False)
self.whenTagSelectionChanged()
self.viewProvider.updatePositions(self.Positions, self.Disabled)
def generateNewTags(self):
count = self.formTags.sbCount.value()
if not self.obj.Proxy.generateTags(self.obj, count):
self.obj.Proxy.execute(self.obj)
self.updateTagsView()
#if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG:
# # this causes a big of an echo and a double click on the spin buttons, don't know why though
# FreeCAD.ActiveDocument.recompute()
def updateModel(self):
self.getFields()
self.updateTagsView()
self.isDirty = True
#FreeCAD.ActiveDocument.recompute()
def whenCountChanged(self):
count = self.formTags.sbCount.value()
self.formTags.pbGenerate.setEnabled(count)
def selectTagWithId(self, index):
PathLog.track(index)
self.formTags.lwTags.setCurrentRow(index)
def whenTagSelectionChanged(self):
index = self.formTags.lwTags.currentRow()
count = self.formTags.lwTags.count()
self.formTags.pbDelete.setEnabled(index != -1 and count > 2)
self.formTags.pbEdit.setEnabled(index != -1)
self.viewProvider.selectTag(index)
def whenTagsViewChanged(self):
self.updateTagsViewWith(self.getTags(True))
def updateTagsViewWith(self, tags):
self.tags = tags
self.Positions = [FreeCAD.Vector(t[0], t[1], 0) for t in self.tags]
self.Disabled = [i for (i,t) in enumerate(self.tags) if not t[2]]
self.updateTagsView()
def deleteSelectedTag(self):
self.updateTagsViewWith(self.getTags(False))
def addNewTagAt(self, point, obj):
if point and obj and self.obj.Proxy.pointIsOnPath(self.obj, point):
PathLog.info("addNewTagAt(%.2f, %.2f)" % (point.x, point.y))
self.Positions.append(FreeCAD.Vector(point.x, point.y, 0))
self.updateTagsView()
else:
print("ignore new tag at %s" % (point))
def addNewTag(self):
self.tags = self.getTags(True)
self.getPoint(self.addNewTagAt)
def editTagAt(self, point, obj):
if point and obj and (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:
tags.append((point.x, point.y, enabled))
else:
tags.append((x, y, enabled))
self.updateTagsViewWith(tags)
def editTag(self, item):
if item:
self.tags = self.getTags(True)
self.editItem = item.data(self.DataID)
x = item.data(self.DataX)
y = item.data(self.DataY)
z = item.data(self.DataZ)
self.getPoint(self.editTagAt, FreeCAD.Vector(x, y, z))
def editSelectedTag(self):
self.editTag(self.formTags.lwTags.currentItem())
def removeGlobalCallbacks(self):
if hasattr(self, 'view') and self.view:
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):
def displayPoint(p):
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()
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)
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 accept():
if start:
self.pointAccept()
else:
self.pointAcceptAndContinue()
def cancel():
self.pointCancel()
self.pointWhenDone = whenDone
self.formTags.hide()
self.formPoint.show()
self.addEscapeShortcut()
if start:
displayPoint(start)
else:
displayPoint(FreeCAD.Vector(0,0,0))
self.view = Draft.get3DView()
self.pointCbClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click)
self.pointCbMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove)
self.buttonBox.setEnabled(False)
def setupSpinBox(self, widget, val, decimals = 2):
if decimals:
widget.setDecimals(decimals)
widget.setValue(val)
def setFields(self):
self.updateTagsView()
self.formTags.sbCount.setValue(len(self.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.formTags.dsbAngle.setValue(self.obj.Angle)
self.formTags.ifRadius.setText(FreeCAD.Units.Quantity(self.obj.Radius, FreeCAD.Units.Length).UserString)
def setupUi(self):
self.Positions = self.obj.Positions
self.Disabled = self.obj.Disabled
self.setFields()
self.whenCountChanged()
if self.obj.Proxy.supportsTagGeneration(self.obj):
self.formTags.sbCount.valueChanged.connect(self.whenCountChanged)
self.formTags.pbGenerate.clicked.connect(self.generateNewTags)
else:
self.formTags.cbTagGeneration.setEnabled(False)
self.formTags.lwTags.itemChanged.connect(self.whenTagsViewChanged)
self.formTags.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged)
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)
self.formPoint.buttonBox.rejected.connect(self.pointReject)
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)
def pointFinish(self, ok, cleanup = True):
obj = FreeCADGui.Snapper.lastSnappedObject
if cleanup:
self.removeGlobalCallbacks();
FreeCADGui.Snapper.off()
self.buttonBox.setEnabled(True)
self.removeEscapeShortcut()
self.formPoint.hide()
self.formTags.show()
self.formTags.setFocus()
if ok:
self.pointWhenDone(self.pt, obj)
else:
self.pointWhenDone(None, None)
def pointDone(self):
self.pointFinish(False)
def pointReject(self):
self.pointFinish(False)
def pointAccept(self):
self.pointFinish(True)
def pointAcceptAndContinue(self):
self.pointFinish(True, False)
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, point, colors):
self.point = point
@@ -111,10 +464,7 @@ class PathDressupTagViewProvider:
if hasattr(i, 'Group') and self.obj.Base.Name in [o.Name for o in i.Group]:
i.Group = [o for o in i.Group if o.Name != self.obj.Base.Name]
if self.obj.Base.ViewObject:
PathLog.info("Setting visibility for %s" % (self.obj.Base.Name))
self.obj.Base.ViewObject.Visibility = False
else:
PathLog.info("Ignoring visibility")
if PathLog.getLevel(PathLog.thisModule()) != PathLog.Level.DEBUG and self.obj.Debug.ViewObject:
self.obj.Debug.ViewObject.Visibility = False
@@ -139,18 +489,21 @@ class PathDressupTagViewProvider:
self.obj.Debug = None
return True
def updatePositions(self, positions, disabled):
for tag in self.tags:
self.switch.removeChild(tag.sep)
tags = []
for i, p in enumerate(positions):
tag = HoldingTagMarker(p, self.colors)
tag.setEnabled(not i in disabled)
tags.append(tag)
self.switch.addChild(tag.sep)
self.tags = tags
def updateData(self, obj, propName):
PathLog.track(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, self.colors)
tag.setEnabled(not i in obj.Disabled)
tags.append(tag)
self.switch.addChild(tag.sep)
self.tags = tags
self.updatePositions(obj.Positions, obj.Disabled)
def onModelChanged(self):
PathLog.track()
@@ -164,27 +517,57 @@ class PathDressupTagViewProvider:
self.obj.Debug.addObject(tag)
tag.purgeTouched()
def setEdit(self, vobj, mode=0):
panel = PathDressupTagTaskPanel(vobj.Object, self)
self.setupTaskPanel(panel)
return True
def unsetEdit(self, vobj, mode):
if hasattr(self, 'panel') and self.panel:
self.panel.abort()
def setupTaskPanel(self, panel):
self.panel = panel
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.removeSelectionGate()
FreeCADGui.Selection.removeObserver(self)
self.turnMarkerDisplayOn(False)
# SelectionObserver interface
def selectTag(self, index):
PathLog.track(index)
for i, tag in enumerate(self.tags):
tag.setSelected(i == index)
def tagAtPoint(self, point):
p = FreeCAD.Vector(point[0], point[1], point[2])
def tagAtPoint(self, point, matchZ):
x = point[0]
y = point[1]
z = point[2]
if self.tags and not matchZ:
z = self.tags[0].point.z
p = FreeCAD.Vector(x, y, z)
for i, tag in enumerate(self.tags):
if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.1):
if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.3):
return i
return -1
# 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)
PathLog.track(doc, obj, sub, point)
if self.panel:
i = self.tagAtPoint(point, sub is None)
self.panel.selectTagWithId(i)
FreeCADGui.updateGui()
@@ -193,8 +576,11 @@ def Create(baseObject, name='DressupTag'):
Create(basePath, name = 'DressupTag') ... create tag dressup object for the given base path.
Use this command only iff the UI is up - for batch processing see PathDressupTag.Create
'''
FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_Tag", "Create a Tag dressup"))
obj = PathDressupTag.Create(baseObject, name)
vp = PathDressupTagViewProvider(obj.ViewObject)
FreeCAD.ActiveDocument.commitTransaction()
obj.ViewObject.startEditing()
return obj
class CommandPathDressupTag: