From 9f3f24ae67d0bca2b644c77d7580a5c23337fe9a Mon Sep 17 00:00:00 2001 From: bgbsww Date: Thu, 19 Sep 2024 20:35:46 -0400 Subject: [PATCH] Support macros and console logs in Assembly --- src/App/DocumentPy.xml | 12 ++++++ src/App/DocumentPyImp.cpp | 12 ++++++ src/App/GeoFeature.cpp | 2 +- src/Gui/Application.h | 4 +- src/Gui/ApplicationPy.cpp | 50 +++++++++++++++++++++++ src/Mod/Assembly/CommandCreateAssembly.py | 19 ++++++--- src/Mod/Assembly/CommandCreateBom.py | 11 +++-- src/Mod/Assembly/CommandCreateJoint.py | 36 ++++++++++------ src/Mod/Assembly/CommandCreateView.py | 38 ++++++++++++----- src/Mod/Assembly/CommandExportASMT.py | 2 +- src/Mod/Assembly/CommandInsertLink.py | 26 +++++++++++- src/Mod/Assembly/CommandSolveAssembly.py | 3 +- src/Mod/Assembly/JointObject.py | 3 ++ src/Mod/Assembly/UtilsAssembly.py | 31 ++++++++++++++ 14 files changed, 212 insertions(+), 37 deletions(-) diff --git a/src/App/DocumentPy.xml b/src/App/DocumentPy.xml index c43726622c442..2e929d7ac1518 100644 --- a/src/App/DocumentPy.xml +++ b/src/App/DocumentPy.xml @@ -56,6 +56,18 @@ For a temporary document it returns its transient directory. + + + getUniqueObjectName(objName) -> objName + + Return the same name, or the name made unique, for Example Box -> Box002 if there are conflicting name + already in the document. + + ObjName : str + Object name. + + + Merges this document with another project file diff --git a/src/App/DocumentPyImp.cpp b/src/App/DocumentPyImp.cpp index c9beed7a997ed..229ba5d7d7a63 100644 --- a/src/App/DocumentPyImp.cpp +++ b/src/App/DocumentPyImp.cpp @@ -217,6 +217,18 @@ PyObject* DocumentPy::getFileName(PyObject* args) return Py::new_reference_to(Py::String(fn)); } +PyObject* DocumentPy::getUniqueObjectName(PyObject *args) +{ + char *sName; + if (!PyArg_ParseTuple(args, "s", &sName)) + return nullptr; + PY_TRY { + auto newName = getDocumentPtr()->getUniqueObjectName(sName); + return Py::new_reference_to(Py::String(newName)); + } + PY_CATCH; +} + PyObject* DocumentPy::mergeProject(PyObject * args) { char* filename; diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index 2ec1640ee606c..e3f448fac9618 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -159,7 +159,7 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn } if(geoFeature) *geoFeature = geo; - if(!obj || (filter && geo!=filter)) + if(filter && geo!=filter) return nullptr; if(!element || !element[0]) { if(append) diff --git a/src/Gui/Application.h b/src/Gui/Application.h index 64d768b0543d5..74f5056e88577 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -28,8 +28,6 @@ #include #include -#define putpix() - #include class QCloseEvent; @@ -340,6 +338,8 @@ class GuiExport Application static PyObject* sDoCommand (PyObject *self,PyObject *args); static PyObject* sDoCommandGui (PyObject *self,PyObject *args); + static PyObject* sDoCommandEval (PyObject *self,PyObject *args); + static PyObject* sDoCommandSkip (PyObject *self,PyObject *args); static PyObject* sAddModule (PyObject *self,PyObject *args); static PyObject* sShowDownloads (PyObject *self,PyObject *args); diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index 781ec22848eea..f948712fdac5e 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -316,6 +316,19 @@ PyMethodDef Application::Methods[] = { "but doesn't record it in macros.\n" "\n" "cmd : str"}, + {"doCommandEval", (PyCFunction) Application::sDoCommandEval, METH_VARARGS, + "doCommandEval(cmd) -> PyObject\n" + "\n" + "Runs the given string without showing in the python console or recording in\n" + "macros, and returns the result.\n" + "\n" + "cmd : str"}, + {"doCommandSkip", (PyCFunction) Application::sDoCommandSkip, METH_VARARGS, + "doCommandSkip(cmd) -> None\n" + "\n" + "Record the given string in the Macro but comment it out in the console\n" + "\n" + "cmd : str"}, {"addModule", (PyCFunction) Application::sAddModule, METH_VARARGS, "addModule(mod) -> None\n" "\n" @@ -1352,6 +1365,43 @@ PyObject* Application::sDoCommandGui(PyObject * /*self*/, PyObject *args) return PyRun_String(sCmd, Py_file_input, dict, dict); } +PyObject* Application::sDoCommandEval(PyObject * /*self*/, PyObject *args) +{ + char *sCmd = nullptr; + if (!PyArg_ParseTuple(args, "s", &sCmd)) + return nullptr; + + Gui::Command::LogDisabler d1; + Gui::SelectionLogDisabler d2; + + PyObject *module, *dict; + + Base::PyGILStateLocker locker; + module = PyImport_AddModule("__main__"); + if (!module) + return nullptr; + + dict = PyModule_GetDict(module); + if (!dict) + return nullptr; + + return PyRun_String(sCmd, Py_eval_input, dict, dict); +} + +PyObject* Application::sDoCommandSkip(PyObject * /*self*/, PyObject *args) +{ + char *sCmd = nullptr; + if (!PyArg_ParseTuple(args, "s", &sCmd)) + return nullptr; + + Gui::Command::LogDisabler d1; + Gui::SelectionLogDisabler d2; + + Gui::Command::printPyCaller(); + Gui::Application::Instance->macroManager()->addLine(MacroManager::App, sCmd); + return Py::None().ptr(); +} + PyObject* Application::sAddModule(PyObject * /*self*/, PyObject *args) { char *pstr; diff --git a/src/Mod/Assembly/CommandCreateAssembly.py b/src/Mod/Assembly/CommandCreateAssembly.py index 1d498ca8b6376..cb1f3ed143c2d 100644 --- a/src/Mod/Assembly/CommandCreateAssembly.py +++ b/src/Mod/Assembly/CommandCreateAssembly.py @@ -70,15 +70,24 @@ def Activated(self): App.setActiveTransaction("Create assembly") activeAssembly = UtilsAssembly.activeAssembly() + Gui.addModule("UtilsAssembly") if activeAssembly: - assembly = activeAssembly.newObject("Assembly::AssemblyObject", "Assembly") + commands = ( + "activeAssembly = UtilsAssembly.activeAssembly()\n" + 'assembly = activeAssembly.newObject("Assembly::AssemblyObject", "Assembly")\n' + ) else: - assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly") + commands = ( + 'assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly")\n' + ) - assembly.Type = "Assembly" + commands = commands + 'assembly.Type = "Assembly"\n' + commands = commands + 'assembly.newObject("Assembly::JointGroup", "Joints")' + + Gui.doCommand(commands) if not activeAssembly: - Gui.ActiveDocument.setEdit(assembly) - assembly.newObject("Assembly::JointGroup", "Joints") + Gui.doCommandGui("Gui.ActiveDocument.setEdit(assembly)") + App.closeActiveTransaction() diff --git a/src/Mod/Assembly/CommandCreateBom.py b/src/Mod/Assembly/CommandCreateBom.py index 5eed4338dcdcd..ee158ee369333 100644 --- a/src/Mod/Assembly/CommandCreateBom.py +++ b/src/Mod/Assembly/CommandCreateBom.py @@ -324,11 +324,16 @@ def isNameDuplicate(self, name): def createBomObject(self): assembly = UtilsAssembly.activeAssembly() + Gui.addModule("UtilsAssembly") if assembly is not None: - bom_group = UtilsAssembly.getBomGroup(assembly) - self.bomObj = bom_group.newObject("Assembly::BomObject", "Bill of Materials") + commands = ( + "bom_group = UtilsAssembly.getBomGroup(assembly)\n" + 'bomObj = bom_group.newObject("Assembly::BomObject", "Bill of Materials")' + ) else: - self.bomObj = App.activeDocument().addObject("Assembly::BomObject", "Bill of Materials") + commands = 'bomObj = App.activeDocument().addObject("Assembly::BomObject", "Bill of Materials")' + Gui.doCommand(commands) + self.bomObj = Gui.doCommandEval("bomObj") def export(self): self.bomObj.recompute() diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py index 8ce6505897458..77e6b3b7f7f7e 100644 --- a/src/Mod/Assembly/CommandCreateJoint.py +++ b/src/Mod/Assembly/CommandCreateJoint.py @@ -58,8 +58,10 @@ def activateJoint(index): if JointObject.activeTask: JointObject.activeTask.reject() - panel = TaskAssemblyCreateJoint(index) - dialog = Gui.Control.showDialog(panel) + Gui.addModule("JointObject") # NOLINT + Gui.doCommand(f"panel = JointObject.TaskAssemblyCreateJoint({index})") + Gui.doCommandGui("dialog = Gui.Control.showDialog(panel)") + dialog = Gui.doCommandEval("dialog") if dialog is not None: dialog.setAutoCloseOnTransactionChange(True) dialog.setDocumentName(App.ActiveDocument.Name) @@ -476,16 +478,21 @@ def IsActive(self): def createGroundedJoint(obj): - assembly = UtilsAssembly.activeAssembly() - if not assembly: + if not UtilsAssembly.activeAssembly(): return - joint_group = UtilsAssembly.getJointGroup(assembly) - - ground = joint_group.newObject("App::FeaturePython", "GroundedJoint") - JointObject.GroundedJoint(ground, obj) - JointObject.ViewProviderGroundedJoint(ground.ViewObject) - return ground + Gui.addModule("UtilsAssembly") + Gui.addModule("JointObject") + commands = ( + f'obj = App.ActiveDocument.getObject("{obj.Name}")\n' + "assembly = UtilsAssembly.activeAssembly()\n" + "joint_group = UtilsAssembly.getJointGroup(assembly)\n" + 'ground = joint_group.newObject("App::FeaturePython", "GroundedJoint")\n' + "JointObject.GroundedJoint(ground, obj)" + ) + Gui.doCommand(commands) + Gui.doCommandGui("JointObject.ViewProviderGroundedJoint(ground.ViewObject)") + return Gui.doCommandEval("ground") class CommandToggleGrounded: @@ -540,9 +547,12 @@ def Activated(self): ungrounded = False for joint in joint_group.Group: if hasattr(joint, "ObjectToGround") and joint.ObjectToGround == moving_part: - doc = App.ActiveDocument - doc.removeObject(joint.Name) - doc.recompute() + commands = ( + "doc = App.ActiveDocument\n" + f'doc.removeObject("{joint.Name}")\n' + "doc.recompute()\n" + ) + Gui.doCommand(commands) ungrounded = True break if ungrounded: diff --git a/src/Mod/Assembly/CommandCreateView.py b/src/Mod/Assembly/CommandCreateView.py index b83841bc13a4f..b0343312bdcd4 100644 --- a/src/Mod/Assembly/CommandCreateView.py +++ b/src/Mod/Assembly/CommandCreateView.py @@ -74,8 +74,10 @@ def Activated(self): if not assembly: return - self.panel = TaskAssemblyCreateView() - Gui.Control.showDialog(self.panel) + Gui.addModule("CommandCreateView") # NOLINT + Gui.doCommand("panel = CommandCreateView.TaskAssemblyCreateView()") + self.panel = Gui.doCommandEval("panel") + Gui.doCommandGui("Gui.Control.showDialog(panel)") ######### Exploded View Object ########### @@ -524,6 +526,11 @@ def accept(self): UtilsAssembly.restoreAssemblyPartsPlacements(self.assembly, self.initialPlcs) for move in self.viewObj.Moves: move.Visibility = False + commands = f'obj = App.ActiveDocument.getObject("{self.viewObj.Name}")\n' + for move in self.viewObj.Moves: + more = UtilsAssembly.generatePropertySettings("obj.Moves[0]", move) + commands = commands + more + Gui.doCommand(commands[:-1]) # Don't use the last \n App.closeActiveTransaction() return True @@ -695,16 +702,27 @@ def setDraggerObjectPlc(self): self.blockDraggerMove = False 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) + Gui.addModule("UtilsAssembly") + commands = ( + f'assembly = App.ActiveDocument.getObject("{self.assembly.Name}")\n' + "view_group = UtilsAssembly.getViewGroup(assembly)\n" + 'viewObj = view_group.newObject("App::FeaturePython", "Exploded View")\n' + "CommandCreateView.ExplodedView(viewObj)" + ) + Gui.doCommand(commands) + self.viewObj = Gui.doCommandEval("viewObj") + Gui.doCommandGui("CommandCreateView.ViewProviderExplodedView(viewObj.ViewObject)") def createExplodedStepObject(self, moveType_index=0): - self.currentStep = self.assembly.newObject("App::FeaturePython", "Move") - ExplodedViewStep(self.currentStep, moveType_index) - ViewProviderExplodedViewStep(self.currentStep.ViewObject) + commands = ( + f'assembly = App.ActiveDocument.getObject("{self.assembly.Name}")\n' + 'currentStep = assembly.newObject("App::FeaturePython", "Move")\n' + f"CommandCreateView.ExplodedViewStep(currentStep, {moveType_index})" + ) + Gui.doCommand(commands) + self.currentStep = Gui.doCommandEval("currentStep") + Gui.doCommandGui("CommandCreateView.ViewProviderExplodedViewStep(currentStep.ViewObject)") self.currentStep.MovementTransform = App.Placement() @@ -727,7 +745,7 @@ def dismissCurrentStep(self): for obj, init_plc in zip(self.selectedObjs, self.selectedObjsInitPlc): obj.Placement = init_plc - self.currentStep.Document.removeObject(self.currentStep.Name) + Gui.doCommand(f'App.ActiveDocument.removeObject("{self.currentStep.Name}")') self.currentStep = None Gui.Selection.clearSelection() diff --git a/src/Mod/Assembly/CommandExportASMT.py b/src/Mod/Assembly/CommandExportASMT.py index 0a7cdcb36daca..88ccf32ef6bc6 100644 --- a/src/Mod/Assembly/CommandExportASMT.py +++ b/src/Mod/Assembly/CommandExportASMT.py @@ -74,7 +74,7 @@ def Activated(self): ) if filePath: - assembly.exportAsASMT(filePath) + Gui.doCommand(f'assembly.exportAsASMT("{filePath}")') if App.GuiUp: diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index ea889c4271568..a5312339b6fa3 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -80,7 +80,6 @@ def Activated(self): if not assembly: return view = Gui.activeDocument().activeView() - self.panel = TaskAssemblyInsertLink(assembly, view) Gui.Control.showDialog(self.panel) @@ -125,7 +124,32 @@ def accept(self): # if self.partMoving: # self.endMove() + Gui.addModule("UtilsAssembly") + commands = "assembly = UtilsAssembly.activeAssembly()\n" + for insertionItem in self.insertionStack: + object = insertionItem["addedObject"] + translation = insertionItem["translation"] + commands = commands + ( + f'item = assembly.newObject("App::Link", "{object.Name}")\n' + f'item.LinkedObject = App.ActiveDocument.getObject("{object.LinkedObject.Name}")\n' + f'item.Label = "{object.Label}"\n' + ) + + if translation != App.Vector(): + commands = commands + ( + f"item.Placement.base = App.Vector({translation.x}." + f"{translation.y}," + f"{translation.z})\n" + ) + + # Ground the first item if that happened + if self.groundedObj: + commands = ( + commands + + f'CommandCreateJoint.createGroundedJoint(App.ActiveDocument.getObject("{self.groundedObj.Name}"))\n' + ) + Gui.doCommandSkip(commands[:-1]) # Get rid of last \n App.closeActiveTransaction() return True diff --git a/src/Mod/Assembly/CommandSolveAssembly.py b/src/Mod/Assembly/CommandSolveAssembly.py index fec765b68259b..f1eba695cd438 100644 --- a/src/Mod/Assembly/CommandSolveAssembly.py +++ b/src/Mod/Assembly/CommandSolveAssembly.py @@ -67,8 +67,9 @@ def Activated(self): if not assembly: return + Gui.addModule("UtilsAssembly") App.setActiveTransaction("Solve assembly") - assembly.solve() + Gui.doCommand("UtilsAssembly.activeAssembly().solve()") App.closeActiveTransaction() diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index 110ed3676e832..5439a9c5a043a 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -1417,6 +1417,9 @@ def accept(self): else: self.joint.Document.removeObject(self.joint.Name) + cmds = UtilsAssembly.generatePropertySettings("obj", self.joint) + Gui.doCommand(cmds) + App.closeActiveTransaction() return True diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index b529c4d09eb7c..ac7a7d79cbe8a 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -1170,3 +1170,34 @@ def getParentPlacementIfNeeded(part): return linkGroup.Placement return Base.Placement() + + +def generatePropertySettings(objectName, documentObject): + commands = [] + if hasattr(documentObject, "Name"): + commands.append(f'{objectName} = App.ActiveDocument.getObject("{documentObject.Name}")') + for propertyName in documentObject.PropertiesList: + propertyValue = documentObject.getPropertyByName(propertyName) + propertyType = documentObject.getTypeIdOfProperty(propertyName) + # Note: OpenCascade precision is 1e-07, angular precision is 1e-05. For purposes of creating a Macro, + # we are forcing a reduction in precision so as to get round numbers like 0 instead of tiny near 0 values + if propertyType == "App::PropertyFloat": + commands.append(f"{objectName}.{propertyName} = {propertyValue:.5f}") + elif propertyType == "App::PropertyInt" or propertyType == "App::PropertyBool": + commands.append(f"{objectName}.{propertyName} = {propertyValue}") + elif propertyType == "App::PropertyString" or propertyType == "App::PropertyEnumeration": + commands.append(f'{objectName}.{propertyName} = "{propertyValue}"') + elif propertyType == "App::PropertyPlacement": + commands.append( + f"{objectName}.{propertyName} = App.Placement(" + f"App.Vector({propertyValue.Base.x:.5f},{propertyValue.Base.y:.5f},{propertyValue.Base.z:.5f})," + f"App.Rotation(*{[round(n,5) for n in propertyValue.Rotation.getYawPitchRoll()]}))" + ) + elif propertyType == "App::PropertyXLinkSubHidden": + commands.append( + f'{objectName}.{propertyName} = [App.ActiveDocument.getObject("{propertyValue[0].Name}"), {propertyValue[1]}]' + ) + else: + print("Not processing properties of type ", propertyType) + pass + return "\n".join(commands) + "\n"