# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * Copyright (c) 2022 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 * # * * # *************************************************************************** from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path import Path.Dressup.DogboneII as DogboneII import Path.Dressup.Utils as PathDressup import PathScripts.PathUtils as PathUtils if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) else: Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate class Marker(object): def __init__(self, pt, r, h, ena): if Path.Geom.isRoughly(h, 0): h = 0.1 self.pt = pt self.r = r self.h = h self.ena = ena self.sep = coin.SoSeparator() self.pos = coin.SoTranslation() self.pos.translation = (pt.x, pt.y, pt.z + h / 2) self.rot = coin.SoRotationXYZ() self.rot.axis = self.rot.X self.rot.angle = DogboneII.PI / 2 self.cyl = coin.SoCylinder() self.cyl.radius = r self.cyl.height = h # self.cyl.removePart(self.cyl.TOP) # self.cyl.removePart(self.cyl.BOTTOM) self.material = coin.SoMaterial() self.sep.addChild(self.pos) self.sep.addChild(self.rot) self.sep.addChild(self.material) self.sep.addChild(self.cyl) self.lowlight() def setSelected(self, selected): if selected: self.highlight() else: self.lowlight() def highlight(self): self.material.diffuseColor = self.color(1) self.material.transparency = 0.75 def lowlight(self): self.material.diffuseColor = self.color(0) self.material.transparency = 0.90 def _colorEnabled(self, id): if id == 1: return coin.SbColor(0.0, 0.9, 0.0) return coin.SbColor(0.0, 0.9, 0.0) def _colorDisabled(self, id): if id == 1: return coin.SbColor(0.9, 0.0, 0.0) return coin.SbColor(0.9, 0.0, 0.0) def color(self, id): if self.ena: return self._colorEnabled(id) return self._colorDisabled(id) class TaskPanel(object): DataIds = QtCore.Qt.ItemDataRole.UserRole DataState = QtCore.Qt.ItemDataRole.UserRole + 1 def __init__(self, viewProvider, obj): self.viewProvider = viewProvider self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/DogboneEdit.ui") self.s = None FreeCAD.ActiveDocument.openTransaction("Edit Dogbone Dress-up") # self.height = 10 ??? self.markers = [] def reject(self): FreeCAD.ActiveDocument.abortTransaction() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() FreeCADGui.Selection.removeObserver(self.s) self.cleanup() def accept(self): self.getFields() FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() FreeCADGui.Selection.removeObserver(self.s) FreeCAD.ActiveDocument.recompute() self.cleanup() def cleanup(self): self.viewProvider.showMarkers(False) for m in self.markers: self.viewProvider.switch.removeChild(m.sep) self.markers = [] def getFields(self): self.obj.Style = str(self.form.styleCombo.currentText()) self.obj.Side = str(self.form.sideCombo.currentText()) self.obj.Incision = str(self.form.incisionCombo.currentText()) self.obj.Custom = self.form.custom.value() blacklist = [] for i in range(0, self.form.bones.count()): item = self.form.bones.item(i) if item.checkState() == QtCore.Qt.CheckState.Unchecked: blacklist.extend(item.data(self.DataIds)) self.obj.BoneBlacklist = sorted(blacklist) self.obj.Proxy.execute(self.obj) def updateBoneList(self): itemList = [] for state in self.obj.Proxy.boneStates(self.obj): pos = state.position() ids = ",".join([str(nr) for nr in state.boneIDs()]) lbl = f"({pos.x:.2f}, {pos.y:.2f}): {ids}" item = QtGui.QListWidgetItem(lbl) if state.isEnabled(): item.setCheckState(QtCore.Qt.CheckState.Checked) else: item.setCheckState(QtCore.Qt.CheckState.Unchecked) flags = QtCore.Qt.ItemFlag.ItemIsSelectable flags |= QtCore.Qt.ItemFlag.ItemIsEnabled flags |= QtCore.Qt.ItemFlag.ItemIsUserCheckable item.setFlags(flags) item.setData(self.DataIds, state.boneIDs()) item.setData(self.DataState, state) itemList.append(item) markers = [] self.form.bones.clear() for item in sorted(itemList, key=lambda item: item.data(self.DataState).boneIDs()[0]): self.form.bones.addItem(item) state = item.data(self.DataState) loc = state.boneTip() r = self.obj.Proxy.toolRadius(self.obj) zs = state.zLevels() markers.append( Marker( FreeCAD.Vector(loc.x, loc.y, min(zs)), r, max(1, max(zs) - min(zs)), state.isEnabled(), ) ) for m in self.markers: self.viewProvider.switch.removeChild(m.sep) for m in markers: self.viewProvider.switch.addChild(m.sep) self.markers = markers def updateUI(self): customSelected = self.obj.Incision == DogboneII.Incision.Custom self.form.custom.setEnabled(customSelected) self.form.customLabel.setEnabled(customSelected) self.updateBoneList() def updateModel(self): self.getFields() self.updateUI() FreeCAD.ActiveDocument.recompute() def setupCombo(self, combo, text, items): if items and len(items) > 0: for i in range(combo.count(), -1, -1): combo.removeItem(i) combo.addItems(items) index = combo.findText(text, QtCore.Qt.MatchFixedString) if index >= 0: combo.setCurrentIndex(index) def setFields(self): self.setupCombo(self.form.styleCombo, self.obj.Style, DogboneII.Style.All) self.setupCombo(self.form.sideCombo, self.obj.Side, DogboneII.Side.All) self.setupCombo(self.form.incisionCombo, self.obj.Incision, DogboneII.Incision.All) self.form.custom.setMinimum(0.0) self.form.custom.setDecimals(3) self.form.custom.setValue(self.obj.Custom) self.updateUI() def open(self): self.s = SelObserver() # install the function mode resident FreeCADGui.Selection.addObserver(self.s) def setupUi(self): self.setFields() # now that the form is filled, setup the signal handlers self.form.styleCombo.currentIndexChanged.connect(self.updateModel) self.form.sideCombo.currentIndexChanged.connect(self.updateModel) self.form.incisionCombo.currentIndexChanged.connect(self.updateModel) self.form.custom.valueChanged.connect(self.updateModel) self.form.bones.itemChanged.connect(self.updateModel) self.form.bones.itemSelectionChanged.connect(self.updateMarkers) self.viewProvider.showMarkers(True) def updateMarkers(self): index = self.form.bones.currentRow() for i, m in enumerate(self.markers): m.setSelected(i == index) class SelObserver(object): def __init__(self): import Path.Op.Gui.Selection as PST PST.eselect() def __del__(self): import Path.Op.Gui.Selection as PST PST.clear() def addSelection(self, doc, obj, sub, pnt): FreeCADGui.doCommand("Gui.Selection.addSelection(FreeCAD.ActiveDocument." + obj + ")") FreeCADGui.updateGui() class ViewProviderDressup(object): def __init__(self, vobj): self.vobj = vobj self.obj = None def attach(self, vobj): self.obj = vobj.Object if self.obj and self.obj.Base: for i in self.obj.Base.InList: if hasattr(i, "Group"): group = i.Group for g in group: if g.Name == self.obj.Base.Name: group.remove(g) i.Group = group # FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False self.switch = coin.SoSwitch() vobj.RootNode.addChild(self.switch) def showMarkers(self, on): sw = coin.SO_SWITCH_ALL if on else coin.SO_SWITCH_NONE self.switch.whichChild = sw def claimChildren(self): return [self.obj.Base] def setEdit(self, vobj, mode=0): FreeCADGui.Control.closeDialog() panel = TaskPanel(self, vobj.Object) FreeCADGui.Control.showDialog(panel) panel.setupUi() return True def dumps(self): return None def loads(self, state): return None def onDelete(self, arg1=None, arg2=None): """this makes sure that the base operation is added back to the project and visible""" if arg1.Object and arg1.Object.Base: FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True job = PathUtils.findParentJob(arg1.Object) if job: job.Proxy.addOperation(arg1.Object.Base, arg1.Object) arg1.Object.Base = None return True def getIcon(self): if getattr(PathDressup.baseOp(self.obj), "Active", True): return ":/icons/CAM_Dressup.svg" else: return ":/icons/CAM_OpActive.svg" def Create(base, name="DressupDogbone"): """ Create(obj, name='DressupDogbone')… dresses the given Path.Op.Profile object with dogbones. """ obj = DogboneII.Create(base, name) job = PathUtils.findParentJob(base) job.Proxy.addOperation(obj, base) if FreeCAD.GuiUp: obj.ViewObject.Proxy = ViewProviderDressup(obj.ViewObject) obj.Base.ViewObject.Visibility = False return obj class CommandDressupDogboneII(object): def GetResources(self): return { "Pixmap": "CAM_Dressup", "MenuText": QT_TRANSLATE_NOOP("CAM_DressupDogbone", "Dogbone"), "ToolTip": QT_TRANSLATE_NOOP( "CAM_DressupDogbone", "Creates a dogbone dress-up object from a selected toolpath", ), } def IsActive(self): if FreeCAD.ActiveDocument is not None: for o in FreeCAD.ActiveDocument.Objects: if o.Name[:3] == "Job": return True return False def Activated(self): # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: FreeCAD.Console.PrintError( translate("CAM_DressupDogbone", "Select one toolpath object") + "\n" ) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): FreeCAD.Console.PrintError( translate("CAM_DressupDogbone", "The selected object is not a toolpath") + "\n" ) return # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Dogbone Dress-up") FreeCADGui.addModule("Path.Dressup.Gui.DogboneII") FreeCADGui.doCommand( "Path.Dressup.Gui.DogboneII.Create(FreeCAD.ActiveDocument.%s)" % baseObject.Name ) # FreeCAD.ActiveDocument.commitTransaction() # Final `commitTransaction()` called via TaskPanel.accept() FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui from pivy import coin FreeCADGui.addCommand("CAM_DressupDogbone", CommandDressupDogboneII()) FreeCAD.Console.PrintLog("Loading DressupDogboneII… done\n")