diff --git a/src/App/Origin.cpp b/src/App/Origin.cpp
index 4db67ca653..2d98938eb1 100644
--- a/src/App/Origin.cpp
+++ b/src/App/Origin.cpp
@@ -219,7 +219,12 @@ bool Origin::OriginExtension::extensionGetSubObject(DocumentObject *&ret, const
ret = obj->getOriginFeature(name.c_str());
if (!ret)
return false;
- ret = ret->getSubObject(subname + name.size() + 1, pyobj, mat, true, depth+1);
+ const char *dot = strchr(subname, '.');
+ if (dot)
+ subname = dot+1;
+ else
+ subname = "";
+ ret = ret->getSubObject(subname, pyobj, mat, true, depth+1);
return true;
}
catch (const Base::Exception& e) {
diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp
index 15717aebc4..a3ae291cfa 100644
--- a/src/Gui/Command.cpp
+++ b/src/Gui/Command.cpp
@@ -369,7 +369,7 @@ void Command::invoke(int i, TriggerSource trigger)
{
CommandTrigger cmdTrigger(_trigger,trigger);
if (displayText.empty()) {
- displayText = getMenuText();
+ displayText = getMenuText() ? getMenuText() : "";
boost::replace_all(displayText,"&","");
if (displayText.empty())
displayText = getName();
diff --git a/src/Gui/CommandPy.xml b/src/Gui/CommandPy.xml
index 0556da17f3..2f416f5c1f 100644
--- a/src/Gui/CommandPy.xml
+++ b/src/Gui/CommandPy.xml
@@ -15,116 +15,107 @@
- Get a given command by name or None if it doesn't exist.
-get(string) -> Command
-
+ get(name) -> Gui.Command or None\n
+Get a given command by name or None if it doesn't exist.\n
+name : str\n Command name.
- Update active status of all commands.
-update() -> None
-
+ update() -> None\n
+Update active status of all commands.
- Returns the name of all commands.
-listAll() -> list of strings
-
+ listAll() -> list of str\n
+Returns the name of all commands.
- Returns a list of all commands, filtered by shortcut.
-listByShortcut(string, bool bUseRegExp=False) -> list of strings
---
-Shortcuts are converted to uppercase and spaces removed prior to comparison.
-
+ listByShortcut(string, useRegExp=False) -> list of str\n
+Returns a list of all commands, filtered by shortcut.
+Shortcuts are converted to uppercase and spaces removed
+prior to comparison.\n
+string : str\n Shortcut to be searched.
+useRegExp : bool\n Filter using regular expression.
- Runs the given command.
-run() -> None
-
+ run(item=0) -> None\n
+Runs the given command.\n
+item : int\n Item to be run.
- Returns True if the command is active, False otherwise.
-isActive() -> bool
-
+ isActive() -> bool\n
+Returns True if the command is active, False otherwise.
- Returns string representing shortcut key accelerator for command.
-getShortcut() -> string
-
+ getShortcut() -> str\n
+Returns string representing shortcut key accelerator for command.
- Sets shortcut for given command, returns bool True for success.
-setShortcut(string) -> bool
-
+ setShortcut(string) -> bool\n
+Sets shortcut for given command, returns True for success.\n
+string : str\n Shortcut to be set.
- Resets shortcut for given command back to the default, returns bool True for success.
-resetShortcut() -> bool
-
+ resetShortcut() -> bool\n
+Resets shortcut for given command back to the default, returns True for success.
- Return information about this command.
-getInfo() -> list of strings
---
-Usage: menuText, tooltipText, whatsThisText, statustipText, pixmapText, shortcutText.
-
+ getInfo() -> dict\n
+Return information about this command.
- Return the associated QAction object.
-getAction() -> list of QAction
+ getAction() -> list of QAction\n
+Return the associated QAction object.
-
+
- Create a custom command for a macro
-createCustomCommand(macrofile, menuText, tooltipText, whatsThisText, statustipText, pixmapText, shortcutText) -> str
---
-Only the macrofile argument is required, and should be the name of the macro file. All other arguments are
-passed on to the command creation routines if they are provided. All arguments except the first accept None.
-
-Returns the name of the created custom command.
-
+ createCustomCommand(macroFile, menuText, toolTip, whatsThis, statusTip, pixmap, shortcut) -> str\n
+Create a custom command for a macro. Returns name of the created command.\n
+macroFile : str\n Macro file.
+menuText : str\n Menu text. Optional.
+toolTip : str\n Tool tip text. Optional.
+whatsThis : str\n `What's this?` text. Optional.
+statusTip : str\n Status tip text. Optional.
+pixmap : str\n Pixmap name. Optional.
+shortcut : str\n Shortcut key sequence. Optional.
- Remove the custom command if it exists
-removeCustomCommand(name) -> bool
---
-Given the name of a custom command, this removes that command. It is not an error
-to remove a non-existent command, the function simply does nothing in that case.
-
-Returns True if something was removed, or False if not.
-
+ removeCustomCommand(name) -> bool\n
+Remove the custom command if it exists.
+Given the name of a custom command, this removes that command.
+It is not an error to remove a non-existent command, the function
+simply does nothing in that case.
+Returns True if something was removed, or False if not.\n
+name : str\n Command name.
- Find the name of a custom command, given a macro name
-findCustomCommand(name) -> Optional[str]
---
-Given the name of a macro, return the name of the custom command for that macro, or
-None if there is no command matching that macro script name.
-
+ findCustomCommand(name) -> str or None\n
+Given the name of a macro, return the name of the custom command for that macro
+or None if there is no command matching that macro script name.\n
+name : str\n Macro name.
diff --git a/src/Gui/CommandPyImp.cpp b/src/Gui/CommandPyImp.cpp
index 527c5a8638..9a8b07fe6b 100644
--- a/src/Gui/CommandPyImp.cpp
+++ b/src/Gui/CommandPyImp.cpp
@@ -33,7 +33,7 @@
#include "Window.h"
#include "PythonWrapper.h"
-// inclusion of the generated files (generated out of AreaPy.xml)
+// inclusion of the generated files (generated out of CommandPy.xml)
#include "CommandPy.h"
#include "CommandPy.cpp"
@@ -86,8 +86,8 @@ PyObject* CommandPy::listAll(PyObject *args)
PyObject* CommandPy::listByShortcut(PyObject *args)
{
char* shortcut_to_find;
- bool bIsRegularExp = false;
- if (!PyArg_ParseTuple(args, "s|b", &shortcut_to_find, &bIsRegularExp))
+ PyObject* bIsRegularExp = Py_False;
+ if (!PyArg_ParseTuple(args, "s|O!", &shortcut_to_find, &PyBool_Type, &bIsRegularExp))
return nullptr;
std::vector cmds = Application::Instance->commandManager().getAllCommands();
@@ -96,7 +96,7 @@ PyObject* CommandPy::listByShortcut(PyObject *args)
Action* action = c->getAction();
if (action){
QString spc = QString::fromLatin1(" ");
- if(bIsRegularExp){
+ if(PyObject_IsTrue(bIsRegularExp)){
QRegExp re = QRegExp(QString::fromLatin1(shortcut_to_find));
re.setCaseSensitivity(Qt::CaseInsensitive);
if (!re.isValid()){
@@ -105,7 +105,7 @@ PyObject* CommandPy::listByShortcut(PyObject *args)
throw Py::RuntimeError(str.str());
}
- if (re.indexIn(action->shortcut().toString().remove(spc).toUpper()) != -1){
+ if (re.indexIn(action->shortcut().toString().remove(spc).toUpper()) != -1) {
matches.push_back(c->getName());
}
}
@@ -261,7 +261,8 @@ PyObject* CommandPy::getInfo(PyObject *args)
Command* cmd = this->getCommandPtr();
if (cmd) {
Action* action = cmd->getAction();
- PyObject* pyList = PyList_New(6);
+ PyObject* pyDict = PyDict_New();
+ const char* cmdName = cmd->getName();
const char* menuTxt = cmd->getMenuText();
const char* tooltipTxt = cmd->getToolTipText();
const char* whatsThisTxt = cmd->getWhatsThis();
@@ -271,19 +272,21 @@ PyObject* CommandPy::getInfo(PyObject *args)
if (action)
shortcutTxt = action->shortcut().toString().toStdString();
+ PyObject* strCmdName = PyUnicode_FromString(cmdName);
PyObject* strMenuTxt = PyUnicode_FromString(menuTxt ? menuTxt : "");
PyObject* strTooltipTxt = PyUnicode_FromString(tooltipTxt ? tooltipTxt : "");
PyObject* strWhatsThisTxt = PyUnicode_FromString(whatsThisTxt ? whatsThisTxt : "");
PyObject* strStatustipTxt = PyUnicode_FromString(statustipTxt ? statustipTxt : "");
PyObject* strPixMapTxt = PyUnicode_FromString(pixMapTxt ? pixMapTxt : "");
PyObject* strShortcutTxt = PyUnicode_FromString(!shortcutTxt.empty() ? shortcutTxt.c_str() : "");
- PyList_SetItem(pyList, 0, strMenuTxt);
- PyList_SetItem(pyList, 1, strTooltipTxt);
- PyList_SetItem(pyList, 2, strWhatsThisTxt);
- PyList_SetItem(pyList, 3, strStatustipTxt);
- PyList_SetItem(pyList, 4, strPixMapTxt);
- PyList_SetItem(pyList, 5, strShortcutTxt);
- return pyList;
+ PyDict_SetItemString(pyDict, "name", strCmdName);
+ PyDict_SetItemString(pyDict, "menuText", strMenuTxt);
+ PyDict_SetItemString(pyDict, "toolTip", strTooltipTxt);
+ PyDict_SetItemString(pyDict, "whatsThis", strWhatsThisTxt);
+ PyDict_SetItemString(pyDict, "statusTip", strStatustipTxt);
+ PyDict_SetItemString(pyDict, "pixmap", strPixMapTxt);
+ PyDict_SetItemString(pyDict, "shortcut", strShortcutTxt);
+ return pyDict;
}
else {
PyErr_Format(Base::PyExc_FC_GeneralError, "No such command");
@@ -322,16 +325,18 @@ PyObject* CommandPy::getAction(PyObject *args)
}
-PyObject* CommandPy::createCustomCommand(PyObject* args)
+PyObject* CommandPy::createCustomCommand(PyObject* args, PyObject* kw)
{
- const char* macroFile = nullptr;
+ const char* macroFile;
const char* menuTxt = nullptr;
const char* tooltipTxt = nullptr;
const char* whatsthisTxt = nullptr;
const char* statustipTxt = nullptr;
const char* pixmapTxt = nullptr;
const char* shortcutTxt = nullptr;
- if (!PyArg_ParseTuple(args, "s|zzzzzz", ¯oFile, &menuTxt, &tooltipTxt, &whatsthisTxt, &statustipTxt, &pixmapTxt, &shortcutTxt))
+ static char* kwlist[] = {"macroFile", "menuText", "toolTip", "whatsThis","statusTip", "pixmap", "shortcut", nullptr};
+ if (!PyArg_ParseTupleAndKeywords(args, kw, "s|zzzzzz", kwlist, ¯oFile, &menuTxt,
+ &tooltipTxt, &whatsthisTxt, &statustipTxt, &pixmapTxt, &shortcutTxt))
return nullptr;
auto name = Application::Instance->commandManager().newMacroName();
@@ -400,13 +405,10 @@ PyObject* CommandPy::findCustomCommand(PyObject* args)
return false;
});
- if (action != macros.end()) {
+ if (action != macros.end())
return PyUnicode_FromString((*action)->getName());
- }
- else {
- Py_INCREF(Py_None);
- return Py_None;
- }
+ else
+ Py_Return;
}
PyObject *CommandPy::getCustomAttributes(const char* /*attr*/) const
diff --git a/src/Gui/SplitView3DInventor.cpp b/src/Gui/SplitView3DInventor.cpp
index 51c424f0f1..1f058250dd 100644
--- a/src/Gui/SplitView3DInventor.cpp
+++ b/src/Gui/SplitView3DInventor.cpp
@@ -719,7 +719,7 @@ Py::Object AbstractSplitViewPy::getViewer(const Py::Tuple& args)
}
}
-Py::Object AbstractSplitViewPy::sequence_item(ssize_t viewIndex)
+Py::Object AbstractSplitViewPy::sequence_item(Py_ssize_t viewIndex)
{
AbstractSplitView* view = getSplitViewPtr();
if (viewIndex >= view->getSize() || viewIndex < 0)
diff --git a/src/Gui/SplitView3DInventor.h b/src/Gui/SplitView3DInventor.h
index 51247ef71d..7cac36de13 100644
--- a/src/Gui/SplitView3DInventor.h
+++ b/src/Gui/SplitView3DInventor.h
@@ -98,7 +98,7 @@ public:
Py::Object viewTop(const Py::Tuple&);
Py::Object viewIsometric(const Py::Tuple&);
Py::Object getViewer(const Py::Tuple&);
- Py::Object sequence_item(ssize_t);
+ Py::Object sequence_item(Py_ssize_t);
Py::Object close(const Py::Tuple&);
int sequence_length();
diff --git a/src/Mod/Arch/ArchWindow.py b/src/Mod/Arch/ArchWindow.py
index 56799c8db9..d7048a2f70 100644
--- a/src/Mod/Arch/ArchWindow.py
+++ b/src/Mod/Arch/ArchWindow.py
@@ -96,7 +96,11 @@ def makeWindow(baseobj=None,width=None,height=None,parts=None,name=None):
else:
if baseobj:
if baseobj.getLinkedObject().isDerivedFrom("Part::Part2DObject"):
+ # create default component
if baseobj.Shape.Wires:
+ tp = "Frame"
+ if len(baseobj.Shape.Wires) == 1:
+ tp = "Solid panel"
i = 0
ws = ''
for w in baseobj.Shape.Wires:
@@ -104,7 +108,7 @@ def makeWindow(baseobj=None,width=None,height=None,parts=None,name=None):
if ws: ws += ","
ws += "Wire" + str(i)
i += 1
- obj.WindowParts = ["Default","Frame",ws,"1","0"]
+ obj.WindowParts = ["Default",tp,ws,"1","0"]
else:
# bind properties from base obj if existing
for prop in ["Height","Width","Subvolume","Tag","Description","Material"]:
diff --git a/src/Mod/Draft/importSVG.py b/src/Mod/Draft/importSVG.py
index 5af8786cde..e01dc52944 100644
--- a/src/Mod/Draft/importSVG.py
+++ b/src/Mod/Draft/importSVG.py
@@ -1591,14 +1591,15 @@ class svgHandler(xml.sax.ContentHandler):
cy = 0
angle = argsplit[0]
if len(argsplit) >= 3:
+ # Rotate around a non-origin centerpoint (note: SVG y axis is opposite FreeCAD y axis)
cx = argsplit[1]
cy = argsplit[2]
- m.move(Vector(cx, -cy, 0))
+ m.move(Vector(-cx, cy, 0)) # Reposition for rotation
# Mirroring one axis is equal to changing the direction
# of rotation
m.rotateZ(math.radians(-angle))
if len(argsplit) >= 3:
- m.move(Vector(-cx, cy, 0))
+ m.move(Vector(cx, -cy, 0)) # Reverse repositioning
elif transformation == 'skewX':
_m = FreeCAD.Matrix(1,
-math.tan(math.radians(argsplit[0])))
diff --git a/src/Mod/Import/DxfPlate/header14.rub b/src/Mod/Import/DxfPlate/header14.rub
index a6cf3fa447..229889048e 100644
--- a/src/Mod/Import/DxfPlate/header14.rub
+++ b/src/Mod/Import/DxfPlate/header14.rub
@@ -31,6 +31,14 @@ $CMLSTYLE
2
STANDARD
9
+$LUNITS
+ 70
+2
+ 9
+$INSUNITS
+ 70
+4
+ 9
$PEXTMAX
10
50
diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp
index ea079e60ee..8900cecc78 100644
--- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp
+++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp
@@ -134,7 +134,7 @@ void ViewProviderSketch::ParameterObserver::updateGridSize(const std::string & s
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General");
- Client.GridSize.setValue(Base::Quantity::parse(QString::fromLatin1(hGrp->GetGroup("GridSize")->GetASCII("Hist0", "10.0").c_str())).getValue());
+ Client.GridSize.setValue(Base::Quantity::parse(QString::fromLatin1(hGrp->GetGroup("GridSize")->GetASCII("GridSize", "10.0").c_str())).getValue());
}
void ViewProviderSketch::ParameterObserver::updateEscapeKeyBehaviour(const std::string & string, App::Property * property)
diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py
index 3b7ca66a33..904aea809a 100644
--- a/src/Mod/Test/Document.py
+++ b/src/Mod/Test/Document.py
@@ -306,6 +306,17 @@ class DocumentBasicCases(unittest.TestCase):
self.assertEqual(obj.getSubObject(("XY_Plane", "YZ_Plane"), retType=4)[0], obj.getSubObject("XY_Plane", retType=4))
self.assertEqual(obj.getSubObject(("XY_Plane", "YZ_Plane"), retType=4)[1], obj.getSubObject("YZ_Plane", retType=4))
+ # Create a second origin object
+ obj2 = self.Doc.addObject("App::Origin", "Origin2")
+ self.Doc.recompute()
+
+ # Use the names of the origin's out-list
+ for i in obj2.OutList:
+ self.assertEqual(obj2.getSubObject(i.Name, retType=1).Name, i.Name)
+ # Add a '.' to the names
+ for i in obj2.OutList:
+ self.assertEqual(obj2.getSubObject(i.Name + '.', retType=1).Name, i.Name)
+
def testExtensions(self):
#we try to create a normal python object and add an extension to it
obj = self.Doc.addObject("App::DocumentObject", "Extension_1")
diff --git a/src/Mod/Test/Gui/UnitTestImp.cpp b/src/Mod/Test/Gui/UnitTestImp.cpp
index 8262ea97ac..6adc37262c 100644
--- a/src/Mod/Test/Gui/UnitTestImp.cpp
+++ b/src/Mod/Test/Gui/UnitTestImp.cpp
@@ -306,7 +306,7 @@ void UnitTestDialog::setProgressFraction(float fraction, const QString& color)
}
/**
- * Emtpies the error listview.
+ * Empties the error listview.
*/
void UnitTestDialog::clearErrorList()
{
diff --git a/src/Mod/Test/Workbench.py b/src/Mod/Test/Workbench.py
index d382421ea6..9b9d83a0b4 100755
--- a/src/Mod/Test/Workbench.py
+++ b/src/Mod/Test/Workbench.py
@@ -24,6 +24,7 @@
# Workbench test module
import FreeCAD, FreeCADGui, os, unittest
+import tempfile
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import QApplication
@@ -79,3 +80,16 @@ class WorkbenchTestCase(unittest.TestCase):
def tearDown(self):
FreeCADGui.activateWorkbench(self.Active.name())
FreeCAD.Console.PrintLog(self.Active.name())
+
+class CommandTestCase(unittest.TestCase):
+ def testPR6889(self):
+ # Fixes a crash
+ TempPath = tempfile.gettempdir()
+ macroName = TempPath + os.sep + "testmacro.py"
+ macroFile = open(macroName, "w")
+ macroFile.write("print ('Hello, World!')")
+ macroFile.close()
+
+ name = FreeCADGui.Command.createCustomCommand(macroName)
+ cmd = FreeCADGui.Command.get(name)
+ cmd.run()