From 0a4d965baf1a0e62b78bfe542f22f946526c5e4a Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 2 Sep 2024 14:00:27 +0200 Subject: [PATCH 1/3] Assembly: Joint creation UI : add advanced offset handling. --- .../panels/TaskAssemblyCreateJoint.ui | 161 +++++++++++++----- src/Mod/Assembly/JointObject.py | 40 ++++- src/Mod/Assembly/UtilsAssembly.py | 21 ++- 3 files changed, 171 insertions(+), 51 deletions(-) diff --git a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui index 4353934916..2f60f15fb1 100644 --- a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui +++ b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui @@ -82,52 +82,121 @@ - - - - - Offset - - - - - - - - 0 - 0 - - - - mm - - - - - - - - - - - Rotation - - - - - - - - 0 - 0 - - - - deg - - - - + + + Attachement offsets + + + + + + + Simple + + + + + + + + Offset + + + + + + + + 0 + 0 + + + + mm + + + + + + + + + + + Rotation + + + + + + + + 0 + 0 + + + + deg + + + + + + + + + + Advanced + + + + + + + + Offset1 + + + + + + + By clicking this button, you can set the attachement offset of the first marker (coordinate system) of the joint. + + + + + + + + + + + + + + Offset2 + + + + + + + By clicking this button, you can set the attachement offset of the second marker (coordinate system) of the joint. + + + + + + + + + + + + + + diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index 13e426fee0..a6756cfb69 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -1288,6 +1288,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): global activeTask activeTask = self + self.blockOffsetRotation = False self.assembly = UtilsAssembly.activeAssembly() if not self.assembly: @@ -1324,7 +1325,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.distanceSpinbox2.valueChanged.connect(self.onDistance2Changed) self.form.offsetSpinbox.valueChanged.connect(self.onOffsetChanged) self.form.rotationSpinbox.valueChanged.connect(self.onRotationChanged) - self.form.PushButtonReverse.clicked.connect(self.onReverseClicked) + self.form.offset1Button.clicked.connect(self.onOffset1Clicked) + self.form.offset2Button.clicked.connect(self.onOffset2Clicked) self.form.limitCheckbox1.stateChanged.connect(self.adaptUi) self.form.limitCheckbox2.stateChanged.connect(self.adaptUi) @@ -1338,6 +1340,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.reverseRotCheckbox.setChecked(self.jType == "Gears") self.form.reverseRotCheckbox.stateChanged.connect(self.reverseRotToggled) + self.form.offsetTabs.currentChanged.connect(self.on_offset_tab_changed) + if jointObj: Gui.Selection.clearSelection() self.creating = False @@ -1502,9 +1506,15 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.joint.Distance2 = self.form.distanceSpinbox2.property("rawValue") def onOffsetChanged(self, quantity): + if self.blockOffsetRotation: + return + self.joint.Offset2.Base = App.Vector(0, 0, self.form.offsetSpinbox.property("rawValue")) def onRotationChanged(self, quantity): + if self.blockOffsetRotation: + return + yaw = self.form.rotationSpinbox.property("rawValue") ypr = self.joint.Offset2.Rotation.getYawPitchRoll() self.joint.Offset2.Rotation.setYawPitchRoll(yaw, ypr[1], ypr[2]) @@ -1645,6 +1655,34 @@ class TaskAssemblyCreateJoint(QtCore.QObject): else: self.form.groupBox_limits.hide() + self.updateOffsetWidgets() + + 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})") + + pos = self.joint.Offset2.Base + self.form.offset2Button.setText(f"({pos.x}, {pos.y}, {pos.z})") + + self.blockOffsetRotation = True + self.form.offsetSpinbox.setProperty("rawValue", pos.z) + self.form.rotationSpinbox.setProperty( + "rawValue", self.joint.Offset2.Rotation.getYawPitchRoll()[0] + ) + self.blockOffsetRotation = False + + def on_offset_tab_changed(self): + self.updateOffsetWidgets() + + def onOffset1Clicked(self): + UtilsAssembly.openEditingPlacementDialog(self.joint, "Offset1") + self.updateOffsetWidgets() + + def onOffset2Clicked(self): + UtilsAssembly.openEditingPlacementDialog(self.joint, "Offset2") + self.updateOffsetWidgets() + def updateTaskboxFromJoint(self): self.refs = [] self.presel_ref = None diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 3c2bdcfee2..8933abe116 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -28,12 +28,10 @@ import Part if App.GuiUp: import FreeCADGui as Gui - -import PySide.QtCore as QtCore -import PySide.QtGui as QtGui + from PySide import QtCore, QtGui, QtWidgets -# translate = App.Qt.translate +translate = App.Qt.translate __title__ = "Assembly utilitary functions" __author__ = "Ondsel" @@ -754,6 +752,21 @@ def findCylindersIntersection(obj, surface, edge, elt_index): return surface.Center +def openEditingPlacementDialog(obj, propName): + task_placement = Gui.TaskPlacement() + dialog = task_placement.form + + # Connect to the placement property + task_placement.setPlacement(getattr(obj, propName)) + task_placement.setSelection([obj]) + task_placement.setPropertyName(propName) + task_placement.bindObject() + task_placement.setIgnoreTransactions(True) + + dialog.findChild(QtWidgets.QPushButton, "selectedVertex").hide() + dialog.exec_() + + def applyOffsetToPlacement(plc, offset): plc.Base = plc.Base + plc.Rotation.multVec(offset) return plc From e0471f580dc9d39e8c29f200e48f0abb05d19896 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Tue, 10 Sep 2024 16:54:40 +0200 Subject: [PATCH 2/3] Assembly: Create SoSwitchMarker.py to externalize the coin stuff that draw the joint's markers(JCS) --- src/Mod/Assembly/CMakeLists.txt | 1 + src/Mod/Assembly/JointObject.py | 175 ++++------------------------ src/Mod/Assembly/SoSwitchMarker.py | 181 +++++++++++++++++++++++++++++ src/Mod/Assembly/UtilsAssembly.py | 34 +++++- 4 files changed, 238 insertions(+), 153 deletions(-) create mode 100644 src/Mod/Assembly/SoSwitchMarker.py diff --git a/src/Mod/Assembly/CMakeLists.txt b/src/Mod/Assembly/CMakeLists.txt index 65de263288..13439df9b4 100644 --- a/src/Mod/Assembly/CMakeLists.txt +++ b/src/Mod/Assembly/CMakeLists.txt @@ -17,6 +17,7 @@ set(Assembly_Scripts JointObject.py Preferences.py AssemblyImport.py + SoSwitchMarker.py UtilsAssembly.py ) diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index a6756cfb69..31ea385055 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -40,6 +40,8 @@ from pivy import coin import UtilsAssembly import Preferences +from SoSwitchMarker import SoSwitchMarker + translate = App.Qt.translate TranslatedJointTypes = [ @@ -792,134 +794,18 @@ class ViewProviderJoint: def attach(self, vobj): """Setup the scene sub-graph of the view provider, this method is mandatory""" - self.axis_thickness = 3 - self.scaleFactor = 20 - - view_params = App.ParamGet("User parameter:BaseApp/Preferences/View") - param_x_axis_color = view_params.GetUnsigned("AxisXColor", 0xCC333300) - param_y_axis_color = view_params.GetUnsigned("AxisYColor", 0x33CC3300) - param_z_axis_color = view_params.GetUnsigned("AxisZColor", 0x3333CC00) - - self.x_axis_so_color = coin.SoBaseColor() - self.x_axis_so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_x_axis_color)) - self.y_axis_so_color = coin.SoBaseColor() - self.y_axis_so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_y_axis_color)) - self.z_axis_so_color = coin.SoBaseColor() - self.z_axis_so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_z_axis_color)) - self.app_obj = vobj.Object - app_doc = self.app_obj.Document - self.gui_doc = Gui.getDocument(app_doc) - self.transform1 = coin.SoTransform() - self.transform2 = coin.SoTransform() - self.transform3 = coin.SoTransform() - - self.draw_style = coin.SoDrawStyle() - self.draw_style.style = coin.SoDrawStyle.LINES - self.draw_style.lineWidth = self.axis_thickness - - self.switch_JCS1 = self.JCS_sep(self.transform1) - self.switch_JCS2 = self.JCS_sep(self.transform2) - self.switch_JCS_preview = self.JCS_sep(self.transform3) - - self.pick = coin.SoPickStyle() - self.setPickableState(True) + self.switch_JCS1 = SoSwitchMarker(vobj) + self.switch_JCS2 = SoSwitchMarker(vobj) + self.switch_JCS_preview = SoSwitchMarker(vobj) self.display_mode = coin.SoType.fromName("SoFCSelection").createInstance() - self.display_mode.addChild(self.pick) self.display_mode.addChild(self.switch_JCS1) self.display_mode.addChild(self.switch_JCS2) self.display_mode.addChild(self.switch_JCS_preview) vobj.addDisplayMode(self.display_mode, "Wireframe") - def JCS_sep(self, soTransform): - JCS = coin.SoAnnotation() - JCS.addChild(soTransform) - - base_plane_sep = self.plane_sep(0.4, 15) - X_axis_sep = self.line_sep([0.5, 0, 0], [1, 0, 0], self.x_axis_so_color) - Y_axis_sep = self.line_sep([0, 0.5, 0], [0, 1, 0], self.y_axis_so_color) - Z_axis_sep = self.line_sep([0, 0, 0], [0, 0, 1], self.z_axis_so_color) - - JCS.addChild(base_plane_sep) - JCS.addChild(X_axis_sep) - JCS.addChild(Y_axis_sep) - JCS.addChild(Z_axis_sep) - - switch_JCS = coin.SoSwitch() - switch_JCS.addChild(JCS) - switch_JCS.whichChild = coin.SO_SWITCH_NONE - - return switch_JCS - - def line_sep(self, startPoint, endPoint, soColor): - line = coin.SoLineSet() - line.numVertices.setValue(2) - coords = coin.SoCoordinate3() - coords.point.setValues(0, [startPoint, endPoint]) - - axis_sep = coin.SoAnnotation() - axis_sep.addChild(self.draw_style) - axis_sep.addChild(soColor) - axis_sep.addChild(coords) - axis_sep.addChild(line) - - scale = coin.SoType.fromName("SoShapeScale").createInstance() - scale.setPart("shape", axis_sep) - scale.scaleFactor = self.scaleFactor - - return scale - - def plane_sep(self, size, num_vertices): - coords = coin.SoCoordinate3() - - for i in range(num_vertices): - angle = float(i) / num_vertices * 2.0 * math.pi - x = math.cos(angle) * size - y = math.sin(angle) * size - coords.point.set1Value(i, x, y, 0) - - face = coin.SoFaceSet() - face.numVertices.setValue(num_vertices) - - transform = coin.SoTransform() - transform.translation.setValue(0, 0, 0) - - draw_style = coin.SoDrawStyle() - draw_style.style = coin.SoDrawStyle.FILLED - - material = coin.SoMaterial() - material.diffuseColor.setValue([0.5, 0.5, 0.5]) - material.ambientColor.setValue([0.5, 0.5, 0.5]) - material.specularColor.setValue([0.5, 0.5, 0.5]) - material.emissiveColor.setValue([0.5, 0.5, 0.5]) - material.transparency.setValue(0.3) - - face_sep = coin.SoAnnotation() - face_sep.addChild(transform) - face_sep.addChild(draw_style) - face_sep.addChild(material) - face_sep.addChild(coords) - face_sep.addChild(face) - - scale = coin.SoType.fromName("SoShapeScale").createInstance() - scale.setPart("shape", face_sep) - scale.scaleFactor = self.scaleFactor - - return scale - - def set_JCS_placement(self, soTransform, placement, ref): - # change plc to be relative to the origin of the document. - global_plc = UtilsAssembly.getGlobalPlacement(ref) - placement = global_plc * placement - - t = placement.Base - soTransform.translation.setValue(t.x, t.y, t.z) - - r = placement.Rotation.Q - soTransform.rotation.setValue(r[0], r[1], r[2], r[3]) - def updateData(self, joint, prop): """If a property of the handled feature has changed we have the chance to handle this here""" # joint is the handled feature, prop is the name of the property that has changed @@ -928,7 +814,7 @@ class ViewProviderJoint: plc = joint.Placement1 self.switch_JCS1.whichChild = coin.SO_SWITCH_ALL - self.set_JCS_placement(self.transform1, plc, joint.Reference1) + self.switch_JCS1.set_marker_placement(plc, joint.Reference1) else: self.switch_JCS1.whichChild = coin.SO_SWITCH_NONE @@ -937,23 +823,22 @@ class ViewProviderJoint: plc = joint.Placement2 self.switch_JCS2.whichChild = coin.SO_SWITCH_ALL - self.set_JCS_placement(self.transform2, plc, joint.Reference2) + self.switch_JCS2.set_marker_placement(plc, joint.Reference2) else: self.switch_JCS2.whichChild = coin.SO_SWITCH_NONE def showPreviewJCS(self, visible, placement=None, ref=None): if visible: self.switch_JCS_preview.whichChild = coin.SO_SWITCH_ALL - self.set_JCS_placement(self.transform3, placement, ref) + self.switch_JCS_preview.set_marker_placement(placement, ref) else: self.switch_JCS_preview.whichChild = coin.SO_SWITCH_NONE def setPickableState(self, state: bool): """Set JCS selectable or unselectable in 3D view""" - if not state: - self.pick.style.setValue(coin.SoPickStyle.UNPICKABLE) - else: - self.pick.style.setValue(coin.SoPickStyle.SHAPE_ON_TOP) + self.switch_JCS1.setPickableState(state) + self.switch_JCS2.setPickableState(state) + self.switch_JCS_preview.setPickableState(state) def getDisplayModes(self, obj): """Return a list of display modes.""" @@ -968,15 +853,10 @@ class ViewProviderJoint: def onChanged(self, vp, prop): """Here we can do something when a single property got changed""" # App.Console.PrintMessage("Change property: " + str(prop) + "\n") - if prop == "color_X_axis": - c = vp.getPropertyByName("color_X_axis") - self.x_axis_so_color.rgb.setValue(c[0], c[1], c[2]) - if prop == "color_Y_axis": - c = vp.getPropertyByName("color_Y_axis") - self.x_axis_so_color.rgb.setValue(c[0], c[1], c[2]) - if prop == "color_Z_axis": - c = vp.getPropertyByName("color_Z_axis") - self.x_axis_so_color.rgb.setValue(c[0], c[1], c[2]) + if prop == "color_X_axis" or prop == "color_Y_axis" or prop == "color_Z_axis": + self.switch_JCS1.onChanged(vp, prop) + self.switch_JCS2.onChanged(vp, prop) + self.switch_JCS_preview.onChanged(vp, prop) def getIcon(self): if self.app_obj.JointType == "Fixed": @@ -1033,7 +913,10 @@ class ViewProviderJoint: self.gui_doc.setEdit(assembly) panel = TaskAssemblyCreateJoint(0, vobj.Object) - Gui.Control.showDialog(panel) + dialog = Gui.Control.showDialog(panel) + if dialog is not None: + dialog.setAutoCloseOnTransactionChange(True) + dialog.setDocumentName(App.ActiveDocument.Name) return True @@ -1377,7 +1260,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): # before handleInitialSelection tries to solve. self.handleInitialSelection() - self.setJointsPickableState(False) + UtilsAssembly.setJointsPickableState(self.doc, False) Gui.Selection.addSelectionGate( MakeJointSelGate(self, self.assembly), Gui.Selection.ResolveMode.NoResolve @@ -1438,7 +1321,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): Gui.Selection.clearSelection() self.view.removeEventCallback("SoLocation2Event", self.callbackMove) self.view.removeEventCallback("SoKeyboardEvent", self.callbackKey) - self.setJointsPickableState(True) + UtilsAssembly.setJointsPickableState(self.doc, True) if Gui.Control.activeDialog(): Gui.Control.closeDialog() @@ -1783,7 +1666,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): # newPos = self.view.getPoint(*info["Position"]) is not OK: it's not pos on the object but on the focal plane newPos = App.Vector(cursor_info["x"], cursor_info["y"], cursor_info["z"]) - vertex_name = UtilsAssembly.findElementClosestVertex(self.assembly, ref, newPos) + vertex_name = UtilsAssembly.findElementClosestVertex(ref, newPos) ref = UtilsAssembly.addVertexToReference(ref, vertex_name) @@ -1857,7 +1740,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): # Selection is acceptable so add it mousePos = App.Vector(mousePos[0], mousePos[1], mousePos[2]) - vertex_name = UtilsAssembly.findElementClosestVertex(self.assembly, ref, mousePos) + vertex_name = UtilsAssembly.findElementClosestVertex(ref, mousePos) # add the vertex name to the reference ref = UtilsAssembly.addVertexToReference(ref, vertex_name) @@ -1895,15 +1778,3 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def clearSelection(self, doc_name): self.refs.clear() self.updateJoint() - - def setJointsPickableState(self, state: bool): - """Make all joints in assembly selectable (True) or unselectable (False) in 3D view""" - if self.activeType == "Assembly": - jointGroup = UtilsAssembly.getJointGroup(self.assembly) - for joint in jointGroup.Group: - if hasattr(joint, "JointType"): - joint.ViewObject.Proxy.setPickableState(state) - else: - for obj in self.assembly.OutList: - if obj.TypeId == "App::FeaturePython" and hasattr(obj, "JointType"): - obj.ViewObject.Proxy.setPickableState(state) diff --git a/src/Mod/Assembly/SoSwitchMarker.py b/src/Mod/Assembly/SoSwitchMarker.py new file mode 100644 index 0000000000..0b7e3a9bf5 --- /dev/null +++ b/src/Mod/Assembly/SoSwitchMarker.py @@ -0,0 +1,181 @@ +# 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 math + +import FreeCAD as App + + +if App.GuiUp: + import FreeCADGui as Gui + +__title__ = "Assembly Marker Inventor object" +__author__ = "Ondsel" +__url__ = "https://www.freecad.org" + +from pivy import coin +import UtilsAssembly +import Preferences + + +class SoSwitchMarker(coin.SoSwitch): + def __init__(self, vobj): + super().__init__() # Initialize the SoSwitch base class + + self.axis_thickness = 3 + self.scaleFactor = 20 + + view_params = App.ParamGet("User parameter:BaseApp/Preferences/View") + param_x_axis_color = view_params.GetUnsigned("AxisXColor", 0xCC333300) + param_y_axis_color = view_params.GetUnsigned("AxisYColor", 0x33CC3300) + param_z_axis_color = view_params.GetUnsigned("AxisZColor", 0x3333CC00) + + self.x_axis_so_color = coin.SoBaseColor() + self.x_axis_so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_x_axis_color)) + self.y_axis_so_color = coin.SoBaseColor() + self.y_axis_so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_y_axis_color)) + self.z_axis_so_color = coin.SoBaseColor() + self.z_axis_so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_z_axis_color)) + + self.app_obj = vobj.Object + app_doc = self.app_obj.Document + self.gui_doc = Gui.getDocument(app_doc) + + self.transform = coin.SoTransform() + + self.draw_style = coin.SoDrawStyle() + self.draw_style.style = coin.SoDrawStyle.LINES + self.draw_style.lineWidth = self.axis_thickness + + self.pick = coin.SoPickStyle() + self.setPickableState(True) + + JCS = coin.SoAnnotation() + JCS.addChild(self.transform) + JCS.addChild(self.pick) + + base_plane_sep = self.plane_sep(0.4, 15) + X_axis_sep = self.line_sep([0.5, 0, 0], [1, 0, 0], self.x_axis_so_color) + Y_axis_sep = self.line_sep([0, 0.5, 0], [0, 1, 0], self.y_axis_so_color) + Z_axis_sep = self.line_sep([0, 0, 0], [0, 0, 1], self.z_axis_so_color) + + JCS.addChild(base_plane_sep) + JCS.addChild(X_axis_sep) + JCS.addChild(Y_axis_sep) + JCS.addChild(Z_axis_sep) + + switch_JCS = coin.SoSwitch() + self.addChild(JCS) + self.whichChild = coin.SO_SWITCH_NONE + + def line_sep(self, startPoint, endPoint, soColor): + line = coin.SoLineSet() + line.numVertices.setValue(2) + coords = coin.SoCoordinate3() + coords.point.setValues(0, [startPoint, endPoint]) + + axis_sep = coin.SoAnnotation() + axis_sep.addChild(self.draw_style) + axis_sep.addChild(soColor) + axis_sep.addChild(coords) + axis_sep.addChild(line) + + scale = coin.SoType.fromName("SoShapeScale").createInstance() + scale.setPart("shape", axis_sep) + scale.scaleFactor = self.scaleFactor + + return scale + + def plane_sep(self, size, num_vertices): + coords = coin.SoCoordinate3() + + for i in range(num_vertices): + angle = float(i) / num_vertices * 2.0 * math.pi + x = math.cos(angle) * size + y = math.sin(angle) * size + coords.point.set1Value(i, x, y, 0) + + face = coin.SoFaceSet() + face.numVertices.setValue(num_vertices) + + transform = coin.SoTransform() + transform.translation.setValue(0, 0, 0) + + draw_style = coin.SoDrawStyle() + draw_style.style = coin.SoDrawStyle.FILLED + + material = coin.SoMaterial() + material.diffuseColor.setValue([0.5, 0.5, 0.5]) + material.ambientColor.setValue([0.5, 0.5, 0.5]) + material.specularColor.setValue([0.5, 0.5, 0.5]) + material.emissiveColor.setValue([0.5, 0.5, 0.5]) + material.transparency.setValue(0.3) + + face_sep = coin.SoAnnotation() + face_sep.addChild(transform) + face_sep.addChild(draw_style) + face_sep.addChild(material) + face_sep.addChild(coords) + face_sep.addChild(face) + + scale = coin.SoType.fromName("SoShapeScale").createInstance() + scale.setPart("shape", face_sep) + scale.scaleFactor = self.scaleFactor + + return scale + + def set_marker_placement(self, placement, ref): + # change plc to be relative to the origin of the document. + global_plc = UtilsAssembly.getGlobalPlacement(ref) + placement = global_plc * placement + + t = placement.Base + self.transform.translation.setValue(t.x, t.y, t.z) + + r = placement.Rotation.Q + self.transform.rotation.setValue(r[0], r[1], r[2], r[3]) + + def setPickableState(self, state: bool): + """Set JCS selectable or unselectable in 3D view""" + if not state: + self.pick.style.setValue(coin.SoPickStyle.UNPICKABLE) + else: + self.pick.style.setValue(coin.SoPickStyle.SHAPE_ON_TOP) + + def show_marker(self, visible, placement=None, ref=None): + if visible: + self.whichChild = coin.SO_SWITCH_ALL + self.set_marker_placement(placement, ref) + else: + self.whichChild = coin.SO_SWITCH_NONE + + def onChanged(self, vp, prop): + if prop == "color_X_axis": + c = vp.getPropertyByName("color_X_axis") + self.x_axis_so_color.rgb.setValue(c[0], c[1], c[2]) + if prop == "color_Y_axis": + c = vp.getPropertyByName("color_Y_axis") + self.y_axis_so_color.rgb.setValue(c[0], c[1], c[2]) + if prop == "color_Z_axis": + c = vp.getPropertyByName("color_Z_axis") + self.z_axis_so_color.rgb.setValue(c[0], c[1], c[2]) diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 8933abe116..9b7743a3d7 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -356,7 +356,7 @@ def extract_type_and_number(element_name): return None, None -def findElementClosestVertex(assembly, ref, mousePos): +def findElementClosestVertex(ref, mousePos): element_name = getElementName(ref[1][0]) if element_name == "": return "" @@ -767,6 +767,21 @@ def openEditingPlacementDialog(obj, propName): dialog.exec_() +def setPickableState(obj, state: bool): + vobj = obj.ViewObject + if hasattr(vobj, "Proxy"): + proxy = vobj.Proxy + if hasattr(proxy, "setPickableState"): + proxy.setPickableState(state) + + +def setJointsPickableState(doc, state: bool): + """Make all joints in document selectable (True) or unselectable (False) in 3D view""" + for obj in doc.Objects: + if obj.TypeId == "App::FeaturePython" and hasattr(obj, "JointType"): + setPickableState(obj, state) + + def applyOffsetToPlacement(plc, offset): plc.Base = plc.Base + plc.Rotation.multVec(offset) return plc @@ -799,6 +814,23 @@ def arePlacementZParallel(plc1, plc2): return zAxis1.cross(zAxis2).Length < 1e-06 +def removeTNPFromSubname(doc_name, obj_name, sub_name): + rootObj = App.getDocument(doc_name).getObject(obj_name) + resolved = rootObj.resolveSubElement(sub_name) + element_name_TNP = resolved[1] + element_name = resolved[2] + + # Preprocess the sub_name to remove the TNP string + # We do this because after we need to add the vertex_name as well. + # And the names will be resolved anyway after. + if len(element_name_TNP.split(".")) == 2: + names = sub_name.split(".") + names.pop(-2) # remove the TNP string + sub_name = ".".join(names) + + return sub_name + + """ So here we want to find a placement that corresponds to a local coordinate system that would be placed at the selected vertex. - obj is usually a App::Link to a PartDesign::Body, or primitive, fasteners. But can also be directly the object.1 From 127d5dd8406d62ce5992a8acef7555b4de390343 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 5 Aug 2024 08:21:47 +0200 Subject: [PATCH 3/3] Assembly: AssemblyLinks. --- src/Mod/Assembly/App/AppAssembly.cpp | 2 + src/Mod/Assembly/App/AssemblyLink.cpp | 582 ++++++++++++++++++ src/Mod/Assembly/App/AssemblyLink.h | 96 +++ src/Mod/Assembly/App/AssemblyLinkPy.xml | 19 + src/Mod/Assembly/App/AssemblyLinkPyImp.cpp | 47 ++ src/Mod/Assembly/App/AssemblyObject.cpp | 18 +- src/Mod/Assembly/App/AssemblyObject.h | 3 +- src/Mod/Assembly/App/CMakeLists.txt | 5 + src/Mod/Assembly/App/JointGroup.cpp | 28 + src/Mod/Assembly/App/JointGroup.h | 2 + src/Mod/Assembly/CommandInsertLink.py | 13 +- src/Mod/Assembly/Gui/AppAssemblyGui.cpp | 2 + src/Mod/Assembly/Gui/CMakeLists.txt | 2 + src/Mod/Assembly/Gui/Resources/Assembly.qrc | 2 + .../Resources/icons/Assembly_AssemblyLink.svg | 359 +++++++++++ .../icons/Assembly_AssemblyLinkRigid.svg | 354 +++++++++++ .../panels/TaskAssemblyInsertLink.ui | 22 + src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 3 +- .../Assembly/Gui/ViewProviderAssemblyLink.cpp | 150 +++++ .../Assembly/Gui/ViewProviderAssemblyLink.h | 81 +++ .../Assembly/Gui/ViewProviderJointGroup.cpp | 7 - src/Mod/Assembly/Gui/ViewProviderJointGroup.h | 6 +- src/Mod/Assembly/UtilsAssembly.py | 4 + 23 files changed, 1792 insertions(+), 15 deletions(-) create mode 100644 src/Mod/Assembly/App/AssemblyLink.cpp create mode 100644 src/Mod/Assembly/App/AssemblyLink.h create mode 100644 src/Mod/Assembly/App/AssemblyLinkPy.xml create mode 100644 src/Mod/Assembly/App/AssemblyLinkPyImp.cpp create mode 100644 src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg create mode 100644 src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg create mode 100644 src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp create mode 100644 src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h diff --git a/src/Mod/Assembly/App/AppAssembly.cpp b/src/Mod/Assembly/App/AppAssembly.cpp index e9152d4dae..a2679171a7 100644 --- a/src/Mod/Assembly/App/AppAssembly.cpp +++ b/src/Mod/Assembly/App/AppAssembly.cpp @@ -28,6 +28,7 @@ #include #include "AssemblyObject.h" +#include "AssemblyLink.h" #include "BomObject.h" #include "BomGroup.h" #include "JointGroup.h" @@ -61,6 +62,7 @@ PyMOD_INIT_FUNC(AssemblyApp) // This function is responsible for adding inherited slots from a type's base class. Assembly::AssemblyObject ::init(); + Assembly::AssemblyLink ::init(); Assembly::BomObject ::init(); Assembly::BomGroup ::init(); diff --git a/src/Mod/Assembly/App/AssemblyLink.cpp b/src/Mod/Assembly/App/AssemblyLink.cpp new file mode 100644 index 0000000000..f8df4f771e --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLink.cpp @@ -0,0 +1,582 @@ +// 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 * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "AssemblyObject.h" +#include "JointGroup.h" + +#include "AssemblyLink.h" +#include "AssemblyLinkPy.h" + +namespace PartApp = Part; + +using namespace Assembly; + +// ================================ Assembly Object ============================ + +PROPERTY_SOURCE(Assembly::AssemblyLink, App::Part) + +AssemblyLink::AssemblyLink() +{ + ADD_PROPERTY_TYPE(Rigid, + (true), + "General", + (App::PropertyType)(App::Prop_None), + "If the sub-assembly is set to Rigid, it will act " + "as a rigid body. Else its joints will be taken into account."); + + ADD_PROPERTY_TYPE(LinkedObject, + (nullptr), + "General", + (App::PropertyType)(App::Prop_None), + "The linked assembly."); +} + +AssemblyLink::~AssemblyLink() = default; + +PyObject* AssemblyLink::getPyObject() +{ + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new AssemblyLinkPy(this), true); + } + return Py::new_reference_to(PythonObject); +} + +App::DocumentObjectExecReturn* AssemblyLink::execute() +{ + updateContents(); + + return App::Part::execute(); +} + +void AssemblyLink::onChanged(const App::Property* prop) +{ + if (App::GetApplication().isRestoring()) { + App::Part::onChanged(prop); + return; + } + + if (prop == &Rigid) { + Base::Placement movePlc; + + if (Rigid.getValue()) { + // movePlc needs to be computed before updateContents. + if (!objLinkMap.empty()) { + auto firstElement = *objLinkMap.begin(); + + App::DocumentObject* obj = firstElement.first; + App::DocumentObject* link = firstElement.second; + auto* prop = + dynamic_cast(obj->getPropertyByName("Placement")); + auto* prop2 = + dynamic_cast(link->getPropertyByName("Placement")); + if (prop && prop2) { + movePlc = prop2->getValue() * prop->getValue().inverse(); + } + } + } + + updateContents(); + + auto* propPlc = dynamic_cast(getPropertyByName("Placement")); + if (!propPlc) { + return; + } + + if (!Rigid.getValue()) { + // when the assemblyLink becomes flexible, we need to make sure its placement is + // identity or it's going to mess up moving parts placement within. + Base::Placement plc = propPlc->getValue(); + if (!plc.isIdentity()) { + propPlc->setValue(Base::Placement()); + + // We need to apply the placement of the assembly link to the children or they will + // move. + std::vector group = Group.getValues(); + for (auto* obj : group) { + if (!obj->isDerivedFrom() && !obj->isDerivedFrom() + && !obj->isDerivedFrom()) { + continue; + } + + auto* prop = + dynamic_cast(obj->getPropertyByName("Placement")); + if (prop) { + prop->setValue(plc * prop->getValue()); + } + } + + AssemblyObject::redrawJointPlacements(getJoints()); + } + } + else { + // For the assemblylink not to move to origin, we need to update its placement. + if (!movePlc.isIdentity()) { + propPlc->setValue(movePlc); + } + } + + return; + } + App::Part::onChanged(prop); +} + +void AssemblyLink::updateContents() +{ + synchronizeComponents(); + + if (isRigid()) { + ensureNoJointGroup(); + } + else { + synchronizeJoints(); + } + + purgeTouched(); +} + +void AssemblyLink::synchronizeComponents() +{ + App::Document* doc = getDocument(); + + AssemblyObject* assembly = getLinkedAssembly(); + if (!assembly) { + return; + } + + objLinkMap.clear(); + + std::vector assemblyGroup = assembly->Group.getValues(); + std::vector assemblyLinkGroup = Group.getValues(); + + // We check if a component needs to be added to the AssemblyLink + for (auto* obj : assemblyGroup) { + if (!obj->isDerivedFrom() && !obj->isDerivedFrom() + && !obj->isDerivedFrom()) { + continue; + } + + // Note, the user can have nested sub-assemblies. + // In which case we need to add an AssemblyLink and not a Link. + App::DocumentObject* link = nullptr; + bool found = false; + for (auto* obj2 : assemblyLinkGroup) { + App::DocumentObject* linkedObj; + + auto* subAsmLink = dynamic_cast(obj2); + auto* link2 = dynamic_cast(obj2); + if (subAsmLink) { + linkedObj = subAsmLink->getLinkedObject2(false); // not recursive + } + else if (link2) { + linkedObj = link2->getLinkedObject(false); // not recursive + } + else { + // We consider only Links and AssemblyLinks in the AssemblyLink. + continue; + } + + + if (linkedObj == obj) { + found = true; + link = obj2; + break; + } + } + if (!found) { + // Add a link or a AssemblyLink to it in the AssemblyLink. + if (obj->isDerivedFrom()) { + auto* asmLink = static_cast(obj); + auto* subAsmLink = new AssemblyLink(); + doc->addObject(subAsmLink, obj->getNameInDocument()); + subAsmLink->LinkedObject.setValue(obj); + subAsmLink->Rigid.setValue(asmLink->Rigid.getValue()); + subAsmLink->Label.setValue(obj->Label.getValue()); + addObject(subAsmLink); + link = subAsmLink; + } + else { + auto* appLink = new App::Link(); + doc->addObject(appLink, obj->getNameInDocument()); + appLink->LinkedObject.setValue(obj); + appLink->Label.setValue(obj->Label.getValue()); + addObject(appLink); + link = appLink; + } + } + + objLinkMap[obj] = link; + // If the assemblyLink is rigid, then we keep the placement synchronized. + if (isRigid()) { + auto* plcProp = + dynamic_cast(obj->getPropertyByName("Placement")); + auto* plcProp2 = + dynamic_cast(link->getPropertyByName("Placement")); + if (plcProp && plcProp2) { + if (!plcProp->getValue().isSame(plcProp2->getValue())) { + plcProp2->setValue(plcProp->getValue()); + } + } + } + } + + // We check if a component needs to be removed from the AssemblyLink + for (auto* link : assemblyLinkGroup) { + // We don't need to update assemblyLinkGroup after the addition since we're not removing + // something we just added. + + if (objLinkMap.find(link) != objLinkMap.end()) { + doc->removeObject(link->getNameInDocument()); + } + + /*if (!link->isDerivedFrom() && !link->isDerivedFrom()) { + // AssemblyLink should contain only Links or assembly links. + continue; + } + + auto* linkedObj = link->getLinkedObject(false); // not recursive + + bool found = false; + for (auto* obj2 : assemblyGroup) { + if (obj2 == linkedObj) { + found = true; + break; + } + } + if (!found) { + doc->removeObject(link->getNameInDocument()); + }*/ + } +} + +namespace +{ +template +void copyPropertyIfDifferent(App::DocumentObject* source, + App::DocumentObject* target, + const char* propertyName) +{ + auto sourceProp = dynamic_cast(source->getPropertyByName(propertyName)); + auto targetProp = dynamic_cast(target->getPropertyByName(propertyName)); + if (sourceProp && targetProp && sourceProp->getValue() != targetProp->getValue()) { + targetProp->setValue(sourceProp->getValue()); + } +} + +std::string removeUpToName(const std::string& sub, const std::string& name) +{ + size_t pos = sub.find(name); + if (pos != std::string::npos) { + // Move the position to the character after the found substring and the following '.' + pos += name.length() + 1; + if (pos < sub.length()) { + return sub.substr(pos); + } + } + // If s2 is not found in s1, return the original string + return sub; +} + +std::string +replaceLastOccurrence(const std::string& str, const std::string& oldStr, const std::string& newStr) +{ + size_t pos = str.rfind(oldStr); + if (pos != std::string::npos) { + std::string result = str; + result.replace(pos, oldStr.length(), newStr); + return result; + } + return str; +} +}; // namespace + +void AssemblyLink::synchronizeJoints() +{ + App::Document* doc = getDocument(); + AssemblyObject* assembly = getLinkedAssembly(); + if (!assembly) { + return; + } + + JointGroup* jGroup = ensureJointGroup(); + + std::vector assemblyJoints = + assembly->getJoints(assembly->isTouched(), false, false); + std::vector assemblyLinkJoints = getJoints(); + + // We delete the excess of joints if any + for (size_t i = assemblyJoints.size(); i < assemblyLinkJoints.size(); ++i) { + doc->removeObject(assemblyLinkJoints[i]->getNameInDocument()); + } + + // We make sure the joints match. + for (size_t i = 0; i < assemblyJoints.size(); ++i) { + App::DocumentObject* joint = assemblyJoints[i]; + App::DocumentObject* lJoint; + if (i < assemblyLinkJoints.size()) { + lJoint = assemblyLinkJoints[i]; + } + else { + auto ret = doc->copyObject({joint}); + if (ret.size() != 1) { + continue; + } + lJoint = ret[0]; + jGroup->addObject(lJoint); + } + + // Then we have to check the properties one by one. + copyPropertyIfDifferent(joint, lJoint, "Activated"); + copyPropertyIfDifferent(joint, lJoint, "Distance"); + copyPropertyIfDifferent(joint, lJoint, "Distance2"); + copyPropertyIfDifferent(joint, lJoint, "JointType"); + copyPropertyIfDifferent(joint, lJoint, "Offset1"); + copyPropertyIfDifferent(joint, lJoint, "Offset2"); + + copyPropertyIfDifferent(joint, lJoint, "Detach1"); + copyPropertyIfDifferent(joint, lJoint, "Detach2"); + + copyPropertyIfDifferent(joint, lJoint, "AngleMax"); + copyPropertyIfDifferent(joint, lJoint, "AngleMin"); + copyPropertyIfDifferent(joint, lJoint, "LengthMax"); + copyPropertyIfDifferent(joint, lJoint, "LengthMin"); + copyPropertyIfDifferent(joint, lJoint, "EnableAngleMax"); + copyPropertyIfDifferent(joint, lJoint, "EnableAngleMin"); + copyPropertyIfDifferent(joint, lJoint, "EnableLengthMax"); + copyPropertyIfDifferent(joint, lJoint, "EnableLengthMin"); + + // The reference needs to be handled specifically + handleJointReference(joint, lJoint, "Reference1"); + handleJointReference(joint, lJoint, "Reference2"); + } + + assemblyLinkJoints = getJoints(); + + AssemblyObject::recomputeJointPlacements(assemblyLinkJoints); + + for (auto* joint : assemblyLinkJoints) { + joint->purgeTouched(); + } +} + + +void AssemblyLink::handleJointReference(App::DocumentObject* joint, + App::DocumentObject* lJoint, + const char* refName) +{ + AssemblyObject* assembly = getLinkedAssembly(); + + auto prop1 = dynamic_cast(joint->getPropertyByName(refName)); + auto prop2 = dynamic_cast(lJoint->getPropertyByName(refName)); + if (!prop1 || !prop2) { + return; + } + + App::DocumentObject* obj1 = nullptr; + App::DocumentObject* obj2 = prop2->getValue(); + std::vector subs1 = prop1->getSubValues(); + std::vector subs2 = prop2->getSubValues(); + if (subs1.empty()) { + return; + } + + // Example : + // Obj1 = docA-Asm1 Subs1 = ["part1.body.pad.face0", "part1.body.pad.vertex1"] + // Obj1 = docA-Part Subs1 = ["Asm1.part1.body.pad.face0", "Asm1.part1.body.pad.vertex1"] // some + // user may put the assembly inside a part... should become : Obj2 = docB-Asm2 Subs2 = + // ["Asm1Link.part1.linkTobody.pad.face0", "Asm1Link.part1.linkTobody.pad.vertex1"] Obj2 = + // docB-Part Sub2 = ["Asm2.Asm1Link.part1.linkTobody.pad.face0", + // "Asm2.Asm1Link.part1.linkTobody.pad.vertex1"] + + std::string asmLink = getNameInDocument(); + for (auto& sub : subs1) { + // First let's remove 'Asm1' name and everything before if any. + sub = removeUpToName(sub, assembly->getNameInDocument()); + // Then we add the assembly link name. + sub = asmLink + "." + sub; + // Then the question is, is there more to prepend? Because the parent assembly may have some + // parents So we check assemblyLink parents and prepend necessary parents. + bool first = true; + std::vector inList = getInList(); + int limit = 0; + while (!inList.empty() && limit < 20) { + ++limit; + bool found = false; + for (auto* obj : inList) { + if (obj->isDerivedFrom()) { + found = true; + if (first) { + first = false; + } + else { + std::string obj1Name = obj1->getNameInDocument(); + sub = obj1Name + "." + sub; + } + obj1 = obj; + break; + } + } + if (found) { + inList = obj1->getInList(); + } + else { + inList = {}; + } + } + + // Lastly we need to replace the object name by its link name. + auto* obj = AssemblyObject::getObjFromRef(prop1); + auto* link = objLinkMap[obj]; + if (!obj || !link) { + return; + } + std::string objName = obj->getNameInDocument(); + std::string linkName = link->getNameInDocument(); + sub = replaceLastOccurrence(sub, objName, linkName); + } + // Now obj1 and the subs1 are what should be in obj2 and subs2 if the joint did not changed + if (obj1 != obj2) { + prop2->setValue(obj1); + } + bool changed = false; + for (size_t i = 0; i < subs1.size(); ++i) { + if (i >= subs2.size() || subs1[i] != subs2[i]) { + changed = true; + break; + } + } + if (changed) { + prop2->setSubValues(std::move(subs1)); + } +} + +void AssemblyLink::ensureNoJointGroup() +{ + // Make sure there is no joint group + JointGroup* jGroup = AssemblyObject::getJointGroup(this); + if (jGroup) { + // If there is a joint group, we delete it and its content. + jGroup->removeObjectsFromDocument(); + getDocument()->removeObject(jGroup->getNameInDocument()); + } +} +JointGroup* AssemblyLink::ensureJointGroup() +{ + // Make sure there is a jointGroup + JointGroup* jGroup = AssemblyObject::getJointGroup(this); + if (!jGroup) { + jGroup = new JointGroup(); + getDocument()->addObject(jGroup, tr("Joints").toStdString().c_str()); + + // we want to add jgroup at the start, so we don't use + // addObject(jGroup); + std::vector grp = Group.getValues(); + grp.insert(grp.begin(), jGroup); + Group.setValues(grp); + } + return jGroup; +} + +App::DocumentObject* AssemblyLink::getLinkedObject2(bool recursive) const +{ + auto* obj = LinkedObject.getValue(); + auto* assembly = dynamic_cast(obj); + if (assembly) { + return assembly; + } + else { + auto* assemblyLink = dynamic_cast(obj); + if (assemblyLink) { + if (recursive) { + return assemblyLink->getLinkedObject2(recursive); + } + else { + return assemblyLink; + } + } + } + + return nullptr; +} + +AssemblyObject* AssemblyLink::getLinkedAssembly() const +{ + return dynamic_cast(getLinkedObject2()); +} + +AssemblyObject* AssemblyLink::getParentAssembly() const +{ + std::vector inList = getInList(); + for (auto* obj : inList) { + auto* assembly = dynamic_cast(obj); + if (assembly) { + return assembly; + } + } + + return nullptr; +} + +bool AssemblyLink::isRigid() +{ + auto* prop = dynamic_cast(getPropertyByName("Rigid")); + if (!prop) { + return true; + } + + return prop->getValue(); +} + +std::vector AssemblyLink::getJoints() +{ + JointGroup* jointGroup = AssemblyObject::getJointGroup(this); + + if (!jointGroup) { + return {}; + } + + return jointGroup->getJoints(); +} diff --git a/src/Mod/Assembly/App/AssemblyLink.h b/src/Mod/Assembly/App/AssemblyLink.h new file mode 100644 index 0000000000..5bbcfefa40 --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLink.h @@ -0,0 +1,96 @@ +// 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 * + * . * + * * + ***************************************************************************/ + + +#ifndef ASSEMBLY_AssemblyLink_H +#define ASSEMBLY_AssemblyLink_H + +#include + +#include + +#include +#include +#include + + +namespace Assembly +{ +class AssemblyObject; +class JointGroup; + +class AssemblyExport AssemblyLink: public App::Part +{ + PROPERTY_HEADER_WITH_OVERRIDE(Assembly::AssemblyLink); + +public: + AssemblyLink(); + ~AssemblyLink() override; + + PyObject* getPyObject() override; + + /// returns the type name of the ViewProvider + const char* getViewProviderName() const override + { + return "AssemblyGui::ViewProviderAssemblyLink"; + } + + App::DocumentObjectExecReturn* execute() override; + + // The linked assembly is the AssemblyObject that this AssemblyLink pseudo-links to recursively. + AssemblyObject* getLinkedAssembly() const; + // The parent assembly is the main assembly in which the linked assembly is contained + AssemblyObject* getParentAssembly() const; + + // Overriding DocumentObject::getLinkedObject is giving bugs + // This function returns the linked object, either an AssemblyObject or an AssemblyLink + App::DocumentObject* getLinkedObject2(bool recurse = true) const; + + bool isRigid(); + + void updateContents(); + + void synchronizeComponents(); + void synchronizeJoints(); + void handleJointReference(App::DocumentObject* joint, + App::DocumentObject* lJoint, + const char* refName); + void ensureNoJointGroup(); + JointGroup* ensureJointGroup(); + std::vector getJoints(); + + App::PropertyXLink LinkedObject; + App::PropertyBool Rigid; + + std::unordered_map objLinkMap; + +protected: + /// get called by the container whenever a property has been changed + void onChanged(const App::Property* prop) override; +}; + + +} // namespace Assembly + + +#endif // ASSEMBLY_AssemblyLink_H diff --git a/src/Mod/Assembly/App/AssemblyLinkPy.xml b/src/Mod/Assembly/App/AssemblyLinkPy.xml new file mode 100644 index 0000000000..2b72b7af31 --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLinkPy.xml @@ -0,0 +1,19 @@ + + + + + + This class handles document objects in Assembly + + + + + diff --git a/src/Mod/Assembly/App/AssemblyLinkPyImp.cpp b/src/Mod/Assembly/App/AssemblyLinkPyImp.cpp new file mode 100644 index 0000000000..2a7123a213 --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLinkPyImp.cpp @@ -0,0 +1,47 @@ +// 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 * + * . * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +// inclusion of the generated files (generated out of AssemblyLink.xml) +#include "AssemblyLinkPy.h" +#include "AssemblyLinkPy.cpp" + +using namespace Assembly; + +// returns a string which represents the object e.g. when printed in python +std::string AssemblyLinkPy::representation() const +{ + return {""}; +} + +PyObject* AssemblyLinkPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int AssemblyLinkPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index aba23b402b..cf74c3f830 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -82,6 +82,7 @@ #include #include +#include "AssemblyLink.h" #include "AssemblyObject.h" #include "AssemblyObjectPy.h" #include "JointGroup.h" @@ -445,6 +446,7 @@ void AssemblyObject::redrawJointPlacement(App::DocumentObject* joint) void AssemblyObject::recomputeJointPlacements(std::vector joints) { // The Placement1 and Placement2 of each joint needs to be updated as the parts moved. + Base::PyGILStateLocker lock; for (auto* joint : joints) { if (!joint) { continue; @@ -1773,17 +1775,17 @@ void AssemblyObject::setObjMasses(std::vector AssemblyObject::getSubAssemblies() +std::vector AssemblyObject::getSubAssemblies() { - std::vector subAssemblies = {}; + std::vector subAssemblies = {}; App::Document* doc = getDocument(); std::vector assemblies = - doc->getObjectsOfType(Assembly::AssemblyObject::getClassTypeId()); + doc->getObjectsOfType(Assembly::AssemblyLink::getClassTypeId()); for (auto assembly : assemblies) { if (hasObject(assembly)) { - subAssemblies.push_back(dynamic_cast(assembly)); + subAssemblies.push_back(dynamic_cast(assembly)); } } @@ -2465,6 +2467,14 @@ App::DocumentObject* AssemblyObject::getMovingPartFromRef(App::DocumentObject* o continue; } + // We ignore dynamic sub-assemblies. + if (obj->isDerivedFrom()) { + auto* pRigid = dynamic_cast(obj->getPropertyByName("Rigid")); + if (pRigid && !pRigid->getValue()) { + continue; + } + } + return obj; } diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index 1ead41f729..b2a083767f 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -59,6 +59,7 @@ class Rotation; namespace Assembly { +class AssemblyLink; class JointGroup; class ViewGroup; @@ -241,7 +242,7 @@ public: double getObjMass(App::DocumentObject* obj); void setObjMasses(std::vector> objectMasses); - std::vector getSubAssemblies(); + std::vector getSubAssemblies(); void updateGroundedJointsPlacements(); private: diff --git a/src/Mod/Assembly/App/CMakeLists.txt b/src/Mod/Assembly/App/CMakeLists.txt index afbf1c1870..70063733c0 100644 --- a/src/Mod/Assembly/App/CMakeLists.txt +++ b/src/Mod/Assembly/App/CMakeLists.txt @@ -19,6 +19,7 @@ set(Assembly_LIBS ) generate_from_xml(AssemblyObjectPy) +generate_from_xml(AssemblyLinkPy) generate_from_xml(BomObjectPy) generate_from_xml(BomGroupPy) generate_from_xml(JointGroupPy) @@ -27,6 +28,8 @@ generate_from_xml(ViewGroupPy) SET(Python_SRCS AssemblyObjectPy.xml AssemblyObjectPyImp.cpp + AssemblyLinkPy.xml + AssemblyLinkPyImp.cpp BomObjectPy.xml BomObjectPyImp.cpp BomGroupPy.xml @@ -49,6 +52,8 @@ SOURCE_GROUP("Module" FILES ${Module_SRCS}) SET(Assembly_SRCS AssemblyObject.cpp AssemblyObject.h + AssemblyLink.cpp + AssemblyLink.h BomObject.cpp BomObject.h BomGroup.cpp diff --git a/src/Mod/Assembly/App/JointGroup.cpp b/src/Mod/Assembly/App/JointGroup.cpp index faccfd4de7..c38a4f16d2 100644 --- a/src/Mod/Assembly/App/JointGroup.cpp +++ b/src/Mod/Assembly/App/JointGroup.cpp @@ -53,3 +53,31 @@ PyObject* JointGroup::getPyObject() } return Py::new_reference_to(PythonObject); } + + +std::vector JointGroup::getJoints() +{ + std::vector joints = {}; + + Base::PyGILStateLocker lock; + for (auto joint : getObjects()) { + if (!joint) { + continue; + } + + auto* prop = dynamic_cast(joint->getPropertyByName("Activated")); + if (!prop || !prop->getValue()) { + // Filter grounded joints and deactivated joints. + continue; + } + + auto proxy = dynamic_cast(joint->getPropertyByName("Proxy")); + if (proxy) { + if (proxy->getValue().hasAttr("setJointConnectors")) { + joints.push_back(joint); + } + } + } + + return joints; +} diff --git a/src/Mod/Assembly/App/JointGroup.h b/src/Mod/Assembly/App/JointGroup.h index 17c328d2ba..8f4611815c 100644 --- a/src/Mod/Assembly/App/JointGroup.h +++ b/src/Mod/Assembly/App/JointGroup.h @@ -49,6 +49,8 @@ public: { return "AssemblyGui::ViewProviderJointGroup"; } + + std::vector getJoints(); }; diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index a5312339b6..a9a1b74dcd 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -98,6 +98,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): pref = Preferences.preferences() self.form.CheckBox_ShowOnlyParts.setChecked(pref.GetBool("InsertShowOnlyParts", False)) + self.form.CheckBox_RigidSubAsm.setChecked(pref.GetBool("InsertRigidSubAssemblies", True)) # Actions self.form.openFileButton.clicked.connect(self.openFiles) @@ -165,6 +166,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): def deactivated(self): pref = Preferences.preferences() pref.SetBool("InsertShowOnlyParts", self.form.CheckBox_ShowOnlyParts.isChecked()) + pref.SetBool("InsertRigidSubAssemblies", self.form.CheckBox_RigidSubAsm.isChecked()) Gui.Selection.clearSelection() def buildPartList(self): @@ -353,7 +355,16 @@ class TaskAssemblyInsertLink(QtCore.QObject): print(selectedPart.Document.Name) documentItem.setText(0, f"{newDocName}.FCStd")""" - addedObject = self.assembly.newObject("App::Link", selectedPart.Label) + if selectedPart.isDerivedFrom("Assembly::AssemblyObject"): + objType = "Assembly::AssemblyLink" + else: + objType = "App::Link" + + addedObject = self.assembly.newObject(objType, selectedPart.Label) + + if selectedPart.isDerivedFrom("Assembly::AssemblyObject"): + addedObject.Rigid = self.form.CheckBox_RigidSubAsm.isChecked() + # set placement of the added object to the center of the screen. view = Gui.activeView() x, y = view.getSize() diff --git a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp index c76bfd1a8a..6b4ec250e9 100644 --- a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp +++ b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp @@ -28,6 +28,7 @@ #include #include "ViewProviderAssembly.h" +#include "ViewProviderAssemblyLink.h" #include "ViewProviderBom.h" #include "ViewProviderBomGroup.h" #include "ViewProviderJointGroup.h" @@ -60,6 +61,7 @@ PyMOD_INIT_FUNC(AssemblyGui) // This function is responsible for adding inherited slots from a type's base class. AssemblyGui::ViewProviderAssembly::init(); + AssemblyGui::ViewProviderAssemblyLink::init(); AssemblyGui::ViewProviderBom::init(); AssemblyGui::ViewProviderBomGroup::init(); AssemblyGui::ViewProviderJointGroup::init(); diff --git a/src/Mod/Assembly/Gui/CMakeLists.txt b/src/Mod/Assembly/Gui/CMakeLists.txt index 13bf42af35..7dda6251f0 100644 --- a/src/Mod/Assembly/Gui/CMakeLists.txt +++ b/src/Mod/Assembly/Gui/CMakeLists.txt @@ -40,6 +40,8 @@ SET(AssemblyGui_SRCS_Module PreCompiled.h ViewProviderAssembly.cpp ViewProviderAssembly.h + ViewProviderAssemblyLink.cpp + ViewProviderAssemblyLink.h ViewProviderBom.cpp ViewProviderBom.h ViewProviderBomGroup.cpp diff --git a/src/Mod/Assembly/Gui/Resources/Assembly.qrc b/src/Mod/Assembly/Gui/Resources/Assembly.qrc index c16225e37d..22b93155a1 100644 --- a/src/Mod/Assembly/Gui/Resources/Assembly.qrc +++ b/src/Mod/Assembly/Gui/Resources/Assembly.qrc @@ -1,5 +1,7 @@ + icons/Assembly_AssemblyLink.svg + icons/Assembly_AssemblyLinkRigid.svg icons/Assembly_InsertLink.svg icons/preferences-assembly.svg icons/Assembly_ToggleGrounded.svg diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg new file mode 100644 index 0000000000..c40a4f8a9a --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path-Stock + 2015-07-04 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Stock.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg new file mode 100644 index 0000000000..f9e6c2924f --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path-Stock + 2015-07-04 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Stock.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui index 4c485ffc75..640f9a93c1 100644 --- a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui +++ b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui @@ -68,6 +68,28 @@ + + + + If checked, the inserted sub-assemblies will not be flexible. +Rigid means that the sub-assembly will be considered as a solid. +Flexible means that the sub-assembly joints will be taken into account in the main assembly. +You can change this property of sub-assemblies at any time by right clicking them. + + + Rigid sub-assemblies + + + true + + + InsertRigidSubAssemblies + + + Mod/Assembly + + + diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index a3e35fcfd4..22e878c4b6 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include "ViewProviderAssembly.h" @@ -1024,7 +1025,7 @@ bool ViewProviderAssembly::onDelete(const std::vector& subNames) for (auto obj : getObject()->getOutList()) { if (obj->getTypeId() == Assembly::JointGroup::getClassTypeId() || obj->getTypeId() == Assembly::ViewGroup::getClassTypeId() - /* || obj->getTypeId() == Assembly::BomGroup::getClassTypeId()*/) { + || obj->getTypeId() == Assembly::BomGroup::getClassTypeId()) { // Delete the group content first. Gui::Command::doCommand(Gui::Command::Doc, diff --git a/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp new file mode 100644 index 0000000000..37c1c89fb8 --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp @@ -0,0 +1,150 @@ +// 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 * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "ViewProviderAssembly.h" +#include "ViewProviderAssemblyLink.h" + + +using namespace Assembly; +using namespace AssemblyGui; + + +PROPERTY_SOURCE(AssemblyGui::ViewProviderAssemblyLink, Gui::ViewProviderPart) + +ViewProviderAssemblyLink::ViewProviderAssemblyLink() +{} + +ViewProviderAssemblyLink::~ViewProviderAssemblyLink() = default; + +QIcon ViewProviderAssemblyLink::getIcon() const +{ + auto* assembly = dynamic_cast(getObject()); + if (assembly->isRigid()) { + return Gui::BitmapFactory().pixmap("Assembly_AssemblyLinkRigid.svg"); + } + else { + return Gui::BitmapFactory().pixmap("Assembly_AssemblyLink.svg"); + } +} + +bool ViewProviderAssemblyLink::setEdit(int mode) +{ + auto* assemblyLink = dynamic_cast(getObject()); + + if (!assemblyLink->isRigid() && mode == (int)ViewProvider::Transform) { + Base::Console().UserTranslatedNotification( + "Flexible sub-assemblies cannot be transformed."); + return true; + } + + return ViewProviderPart::setEdit(mode); +} + +bool ViewProviderAssemblyLink::doubleClicked() +{ + auto* link = dynamic_cast(getObject()); + + if (!link) { + return true; + } + auto* assembly = link->getLinkedAssembly(); + + auto* vpa = + dynamic_cast(Gui::Application::Instance->getViewProvider(assembly)); + if (!vpa) { + return true; + } + + return vpa->doubleClicked(); +} + +bool ViewProviderAssemblyLink::onDelete(const std::vector& subNames) +{ + Q_UNUSED(subNames) + + Base::Console().Warning("onDelete\n"); + + Gui::Command::doCommand(Gui::Command::Doc, + "App.getDocument(\"%s\").getObject(\"%s\").removeObjectsFromDocument()", + getObject()->getDocument()->getName(), + getObject()->getNameInDocument()); + + // getObject()->purgeTouched(); + + return ViewProviderPart::onDelete(subNames); +} + +void ViewProviderAssemblyLink::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) +{ + auto func = new Gui::ActionFunction(menu); + QAction* act; + auto* assemblyLink = dynamic_cast(getObject()); + if (assemblyLink->isRigid()) { + act = menu->addAction(QObject::tr("Turn flexible")); + act->setToolTip(QObject::tr( + "Your sub-assembly is currently rigid. This will make it flexible instead.")); + } + else { + act = menu->addAction(QObject::tr("Turn rigid")); + act->setToolTip(QObject::tr( + "Your sub-assembly is currently flexible. This will make it rigid instead.")); + } + + func->trigger(act, [this]() { + auto* assemblyLink = dynamic_cast(getObject()); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Toggle Rigid")); + Gui::cmdAppObjectArgs(assemblyLink, + "Rigid = %s", + assemblyLink->Rigid.getValue() ? "False" : "True"); + + Gui::Command::commitCommand(); + Gui::Selection().clearSelection(); + }); + + Q_UNUSED(receiver) + Q_UNUSED(member) +} diff --git a/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h new file mode 100644 index 0000000000..190deb32b4 --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h @@ -0,0 +1,81 @@ +// 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 * + * . * + * * + ***************************************************************************/ + +#ifndef ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssemblyLink_H +#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssemblyLink_H + +#include + +#include + +#include + + +namespace AssemblyGui +{ + +class AssemblyGuiExport ViewProviderAssemblyLink: public Gui::ViewProviderPart +{ + Q_DECLARE_TR_FUNCTIONS(AssemblyGui::ViewProviderAssemblyLink) + PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderAssemblyLink); + +public: + ViewProviderAssemblyLink(); + ~ViewProviderAssemblyLink() override; + + /// deliver the icon shown in the tree view. Override from ViewProvider.h + QIcon getIcon() const override; + + bool setEdit(int ModNum) override; + + bool doubleClicked() override; + + // When the assembly link is deleted, we delete all its content as well. + bool onDelete(const std::vector& subNames) override; + + // Prevent deletion of the link assembly's content. + bool canDelete(App::DocumentObject*) const override + { + return false; + }; + + // Prevent drag/drop of objects within the assembly link. + bool canDragObjects() const override + { + return false; + }; + bool canDropObjects() const override + { + return false; + }; + bool canDragAndDropObject(App::DocumentObject*) const override + { + return false; + }; + + void setupContextMenu(QMenu*, QObject*, const char*) override; +}; + +} // namespace AssemblyGui + +#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssemblyLink_H diff --git a/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp b/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp index 9f598654ec..f56dbde95e 100644 --- a/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp @@ -47,10 +47,3 @@ QIcon ViewProviderJointGroup::getIcon() const { return Gui::BitmapFactory().pixmap("Assembly_JointGroup.svg"); } - -// Make the joint group impossible to delete. -bool ViewProviderJointGroup::onDelete(const std::vector& subNames) -{ - Q_UNUSED(subNames); - return false; -} diff --git a/src/Mod/Assembly/Gui/ViewProviderJointGroup.h b/src/Mod/Assembly/Gui/ViewProviderJointGroup.h index 76ca1b6230..96ccbc4850 100644 --- a/src/Mod/Assembly/Gui/ViewProviderJointGroup.h +++ b/src/Mod/Assembly/Gui/ViewProviderJointGroup.h @@ -57,7 +57,11 @@ public: return false; }; - bool onDelete(const std::vector& subNames) override; + // Make the joint group impossible to delete. + bool onDelete(const std::vector&) override + { + return false; + }; // protected: /// get called by the container whenever a property has been changed diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 9b7743a3d7..279c6d2868 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -1112,6 +1112,10 @@ def getMovingPart(assembly, ref): if obj.TypeId == "App::DocumentObjectGroup": continue # we ignore groups. + # We ignore dynamic sub-assemblies. + if obj.isDerivedFrom("Assembly::AssemblyLink") and obj.Rigid == False: + continue + # If it is a LinkGroup then we skip it if isLinkGroup(obj): continue