diff --git a/src/Mod/Assembly/App/AppAssembly.cpp b/src/Mod/Assembly/App/AppAssembly.cpp
index c73899dd84a63..76edd19cc3c9e 100644
--- a/src/Mod/Assembly/App/AppAssembly.cpp
+++ b/src/Mod/Assembly/App/AppAssembly.cpp
@@ -28,7 +28,9 @@
#include
#include "AssemblyObject.h"
+#include "ExplodedView.h"
#include "JointGroup.h"
+#include "ViewGroup.h"
namespace Assembly
@@ -57,7 +59,9 @@ PyMOD_INIT_FUNC(AssemblyApp)
// This function is responsible for adding inherited slots from a type's base class.
Assembly::AssemblyObject ::init();
+ Assembly::ExplodedView ::init();
Assembly::JointGroup ::init();
+ Assembly::ViewGroup ::init();
PyMOD_Return(mod);
}
diff --git a/src/Mod/Assembly/App/CMakeLists.txt b/src/Mod/Assembly/App/CMakeLists.txt
index 724f22e1f8d7c..4c559ad40762d 100644
--- a/src/Mod/Assembly/App/CMakeLists.txt
+++ b/src/Mod/Assembly/App/CMakeLists.txt
@@ -18,13 +18,19 @@ set(Assembly_LIBS
)
generate_from_xml(AssemblyObjectPy)
+generate_from_xml(ExplodedViewPy)
generate_from_xml(JointGroupPy)
+generate_from_xml(ViewGroupPy)
SET(Python_SRCS
AssemblyObjectPy.xml
AssemblyObjectPyImp.cpp
+ ExplodedViewPy.xml
+ ExplodedViewPyImp.cpp
JointGroupPy.xml
JointGroupPyImp.cpp
+ ViewGroupPy.xml
+ ViewGroupPyImp.cpp
)
SOURCE_GROUP("Python" FILES ${Python_SRCS})
@@ -39,8 +45,12 @@ SOURCE_GROUP("Module" FILES ${Module_SRCS})
SET(Assembly_SRCS
AssemblyObject.cpp
AssemblyObject.h
+ ExplodedView.cpp
+ ExplodedView.h
JointGroup.cpp
JointGroup.h
+ ViewGroup.cpp
+ ViewGroup.h
${Module_SRCS}
${Python_SRCS}
)
diff --git a/src/Mod/Assembly/App/ExplodedView.cpp b/src/Mod/Assembly/App/ExplodedView.cpp
new file mode 100644
index 0000000000000..50b19434699de
--- /dev/null
+++ b/src/Mod/Assembly/App/ExplodedView.cpp
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "ExplodedView.h"
+#include "ExplodedViewPy.h"
+
+using namespace Assembly;
+
+
+PROPERTY_SOURCE(Assembly::ExplodedView, App::DocumentObjectGroup)
+
+ExplodedView::ExplodedView()
+{}
+
+ExplodedView::~ExplodedView() = default;
+
+PyObject* ExplodedView::getPyObject()
+{
+ if (PythonObject.is(Py::_None())) {
+ // ref counter is set to 1
+ PythonObject = Py::Object(new ExplodedViewPy(this), true);
+ }
+ return Py::new_reference_to(PythonObject);
+}
diff --git a/src/Mod/Assembly/App/ExplodedView.h b/src/Mod/Assembly/App/ExplodedView.h
new file mode 100644
index 0000000000000..c45d034c9355a
--- /dev/null
+++ b/src/Mod/Assembly/App/ExplodedView.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_ExplodedView_H
+#define ASSEMBLY_ExplodedView_H
+
+#include
+
+#include
+#include
+
+
+namespace Assembly
+{
+
+class AssemblyExport ExplodedView: public App::DocumentObjectGroup
+{
+ PROPERTY_HEADER_WITH_OVERRIDE(Assembly::ExplodedView);
+
+public:
+ ExplodedView();
+ ~ExplodedView() override;
+
+ PyObject* getPyObject() override;
+
+ /// returns the type name of the ViewProvider
+ const char* getViewProviderName() const override
+ {
+ return "AssemblyGui::ViewProviderExplodedView";
+ }
+};
+
+
+} // namespace Assembly
+
+
+#endif // ASSEMBLY_ExplodedView_H
diff --git a/src/Mod/Assembly/App/ExplodedViewPy.xml b/src/Mod/Assembly/App/ExplodedViewPy.xml
new file mode 100644
index 0000000000000..a5b0d19457e59
--- /dev/null
+++ b/src/Mod/Assembly/App/ExplodedViewPy.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ This class is a group subclass for joints.
+
+
+
+
+
diff --git a/src/Mod/Assembly/App/ExplodedViewPyImp.cpp b/src/Mod/Assembly/App/ExplodedViewPyImp.cpp
new file mode 100644
index 0000000000000..12303b578ce55
--- /dev/null
+++ b/src/Mod/Assembly/App/ExplodedViewPyImp.cpp
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * Copyright (c) 2014 Jürgen Riegel *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU Library General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Library General Public *
+ * License along with this library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+
+#include "PreCompiled.h"
+
+// inclusion of the generated files (generated out of ExplodedView.xml)
+#include "ExplodedViewPy.h"
+#include "ExplodedViewPy.cpp"
+
+using namespace Assembly;
+
+// returns a string which represents the object e.g. when printed in python
+std::string ExplodedViewPy::representation() const
+{
+ return {""};
+}
+
+PyObject* ExplodedViewPy::getCustomAttributes(const char* /*attr*/) const
+{
+ return nullptr;
+}
+
+int ExplodedViewPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
+{
+ return 0;
+}
diff --git a/src/Mod/Assembly/App/ViewGroup.cpp b/src/Mod/Assembly/App/ViewGroup.cpp
new file mode 100644
index 0000000000000..864e692a62ea1
--- /dev/null
+++ b/src/Mod/Assembly/App/ViewGroup.cpp
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "ViewGroup.h"
+#include "ViewGroupPy.h"
+
+using namespace Assembly;
+
+
+PROPERTY_SOURCE(Assembly::ViewGroup, App::DocumentObjectGroup)
+
+ViewGroup::ViewGroup()
+{}
+
+ViewGroup::~ViewGroup() = default;
+
+PyObject* ViewGroup::getPyObject()
+{
+ if (PythonObject.is(Py::_None())) {
+ // ref counter is set to 1
+ PythonObject = Py::Object(new ViewGroupPy(this), true);
+ }
+ return Py::new_reference_to(PythonObject);
+}
diff --git a/src/Mod/Assembly/App/ViewGroup.h b/src/Mod/Assembly/App/ViewGroup.h
new file mode 100644
index 0000000000000..0a90e35091831
--- /dev/null
+++ b/src/Mod/Assembly/App/ViewGroup.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_ViewGroup_H
+#define ASSEMBLY_ViewGroup_H
+
+#include
+
+#include
+#include
+
+
+namespace Assembly
+{
+
+class AssemblyExport ViewGroup: public App::DocumentObjectGroup
+{
+ PROPERTY_HEADER_WITH_OVERRIDE(Assembly::ViewGroup);
+
+public:
+ ViewGroup();
+ ~ViewGroup() override;
+
+ PyObject* getPyObject() override;
+
+ /// returns the type name of the ViewProvider
+ const char* getViewProviderName() const override
+ {
+ return "AssemblyGui::ViewProviderViewGroup";
+ }
+};
+
+
+} // namespace Assembly
+
+
+#endif // ASSEMBLY_ViewGroup_H
diff --git a/src/Mod/Assembly/App/ViewGroupPy.xml b/src/Mod/Assembly/App/ViewGroupPy.xml
new file mode 100644
index 0000000000000..69446cba20647
--- /dev/null
+++ b/src/Mod/Assembly/App/ViewGroupPy.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ This class is a group subclass for joints.
+
+
+
+
+
diff --git a/src/Mod/Assembly/App/ViewGroupPyImp.cpp b/src/Mod/Assembly/App/ViewGroupPyImp.cpp
new file mode 100644
index 0000000000000..aa86e9d4165db
--- /dev/null
+++ b/src/Mod/Assembly/App/ViewGroupPyImp.cpp
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * Copyright (c) 2014 Jürgen Riegel *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU Library General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Library General Public *
+ * License along with this library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+
+#include "PreCompiled.h"
+
+// inclusion of the generated files (generated out of ViewGroup.xml)
+#include "ViewGroupPy.h"
+#include "ViewGroupPy.cpp"
+
+using namespace Assembly;
+
+// returns a string which represents the object e.g. when printed in python
+std::string ViewGroupPy::representation() const
+{
+ return {""};
+}
+
+PyObject* ViewGroupPy::getCustomAttributes(const char* /*attr*/) const
+{
+ return nullptr;
+}
+
+int ViewGroupPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
+{
+ return 0;
+}
diff --git a/src/Mod/Assembly/CMakeLists.txt b/src/Mod/Assembly/CMakeLists.txt
index b6f964016c8e9..ea597ccd43290 100644
--- a/src/Mod/Assembly/CMakeLists.txt
+++ b/src/Mod/Assembly/CMakeLists.txt
@@ -10,6 +10,7 @@ set(Assembly_Scripts
CommandInsertLink.py
CommandSolveAssembly.py
CommandCreateJoint.py
+ CommandCreateView.py
CommandExportASMT.py
TestAssemblyWorkbench.py
JointObject.py
diff --git a/src/Mod/Assembly/CommandCreateView.py b/src/Mod/Assembly/CommandCreateView.py
new file mode 100644
index 0000000000000..43bf9878083b0
--- /dev/null
+++ b/src/Mod/Assembly/CommandCreateView.py
@@ -0,0 +1,575 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# /**************************************************************************
+# *
+# Copyright (c) 2023 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 pivy import coin
+
+from PySide.QtCore import QT_TRANSLATE_NOOP
+
+if App.GuiUp:
+ import FreeCADGui as Gui
+ from PySide import QtCore, QtGui, QtWidgets
+
+import UtilsAssembly
+import Preferences
+
+# translate = App.Qt.translate
+
+__title__ = "Assembly Command Create Exploded View"
+__author__ = "Ondsel"
+__url__ = "https://www.freecad.org"
+
+
+class CommandCreateView:
+ def __init__(self):
+ pass
+
+ def GetResources(self):
+ return {
+ "Pixmap": "Assembly_ExplodedView",
+ "MenuText": QT_TRANSLATE_NOOP("Assembly_CreateView", "Create Exploded View"),
+ "Accel": "V",
+ "ToolTip": "
"
+ + QT_TRANSLATE_NOOP(
+ "Assembly_CreateView",
+ "Create an exploded view of the current assembly.",
+ )
+ + "
",
+ "CmdType": "ForEdit",
+ }
+
+ def IsActive(self):
+ return UtilsAssembly.isAssemblyCommandActive()
+
+ def Activated(self):
+ assembly = UtilsAssembly.activeAssembly()
+ if not assembly:
+ return
+
+ self.panel = TaskAssemblyCreateView()
+ Gui.Control.showDialog(self.panel)
+
+
+######### Exploded View Object ###########
+class ExplodedView:
+ def __init__(self, expView):
+ expView.addProperty(
+ "App::PropertyLinkList", "Steps", "Exploded View", "Step objects of the exploded view."
+ )
+ expView.Proxy = self
+
+ def dumps(self):
+ return None
+
+ def loads(self, state):
+ return None
+
+ def getAssembly(self, viewObj):
+ return viewObj.InList[0]
+
+ def onChanged(self, viewObj, prop):
+ """Do something when a property has changed"""
+ pass
+
+ def execute(self, fp):
+ """Do something when doing a recomputation, this method is mandatory"""
+ # App.Console.PrintMessage("Recompute Python Box feature\n")
+ pass
+
+
+class ViewProviderExplodedView:
+ def __init__(self, vobj):
+ """Set this object to the proxy object of the actual view provider"""
+ vobj.Proxy = self
+
+ def attach(self, vobj):
+ """Setup the scene sub-graph of the view provider, this method is mandatory"""
+ self.app_obj = vobj.Object
+
+ # self.dragger = coin.SoType.fromName("SoFCCSysDragger").createInstance()
+ # self.switch_dragger = coin.SoSwitch() # This container is used to show/hide the dragger
+ # self.switch_dragger.addChild(self.dragger)
+ # self.switch_dragger.whichChild = coin.SO_SWITCH_NONE
+
+ # self.pick = coin.SoPickStyle()
+ # self.pick.style.setValue(coin.SoPickStyle.SHAPE_ON_TOP)
+
+ self.display_mode = coin.SoType.fromName("SoFCSelection").createInstance()
+ # self.display_mode.addChild(self.pick)
+ # self.display_mode.addChild(self.switch_dragger)
+ vobj.addDisplayMode(self.display_mode, "Wireframe")
+
+ """def camera_callback(self, *args):
+ scaleF = self.get_JCS_size()
+ self.axisScale.scaleFactor.setValue(scaleF, scaleF, scaleF) """
+
+ 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
+ pass
+
+ def getDisplayModes(self, obj):
+ """Return a list of display modes."""
+ return ["Wireframe"]
+
+ def getDefaultDisplayMode(self):
+ """Return the name of the default display mode. It must be defined in getDisplayModes."""
+ return "Wireframe"
+
+ def onChanged(self, vp, prop):
+ """Here we can do something when a single property got changed"""
+ # App.Console.PrintMessage("Change property: " + str(prop) + "\n")
+ pass
+
+ def getIcon(self):
+ return ":/icons/Assembly_ExplodedView.svg"
+
+ def dumps(self):
+ """When saving the document this object gets stored using Python's json module.\
+ Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\
+ to return a tuple of all serializable objects or None."""
+ return None
+
+ def loads(self, state):
+ """When restoring the serialized object from document we have the chance to set some internals here.\
+ Since no data were serialized nothing needs to be done here."""
+ return None
+
+ def claimChildren(self):
+ return self.app_obj.Steps
+
+ def doubleClicked(self, vobj):
+ assembly = vobj.Object.InList[0]
+ if UtilsAssembly.activeAssembly() != assembly:
+ Gui.ActiveDocument.setEdit(assembly)
+
+ panel = TaskAssemblyCreateView(vobj.Object)
+ Gui.Control.showDialog(panel)
+
+
+######### Exploded View Step #########
+class ExplodedViewStep:
+ def __init__(self, evStep):
+ evStep.Proxy = self
+
+ evStep.addProperty(
+ "App::PropertyLinkList",
+ "Parts",
+ "Exploded Step",
+ QT_TRANSLATE_NOOP("App::Property", "The parts moved by the step"),
+ )
+
+ evStep.addProperty(
+ "App::PropertyPlacement",
+ "Placement",
+ "Exploded Step",
+ QT_TRANSLATE_NOOP(
+ "App::Property",
+ "This is the movement of the step. The end placement is the result of the start placement * this placement.",
+ ),
+ )
+
+ def dumps(self):
+ return None
+
+ def loads(self, state):
+ return None
+
+ def onChanged(self, joint, prop):
+ """Do something when a property has changed"""
+ pass
+
+ def execute(self, fp):
+ """Do something when doing a recomputation, this method is mandatory"""
+ # App.Console.PrintMessage("Recompute Python Box feature\n")
+ pass
+
+
+class ViewProviderExplodedViewStep:
+ def __init__(self, vobj):
+ """Set this object to the proxy object of the actual view provider"""
+ vobj.Proxy = self
+
+ def attach(self, vobj):
+ """Setup the scene sub-graph of the view provider, this method is mandatory"""
+ self.app_obj = vobj.Object
+
+ pref = Preferences.preferences()
+
+ self.line_thickness = pref.GetInt("StepLineThickness", 3)
+
+ param_step_line_color = pref.GetUnsigned("StepLineColor", 0xCC333300)
+ self.so_color = coin.SoBaseColor()
+ self.so_color.rgb.setValue(UtilsAssembly.color_from_unsigned(param_step_line_color))
+
+ self.draw_style = coin.SoDrawStyle()
+ self.draw_style.style = coin.SoDrawStyle.LINES
+ self.draw_style.lineWidth = self.line_thickness
+ self.draw_style.linePattern = 0xF0F0 # Dashed line pattern
+
+ # Create a separator to hold all dashed lines
+ self.lineSetGroup = coin.SoSeparator()
+
+ self.display_mode = coin.SoType.fromName("SoFCSelection").createInstance()
+ self.display_mode.addChild(self.lineSetGroup) # Add the group to the display mode
+ vobj.addDisplayMode(self.display_mode, "Wireframe")
+
+ # Initial update
+ self.updateData(vobj.Object, "Parts")
+
+ def updateData(self, stepObj, prop):
+ """If a property of the handled feature has changed we have the chance to handle this here"""
+ # stepObj is the handled feature, prop is the name of the property that has changed
+ if prop in ["Parts", "Placement"]:
+ self.redrawLines(stepObj)
+
+ def redrawLines(self, stepObj):
+ # Clear existing lines
+ self.lineSetGroup.removeAllChildren()
+
+ if hasattr(stepObj, "Parts") and stepObj.Parts:
+ for part in stepObj.Parts:
+ if part: # Check if the part is valid
+ plc1 = part.Placement # Current placement
+ plc2 = plc1 * stepObj.Placement.inverse()
+
+ # Create the line
+ line = coin.SoLineSet()
+ line.numVertices.setValue(2)
+ coords = coin.SoCoordinate3()
+ startPoint = plc1.Base
+ endPoint = plc2.Base
+ coords.point.setValues(0, [startPoint, endPoint])
+
+ # Create separator for this line to apply the style
+ line_sep = coin.SoSeparator()
+ line_sep.addChild(self.draw_style)
+ line_sep.addChild(self.so_color)
+ line_sep.addChild(coords)
+ line_sep.addChild(line)
+
+ # Add to the group
+ self.lineSetGroup.addChild(line_sep)
+
+ def getDisplayModes(self, obj):
+ """Return a list of display modes."""
+ modes = []
+ modes.append("Wireframe")
+ return modes
+
+ def getDefaultDisplayMode(self):
+ """Return the name of the default display mode. It must be defined in getDisplayModes."""
+ return "Wireframe"
+
+ def onChanged(self, vp, prop):
+ """Here we can do something when a single property got changed"""
+ # App.Console.PrintMessage("Change property: " + str(prop) + "\n")
+ pass
+
+ def getIcon(self):
+ return ":/icons/Assembly_ExplodedViewStep.svg"
+
+ def dumps(self):
+ """When saving the document this object gets stored using Python's json module.\
+ Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\
+ to return a tuple of all serializable objects or None."""
+ return None
+
+ def loads(self, state):
+ """When restoring the serialized object from document we have the chance to set some internals here.\
+ Since no data were serialized nothing needs to be done here."""
+ return None
+
+
+class ExplodedViewSelGate:
+ def __init__(self, taskbox, assembly):
+ self.taskbox = taskbox
+ self.assembly = assembly
+
+ def allow(self, doc, obj, sub):
+ if not sub:
+ return False
+
+ objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(obj.Name, sub)
+
+ if self.assembly.Name not in objs_names:
+ # Only objects within the assembly.
+ return False
+
+ return True
+
+
+######### Create Exploded View Task ###########
+class TaskAssemblyCreateView(QtCore.QObject):
+ def __init__(self, viewObj=None):
+ super().__init__()
+
+ view = Gui.activeDocument().activeView()
+
+ self.assembly = UtilsAssembly.activeAssembly()
+ self.assembly.ViewObject.EnableMovement = False
+ self.asmDragger = self.assembly.ViewObject.getDragger()
+ self.cbFin = view.addDraggerCallback(
+ self.asmDragger, "addFinishCallback", self.draggerFinished
+ )
+ self.cbMov = view.addDraggerCallback(
+ self.asmDragger, "addMotionCallback", self.draggerMoved
+ )
+
+ # self.doc = App.ActiveDocument
+
+ Gui.Selection.clearSelection()
+
+ self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateView.ui")
+ self.form.installEventFilter(self)
+ self.form.stepList.installEventFilter(self)
+ self.form.stepList.itemClicked.connect(self.onItemClicked)
+
+ self.saveAssemblyPartsPlacements(self.assembly)
+
+ if viewObj:
+ self.viewObj = viewObj
+ App.setActiveTransaction("Edit Exploded View")
+ self.setPositionsFromSteps()
+
+ else:
+ App.setActiveTransaction("Create Exploded View")
+ self.createExplodedViewObject()
+
+ Gui.Selection.addSelectionGate(
+ MakeJointSelGate(self, self.assembly), Gui.Selection.ResolveMode.NoResolve
+ )
+ Gui.Selection.addObserver(self, Gui.Selection.ResolveMode.NoResolve)
+
+ self.callbackKey = view.addEventCallback("SoKeyboardEvent", self.KeyboardEvent)
+
+ self.blockDraggerMove = True
+ self.currentStep = None
+
+ def accept(self):
+ self.deactivate()
+ self.restoreAssemblyPartsPlacements(self.assembly)
+ App.closeActiveTransaction()
+ return True
+
+ def reject(self):
+ self.deactivate()
+ App.closeActiveTransaction(True)
+ return True
+
+ def deactivate(self):
+ view = Gui.activeDocument().activeView()
+ view.removeDraggerCallback(self.asmDragger, "addFinishCallback", self.cbFin)
+ view.removeDraggerCallback(self.asmDragger, "addMotionCallback", self.cbMov)
+
+ self.assembly.ViewObject.DraggerVisibility = False
+ self.assembly.ViewObject.EnableMovement = True
+
+ Gui.Selection.removeSelectionGate()
+ Gui.Selection.removeObserver(self)
+ Gui.Selection.clearSelection()
+
+ # view.removeEventCallback("SoLocation2Event", self.callbackMove)
+ # view.removeEventCallback("SoMouseButtonEvent", self.callbackClick)
+ view.removeEventCallback("SoKeyboardEvent", self.callbackKey)
+
+ if Gui.Control.activeDialog():
+ Gui.Control.closeDialog()
+
+ def saveAssemblyPartsPlacements(self, assembly):
+ self.initialPlcDict = {}
+ assemblyParts = UtilsAssembly.getMovablePartsWithin(assembly)
+ for part in assemblyParts:
+ self.initialPlcDict[part.Name] = part.Placement
+
+ def restoreAssemblyPartsPlacements(self, assembly):
+ assemblyParts = UtilsAssembly.getMovablePartsWithin(assembly)
+ for part in assemblyParts:
+ if part.Name in self.initialPlcDict:
+ part.Placement = self.initialPlcDict[part.Name]
+
+ def setPositionsFromSteps(self):
+ for step in self.viewObj.Steps:
+ for part in step.Parts:
+ part.Placement = part.Placement * step.Placement
+
+ def setDragger(self):
+ self.dismissCurrentStep()
+ self.selectedParts = []
+ self.selectedPartsInitPlc = []
+ selection = Gui.Selection.getSelectionEx("*", 0)
+ if not selection:
+ self.assembly.ViewObject.DraggerVisibility = False
+ return
+ for sel in selection:
+ # If you select 2 solids (bodies for example) within an assembly.
+ # There'll be a single sel but 2 SubElementNames.
+
+ if not sel.SubElementNames:
+ # no subnames, so its a root assembly itself that is selected.
+ Gui.Selection.removeSelection(sel.Object)
+ continue
+
+ for sub_name in sel.SubElementNames:
+ # Only objects within the assembly.
+ objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(
+ sel.ObjectName, sub_name
+ )
+ if self.assembly.Name not in objs_names:
+ Gui.Selection.removeSelection(sel.Object, sub_name)
+ continue
+
+ obj_name = sel.ObjectName
+
+ full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name)
+ full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name)
+ selected_object = UtilsAssembly.getObject(full_element_name)
+ element_name = UtilsAssembly.getElementName(full_element_name)
+ part = UtilsAssembly.getContainingPart(
+ full_element_name, selected_object, self.assembly
+ )
+
+ if selected_object == self.assembly or element_name != "":
+ # do not accept selection of assembly itself or elements
+ Gui.Selection.removeSelection(sel.Object, sub_name)
+ continue
+
+ if not part in self.selectedParts:
+ self.selectedParts.append(part)
+ self.selectedPartsInitPlc.append(App.Placement(part.Placement))
+
+ if len(self.selectedParts) != 0:
+ self.assembly.ViewObject.DraggerVisibility = True
+ self.initialDraggerPlc = App.Placement(self.selectedParts[0].Placement)
+ # self.initialDraggerPlc.Base = UtilsAssembly.getCenterOfMass(self.selectedParts)
+
+ self.blockDraggerMove = True
+ self.assembly.ViewObject.DraggerPlacement = self.initialDraggerPlc
+ self.blockDraggerMove = False
+
+ else:
+ self.assembly.ViewObject.DraggerVisibility = False
+
+ def onItemClicked(self, item):
+ pass
+
+ def createExplodedViewObject(self):
+ view_group = UtilsAssembly.getViewGroup(self.assembly)
+ self.viewObj = view_group.newObject("App::FeaturePython", "Exploded View")
+
+ ExplodedView(self.viewObj)
+ ViewProviderExplodedView(self.viewObj.ViewObject)
+
+ def createExplodedStepObject(self):
+ self.currentStep = App.ActiveDocument.addObject("App::FeaturePython", "Exploded step")
+
+ ExplodedViewStep(self.currentStep)
+ ViewProviderExplodedViewStep(self.currentStep.ViewObject)
+
+ # Note: self.viewObj.Steps.append(self.currentStep) does not work
+ listOfSteps = self.viewObj.Steps
+ listOfSteps.append(self.currentStep)
+ self.viewObj.Steps = listOfSteps
+
+ self.currentStep.Placement = App.Placement()
+ self.currentStep.Parts = self.selectedParts
+
+ def dismissCurrentStep(self):
+ if self.currentStep is None:
+ return
+
+ self.currentStep.Document.removeObject(self.currentStep.Name)
+ self.currentStep = None
+
+ def draggerMoved(self, event):
+ if self.blockDraggerMove:
+ return
+
+ if self.currentStep is None:
+ self.createExplodedStepObject()
+
+ draggerPlc = self.assembly.ViewObject.DraggerPlacement
+ movePlc = self.initialDraggerPlc.inverse() * draggerPlc
+
+ for part, init_plc in zip(self.selectedParts, self.selectedPartsInitPlc):
+ part.Placement = init_plc * movePlc
+
+ # we update the step Placement after parts placement has updated.
+ self.currentStep.Placement = movePlc
+
+ def draggerFinished(self, event):
+ self.currentStep = None
+
+ # Reset the initial placements
+ self.initialDraggerPlc = App.Placement(self.selectedParts[0].Placement)
+ # self.initialDraggerPlc.Base = UtilsAssembly.getCenterOfMass(self.selectedParts)
+
+ for i, part in enumerate(self.selectedParts):
+ self.selectedPartsInitPlc[i] = App.Placement(part.Placement)
+
+ for init_plc in self.selectedPartsInitPlc:
+ print(init_plc)
+
+ # 3D view keyboard handler
+ def KeyboardEvent(self, info):
+ if info["State"] == "UP" and info["Key"] == "ESCAPE":
+ if self.currentStep is None:
+ self.reject()
+ else:
+ self.dismissCurrentStep()
+
+ # Taskbox keyboard event handler
+ def eventFilter(self, watched, event):
+ return super().eventFilter(watched, event)
+
+ # selectionObserver stuff
+ def addSelection(self, doc_name, obj_name, sub_name, mousePos):
+ full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name)
+ selected_object = UtilsAssembly.getObject(full_element_name)
+ element_name = UtilsAssembly.getElementName(full_element_name)
+ part_containing_selected_object = UtilsAssembly.getContainingPart(
+ full_element_name, selected_object, self.assembly
+ )
+
+ if element_name != "":
+ # When selecting, we do not want to select an element, but only the containing part.
+ Gui.Selection.removeSelection(selected_object, element_name)
+ if Gui.Selection.isSelected(part_containing_selected_object, ""):
+ Gui.Selection.removeSelection(part_containing_selected_object, "")
+ else:
+ Gui.Selection.addSelection(part_containing_selected_object, "")
+
+ self.setDragger()
+
+ def removeSelection(self, doc_name, obj_name, sub_name, mousePos=None):
+ self.setDragger()
+
+ def clearSelection(self, doc_name):
+ self.setDragger()
+
+
+if App.GuiUp:
+ Gui.addCommand("Assembly_CreateView", CommandCreateView())
diff --git a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp
index c0a2e8439b73a..7752b7da5a19c 100644
--- a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp
+++ b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp
@@ -27,7 +27,9 @@
#include
#include "ViewProviderAssembly.h"
+#include "ViewProviderExplodedView.h"
#include "ViewProviderJointGroup.h"
+#include "ViewProviderViewGroup.h"
namespace AssemblyGui
@@ -47,7 +49,9 @@ PyMOD_INIT_FUNC(AssemblyGui)
// This function is responsible for adding inherited slots from a type's base class.
AssemblyGui::ViewProviderAssembly ::init();
+ AssemblyGui::ViewProviderExplodedView::init();
AssemblyGui::ViewProviderJointGroup::init();
+ AssemblyGui::ViewProviderViewGroup::init();
PyMOD_Return(mod);
}
diff --git a/src/Mod/Assembly/Gui/CMakeLists.txt b/src/Mod/Assembly/Gui/CMakeLists.txt
index b20c30bad8267..855cb612bbd20 100644
--- a/src/Mod/Assembly/Gui/CMakeLists.txt
+++ b/src/Mod/Assembly/Gui/CMakeLists.txt
@@ -38,8 +38,12 @@ SET(AssemblyGui_SRCS_Module
PreCompiled.h
ViewProviderAssembly.cpp
ViewProviderAssembly.h
+ ViewProviderExplodedView.cpp
+ ViewProviderExplodedView.h
ViewProviderJointGroup.cpp
ViewProviderJointGroup.h
+ ViewProviderViewGroup.cpp
+ ViewProviderViewGroup.h
${Assembly_QRC_SRCS}
)
diff --git a/src/Mod/Assembly/Gui/Resources/Assembly.qrc b/src/Mod/Assembly/Gui/Resources/Assembly.qrc
index ad258eecf355b..855fff4fd0957 100644
--- a/src/Mod/Assembly/Gui/Resources/Assembly.qrc
+++ b/src/Mod/Assembly/Gui/Resources/Assembly.qrc
@@ -12,8 +12,12 @@
icons/Assembly_CreateJointTangent.svgicons/Assembly_ExportASMT.svgicons/Assembly_SolveAssembly.svg
+ icons/Assembly_JointGroup.svg
+ icons/Assembly_ExplodedView.svg
+ icons/Assembly_ExplodedViewGroup.svgpanels/TaskAssemblyCreateJoint.uipanels/TaskAssemblyInsertLink.ui
+ panels/TaskAssemblyCreateView.uipreferences/Assembly.uiicons/Assembly_CreateJointDistance.svg
diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_ExplodedView.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_ExplodedView.svg
new file mode 100644
index 0000000000000..9a388a0b4a9cf
--- /dev/null
+++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_ExplodedView.svg
@@ -0,0 +1,405 @@
+
+
diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_ExplodedViewGroup.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_ExplodedViewGroup.svg
new file mode 100644
index 0000000000000..0119fe3a54f2c
--- /dev/null
+++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_ExplodedViewGroup.svg
@@ -0,0 +1,733 @@
+
+
diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_Joint.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_JointGroup.svg
similarity index 51%
rename from src/Mod/Assembly/Gui/Resources/icons/Assembly_Joint.svg
rename to src/Mod/Assembly/Gui/Resources/icons/Assembly_JointGroup.svg
index ddeb779e69495..ee7e62d2a4fc2 100644
--- a/src/Mod/Assembly/Gui/Resources/icons/Assembly_Joint.svg
+++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_JointGroup.svg
@@ -1,13 +1,11 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ inkscape:deskcolor="#d1d1d1"
+ objecttolerance="10.0"
+ gridtolerance="10.0"
+ guidetolerance="10.0">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ transform="matrix(0.52234622,0,0,0.52234622,19.840028,21.184948)">
+
+ TaskAssemblyCreateView
+
+
+
+ 0
+ 0
+ 376
+ 387
+
+
+
+ Create Exploded View
+
+
+
+
+
+
+
+
+
+ Gui::PrefCheckBox
+ QCheckBox
+ Gui/PrefWidgets.h
+
+
+
+
+
diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp
index f165bfc85fdf8..485e429f62173 100644
--- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp
+++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp
@@ -28,19 +28,28 @@
#include
#include
#include
+#include
#include
+#include
+#include
+#include
+#include
#endif
#include
#include
#include
#include
+
#include
#include
#include
#include
+#include
#include
#include
+#include
+
#include
#include
#include
@@ -184,6 +193,8 @@ bool ViewProviderAssembly::setEdit(int ModNum)
this->getObject()->getDocument()->getName(),
this->getObject()->getNameInDocument());
+ setDragger();
+
return true;
}
@@ -194,6 +205,8 @@ void ViewProviderAssembly::unsetEdit(int ModNum)
partMoving = false;
docsToMove = {};
+ unsetDragger();
+
// Check if the view is still active before trying to deactivate the assembly.
auto doc = getDocument();
if (!doc) {
@@ -212,6 +225,58 @@ void ViewProviderAssembly::unsetEdit(int ModNum)
PARTKEY);
}
+void ViewProviderAssembly::setDragger()
+{
+ // Create the dragger coin object
+ assert(!asmDragger);
+ asmDragger = new Gui::SoFCCSysDragger();
+ asmDragger->setAxisColors(Gui::ViewParams::instance()->getAxisXColor(),
+ Gui::ViewParams::instance()->getAxisYColor(),
+ Gui::ViewParams::instance()->getAxisZColor());
+ asmDragger->draggerSize.setValue(0.05f);
+
+ /*translationSensor = new SoFieldSensor(onDraggerTranslationChanged, this);
+ translationSensor->attach(&asmDragger->translation);
+ translationSensor->setPriority(0);
+ rotationSensor = new SoFieldSensor(onDraggerRotationChanged, this);
+ rotationSensor->attach(&asmDragger->rotation);
+ rotationSensor->setPriority(0); // Set priority if needed, 0 means 'immediate callback'*/
+
+ // asmDragger->addFinishCallback(draggerReleased, this);
+ // asmDragger->addMotionCallback(draggerMoved, this);
+
+ asmDraggerSwitch = new SoSwitch(SO_SWITCH_NONE);
+ asmDraggerSwitch->addChild(asmDragger);
+
+ pcRoot->insertChild(asmDraggerSwitch, 0);
+ asmDraggerSwitch->ref();
+ asmDragger->ref();
+}
+
+void ViewProviderAssembly::unsetDragger()
+{
+ pcRoot->removeChild(asmDraggerSwitch);
+ asmDragger->unref();
+ asmDragger = nullptr;
+ asmDraggerSwitch->unref();
+ asmDraggerSwitch = nullptr;
+ /*translationSensor->detach();
+ delete translationSensor;
+ translationSensor = nullptr;
+ rotationSensor->detach();
+ delete rotationSensor;
+ rotationSensor = nullptr;*/
+}
+
+void ViewProviderAssembly::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
+{
+ ViewProviderPart::setEditViewer(viewer, ModNum);
+
+ if (asmDragger && viewer) {
+ asmDragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera());
+ }
+}
+
bool ViewProviderAssembly::isInEditMode() const
{
App::DocumentObject* activePart = getActivePart();
@@ -753,6 +818,84 @@ bool ViewProviderAssembly::onDelete(const std::vector& subNames)
return ViewProviderPart::onDelete(subNames);
}
+void ViewProviderAssembly::setDraggerVisibility(bool val)
+{
+ asmDraggerSwitch->whichChild = val ? SO_SWITCH_ALL : SO_SWITCH_NONE;
+}
+bool ViewProviderAssembly::getDraggerVisibility()
+{
+ return asmDraggerSwitch->whichChild.getValue() == SO_SWITCH_ALL;
+}
+
+void ViewProviderAssembly::setDraggerPlacement(Base::Placement plc)
+{
+ double q0, q1, q2, q3;
+ plc.getRotation().getValue(q0, q1, q2, q3);
+ Base::Vector3d pos = plc.getPosition();
+ asmDragger->rotation.setValue(q0, q1, q2, q3);
+ asmDragger->translation.setValue(pos.x, pos.y, pos.z);
+}
+
+Base::Placement ViewProviderAssembly::getDraggerPlacement()
+{
+ Base::Placement plc;
+ SbVec3f pos = asmDragger->translation.getValue();
+ plc.setPosition(Base::Vector3d(pos[0], pos[1], pos[2]));
+
+ SbVec3f axis;
+ float angle;
+ asmDragger->rotation.getValue(axis, angle);
+ Base::Vector3d axisV = Base::Vector3d(axis[0], axis[1], axis[2]);
+ Base::Rotation rot(axisV, angle);
+ plc.setRotation(rot);
+
+ return plc;
+}
+
+Gui::SoFCCSysDragger* ViewProviderAssembly::getDragger()
+{
+ return asmDragger;
+}
+
+/*void ViewProviderAssembly::onDraggerTranslationChanged(void* data, SoSensor* sensor)
+{
+ auto* fieldSensor = static_cast(sensor);
+ if (!fieldSensor) {
+ return;
+ }
+ auto* translationField = static_cast(fieldSensor->getAttachedField());
+
+ if (translationField) {
+ const SbVec3f& newTranslation = translationField->getValue();
+ //Base::Console().Warning("hello onDraggerTranslationChanged %s\n",
+newTranslation.toString().getString());
+ }
+}
+
+void ViewProviderAssembly::onDraggerRotationChanged(void* data, SoSensor* sensor)
+{
+ auto* fieldSensor = static_cast(sensor);
+ if (!fieldSensor) {
+ return;
+ }
+ auto* rotationField = static_cast(fieldSensor->getAttachedField());
+
+ if (rotationField) {
+ const SbVec3f& newRotation = rotationField->getValue();
+ //Base::Console().Warning("hello onDraggerRotationChanged %s\n",
+newRotation.toString().getString());
+ }
+
+void ViewProviderAssembly::draggerMoved(void* data, SoDragger* d)
+{
+ Base::Console().Warning("hello draggerMoved\n");
+}
+void ViewProviderAssembly::draggerReleased(void* data, SoDragger* d)
+{
+ Base::Console().Warning("hello dragFinishCb\n");
+}
+}*/
+
PyObject* ViewProviderAssembly::getPyObject()
{
if (!pyViewObject) {
diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.h b/src/Mod/Assembly/Gui/ViewProviderAssembly.h
index 1ad14ba49ba7a..7c0974097269e 100644
--- a/src/Mod/Assembly/Gui/ViewProviderAssembly.h
+++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.h
@@ -31,10 +31,16 @@
#include
#include
+class SoSwitch;
+class SoSensor;
+class SoDragger;
+class SoFieldSensor;
+
namespace Gui
{
+class SoFCCSysDragger;
class View3DInventorViewer;
-}
+} // namespace Gui
namespace AssemblyGui
{
@@ -71,6 +77,7 @@ class AssemblyGuiExport ViewProviderAssembly: public Gui::ViewProviderPart,
//@{
bool setEdit(int ModNum) override;
void unsetEdit(int ModNum) override;
+ void setEditViewer(Gui::View3DInventorViewer*, int ModNum) override;
bool isInEditMode() const;
/// Ask the view provider if it accepts object deletions while in edit
@@ -120,6 +127,19 @@ class AssemblyGuiExport ViewProviderAssembly: public Gui::ViewProviderPart,
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
+ // Dragger controls:
+ void setDragger();
+ void unsetDragger();
+ void setDraggerVisibility(bool val);
+ bool getDraggerVisibility();
+ void setDraggerPlacement(Base::Placement plc);
+ Base::Placement getDraggerPlacement();
+ Gui::SoFCCSysDragger* getDragger();
+ // static void onDraggerTranslationChanged(void* data, SoSensor* sensor);
+ // static void onDraggerRotationChanged(void* data, SoSensor* sensor);
+ // static void draggerMoved(void* data, SoDragger* d);
+ // static void draggerReleased(void* data, SoDragger* d);
+
DragMode dragMode;
bool canStartDragging;
bool partMoving;
@@ -136,6 +156,11 @@ class AssemblyGuiExport ViewProviderAssembly: public Gui::ViewProviderPart,
std::vector> objectMasses;
std::vector> docsToMove;
+
+ Gui::SoFCCSysDragger* asmDragger = nullptr;
+ SoSwitch* asmDraggerSwitch = nullptr;
+ SoFieldSensor* translationSensor = nullptr;
+ SoFieldSensor* rotationSensor = nullptr;
};
} // namespace AssemblyGui
diff --git a/src/Mod/Assembly/Gui/ViewProviderAssemblyPy.xml b/src/Mod/Assembly/Gui/ViewProviderAssemblyPy.xml
index 503bf213ab54c..8d2fac8863703 100644
--- a/src/Mod/Assembly/Gui/ViewProviderAssemblyPy.xml
+++ b/src/Mod/Assembly/Gui/ViewProviderAssemblyPy.xml
@@ -13,11 +13,35 @@
This is the ViewProviderAssembly class
+
+
+
+
+ Return the assembly dragger coin object.
+
+ getDragger() -> SoFCCSysDragger
+
+ Returns: dragger coin object of the assembly
+
+
+ Enable moving the parts by clicking and dragging.
+
+
+ Show or hide the assembly dragger.
+
+
+
+
+
+ Placement of the assembly dragger object.
+
+
+
diff --git a/src/Mod/Assembly/Gui/ViewProviderAssemblyPyImp.cpp b/src/Mod/Assembly/Gui/ViewProviderAssemblyPyImp.cpp
index 5d6cffe4f1b37..4e3bd156c20e7 100644
--- a/src/Mod/Assembly/Gui/ViewProviderAssemblyPyImp.cpp
+++ b/src/Mod/Assembly/Gui/ViewProviderAssemblyPyImp.cpp
@@ -22,6 +22,10 @@
#include "PreCompiled.h"
+#include
+#include
+#include
+
// inclusion of the generated files (generated out of ViewProviderAssemblyPy.xml)
#include "ViewProviderAssemblyPy.h"
#include "ViewProviderAssemblyPy.cpp"
@@ -48,6 +52,46 @@ void ViewProviderAssemblyPy::setEnableMovement(Py::Boolean arg)
getViewProviderAssemblyPtr()->setEnableMovement(arg);
}
+Py::Boolean ViewProviderAssemblyPy::getDraggerVisibility() const
+{
+ return {getViewProviderAssemblyPtr()->getDraggerVisibility()};
+}
+
+void ViewProviderAssemblyPy::setDraggerVisibility(Py::Boolean arg)
+{
+ getViewProviderAssemblyPtr()->setDraggerVisibility(arg);
+}
+
+PyObject* ViewProviderAssemblyPy::getDragger(PyObject* args)
+{
+ if (!PyArg_ParseTuple(args, "")) {
+ return nullptr;
+ }
+ Gui::SoFCCSysDragger* asmDragger = getViewProviderAssemblyPtr()->getDragger();
+
+ return Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoDragger *", asmDragger, 0);
+}
+
+
+Py::Object ViewProviderAssemblyPy::getDraggerPlacement() const
+{
+ return Py::Placement(getViewProviderAssemblyPtr()->getDraggerPlacement());
+}
+
+void ViewProviderAssemblyPy::setDraggerPlacement(Py::Object arg)
+{
+ PyObject* p = arg.ptr();
+ if (PyObject_TypeCheck(p, &(Base::PlacementPy::Type))) {
+ Base::Placement* trf = static_cast(p)->getPlacementPtr();
+ getViewProviderAssemblyPtr()->setDraggerPlacement(*trf);
+ }
+ else {
+ std::string error = std::string("type must be 'Placement', not ");
+ error += p->ob_type->tp_name;
+ throw Py::TypeError(error);
+ }
+}
+
PyObject* ViewProviderAssemblyPy::getCustomAttributes(const char* /*attr*/) const
{
return nullptr;
diff --git a/src/Mod/Assembly/Gui/ViewProviderExplodedView.cpp b/src/Mod/Assembly/Gui/ViewProviderExplodedView.cpp
new file mode 100644
index 0000000000000..f52f7c041fde3
--- /dev/null
+++ b/src/Mod/Assembly/Gui/ViewProviderExplodedView.cpp
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "ViewProviderExplodedView.h"
+
+
+using namespace AssemblyGui;
+
+PROPERTY_SOURCE(AssemblyGui::ViewProviderExplodedView, Gui::ViewProviderDocumentObjectGroup)
+
+ViewProviderExplodedView::ViewProviderExplodedView()
+{}
+
+ViewProviderExplodedView::~ViewProviderExplodedView() = default;
+
+QIcon ViewProviderExplodedView::getIcon() const
+{
+ return Gui::BitmapFactory().pixmap("Assembly_ExplodedView.svg");
+}
diff --git a/src/Mod/Assembly/Gui/ViewProviderExplodedView.h b/src/Mod/Assembly/Gui/ViewProviderExplodedView.h
new file mode 100644
index 0000000000000..47c7f54f363b2
--- /dev/null
+++ b/src/Mod/Assembly/Gui/ViewProviderExplodedView.h
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_ViewProviderExplodedView_H
+#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderExplodedView_H
+
+#include
+
+#include
+
+
+namespace AssemblyGui
+{
+
+class AssemblyGuiExport ViewProviderExplodedView: public Gui::ViewProviderDocumentObjectGroup
+{
+ PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderExplodedView);
+
+public:
+ ViewProviderExplodedView();
+ ~ViewProviderExplodedView() override;
+
+ /// deliver the icon shown in the tree view. Override from ViewProvider.h
+ QIcon getIcon() const override;
+
+ // Prevent dragging of the joints and dropping things inside the joint group.
+ bool canDragObjects() const override
+ {
+ return false;
+ };
+ bool canDropObjects() const override
+ {
+ return false;
+ };
+ bool canDragAndDropObject(App::DocumentObject*) const override
+ {
+ return false;
+ };
+
+ // protected:
+ /// get called by the container whenever a property has been changed
+ // void onChanged(const App::Property* prop) override;
+};
+
+} // namespace AssemblyGui
+
+#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderExplodedView_H
diff --git a/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp b/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp
index 29302adfb05be..e13111b468db0 100644
--- a/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp
+++ b/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp
@@ -45,7 +45,7 @@ ViewProviderJointGroup::~ViewProviderJointGroup() = default;
QIcon ViewProviderJointGroup::getIcon() const
{
- return Gui::BitmapFactory().pixmap("Assembly_CreateJointFixed.svg");
+ return Gui::BitmapFactory().pixmap("Assembly_JointGroup.svg");
}
// Make the joint group impossible to delete.
diff --git a/src/Mod/Assembly/Gui/ViewProviderViewGroup.cpp b/src/Mod/Assembly/Gui/ViewProviderViewGroup.cpp
new file mode 100644
index 0000000000000..bd32b579f3417
--- /dev/null
+++ b/src/Mod/Assembly/Gui/ViewProviderViewGroup.cpp
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "ViewProviderViewGroup.h"
+
+
+using namespace AssemblyGui;
+
+PROPERTY_SOURCE(AssemblyGui::ViewProviderViewGroup, Gui::ViewProviderDocumentObjectGroup)
+
+ViewProviderViewGroup::ViewProviderViewGroup()
+{}
+
+ViewProviderViewGroup::~ViewProviderViewGroup() = default;
+
+QIcon ViewProviderViewGroup::getIcon() const
+{
+ return Gui::BitmapFactory().pixmap("Assembly_ExplodedViewGroup.svg");
+}
diff --git a/src/Mod/Assembly/Gui/ViewProviderViewGroup.h b/src/Mod/Assembly/Gui/ViewProviderViewGroup.h
new file mode 100644
index 0000000000000..c48e89aed7688
--- /dev/null
+++ b/src/Mod/Assembly/Gui/ViewProviderViewGroup.h
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2023 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_ViewProviderViewGroup_H
+#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderViewGroup_H
+
+#include
+
+#include
+
+
+namespace AssemblyGui
+{
+
+class AssemblyGuiExport ViewProviderViewGroup: public Gui::ViewProviderDocumentObjectGroup
+{
+ PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderViewGroup);
+
+public:
+ ViewProviderViewGroup();
+ ~ViewProviderViewGroup() override;
+
+ /// deliver the icon shown in the tree view. Override from ViewProvider.h
+ QIcon getIcon() const override;
+
+ // Prevent dragging of the joints and dropping things inside the joint group.
+ bool canDragObjects() const override
+ {
+ return false;
+ };
+ bool canDropObjects() const override
+ {
+ return false;
+ };
+ bool canDragAndDropObject(App::DocumentObject*) const override
+ {
+ return false;
+ };
+
+ // protected:
+ /// get called by the container whenever a property has been changed
+ // void onChanged(const App::Property* prop) override;
+};
+
+} // namespace AssemblyGui
+
+#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderViewGroup_H
diff --git a/src/Mod/Assembly/InitGui.py b/src/Mod/Assembly/InitGui.py
index 723b877aa3c04..23089919ebb4c 100644
--- a/src/Mod/Assembly/InitGui.py
+++ b/src/Mod/Assembly/InitGui.py
@@ -63,7 +63,7 @@ def Initialize(self):
# load the builtin modules
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
- import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT
+ import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT, CommandCreateView
from Preferences import PreferencesPage
# from Preferences import preferences
@@ -78,6 +78,7 @@ def Initialize(self):
"Assembly_CreateAssembly",
"Assembly_InsertLink",
"Assembly_SolveAssembly",
+ "Assembly_CreateView",
]
cmdListMenuOnly = [
diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py
index 4ddd3e0a1b5c4..4ad8c72bcdb59 100644
--- a/src/Mod/Assembly/UtilsAssembly.py
+++ b/src/Mod/Assembly/UtilsAssembly.py
@@ -606,6 +606,20 @@ def getJointGroup(assembly):
return joint_group
+def getViewGroup(assembly):
+ view_group = None
+
+ for obj in assembly.OutList:
+ if obj.TypeId == "Assembly::ViewGroup":
+ view_group = obj
+ break
+
+ if not view_group:
+ view_group = assembly.newObject("Assembly::ViewGroup", "Exploded Views")
+
+ return view_group
+
+
def isAssemblyGrounded():
assembly = activeAssembly()
if not assembly:
@@ -641,3 +655,69 @@ def addsubobjs(obj, toremoveset):
for obj in toremove:
if obj:
obj.Document.removeObject(obj.Name)
+
+
+# This function returns all the objects within the argument that can move:
+# - Part::Features (outside of parts)
+# - App::parts
+# - App::Links to Part::Features or App::parts.
+# It does not include Part::Features that are within App::Parts.
+# It includes things inside Groups.
+def getMovablePartsWithin(group):
+ parts = []
+ for obj in group.OutList:
+ parts = parts + getSubMovingParts(obj)
+ return parts
+
+
+def getSubMovingParts(obj):
+ if obj.TypeId == "App::Part" or obj.isDerivedFrom("Part::Feature"):
+ return [obj]
+
+ elif obj.TypeId in {"Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
+ return getMovablePartsWithin(obj)
+
+ if obj.TypeId == "App::Link":
+ linked_obj = obj.getLinkedObject()
+ if linked_obj.TypeId == "App::Part" or linked_obj.isDerivedFrom("Part::Feature"):
+ return [obj]
+
+ return []
+
+
+# Find the center of mass of a list of parts.
+# Note it could be useful to move this to Measure mod.
+def getCenterOfMass(parts):
+ total_mass = 0
+ total_com = App.Vector(0, 0, 0)
+
+ for part in parts:
+ if part.TypeId == "App::Link":
+ part = part.getLinkedObject()
+
+ if part.TypeId == "PartDesign::Body":
+ part = part.Tip
+
+ if part.isDerivedFrom("Part::Feature"):
+ # For simple Part::Feature, just accumulate the mass and center of mass
+ mass = part.Shape.Volume # Assuming density is uniform and cancels out
+ com = part.Shape.CenterOfMass
+ total_mass += mass
+ total_com += com * mass
+
+ elif part.isDerivedFrom("App::Part") or part.isDerivedFrom("App::DocumentObjectGroup"):
+ # For compound objects, accumulate mass and center of mass of each subpart
+ for subpart in part.OutListRecursive:
+ if subpart.isDerivedFrom("Part::Feature"):
+ mass = subpart.Shape.Volume # Assuming density is uniform and cancels out
+ com = subpart.Shape.CenterOfMass
+ total_mass += mass
+ total_com += com * mass
+
+ # After all parts are processed, calculate the overall center of mass
+ if total_mass > 0: # Avoid division by zero
+ overall_com = total_com / total_mass
+ else:
+ overall_com = App.Vector(0, 0, 0) # Default if no mass is found
+
+ return overall_com