Download src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 30.1 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py
-
curl -L -o TaskAttachmentEditor.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py
30.1 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # /*************************************************************************** | |
| # * Copyright (c) 2016 Victor Titov (DeepSOIC) <vv.titov@gmail.com> * | |
| # * * | |
| # * This file is part of the FreeCAD CAx development system. * | |
| # * * | |
| # * This library is free software; you can redistribute it and/or * | |
| # * modify it under the terms of the GNU Library General Public * | |
| # * License as published by the Free Software Foundation; either * | |
| # * version 2 of the License, or (at your option) any later version. * | |
| # * * | |
| # * This library 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 library; see the file COPYING.LIB. If not, * | |
| # * write to the Free Software Foundation, Inc., 59 Temple Place, * | |
| # * Suite 330, Boston, MA 02111-1307, USA * | |
| # * * | |
| # ***************************************************************************/ | |
| from __future__ import absolute_import | |
| import FreeCAD as App | |
| import Part | |
| mm = App.Units.MilliMetre | |
| deg = App.Units.Degree | |
| Q = App.Units.Quantity | |
| from AttachmentEditor.FrozenClass import FrozenClass | |
| try: | |
| from Show import TempoVis | |
| from Show.DepGraphTools import getAllDependent | |
| except ImportError as err: | |
| def TempoVis(doc, tag): | |
| return None | |
| def getAllDependent(feature): | |
| return [] | |
| App.Console.PrintWarning( | |
| "AttachmentEditor: Failed to import some code from Show module. Functionality will be limited.\n" | |
| ) | |
| App.Console.PrintWarning(str(err)) | |
| if App.GuiUp: | |
| import FreeCADGui as Gui | |
| from PySide import QtCore, QtGui | |
| from FreeCADGui import PySideUic as uic | |
| # -------------------------- translation-related code ---------------------------------------- | |
| # Thanks, yorik! (see forum thread "A new Part tool is being born... JoinFeatures!" | |
| # https://forum.freecad.org/viewtopic.php?f=22&t=11112&start=30#p90239 ) | |
| try: | |
| _fromUtf8 = QtCore.QString.fromUtf8 | |
| except AttributeError: | |
| def _fromUtf8(s): | |
| return s | |
| translate = App.Qt.translate | |
| # --------------------------/translation-related code ---------------------------------------- | |
| def linkSubList_convertToOldStyle(references): | |
| ( | |
| "input: [(obj1, (sub1, sub2)), (obj2, (sub1, sub2))]\n" | |
| "output: [(obj1, sub1), (obj1, sub2), (obj2, sub1), (obj2, sub2)]" | |
| ) | |
| result = [] | |
| for tup in references: | |
| if type(tup[1]) is tuple or type(tup[1]) is list: | |
| for subname in tup[1]: | |
| result.append((tup[0], subname)) | |
| if len(tup[1]) == 0: | |
| result.append((tup[0], "")) | |
| elif isinstance(tup[1], str): | |
| # old style references, no conversion required | |
| result.append(tup) | |
| return result | |
| def StrFromLink(feature, subname): | |
| return feature.Name + ((":" + subname) if subname else "") | |
| def LinkFromStr(strlink, document): | |
| if len(strlink) == 0: | |
| return None | |
| pieces = strlink.split(":") | |
| feature = document.getObject(pieces[0]) | |
| subname = "" | |
| if feature is None: | |
| raise ValueError( | |
| translate("AttachmentEditor", "No object named {}", None).format(pieces[0]) | |
| ) | |
| if len(pieces) == 2: | |
| subname = pieces[1] | |
| elif len(pieces) > 2: | |
| raise ValueError( | |
| translate( | |
| "AttachmentEditor", "Failed to parse link (more than one colon encountered)", None | |
| ) | |
| ) | |
| return ( | |
| feature, | |
| str(subname), | |
| ) # wrap in str to remove unicode, which confuses assignment to PropertyLinkSubList. | |
| def StrListFromRefs(references): | |
| """input: PropertyLinkSubList. Output: list of strings for UI.""" | |
| references_oldstyle = linkSubList_convertToOldStyle(references) | |
| return [StrFromLink(feature, subelement) for (feature, subelement) in references_oldstyle] | |
| def RefsFromStrList(strings, document): | |
| """input: strings as from UI. Output: list of tuples that can be assigned to PropertyLinkSubList.""" | |
| refs = [] | |
| for st in strings: | |
| lnk = LinkFromStr(st, document) | |
| if lnk is not None: | |
| refs.append(lnk) | |
| return refs | |
| def GetSelectionAsLinkSubList(): | |
| sel = Gui.Selection.getSelectionEx("", 0) | |
| result = [] | |
| for selobj in sel: | |
| for subname in selobj.SubElementNames: | |
| result.append((selobj.Object, subname)) | |
| if len(selobj.SubElementNames) == 0: | |
| result.append((selobj.Object, "")) | |
| return result | |
| def PlacementsFuzzyCompare(plm1, plm2): | |
| pos_eq = (plm1.Base - plm2.Base).Length < 1e-7 # 1e-7 is OCC's Precision::Confusion | |
| q1 = plm1.Rotation.Q | |
| q2 = plm2.Rotation.Q | |
| # rotations are equal if q1 == q2 or q1 == -q2. | |
| # Invert one of Q's if their scalar product is negative, before comparison. | |
| if q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3] < 0: | |
| q2 = [-v for v in q2] | |
| rot_eq = ( | |
| abs(q1[0] - q2[0]) + abs(q1[1] - q2[1]) + abs(q1[2] - q2[2]) + abs(q1[3] - q2[3]) | |
| ) < 1e-12 # 1e-12 is OCC's Precision::Angular (in radians) | |
| return pos_eq and rot_eq | |
| class CancelError(Exception): | |
| def __init__(self): | |
| self.message = "Canceled by user" | |
| self.isCancelError = True | |
| class AttachmentEditorTaskPanel(FrozenClass): | |
| """The editmode TaskPanel for attachment editing""" | |
| KEYmode = ( | |
| QtCore.Qt.ItemDataRole.UserRole | |
| ) # Key to use in Item.data(key) to obtain a mode associated with list item | |
| KEYon = ( | |
| QtCore.Qt.ItemDataRole.UserRole + 1 | |
| ) # Key to use in Item.data(key) to obtain if the mode is valid | |
| def __define_attributes(self): | |
| self.obj = None # feature being attached | |
| self.attacher = None # AttachEngine that is being actively used by the dialog. Its parameters are constantly and actively kept in sync with the dialog. | |
| self.obj_is_attachable = ( | |
| True # False when editing non-attachable objects (alignment, not attachment) | |
| ) | |
| self.last_sugr = None # result of last execution of suggestor | |
| self.form = None # Qt widget of dialog interface | |
| self.block = False # when True, event handlers return without doing anything (instead of doing-undoing blockSignals to everything) | |
| self.refLines = [] # reference lineEdit widgets, packed into a list for convenience | |
| self.refButtons = [] # buttons next to reference lineEdits | |
| self.attachmentOffsetEdits = [] # all edit boxes related to attachmentOffset | |
| self.i_active_ref = ( | |
| -1 | |
| ) # index of reference being selected (-1 means no reaction to selecting) | |
| self.auto_next = False # if true, references being selected are appended ('Selecting' state is automatically advanced to next button) | |
| self.tv = None # TempoVis class instance | |
| self.create_transaction = True # if false, dialog doesn't mess with transactions. | |
| self.callback_OK = None | |
| self.callback_Cancel = None | |
| self.callback_Apply = None | |
| self._freeze() | |
| def __init__( | |
| self, | |
| obj_to_attach, | |
| take_selection=False, | |
| create_transaction=True, | |
| callback_OK=None, | |
| callback_Cancel=None, | |
| callback_Apply=None, | |
| ): | |
| self.__define_attributes() | |
| self.create_transaction = create_transaction | |
| self.callback_OK = callback_OK | |
| self.callback_Cancel = callback_Cancel | |
| self.callback_Apply = callback_Apply | |
| self.obj = obj_to_attach | |
| try: | |
| if not self.obj.hasExtension("Part::AttachExtension"): | |
| self.obj.addExtension("Part::AttachExtensionPython") | |
| except Exception: | |
| pass | |
| if hasattr(obj_to_attach, "Attacher"): | |
| self.attacher = obj_to_attach.Attacher | |
| elif hasattr(obj_to_attach, "AttacherType"): | |
| self.attacher = Part.AttachEngine(obj_to_attach.AttacherType) | |
| else: | |
| movable = True | |
| if not hasattr(self.obj, "Placement"): | |
| movable = False | |
| if "Hidden" in self.obj.getEditorMode( | |
| "Placement" | |
| ) or "ReadOnly" in self.obj.getEditorMode("Placement"): | |
| movable = False | |
| if not movable: | |
| if self.callback_Cancel: | |
| self.callback_Cancel() | |
| raise ValueError( | |
| translate( | |
| "AttachmentEditor", | |
| "Object {} is neither movable nor attachable, can't edit attachment", | |
| None, | |
| ).format(self.obj.Label) | |
| ) | |
| self.obj_is_attachable = False | |
| self.attacher = Part.AttachEngine() | |
| mb = QtGui.QMessageBox() | |
| mb.setIcon(mb.Icon.Warning) | |
| mb.setText( | |
| translate( | |
| "AttachmentEditor", | |
| "{} is not attachable. The attachment editor can still be used to align the object, but the attachment will not be parametric.", | |
| None, | |
| ).format(obj_to_attach.Label) | |
| ) | |
| mb.setWindowTitle(translate("AttachmentEditor", "Attachment", None)) | |
| btnAbort = mb.addButton(QtGui.QMessageBox.StandardButton.Abort) | |
| btnOK = mb.addButton( | |
| translate("AttachmentEditor", "Continue", None), | |
| QtGui.QMessageBox.ButtonRole.ActionRole, | |
| ) | |
| mb.setDefaultButton(btnOK) | |
| mb.exec_() | |
| if mb.clickedButton() is btnAbort: | |
| if self.callback_Cancel: | |
| self.callback_Cancel() | |
| raise CancelError() | |
| import os | |
| self.form = uic.loadUi(os.path.dirname(__file__) + os.path.sep + "TaskAttachmentEditor.ui") | |
| self.form.setWindowIcon(QtGui.QIcon(":/icons/tools/Part_Attachment.svg")) | |
| self.form.setWindowTitle(translate("AttachmentEditor", "Attachment", None)) | |
| self.form.attachmentOffsetX.setProperty("unit", "mm") | |
| self.form.attachmentOffsetY.setProperty("unit", "mm") | |
| self.form.attachmentOffsetZ.setProperty("unit", "mm") | |
| Gui.ExpressionBinding(self.form.attachmentOffsetX).bind(self.obj, "AttachmentOffset.Base.x") | |
| Gui.ExpressionBinding(self.form.attachmentOffsetY).bind(self.obj, "AttachmentOffset.Base.y") | |
| Gui.ExpressionBinding(self.form.attachmentOffsetZ).bind(self.obj, "AttachmentOffset.Base.z") | |
| Gui.ExpressionBinding(self.form.attachmentOffsetYaw).bind( | |
| self.obj, "AttachmentOffset.Rotation.Yaw" | |
| ) | |
| Gui.ExpressionBinding(self.form.attachmentOffsetPitch).bind( | |
| self.obj, "AttachmentOffset.Rotation.Pitch" | |
| ) | |
| Gui.ExpressionBinding(self.form.attachmentOffsetRoll).bind( | |
| self.obj, "AttachmentOffset.Rotation.Roll" | |
| ) | |
| self.refLines = [ | |
| self.form.lineRef1, | |
| self.form.lineRef2, | |
| self.form.lineRef3, | |
| self.form.lineRef4, | |
| ] | |
| self.refButtons = [ | |
| self.form.buttonRef1, | |
| self.form.buttonRef2, | |
| self.form.buttonRef3, | |
| self.form.buttonRef4, | |
| ] | |
| self.attachmentOffsetEdits = [ | |
| self.form.attachmentOffsetX, | |
| self.form.attachmentOffsetY, | |
| self.form.attachmentOffsetZ, | |
| self.form.attachmentOffsetYaw, | |
| self.form.attachmentOffsetPitch, | |
| self.form.attachmentOffsetRoll, | |
| ] | |
| self.block = False | |
| for i in range(len(self.refLines)): | |
| QtCore.QObject.connect( | |
| self.refLines[i], | |
| QtCore.SIGNAL("textEdited(QString)"), | |
| lambda txt, i=i: self.lineRefChanged(i, txt), | |
| ) | |
| for i in range(len(self.refLines)): | |
| QtCore.QObject.connect( | |
| self.refButtons[i], QtCore.SIGNAL("clicked()"), lambda i=i: self.refButtonClicked(i) | |
| ) | |
| for i in range(len(self.attachmentOffsetEdits)): | |
| QtCore.QObject.connect( | |
| self.attachmentOffsetEdits[i], | |
| QtCore.SIGNAL("valueChanged(double)"), | |
| lambda val, i=i: self.attachmentOffsetChanged(i, val), | |
| ) | |
| QtCore.QObject.connect( | |
| self.form.checkBoxFlip, QtCore.SIGNAL("clicked()"), self.checkBoxFlipClicked | |
| ) | |
| QtCore.QObject.connect( | |
| self.form.listOfModes, QtCore.SIGNAL("itemSelectionChanged()"), self.modeSelected | |
| ) | |
| if self.create_transaction: | |
| self.obj.Document.openTransaction( | |
| translate("AttachmentEditor", "Edit attachment of {}", None).format(self.obj.Name) | |
| ) | |
| self.readParameters() | |
| if len(self.attacher.References) == 0 and take_selection: | |
| sel = GetSelectionAsLinkSubList() | |
| for i in range(len(sel))[::-1]: | |
| if sel[i][0] is obj_to_attach: | |
| sel.pop(i) | |
| self.attacher.References = sel | |
| # need to update textboxes | |
| self.fillAllRefLines() | |
| if len(self.attacher.References) == 0: | |
| self.i_active_ref = 0 | |
| self.auto_next = True | |
| else: | |
| self.i_active_ref = -1 | |
| self.auto_next = False | |
| Gui.Selection.addObserver(self, 0) | |
| self.updatePreview() | |
| self.updateRefButtons() | |
| self.tv = TempoVis(self.obj.Document, tag="PartGui.TaskAttachmentEditor") | |
| if self.tv: # tv will still be None if Show module is unavailable | |
| self.tv.hide_all_dependent(self.obj) | |
| self.tv.show(self.obj) | |
| self.tv.setUnpickable(self.obj) | |
| self.tv.modifyVPProperty(self.obj, "Transparency", 70) | |
| self.tv.show([obj for (obj, subname) in self.attacher.References]) | |
| # task dialog handling | |
| def getStandardButtons(self): | |
| return ( | |
| QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Apply | |
| ) | |
| def clicked(self, button): | |
| if button == QtGui.QDialogButtonBox.Apply: | |
| if self.obj_is_attachable: | |
| self.writeParameters() | |
| if self.create_transaction: | |
| self.obj.Document.commitTransaction() | |
| self.obj.Document.openTransaction( | |
| translate("AttachmentEditor", "Edit attachment of {}", None).format( | |
| self.obj.Name | |
| ) | |
| ) | |
| self.updatePreview() | |
| if self.callback_Apply: | |
| self.callback_Apply() | |
| def accept(self): | |
| if self.obj_is_attachable: | |
| self.writeParameters() | |
| if self.create_transaction: | |
| self.obj.Document.commitTransaction() | |
| self.cleanUp() | |
| Gui.Control.closeDialog() | |
| if self.callback_OK: | |
| self.callback_OK() | |
| def reject(self): | |
| if self.create_transaction: | |
| self.obj.Document.abortTransaction() | |
| self.cleanUp() | |
| Gui.Control.closeDialog() | |
| if self.callback_Cancel: | |
| self.callback_Cancel() | |
| # selectionObserver stuff | |
| def addSelection(self, docname, objname, subname, pnt): | |
| i = self.i_active_ref | |
| if i < 0: | |
| # not selecting any reference | |
| return | |
| if i > 0 and self.auto_next: | |
| prevref = LinkFromStr(self.refLines[i - 1].text(), self.obj.Document) | |
| if prevref[0].Name == objname and subname == "": | |
| # whole object was selected by double-clicking | |
| # its subelement was already written to line[i-1], so we decrease i to overwrite the lineRefChanged | |
| i -= 1 | |
| if i > len(self.refLines) - 1: | |
| # all 4 references have been selected, finish | |
| assert self.auto_next | |
| self.i_active_ref = -1 | |
| self.updateRefButtons() | |
| return | |
| if i > -1: | |
| if ";" in subname: | |
| # Test code: | |
| # Gui.Selection.getSelectionEx("", 0)[0].SubElementNames | |
| # Return value examples: | |
| # ('Part.Body.;Edge10;:Hf19,E.Edge10',) | |
| # ('Part.Body.;#1a:1;:H-f28,E;:Hf28,E.Edge15',) | |
| # ('Part.Body.;#16:1;:G#28;FUS;:H-f28:a,E;:Hf28,E.Edge10',) | |
| # ('Part.Body.Pad.;#1d:1;:H-f28,F.Face7',) | |
| # ('Part.Box.Edge10',) | |
| # ('Part.Sketch.;g1;SKT.Edge1',) | |
| tmp = subname.split(";") | |
| subname = tmp[0] + tmp[-1].split(".")[-1] | |
| parent = self.obj.getParentGeoFeatureGroup() | |
| if parent is not None: | |
| path = [objname] + subname.split(".") | |
| if parent.Name in path: | |
| path = path[path.index(parent.Name) + 1 :] | |
| if len(path) > 1: | |
| objname = path[0] | |
| subname = ".".join(path[1:]) | |
| # assign the selected reference | |
| if objname == self.obj.Name: | |
| self.form.message.setText( | |
| translate("AttachmentEditor", "Ignored. Can't attach object to itself!", None) | |
| ) | |
| return | |
| if App.getDocument(docname).getObject(objname) in getAllDependent(self.obj): | |
| self.form.message.setText( | |
| translate( | |
| "AttachmentEditor", | |
| "{} depends on object being attached, can't use it for attachment", | |
| None, | |
| ).format(objname) | |
| ) | |
| return | |
| self.refLines[i].setText( | |
| StrFromLink(App.getDocument(docname).getObject(objname), subname) | |
| ) | |
| self.lineRefChanged(i, "") | |
| if self.auto_next: | |
| i += 1 | |
| self.i_active_ref = i | |
| self.updateRefButtons() | |
| # slots | |
| def attachmentOffsetChanged(self, index, value): | |
| if self.block: | |
| return | |
| plm = self.attacher.AttachmentOffset | |
| pos = plm.Base | |
| if index == 0: | |
| pos.x = Q(self.form.attachmentOffsetX.text()).Value | |
| if index == 1: | |
| pos.y = Q(self.form.attachmentOffsetY.text()).Value | |
| if index == 2: | |
| pos.z = Q(self.form.attachmentOffsetZ.text()).Value | |
| if index >= 0 and index <= 2: | |
| plm.Base = pos | |
| if index >= 3 and index <= 5: | |
| yaw = Q(self.form.attachmentOffsetYaw.text()).Value | |
| pitch = Q(self.form.attachmentOffsetPitch.text()).Value | |
| roll = Q(self.form.attachmentOffsetRoll.text()).Value | |
| rot = App.Rotation(yaw, pitch, roll) | |
| plm.Rotation = rot | |
| self.attacher.AttachmentOffset = plm | |
| self.updatePreview() | |
| def checkBoxFlipClicked(self): | |
| if self.block: | |
| return | |
| self.attacher.Reverse = self.form.checkBoxFlip.isChecked() | |
| self.updatePreview() | |
| def lineRefChanged(self, index, value): | |
| if self.block: | |
| return | |
| # not parsing links here, because doing it in updatePreview will display error message | |
| self.updatePreview() | |
| def refButtonClicked(self, index): | |
| if self.block: | |
| return | |
| if self.i_active_ref == index: | |
| # stop selecting | |
| self.i_active_ref = -1 | |
| else: | |
| # start selecting | |
| self.i_active_ref = index | |
| self.auto_next = False | |
| self.updateRefButtons() | |
| def modeSelected(self): | |
| if self.block: | |
| return | |
| self.attacher.Mode = self.getCurrentMode() | |
| self.updatePreview() | |
| # internal methods | |
| def writeParameters(self): | |
| "Transfer from the dialog to the object" | |
| self.attacher.writeParametersToFeature(self.obj) | |
| def readParameters(self): | |
| "Transfer from the object to the dialog" | |
| if self.obj_is_attachable: | |
| self.attacher.readParametersFromFeature(self.obj) | |
| plm = self.attacher.AttachmentOffset | |
| try: | |
| old_selfblock = self.block | |
| self.block = True | |
| self.form.attachmentOffsetX.lineEdit().setText((plm.Base.x * mm).UserString) | |
| self.form.attachmentOffsetY.lineEdit().setText((plm.Base.y * mm).UserString) | |
| self.form.attachmentOffsetZ.lineEdit().setText((plm.Base.z * mm).UserString) | |
| self.form.attachmentOffsetYaw.lineEdit().setText( | |
| (plm.Rotation.toEuler()[0] * deg).UserString | |
| ) | |
| self.form.attachmentOffsetPitch.lineEdit().setText( | |
| (plm.Rotation.toEuler()[1] * deg).UserString | |
| ) | |
| self.form.attachmentOffsetRoll.lineEdit().setText( | |
| (plm.Rotation.toEuler()[2] * deg).UserString | |
| ) | |
| self.form.checkBoxFlip.setChecked(self.attacher.Reverse) | |
| self.fillAllRefLines() | |
| finally: | |
| self.block = old_selfblock | |
| def fillAllRefLines(self): | |
| old_block = self.block | |
| try: | |
| self.block = True | |
| strings = StrListFromRefs(self.attacher.References) | |
| if len(strings) < len(self.refLines): | |
| strings.extend([""] * (len(self.refLines) - len(strings))) | |
| for i in range(len(self.refLines)): | |
| self.refLines[i].setText(strings[i]) | |
| finally: | |
| self.block = old_block | |
| def parseAllRefLines(self): | |
| self.attacher.References = RefsFromStrList( | |
| [le.text() for le in self.refLines], self.obj.Document | |
| ) | |
| def updateListOfModes(self): | |
| """needs suggestor to have been called, and assigned to self.last_sugr""" | |
| try: | |
| old_selfblock = self.block | |
| self.block = True | |
| list_widget = self.form.listOfModes | |
| list_widget.clear() | |
| sugr = self.last_sugr | |
| # always have the option to choose Deactivated mode | |
| valid_modes = ["Deactivated"] + sugr["allApplicableModes"] | |
| # add valid modes | |
| for m in valid_modes: | |
| item = QtGui.QListWidgetItem() | |
| txt = self.attacher.getModeInfo(m)["UserFriendlyName"] | |
| item.setText(txt) | |
| item.setData(self.KEYmode, m) | |
| item.setData(self.KEYon, True) | |
| if m == sugr["bestFitMode"]: | |
| f = item.font() | |
| f.setBold(True) | |
| item.setFont(f) | |
| list_widget.addItem(item) | |
| item.setSelected(self.attacher.Mode == m) | |
| # add potential modes | |
| for m in sugr["reachableModes"].keys(): | |
| item = QtGui.QListWidgetItem() | |
| txt = self.attacher.getModeInfo(m)["UserFriendlyName"] | |
| listlistrefs = sugr["reachableModes"][m] | |
| if len(listlistrefs) == 1: | |
| listrefs_userfriendly = [ | |
| self.attacher.getRefTypeInfo(t)["UserFriendlyName"] for t in listlistrefs[0] | |
| ] | |
| txt = translate("AttachmentEditor", "{} (add {})", None).format( | |
| txt, "+".join(listrefs_userfriendly) | |
| ) | |
| else: | |
| txt = translate("AttachmentEditor", "{} (add more references)", None).format( | |
| txt | |
| ) | |
| item.setText(txt) | |
| item.setData(self.KEYmode, m) | |
| item.setData(self.KEYon, True) | |
| if m == sugr["bestFitMode"]: | |
| f = item.font() | |
| f.setBold(True) | |
| item.setFont(f) | |
| # disable this item | |
| f = item.flags() | |
| f = f & ~(QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable) | |
| item.setFlags(f) | |
| list_widget.addItem(item) | |
| # re-scan the list to fill in tooltips | |
| for item in list_widget.findItems("", QtCore.Qt.MatchContains): | |
| m = item.data(self.KEYmode) | |
| on = item.data(self.KEYon) | |
| mi = self.attacher.getModeInfo(m) | |
| cmb = [] | |
| for refstr in mi["ReferenceCombinations"]: | |
| refstr_userfriendly = [ | |
| self.attacher.getRefTypeInfo(t)["UserFriendlyName"] for t in refstr | |
| ] | |
| cmb.append(", ".join(refstr_userfriendly)) | |
| tip = mi["BriefDocu"] | |
| if m != "Deactivated": | |
| tip += "\n\n" | |
| tip += translate( | |
| "AttachmentEditor", "Reference combinations:", None | |
| ) + " \n\n".join(cmb) | |
| item.setToolTip(tip) | |
| finally: | |
| self.block = old_selfblock | |
| def updateRefButtons(self): | |
| try: | |
| old_selfblock = self.block | |
| self.block = True | |
| for i in range(len(self.refButtons)): | |
| btn = self.refButtons[i] | |
| btn.setCheckable(True) | |
| btn.setChecked(self.i_active_ref == i) | |
| typ = translate("AttachmentEditor", "Reference{}", None).format(str(i + 1)) | |
| if self.last_sugr is not None: | |
| typestr = self.last_sugr["references_Types"] | |
| if i < len(typestr): | |
| typ = self.attacher.getRefTypeInfo(typestr[i])["UserFriendlyName"] | |
| btn.setText( | |
| translate("AttachmentEditor", "Selecting…", None) | |
| if self.i_active_ref == i | |
| else typ | |
| ) | |
| finally: | |
| self.block = old_selfblock | |
| def getCurrentMode(self): | |
| list_widget = self.form.listOfModes | |
| sel = list_widget.selectedItems() | |
| if len(sel) == 1: | |
| if sel[0].data(self.KEYon): | |
| return str( | |
| sel[0].data(self.KEYmode) | |
| ) # data() returns unicode, which confuses attacher | |
| # nothing selected in list. Return suggested | |
| if self.last_sugr is not None: | |
| if self.last_sugr["message"] == "OK": | |
| return self.last_sugr["bestFitMode"] | |
| # no suggested mode. Return current, so it doesn't change | |
| return self.attacher.Mode | |
| def updatePreview(self): | |
| new_plm = None | |
| try: | |
| self.parseAllRefLines() | |
| self.last_sugr = self.attacher.suggestModes() | |
| if self.last_sugr["message"] == "LinkBroken": | |
| raise ValueError( | |
| translate("AttachmentEditor", "Failed to resolve links. {}", None).format( | |
| self.last_sugr["error"] | |
| ) | |
| ) | |
| self.updateListOfModes() | |
| self.attacher.Mode = self.getCurrentMode() | |
| new_plm = self.attacher.calculateAttachedPlacement(self.obj.Placement) | |
| if new_plm is None: | |
| self.form.message.setText(translate("AttachmentEditor", "Not attached", None)) | |
| else: | |
| self.form.message.setText( | |
| translate("AttachmentEditor", "Attached with mode {}", None).format( | |
| self.attacher.getModeInfo(self.getCurrentMode())["UserFriendlyName"] | |
| ) | |
| ) | |
| if PlacementsFuzzyCompare(self.obj.Placement, new_plm) == False: | |
| # assign only if placement changed. this avoids touching the object | |
| # when entering and exiting dialog without changing anything | |
| self.obj.Placement = new_plm | |
| except Exception as err: | |
| self.form.message.setText( | |
| translate("AttachmentEditor", "Error: {}", None).format(str(err)) | |
| ) | |
| if new_plm is not None: | |
| self.form.groupBox_AttachmentOffset.setTitle( | |
| translate("AttachmentEditor", "Attachment Offset (in local coordinates):", None) | |
| ) | |
| self.form.groupBox_AttachmentOffset.setEnabled(True) | |
| else: | |
| self.form.groupBox_AttachmentOffset.setTitle( | |
| translate("AttachmentEditor", "Attachment Offset (inactive - not attached):", None) | |
| ) | |
| self.form.groupBox_AttachmentOffset.setEnabled(False) | |
| def cleanUp(self): | |
| """stuff that needs to be done when dialog is closed.""" | |
| Gui.Selection.removeObserver(self) | |
| if self.tv: | |
| self.tv.restore() | |