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.svg icons/Assembly_ExportASMT.svg icons/Assembly_SolveAssembly.svg + icons/Assembly_JointGroup.svg + icons/Assembly_ExplodedView.svg + icons/Assembly_ExplodedViewGroup.svg panels/TaskAssemblyCreateJoint.ui panels/TaskAssemblyInsertLink.ui + panels/TaskAssemblyCreateView.ui preferences/Assembly.ui icons/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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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_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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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_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