From f6268ffd2877dadb4440ade541e12c388a517d6e Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 2 Dec 2024 18:34:02 +0100 Subject: [PATCH] Assembly: Insert new part (#17922) * Assembly: Joint Object : encapsulate the joint creation widget such that the task can be subclassed and ui customized by other commands. * Assembly: Insert New Part * Update src/Mod/Assembly/CommandInsertNewPart.py Co-authored-by: Kacper Donat --------- Co-authored-by: Kacper Donat --- src/Mod/Assembly/CMakeLists.txt | 1 + src/Mod/Assembly/CommandInsertLink.py | 56 +++-- src/Mod/Assembly/CommandInsertNewPart.py | 194 ++++++++++++++++ src/Mod/Assembly/InitGui.py | 4 +- src/Mod/Assembly/JointObject.py | 277 ++++++++++++----------- src/Mod/Assembly/UtilsAssembly.py | 18 ++ 6 files changed, 400 insertions(+), 150 deletions(-) create mode 100644 src/Mod/Assembly/CommandInsertNewPart.py diff --git a/src/Mod/Assembly/CMakeLists.txt b/src/Mod/Assembly/CMakeLists.txt index 13439df9b4..3c48c61cff 100644 --- a/src/Mod/Assembly/CMakeLists.txt +++ b/src/Mod/Assembly/CMakeLists.txt @@ -9,6 +9,7 @@ set(Assembly_Scripts CommandCreateAssembly.py CommandCreateBom.py CommandInsertLink.py + CommandInsertNewPart.py CommandSolveAssembly.py CommandCreateJoint.py CommandCreateView.py diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index 7d65179837..c31629dd40 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -43,6 +43,43 @@ __author__ = "Ondsel" __url__ = "https://www.freecad.org" +tooltip = ( + "

" + + QT_TRANSLATE_NOOP( + "Assembly_InsertLink", + "Insert a component into the active assembly. This will create dynamic links to parts, bodies, primitives, and assemblies. To insert external components, make sure that the file is open in the current session", + ) + + "

  • " + + QT_TRANSLATE_NOOP("Assembly_InsertLink", "Insert by left clicking items in the list.") + + "
  • " + + QT_TRANSLATE_NOOP("Assembly_InsertLink", "Remove by right clicking items in the list.") + + "
  • " + + QT_TRANSLATE_NOOP( + "Assembly_InsertLink", + "Press shift to add several instances of the component while clicking on the view.", + ) + + "

" +) + + +class CommandGroupInsert: + def GetCommands(self): + return ("Assembly_InsertLink", "Assembly_InsertNewPart") + + def GetResources(self): + """Set icon, menu and tooltip.""" + + return { + "Pixmap": "Assembly_InsertLink", + "MenuText": QT_TRANSLATE_NOOP("Assembly_Insert", "Insert"), + "ToolTip": tooltip, + "CmdType": "ForEdit", + } + + def IsActive(self): + return UtilsAssembly.isAssemblyCommandActive() + + class CommandInsertLink: def __init__(self): pass @@ -52,23 +89,7 @@ class CommandInsertLink: "Pixmap": "Assembly_InsertLink", "MenuText": QT_TRANSLATE_NOOP("Assembly_InsertLink", "Insert Component"), "Accel": "I", - "ToolTip": "

" - + QT_TRANSLATE_NOOP( - "Assembly_InsertLink", - "Insert a component into the active assembly. This will create dynamic links to parts, bodies, primitives, and assemblies. To insert external components, make sure that the file is open in the current session", - ) - + "

  • " - + QT_TRANSLATE_NOOP("Assembly_InsertLink", "Insert by left clicking items in the list.") - + "
  • " - + QT_TRANSLATE_NOOP( - "Assembly_InsertLink", "Remove by right clicking items in the list." - ) - + "
  • " - + QT_TRANSLATE_NOOP( - "Assembly_InsertLink", - "Press shift to add several instances of the component while clicking on the view.", - ) - + "

", + "ToolTip": tooltip, "CmdType": "ForEdit", } @@ -611,3 +632,4 @@ class TaskAssemblyInsertLink(QtCore.QObject): if App.GuiUp: Gui.addCommand("Assembly_InsertLink", CommandInsertLink()) + Gui.addCommand("Assembly_Insert", CommandGroupInsert()) diff --git a/src/Mod/Assembly/CommandInsertNewPart.py b/src/Mod/Assembly/CommandInsertNewPart.py new file mode 100644 index 0000000000..edcc2cee73 --- /dev/null +++ b/src/Mod/Assembly/CommandInsertNewPart.py @@ -0,0 +1,194 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# /************************************************************************** +# * +# Copyright (c) 2024 Ondsel * +# * +# This file is part of FreeCAD. * +# * +# FreeCAD is free software: you can redistribute it and/or modify it * +# under the terms of the GNU Lesser General Public License as * +# published by the Free Software Foundation, either version 2.1 of the * +# License, or (at your option) any later version. * +# * +# FreeCAD 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 * +# Lesser General Public License for more details. * +# * +# You should have received a copy of the GNU Lesser General Public * +# License along with FreeCAD. If not, see * +# . * +# * +# **************************************************************************/ + +import re +import os +import FreeCAD as App + +from PySide.QtCore import QT_TRANSLATE_NOOP + +if App.GuiUp: + import FreeCADGui as Gui + from PySide import QtCore, QtGui, QtWidgets + from PySide.QtGui import QIcon + from PySide.QtCore import QTimer + +import UtilsAssembly +import Preferences +import JointObject + +translate = App.Qt.translate + +__title__ = "Assembly Command Insert New Part" +__author__ = "Ondsel" +__url__ = "https://www.freecad.org" + + +class CommandInsertNewPart: + def __init__(self): + pass + + def GetResources(self): + return { + "Pixmap": "Geofeaturegroup", + "MenuText": QT_TRANSLATE_NOOP("Assembly_InsertNewPart", "Insert a new part"), + "Accel": "P", + "ToolTip": "

" + + QT_TRANSLATE_NOOP( + "Assembly_InsertNewPart", + "Insert a new part into the active assembly. The new part's origin can be positioned in the assembly.", + ) + + "

", + "CmdType": "ForEdit", + } + + def IsActive(self): + return UtilsAssembly.isAssemblyCommandActive() + + def Activated(self): + panel = TaskAssemblyNewPart() + Gui.Control.showDialog(panel) + + +class TaskAssemblyNewPart(JointObject.TaskAssemblyCreateJoint): + def __init__(self): + super().__init__(0, None, True) + + self.assembly = UtilsAssembly.activeAssembly() + + # Retrieve the existing layout of `self.form` + mainLayout = self.form.layout() + + # Add a name input + nameLayout = QtWidgets.QHBoxLayout() + nameLabel = QtWidgets.QLabel(translate("Assembly", "Part name")) + self.nameEdit = QtWidgets.QLineEdit() + nameLayout.addWidget(nameLabel) + nameLayout.addWidget(self.nameEdit) + mainLayout.addLayout(nameLayout) + self.nameEdit.setText(translate("Assembly", "Part")) + + # Add a checkbox + self.createInNewFileCheck = QtWidgets.QCheckBox( + translate("Assembly", "Create part in new file") + ) + mainLayout.addWidget(self.createInNewFileCheck) + self.createInNewFileCheck.setChecked( + Preferences.preferences().GetBool("PartInNewFile", True) + ) + + # Wrap the joint creation UI in a groupbox + jointGroupBox = QtWidgets.QGroupBox(translate("Assembly", "Joint new part origin")) + jointLayout = QtWidgets.QVBoxLayout(jointGroupBox) + jointLayout.addWidget(self.jForm) + jointLayout.setContentsMargins(0, 0, 0, 0) + jointLayout.setSpacing(0) + mainLayout.addWidget(jointGroupBox) + + self.link = self.assembly.newObject("App::Link", "Link") + # add the link as the first object of the joint + Gui.Selection.addSelection( + self.assembly.Document.Name, self.assembly.Name, self.link.Name + "." + ) + + def createPart(self): + partName = self.nameEdit.text() + newFile = self.createInNewFileCheck.isChecked() + + doc = self.assembly.Document + if newFile: + doc = App.newDocument(partName) + + part, body = UtilsAssembly.createPart(partName, doc) + + App.setActiveDocument(self.assembly.Document.Name) + + # Then we need to link the part. + if newFile: + # New file must be saved or we can't link + if not Gui.getDocument(doc).saveAs(): + msgBox = QtWidgets.QMessageBox() + msgBox.setIcon(QtWidgets.QMessageBox.Warning) + msgBox.setText( + "If the new document is not saved the new part cannot be linked in the assembly." + ) + msgBox.setWindowTitle(translate("Assembly", "Save Document")) + saveButton = msgBox.addButton( + translate("Assembly", "Save"), QtWidgets.QMessageBox.AcceptRole + ) + cancelButton = msgBox.addButton( + translate("Assembly", "Don't link"), QtWidgets.QMessageBox.RejectRole + ) + + msgBox.exec_() + + if not (msgBox.clickedButton() == saveButton and Gui.getDocument(doc).saveAs()): + return + + self.link.LinkedObject = part + self.link.touch() + + self.link.Label = part.Label + + # Set the body as active in the assembly doc + self.expandLinkManually(self.link) + doc = self.assembly.Document + Gui.getDocument(doc).ActiveView.setActiveObject("pdbody", body) + doc.recompute() + + def expandLinkManually(self, link): + # Shoud not be necessary + # This is a workaround of https://github.com/FreeCAD/FreeCAD/issues/17904 + mw = Gui.getMainWindow() + trees = mw.findChildren(QtGui.QTreeWidget) + + Gui.Selection.addSelection(link) + for tree in trees: + for item in tree.selectedItems(): + tree.expandItem(item) + + def accept(self): + if len(self.refs) != 2: + # if the joint is not complete we cancel the joint but not the new part! + self.joint.Document.removeObject(self.joint.Name) + else: + JointObject.solveIfAllowed(self.assembly) + self.joint.Visibility = False + cmds = UtilsAssembly.generatePropertySettings(self.joint) + Gui.doCommand(cmds) + + self.createPart() + + self.deactivate() + + App.closeActiveTransaction() + + return True + + def deactivate(self): + Preferences.preferences().SetBool("PartInNewFile", self.createInNewFileCheck.isChecked()) + super().deactivate() + + +if App.GuiUp: + Gui.addCommand("Assembly_InsertNewPart", CommandInsertNewPart()) diff --git a/src/Mod/Assembly/InitGui.py b/src/Mod/Assembly/InitGui.py index 369d777d84..64d266b362 100644 --- a/src/Mod/Assembly/InitGui.py +++ b/src/Mod/Assembly/InitGui.py @@ -63,7 +63,7 @@ class AssemblyWorkbench(Workbench): # load the builtin modules from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP - import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT, CommandCreateView, CommandCreateBom + import CommandCreateAssembly, CommandInsertLink, CommandInsertNewPart, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT, CommandCreateView, CommandCreateBom import Preferences FreeCADGui.addLanguagePath(":/translations") @@ -76,7 +76,7 @@ class AssemblyWorkbench(Workbench): # build commands list cmdList = [ "Assembly_CreateAssembly", - "Assembly_InsertLink", + "Assembly_Insert", "Assembly_SolveAssembly", "Assembly_CreateView", "Assembly_CreateBom", diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index ba4405901b..484d6d55a6 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -32,6 +32,7 @@ from collections.abc import Sequence if App.GuiUp: import FreeCADGui as Gui + from PySide import QtWidgets __title__ = "Assembly Joint object" __author__ = "Ondsel" @@ -1170,6 +1171,9 @@ class MakeJointSelGate: ): if UtilsAssembly.isLink(selected_object): linked = selected_object.getLinkedObject() + if linked == selected_object: + # We accept empty links + return True if not (linked.isDerivedFrom("Part::Feature") or linked.isDerivedFrom("App::Part")): return False @@ -1183,7 +1187,7 @@ activeTask = None class TaskAssemblyCreateJoint(QtCore.QObject): - def __init__(self, jointTypeIndex, jointObj=None): + def __init__(self, jointTypeIndex, jointObj=None, subclass=False): super().__init__() global activeTask @@ -1210,22 +1214,33 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.assembly.ViewObject.MoveOnlyPreselected = True self.assembly.ViewObject.MoveInCommand = False - self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateJoint.ui") + # Create a top-level container widget for subclasses of TaskAssemblyCreateJoint + self.form = QtWidgets.QWidget() + + # Load the joint creation UI and parent it to `self.form` + self.jForm = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateJoint.ui", self.form) + + # Create a layout for `self.form` and add `self.jForm` to it + layout = QtWidgets.QVBoxLayout(self.form) + if not subclass: + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(self.jForm) if self.activeType == "Part": - self.form.setWindowTitle("Match parts") - self.form.jointType.hide() + self.jForm.setWindowTitle("Match parts") + self.jForm.jointType.hide() - self.form.jointType.addItems(TranslatedJointTypes) + self.jForm.jointType.addItems(TranslatedJointTypes) - self.form.jointType.setCurrentIndex(jointTypeIndex) - self.jType = JointTypes[self.form.jointType.currentIndex()] - self.form.jointType.currentIndexChanged.connect(self.onJointTypeChanged) + self.jForm.jointType.setCurrentIndex(jointTypeIndex) + self.jType = JointTypes[self.jForm.jointType.currentIndex()] + self.jForm.jointType.currentIndexChanged.connect(self.onJointTypeChanged) - self.form.reverseRotCheckbox.setChecked(self.jType == "Gears") - self.form.reverseRotCheckbox.stateChanged.connect(self.reverseRotToggled) + self.jForm.reverseRotCheckbox.setChecked(self.jType == "Gears") + self.jForm.reverseRotCheckbox.stateChanged.connect(self.reverseRotToggled) - self.form.advancedOffsetCheckbox.stateChanged.connect(self.advancedOffsetToggled) + self.jForm.advancedOffsetCheckbox.stateChanged.connect(self.advancedOffsetToggled) if jointObj: Gui.Selection.clearSelection() @@ -1240,7 +1255,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): else: self.creating = True - self.jointName = self.form.jointType.currentText().replace(" ", "") + self.jointName = self.jForm.jointType.currentText().replace(" ", "") if self.activeType == "Part": App.setActiveTransaction("Transform") else: @@ -1254,33 +1269,33 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.adaptUi() - self.form.distanceSpinbox.valueChanged.connect(self.onDistanceChanged) - self.form.distanceSpinbox2.valueChanged.connect(self.onDistance2Changed) - self.form.offsetSpinbox.valueChanged.connect(self.onOffsetChanged) - self.form.rotationSpinbox.valueChanged.connect(self.onRotationChanged) - bind = Gui.ExpressionBinding(self.form.distanceSpinbox).bind(self.joint, "Distance") - bind = Gui.ExpressionBinding(self.form.distanceSpinbox2).bind(self.joint, "Distance2") - bind = Gui.ExpressionBinding(self.form.offsetSpinbox).bind(self.joint, "Offset2.Base.z") - bind = Gui.ExpressionBinding(self.form.rotationSpinbox).bind( + self.jForm.distanceSpinbox.valueChanged.connect(self.onDistanceChanged) + self.jForm.distanceSpinbox2.valueChanged.connect(self.onDistance2Changed) + self.jForm.offsetSpinbox.valueChanged.connect(self.onOffsetChanged) + self.jForm.rotationSpinbox.valueChanged.connect(self.onRotationChanged) + bind = Gui.ExpressionBinding(self.jForm.distanceSpinbox).bind(self.joint, "Distance") + bind = Gui.ExpressionBinding(self.jForm.distanceSpinbox2).bind(self.joint, "Distance2") + bind = Gui.ExpressionBinding(self.jForm.offsetSpinbox).bind(self.joint, "Offset2.Base.z") + bind = Gui.ExpressionBinding(self.jForm.rotationSpinbox).bind( self.joint, "Offset2.Rotation.Yaw" ) - self.form.offset1Button.clicked.connect(self.onOffset1Clicked) - self.form.offset2Button.clicked.connect(self.onOffset2Clicked) - self.form.PushButtonReverse.clicked.connect(self.onReverseClicked) + self.jForm.offset1Button.clicked.connect(self.onOffset1Clicked) + self.jForm.offset2Button.clicked.connect(self.onOffset2Clicked) + self.jForm.PushButtonReverse.clicked.connect(self.onReverseClicked) - self.form.limitCheckbox1.stateChanged.connect(self.adaptUi) - self.form.limitCheckbox2.stateChanged.connect(self.adaptUi) - self.form.limitCheckbox3.stateChanged.connect(self.adaptUi) - self.form.limitCheckbox4.stateChanged.connect(self.adaptUi) + self.jForm.limitCheckbox1.stateChanged.connect(self.adaptUi) + self.jForm.limitCheckbox2.stateChanged.connect(self.adaptUi) + self.jForm.limitCheckbox3.stateChanged.connect(self.adaptUi) + self.jForm.limitCheckbox4.stateChanged.connect(self.adaptUi) - self.form.limitLenMinSpinbox.valueChanged.connect(self.onLimitLenMinChanged) - self.form.limitLenMaxSpinbox.valueChanged.connect(self.onLimitLenMaxChanged) - self.form.limitRotMinSpinbox.valueChanged.connect(self.onLimitRotMinChanged) - self.form.limitRotMaxSpinbox.valueChanged.connect(self.onLimitRotMaxChanged) - bind = Gui.ExpressionBinding(self.form.limitLenMinSpinbox).bind(self.joint, "LengthMin") - bind = Gui.ExpressionBinding(self.form.limitLenMaxSpinbox).bind(self.joint, "LengthMax") - bind = Gui.ExpressionBinding(self.form.limitRotMinSpinbox).bind(self.joint, "AngleMin") - bind = Gui.ExpressionBinding(self.form.limitRotMaxSpinbox).bind(self.joint, "AngleMax") + self.jForm.limitLenMinSpinbox.valueChanged.connect(self.onLimitLenMinChanged) + self.jForm.limitLenMaxSpinbox.valueChanged.connect(self.onLimitLenMaxChanged) + self.jForm.limitRotMinSpinbox.valueChanged.connect(self.onLimitRotMinChanged) + self.jForm.limitRotMaxSpinbox.valueChanged.connect(self.onLimitRotMaxChanged) + bind = Gui.ExpressionBinding(self.jForm.limitLenMinSpinbox).bind(self.joint, "LengthMin") + bind = Gui.ExpressionBinding(self.jForm.limitLenMaxSpinbox).bind(self.joint, "LengthMax") + bind = Gui.ExpressionBinding(self.jForm.limitRotMinSpinbox).bind(self.joint, "AngleMin") + bind = Gui.ExpressionBinding(self.jForm.limitRotMaxSpinbox).bind(self.joint, "AngleMax") if self.creating: # This has to be after adaptUi so that properties default values are adapted @@ -1299,7 +1314,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.callbackMove = self.view.addEventCallback("SoLocation2Event", self.moveMouse) self.callbackKey = self.view.addEventCallback("SoKeyboardEvent", self.KeyboardEvent) - self.form.featureList.installEventFilter(self) + self.jForm.featureList.installEventFilter(self) self.addition_rejected = False @@ -1393,7 +1408,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.updateJoint() def createJointObject(self): - type_index = self.form.jointType.currentIndex() + type_index = self.jForm.jointType.currentIndex() if self.activeType == "Part": self.joint = self.assembly.newObject("App::FeaturePython", "Temporary joint") @@ -1406,139 +1421,139 @@ class TaskAssemblyCreateJoint(QtCore.QObject): ViewProviderJoint(self.joint.ViewObject) def onJointTypeChanged(self, index): - self.jType = JointTypes[self.form.jointType.currentIndex()] + self.jType = JointTypes[self.jForm.jointType.currentIndex()] self.joint.Proxy.setJointType(self.joint, self.jType) self.adaptUi() def onDistanceChanged(self, quantity): - self.joint.Distance = self.form.distanceSpinbox.property("rawValue") + self.joint.Distance = self.jForm.distanceSpinbox.property("rawValue") def onDistance2Changed(self, quantity): - self.joint.Distance2 = self.form.distanceSpinbox2.property("rawValue") + self.joint.Distance2 = self.jForm.distanceSpinbox2.property("rawValue") def onOffsetChanged(self, quantity): if self.blockOffsetRotation: return - self.joint.Offset2.Base = App.Vector(0, 0, self.form.offsetSpinbox.property("rawValue")) + self.joint.Offset2.Base = App.Vector(0, 0, self.jForm.offsetSpinbox.property("rawValue")) def onRotationChanged(self, quantity): if self.blockOffsetRotation: return - yaw = self.form.rotationSpinbox.property("rawValue") + yaw = self.jForm.rotationSpinbox.property("rawValue") ypr = self.joint.Offset2.Rotation.getYawPitchRoll() self.joint.Offset2.Rotation.setYawPitchRoll(yaw, ypr[1], ypr[2]) def onLimitLenMinChanged(self, quantity): - if self.form.limitCheckbox1.isChecked(): - self.joint.LengthMin = self.form.limitLenMinSpinbox.property("rawValue") + if self.jForm.limitCheckbox1.isChecked(): + self.joint.LengthMin = self.jForm.limitLenMinSpinbox.property("rawValue") def onLimitLenMaxChanged(self, quantity): - if self.form.limitCheckbox2.isChecked(): - self.joint.LengthMax = self.form.limitLenMaxSpinbox.property("rawValue") + if self.jForm.limitCheckbox2.isChecked(): + self.joint.LengthMax = self.jForm.limitLenMaxSpinbox.property("rawValue") def onLimitRotMinChanged(self, quantity): - if self.form.limitCheckbox3.isChecked(): - self.joint.AngleMin = self.form.limitRotMinSpinbox.property("rawValue") + if self.jForm.limitCheckbox3.isChecked(): + self.joint.AngleMin = self.jForm.limitRotMinSpinbox.property("rawValue") def onLimitRotMaxChanged(self, quantity): - if self.form.limitCheckbox4.isChecked(): - self.joint.AngleMax = self.form.limitRotMaxSpinbox.property("rawValue") + if self.jForm.limitCheckbox4.isChecked(): + self.joint.AngleMax = self.jForm.limitRotMaxSpinbox.property("rawValue") def onReverseClicked(self): self.joint.Proxy.flipOnePart(self.joint) def reverseRotToggled(self, val): if val: - self.form.jointType.setCurrentIndex(JointTypes.index("Gears")) + self.jForm.jointType.setCurrentIndex(JointTypes.index("Gears")) else: - self.form.jointType.setCurrentIndex(JointTypes.index("Belt")) + self.jForm.jointType.setCurrentIndex(JointTypes.index("Belt")) def adaptUi(self): jType = self.jType needDistance = jType in JointUsingDistance - self.form.distanceLabel.setVisible(needDistance) - self.form.distanceSpinbox.setVisible(needDistance) + self.jForm.distanceLabel.setVisible(needDistance) + self.jForm.distanceSpinbox.setVisible(needDistance) if needDistance: if jType == "Distance": - self.form.distanceLabel.setText(translate("Assembly", "Distance")) + self.jForm.distanceLabel.setText(translate("Assembly", "Distance")) elif jType == "Angle": - self.form.distanceLabel.setText(translate("Assembly", "Angle")) + self.jForm.distanceLabel.setText(translate("Assembly", "Angle")) elif jType == "Gears" or jType == "Belt": - self.form.distanceLabel.setText(translate("Assembly", "Radius 1")) + self.jForm.distanceLabel.setText(translate("Assembly", "Radius 1")) else: - self.form.distanceLabel.setText(translate("Assembly", "Pitch radius")) + self.jForm.distanceLabel.setText(translate("Assembly", "Pitch radius")) if jType == "Angle": - self.form.distanceSpinbox.setProperty("unit", "deg") + self.jForm.distanceSpinbox.setProperty("unit", "deg") else: - self.form.distanceSpinbox.setProperty("unit", "mm") + self.jForm.distanceSpinbox.setProperty("unit", "mm") needDistance2 = jType in JointUsingDistance2 - self.form.distanceLabel2.setVisible(needDistance2) - self.form.distanceSpinbox2.setVisible(needDistance2) - self.form.reverseRotCheckbox.setVisible(needDistance2) + self.jForm.distanceLabel2.setVisible(needDistance2) + self.jForm.distanceSpinbox2.setVisible(needDistance2) + self.jForm.reverseRotCheckbox.setVisible(needDistance2) if jType in JointNoNegativeDistance: # Setting minimum to 0.01 to prevent 0 and negative values - self.form.distanceSpinbox.setProperty("minimum", 1e-7) - if self.form.distanceSpinbox.property("rawValue") == 0.0: - self.form.distanceSpinbox.setProperty("rawValue", 1.0) + self.jForm.distanceSpinbox.setProperty("minimum", 1e-7) + if self.jForm.distanceSpinbox.property("rawValue") == 0.0: + self.jForm.distanceSpinbox.setProperty("rawValue", 1.0) if jType == "Gears" or jType == "Belt": - self.form.distanceSpinbox2.setProperty("minimum", 1e-7) - if self.form.distanceSpinbox2.property("rawValue") == 0.0: - self.form.distanceSpinbox2.setProperty("rawValue", 1.0) + self.jForm.distanceSpinbox2.setProperty("minimum", 1e-7) + if self.jForm.distanceSpinbox2.property("rawValue") == 0.0: + self.jForm.distanceSpinbox2.setProperty("rawValue", 1.0) else: - self.form.distanceSpinbox.setProperty("minimum", float("-inf")) - self.form.distanceSpinbox2.setProperty("minimum", float("-inf")) + self.jForm.distanceSpinbox.setProperty("minimum", float("-inf")) + self.jForm.distanceSpinbox2.setProperty("minimum", float("-inf")) - advancedOffset = self.form.advancedOffsetCheckbox.isChecked() + advancedOffset = self.jForm.advancedOffsetCheckbox.isChecked() needOffset = jType in JointUsingOffset needRotation = jType in JointUsingRotation - self.form.offset1Label.setVisible(advancedOffset) - self.form.offset2Label.setVisible(advancedOffset) - self.form.offset1Button.setVisible(advancedOffset) - self.form.offset2Button.setVisible(advancedOffset) - self.form.offsetLabel.setVisible(not advancedOffset and needOffset) - self.form.offsetSpinbox.setVisible(not advancedOffset and needOffset) - self.form.rotationLabel.setVisible(not advancedOffset and needRotation) - self.form.rotationSpinbox.setVisible(not advancedOffset and needRotation) + self.jForm.offset1Label.setVisible(advancedOffset) + self.jForm.offset2Label.setVisible(advancedOffset) + self.jForm.offset1Button.setVisible(advancedOffset) + self.jForm.offset2Button.setVisible(advancedOffset) + self.jForm.offsetLabel.setVisible(not advancedOffset and needOffset) + self.jForm.offsetSpinbox.setVisible(not advancedOffset and needOffset) + self.jForm.rotationLabel.setVisible(not advancedOffset and needRotation) + self.jForm.rotationSpinbox.setVisible(not advancedOffset and needRotation) - self.form.PushButtonReverse.setVisible(jType in JointUsingReverse) + self.jForm.PushButtonReverse.setVisible(jType in JointUsingReverse) needLengthLimits = jType in JointUsingLimitLength needAngleLimits = jType in JointUsingLimitAngle needLimits = needLengthLimits or needAngleLimits - self.form.groupBox_limits.setVisible(needLimits) + self.jForm.groupBox_limits.setVisible(needLimits) if needLimits: - self.joint.EnableLengthMin = self.form.limitCheckbox1.isChecked() - self.joint.EnableLengthMax = self.form.limitCheckbox2.isChecked() - self.joint.EnableAngleMin = self.form.limitCheckbox3.isChecked() - self.joint.EnableAngleMax = self.form.limitCheckbox4.isChecked() + self.joint.EnableLengthMin = self.jForm.limitCheckbox1.isChecked() + self.joint.EnableLengthMax = self.jForm.limitCheckbox2.isChecked() + self.joint.EnableAngleMin = self.jForm.limitCheckbox3.isChecked() + self.joint.EnableAngleMax = self.jForm.limitCheckbox4.isChecked() - self.form.limitCheckbox1.setVisible(needLengthLimits) - self.form.limitCheckbox2.setVisible(needLengthLimits) - self.form.limitLenMinSpinbox.setVisible(needLengthLimits) - self.form.limitLenMaxSpinbox.setVisible(needLengthLimits) + self.jForm.limitCheckbox1.setVisible(needLengthLimits) + self.jForm.limitCheckbox2.setVisible(needLengthLimits) + self.jForm.limitLenMinSpinbox.setVisible(needLengthLimits) + self.jForm.limitLenMaxSpinbox.setVisible(needLengthLimits) - self.form.limitCheckbox3.setVisible(needAngleLimits) - self.form.limitCheckbox4.setVisible(needAngleLimits) - self.form.limitRotMinSpinbox.setVisible(needAngleLimits) - self.form.limitRotMaxSpinbox.setVisible(needAngleLimits) + self.jForm.limitCheckbox3.setVisible(needAngleLimits) + self.jForm.limitCheckbox4.setVisible(needAngleLimits) + self.jForm.limitRotMinSpinbox.setVisible(needAngleLimits) + self.jForm.limitRotMaxSpinbox.setVisible(needAngleLimits) if needLengthLimits: - self.form.limitLenMinSpinbox.setEnabled(self.joint.EnableLengthMin) - self.form.limitLenMaxSpinbox.setEnabled(self.joint.EnableLengthMax) + self.jForm.limitLenMinSpinbox.setEnabled(self.joint.EnableLengthMin) + self.jForm.limitLenMaxSpinbox.setEnabled(self.joint.EnableLengthMax) self.onLimitLenMinChanged(0) # dummy value self.onLimitLenMaxChanged(0) if needAngleLimits: - self.form.limitRotMinSpinbox.setEnabled(self.joint.EnableAngleMin) - self.form.limitRotMaxSpinbox.setEnabled(self.joint.EnableAngleMax) + self.jForm.limitRotMinSpinbox.setEnabled(self.joint.EnableAngleMin) + self.jForm.limitRotMaxSpinbox.setEnabled(self.joint.EnableAngleMax) self.onLimitRotMinChanged(0) self.onLimitRotMaxChanged(0) @@ -1547,14 +1562,14 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def updateOffsetWidgets(self): # Makes sure the values in both the simplified and advanced tabs are sync. pos = self.joint.Offset1.Base - self.form.offset1Button.setText(f"({pos.x}, {pos.y}, {pos.z})") + self.jForm.offset1Button.setText(f"({pos.x}, {pos.y}, {pos.z})") pos = self.joint.Offset2.Base - self.form.offset2Button.setText(f"({pos.x}, {pos.y}, {pos.z})") + self.jForm.offset2Button.setText(f"({pos.x}, {pos.y}, {pos.z})") self.blockOffsetRotation = True - self.form.offsetSpinbox.setProperty("rawValue", pos.z) - self.form.rotationSpinbox.setProperty( + self.jForm.offsetSpinbox.setProperty("rawValue", pos.z) + self.jForm.rotationSpinbox.setProperty( "rawValue", self.joint.Offset2.Rotation.getYawPitchRoll()[0] ) self.blockOffsetRotation = False @@ -1584,23 +1599,23 @@ class TaskAssemblyCreateJoint(QtCore.QObject): Gui.Selection.addSelection(ref1[0].Document.Name, ref1[0].Name, ref1[1][0]) Gui.Selection.addSelection(ref2[0].Document.Name, ref2[0].Name, ref2[1][0]) - self.form.distanceSpinbox.setProperty("rawValue", self.joint.Distance) - self.form.distanceSpinbox2.setProperty("rawValue", self.joint.Distance2) - self.form.offsetSpinbox.setProperty("rawValue", self.joint.Offset2.Base.z) - self.form.rotationSpinbox.setProperty( + self.jForm.distanceSpinbox.setProperty("rawValue", self.joint.Distance) + self.jForm.distanceSpinbox2.setProperty("rawValue", self.joint.Distance2) + self.jForm.offsetSpinbox.setProperty("rawValue", self.joint.Offset2.Base.z) + self.jForm.rotationSpinbox.setProperty( "rawValue", self.joint.Offset2.Rotation.getYawPitchRoll()[0] ) - self.form.limitCheckbox1.setChecked(self.joint.EnableLengthMin) - self.form.limitCheckbox2.setChecked(self.joint.EnableLengthMax) - self.form.limitCheckbox3.setChecked(self.joint.EnableAngleMin) - self.form.limitCheckbox4.setChecked(self.joint.EnableAngleMax) - self.form.limitLenMinSpinbox.setProperty("rawValue", self.joint.LengthMin) - self.form.limitLenMaxSpinbox.setProperty("rawValue", self.joint.LengthMax) - self.form.limitRotMinSpinbox.setProperty("rawValue", self.joint.AngleMin) - self.form.limitRotMaxSpinbox.setProperty("rawValue", self.joint.AngleMax) + self.jForm.limitCheckbox1.setChecked(self.joint.EnableLengthMin) + self.jForm.limitCheckbox2.setChecked(self.joint.EnableLengthMax) + self.jForm.limitCheckbox3.setChecked(self.joint.EnableAngleMin) + self.jForm.limitCheckbox4.setChecked(self.joint.EnableAngleMax) + self.jForm.limitLenMinSpinbox.setProperty("rawValue", self.joint.LengthMin) + self.jForm.limitLenMaxSpinbox.setProperty("rawValue", self.joint.LengthMax) + self.jForm.limitRotMinSpinbox.setProperty("rawValue", self.joint.AngleMin) + self.jForm.limitRotMaxSpinbox.setProperty("rawValue", self.joint.AngleMax) - self.form.jointType.setCurrentIndex(JointTypes.index(self.joint.JointType)) + self.jForm.jointType.setCurrentIndex(JointTypes.index(self.joint.JointType)) self.updateJointList() def updateJoint(self): @@ -1611,7 +1626,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.joint.Proxy.setJointConnectors(self.joint, self.refs) def updateJointList(self): - self.form.featureList.clear() + self.jForm.featureList.clear() simplified_names = [] for ref in self.refs: @@ -1621,7 +1636,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): if element_name != "": sname = sname + "." + element_name simplified_names.append(sname) - self.form.featureList.addItems(simplified_names) + self.jForm.featureList.addItems(simplified_names) def updateLimits(self): needLengthLimits = self.jType in JointUsingLimitLength @@ -1629,28 +1644,28 @@ class TaskAssemblyCreateJoint(QtCore.QObject): if needLengthLimits: distance = UtilsAssembly.getJointDistance(self.joint) if ( - not self.form.limitCheckbox1.isChecked() - and self.form.limitLenMinSpinbox.property("expression") == "" + not self.jForm.limitCheckbox1.isChecked() + and self.jForm.limitLenMinSpinbox.property("expression") == "" ): - self.form.limitLenMinSpinbox.setProperty("rawValue", distance) + self.jForm.limitLenMinSpinbox.setProperty("rawValue", distance) if ( - not self.form.limitCheckbox2.isChecked() - and self.form.limitLenMaxSpinbox.property("expression") == "" + not self.jForm.limitCheckbox2.isChecked() + and self.jForm.limitLenMaxSpinbox.property("expression") == "" ): - self.form.limitLenMaxSpinbox.setProperty("rawValue", distance) + self.jForm.limitLenMaxSpinbox.setProperty("rawValue", distance) if needAngleLimits: angle = UtilsAssembly.getJointXYAngle(self.joint) / math.pi * 180 if ( - not self.form.limitCheckbox3.isChecked() - and self.form.limitRotMinSpinbox.property("expression") == "" + not self.jForm.limitCheckbox3.isChecked() + and self.jForm.limitRotMinSpinbox.property("expression") == "" ): - self.form.limitRotMinSpinbox.setProperty("rawValue", angle) + self.jForm.limitRotMinSpinbox.setProperty("rawValue", angle) if ( - not self.form.limitCheckbox4.isChecked() - and self.form.limitRotMaxSpinbox.property("expression") == "" + not self.jForm.limitCheckbox4.isChecked() + and self.jForm.limitRotMaxSpinbox.property("expression") == "" ): - self.form.limitRotMaxSpinbox.setProperty("rawValue", angle) + self.jForm.limitRotMaxSpinbox.setProperty("rawValue", angle) def moveMouse(self, info): if len(self.refs) >= 2 or ( @@ -1700,7 +1715,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.accept() def eventFilter(self, watched, event): - if self.form is not None and watched == self.form.featureList: + if self.jForm is not None and watched == self.jForm.featureList: if event.type() == QtCore.QEvent.ShortcutOverride: if event.key() == QtCore.Qt.Key_Delete: event.accept() # Accept the event only if the key is Delete @@ -1709,7 +1724,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): elif event.type() == QtCore.QEvent.KeyPress: if event.key() == QtCore.Qt.Key_Delete: - selected_indexes = self.form.featureList.selectedIndexes() + selected_indexes = self.jForm.featureList.selectedIndexes() for index in selected_indexes: row = index.row() diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 5be6e55a57..f3f24c927b 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -1223,6 +1223,24 @@ def addVertexToReference(ref, vertex_name): return ref +def createPart(partName, doc): + if not doc: + raise ValueError("No active document to add a part to.") + + part = doc.addObject("App::Part", partName) + body = part.newObject("PartDesign::Body", "Body") + # Gui.ActiveDocument.ActiveView.setActiveObject('pdbody', body) + sketch = body.newObject("Sketcher::SketchObject", "Sketch") + sketch.MapMode = "FlatFace" + sketch.AttachmentSupport = [(body.Origin.OriginFeatures[3], "")] # XY_Plane + + # add a circle as a base shape for visualisation + sketch.addGeometry(Part.Circle(App.Vector(0, 0), App.Vector(0, 0, 1), 5), False) + doc.recompute() + + return part, body + + def getLinkGroup(linkElement): if linkElement.TypeId == "App::LinkElement": for obj in linkElement.InList: