Attacher: add general purpose editor UI (python)

Added as PartGui.AttachmentEditor, and Part_EditAttachment gui command
This commit is contained in:
DeepSOIC 2016-05-16 01:40:25 +03:00 committed by wmayer
parent 836d5b1525
commit 7f5197695d
12 changed files with 1378 additions and 53 deletions

View File

@ -92,49 +92,49 @@ void PyException::ReportException (void) const
SystemExitException::SystemExitException()
{
// Set exception message and code based upon the pthon sys.exit() code and/or message
// based upon the the following sys.exit() call semantics.
//
// Invocation | _exitCode | _sErrMsg
// ---------------- + --------- + --------
// sys.exit(int#) | int# | "System Exit"
// sys.exit(string) | 1 | string
// sys.exit() | 1 | "System Exit"
long int errCode = 1;
std::string errMsg = "System exit";
PyObject *type, *value, *traceback, *code;
PyGILStateLocker locker;
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
if (value) {
code = PyObject_GetAttrString(value, "code");
if (code != NULL && value != Py_None) {
Py_DECREF(value);
value = code;
}
if (PyInt_Check(value)) {
errCode = PyInt_AsLong(value);
}
else {
const char *str = PyString_AsString(value);
if (str)
errMsg = errMsg + ": " + str;
}
}
// Set exception message and code based upon the pthon sys.exit() code and/or message
// based upon the the following sys.exit() call semantics.
//
// Invocation | _exitCode | _sErrMsg
// ---------------- + --------- + --------
// sys.exit(int#) | int# | "System Exit"
// sys.exit(string) | 1 | string
// sys.exit() | 1 | "System Exit"
long int errCode = 1;
std::string errMsg = "System exit";
PyObject *type, *value, *traceback, *code;
PyGILStateLocker locker;
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
if (value) {
code = PyObject_GetAttrString(value, "code");
if (code != NULL && value != Py_None) {
Py_DECREF(value);
value = code;
}
if (PyInt_Check(value)) {
errCode = PyInt_AsLong(value);
}
else {
const char *str = PyString_AsString(value);
if (str)
errMsg = errMsg + ": " + str;
}
}
_sErrMsg = errMsg;
_exitCode = errCode;
_exitCode = errCode;
}
SystemExitException::SystemExitException(const SystemExitException &inst)
: Exception(inst), _exitCode(inst._exitCode)
{
}
// ---------------------------------------------------------
@ -214,6 +214,30 @@ std::string InterpreterSingleton::runString(const char *sCmd)
}
}
Py::Object InterpreterSingleton::runString_returnObject(const char *sCmd)
{
PyObject *module, *dict, *presult; /* "exec code in d, d" */
PyGILStateLocker locker;
module = PP_Load_Module("__main__"); /* get module, init python */
if (module == NULL)
throw PyException(); /* not incref'd */
dict = PyModule_GetDict(module); /* get dict namespace */
if (dict == NULL)
throw PyException(); /* not incref'd */
presult = PyRun_String(sCmd, Py_eval_input, dict, dict); /* eval direct */
if (!presult) {
if (PyErr_ExceptionMatches(PyExc_SystemExit))
throw SystemExitException();
else
throw PyException();
}
return Py::Object(presult, true);
}
void InterpreterSingleton::systemExit(void)
{
/* This code is taken from the original Python code */
@ -314,22 +338,22 @@ void InterpreterSingleton::runFile(const char*pxFileName, bool local)
Py_INCREF(dict); // avoid to further distinguish between local and global dict
}
if (PyDict_GetItemString(dict, "__file__") == NULL) {
PyObject *f = PyString_FromString(pxFileName);
if (f == NULL) {
fclose(fp);
Py_DECREF(dict);
return;
}
if (PyDict_SetItemString(dict, "__file__", f) < 0) {
Py_DECREF(f);
fclose(fp);
Py_DECREF(dict);
return;
}
Py_DECREF(f);
}
if (PyDict_GetItemString(dict, "__file__") == NULL) {
PyObject *f = PyString_FromString(pxFileName);
if (f == NULL) {
fclose(fp);
Py_DECREF(dict);
return;
}
if (PyDict_SetItemString(dict, "__file__", f) < 0) {
Py_DECREF(f);
fclose(fp);
Py_DECREF(dict);
return;
}
Py_DECREF(f);
}
PyObject *result = PyRun_File(fp, pxFileName, Py_file_input, dict, dict);
fclose(fp);
Py_DECREF(dict);

View File

@ -34,6 +34,8 @@
#include <Python.h>
#include <CXX/Extensions.hxx>
#ifdef FC_OS_MACOSX
#undef toupper
@ -159,6 +161,8 @@ public:
//@{
/// Run a statement on the python interpreter and gives back a string with the representation of the result.
std::string runString(const char *psCmd);
/// Runs a string (expression) and returns object returned by expression.
Py::Object runString_returnObject(const char *sCmd);
/// Run a statement on the python interpreter and gives back a string with the representation of the result.
void runInteractiveString(const char *psCmd);
/// Run file (script) on the python interpreter

View File

@ -287,6 +287,13 @@ SET(Part_Scripts
TestPartApp.py
MakeBottle.py
JoinFeatures.py
AttachmentEditor/__init__.py
AttachmentEditor/Commands.py
AttachmentEditor/DepGraphTools.py
AttachmentEditor/FrozenClass.py
AttachmentEditor/TaskAttachmentEditor.py
AttachmentEditor/TaskAttachmentEditor.ui
AttachmentEditor/TempoVis.py
)
add_library(Part SHARED ${Part_SRCS})

View File

@ -0,0 +1,100 @@
#/***************************************************************************
# * Copyright (c) Victor Titov (DeepSOIC) *
# * (vv.titov@gmail.com) 2016 *
# * *
# * 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 *
# * *
# ***************************************************************************/
from __future__ import absolute_import
import FreeCAD as App
from PySide import QtCore
def editAttachment(feature = None,
take_selection = False,
create_transaction = True,
callback_OK = None,
callback_Cancel = None,
callback_Apply = None):
'''Opens attachment editing dialog.
editAttachment(feature = None,
take_selection = False,
create_transaction = True,
callback_OK = None,
callback_Cancel = None,
callback_Apply = None)
feature: object to attach/modify. If None is supplied, the object is taken from
selection.
take_selection: if True, current selection is filled into first references (but only
if object to be attached doesn't have any references already)
create_transaction = if False, no undo transation operations will be done by the
dialog (consequently, canceling the dialog will not reset the feature to original
state).
callback_OK: function to be called upon OK. Invoked after writing values to feature,
committing transaction and closing the dialog.
callback_Cancel: called after closing the dialog and aborting transaction.
callback_Apply: invoked after writing values to feature.'''
import AttachmentEditor.TaskAttachmentEditor as TaskAttachmentEditor
global taskd # exposing to outside, for ease of debugging
if feature is None:
feature = Gui.Selection.getSelectionEx()[0].Object
try:
taskd = TaskAttachmentEditor.AttachmentEditorTaskPanel(feature,
take_selection= take_selection,
create_transaction= create_transaction,
callback_OK= callback_OK,
callback_Cancel= callback_Cancel,
callback_Apply= callback_Apply)
Gui.Control.showDialog(taskd)
except TaskAttachmentEditor.CancelError:
pass
class CommandEditAttachment:
'Command to edit attachment'
def GetResources(self):
return {'MenuText': QtCore.QT_TRANSLATE_NOOP("AttachmentEditor","Attachment..."),
'Accel': "",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("AttachmentEditor","Edit attachment of selected object.")}
def Activated(self):
try:
editAttachment()
except Exception as err:
from PySide import QtGui
mb = QtGui.QMessageBox()
mb.setIcon(mb.Icon.Warning)
mb.setText(err.message)
mb.setWindowTitle("Error")
mb.exec_()
def IsActive(self):
sel = Gui.Selection.getSelectionEx()
if len(sel) == 1:
if hasattr(sel[0].Object,"Placement"):
return True
return False
if App.GuiUp:
global command_instance
import FreeCADGui as Gui
command_instance = CommandEditAttachment()
Gui.addCommand('Part_EditAttachment', command_instance)

View File

@ -0,0 +1,82 @@
#/***************************************************************************
# * Copyright (c) Victor Titov (DeepSOIC) *
# * (vv.titov@gmail.com) 2016 *
# * *
# * 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 *
# * *
# ***************************************************************************/
import FreeCAD as App
def getAllDependencies(feat):
'''getAllDependencies(feat): gets all features feat depends on, directly or indirectly.
Returns a list, with deepest dependencies last. feat is not included in the list, except
if the feature depends on itself (dependency loop).'''
list_traversing_now = [feat]
set_of_deps = set()
list_of_deps = []
while len(list_traversing_now) > 0:
list_to_be_traversed_next = []
for feat in list_traversing_now:
for dep in feat.OutList:
if not (dep in set_of_deps):
set_of_deps.add(dep)
list_of_deps.append(dep)
list_to_be_traversed_next.append(dep)
list_traversing_now = list_to_be_traversed_next
return list_of_deps
def getAllDependent(feat):
'''getAllDependent(feat): gets all features that depend on feat, directly or indirectly.
Returns a list, with deepest dependencies last. feat is not included in the list, except
if the feature depends on itself (dependency loop).'''
list_traversing_now = [feat]
set_of_deps = set()
list_of_deps = []
while len(list_traversing_now) > 0:
list_to_be_traversed_next = []
for feat in list_traversing_now:
for dep in feat.InList:
if not (dep in set_of_deps):
set_of_deps.add(dep)
list_of_deps.append(dep)
list_to_be_traversed_next.append(dep)
list_traversing_now = list_to_be_traversed_next
return list_of_deps
def isContainer(obj):
'''isContainer(obj): returns True if obj is an object container, such as
Group, Part, Body. The important characterisic of an object being a
container is its action on visibility of linked objects. E.g. a
Part::Compound is not a group, because it does not affect visibility
of originals. Documents are considered containers, too.'''
if obj.isDerivedFrom("App::DocumentObjectGroup"):
return True
if obj.isDerivedFrom("PartDesign::Body"):
return True
if obj.isDerivedFrom("App::Origin"):
return True
if obj.isDerivedFrom('App::Document'):
return True

View File

@ -0,0 +1,37 @@
#/***************************************************************************
# * Copyright (c) Victor Titov (DeepSOIC) *
# * (vv.titov@gmail.com) 2016 *
# * *
# * 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 *
# * *
# ***************************************************************************/
# adapted from http://stackoverflow.com/a/3603824/6285007
class FrozenClass(object):
'''FrozenClass: prevents adding new attributes to class outside of __init__'''
__isfrozen = False
def __setattr__(self, key, value):
if self.__isfrozen and not hasattr(self, key):
raise TypeError( "{cls} has no attribute {attr}".format(cls= self.__class__.__name__, attr= key) )
object.__setattr__(self, key, value)
def _freeze(self):
self.__isfrozen = True
def _unfreeze(self):
self.__isfrozen = False

View File

@ -0,0 +1,557 @@
#/***************************************************************************
# * Copyright (c) Victor Titov (DeepSOIC) *
# * (vv.titov@gmail.com) 2016 *
# * *
# * 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 *
# * *
# ***************************************************************************/
from __future__ import absolute_import
import FreeCAD as App
import Part
from FreeCAD import Units
from Units import MilliMetre as mm
from Units import Degree as deg
from Units import Quantity as Q
from AttachmentEditor.FrozenClass import FrozenClass
from AttachmentEditor.TempoVis import TempoVis
from AttachmentEditor.DepGraphTools import getAllDependent
if App.GuiUp:
import FreeCADGui as Gui
from PySide import QtCore, QtGui
from FreeCADGui import PySideUic as uic
#-------------------------- translation-related code ----------------------------------------
#Thanks, yorik! (see forum thread "A new Part tool is being born... JoinFeatures!"
#http://forum.freecadweb.org/viewtopic.php?f=22&t=11112&start=30#p90239 )
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
#--------------------------/translation-related code ----------------------------------------
def StrFromLink(feature, subname):
return feature.Name+ ((':'+subname) if subname else '')
def LinkFromStr(strlink, document):
if len(strlink) == 0:
return None
pieces = strlink.split(':')
feature = document.getObject(pieces[0])
subname = ''
if feature is None:
raise ValueError(_translate('AttachmentEditor',"No object named {name}",None).format(name= pieces[0]))
if len(pieces) == 2:
subname = pieces[1]
elif len(pieces) > 2:
raise ValueError(_translate('AttachmentEditor',"Failed to parse link (more than one colon encountered)",None))
return (feature,str(subname)) #wrap in str to remove unicode, which confuses assignment to PropertyLinkSubList.
def StrListFromRefs(references):
'''input: PropertyLinkSubList. Output: list of strings for UI.'''
return [StrFromLink(feature,subelement) for (feature, subelement) in references]
def RefsFromStrList(strings, document):
'''input: strings as from UI. Output: list of tuples that can be assigned to PropertyLinkSubList.'''
refs = []
for st in strings:
lnk = LinkFromStr(st, document)
if lnk is not None:
refs.append(lnk)
return refs
def GetSelectionAsLinkSubList():
sel = Gui.Selection.getSelectionEx()
result = []
for selobj in sel:
for subname in selobj.SubElementNames:
result.append((selobj, subname))
if len(selobj.SubElementNames) == 0:
result.append((selobj, ''))
return result
def PlacementsFuzzyCompare(plm1, plm2):
pos_eq = (plm1.Base - plm2.Base).Length < 1e-7 # 1e-7 is OCC's Precision::Confusion
q1 = plm1.Rotation.Q
q2 = plm2.Rotation.Q
# rotations are equal if q1 == q2 or q1 == -q2.
# Invert one of Q's if their scalar product is negative, before comparison.
if q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3] < 0:
q2 = [-v for v in q2]
rot_eq = ( abs(q1[0]-q2[0]) +
abs(q1[1]-q2[1]) +
abs(q1[2]-q2[2]) +
abs(q1[3]-q2[3]) ) < 1e-12 # 1e-12 is OCC's Precision::Angular (in radians)
return pos_eq and rot_eq
class CancelError(Exception):
def __init__(self):
self.message = 'Canceled by user'
self.isCancelError = True
class AttachmentEditorTaskPanel(FrozenClass):
'''The editmode TaskPanel for attachment editing'''
KEYmode = QtCore.Qt.ItemDataRole.UserRole # Key to use in Item.data(key) to obtain a mode associated with list item
KEYon = QtCore.Qt.ItemDataRole.UserRole + 1 # Key to use in Item.data(key) to obtain if the mode is valid
def __define_attributes(self):
self.obj = None #feature being attached
self.attacher = None #AttachEngine that is being actively used by the dialog. Its parameters are constantly and actively kept in sync with the dialog.
self.obj_is_attachable = True # False when editing non-attachable objects (alignment, not attachment)
self.last_sugr = None #result of last execution of suggestor
self.form = None #Qt widget of dialog interface
self.block = False #when True, event handlers return without doing anything (instead of doing-undoing blockSignals to everything)
self.refLines = [] #reference lineEdit widgets, packed into a list for convenience
self.refButtons = [] #buttons next to reference lineEdits
self.superPlacementEdits = [] #all edit boxes related to superplacement
self.i_active_ref = -1 #index of reference being selected (-1 means no reaction to selecting)
self.auto_next = False #if true, references being selected are appended ('Selecting' state is automatically advanced to next button)
self.tv = None #TempoVis class instance
self.create_transaction = True # if false, dialog doesn't mess with transactions.
self.callback_OK = None
self.callback_Cancel = None
self.callback_Apply = None
self._freeze()
def __init__(self, obj_to_attach,
take_selection = False,
create_transaction = True,
callback_OK = None,
callback_Cancel = None,
callback_Apply = None):
self.__define_attributes()
self.create_transaction = create_transaction
self.callback_OK = callback_OK
self.callback_Cancel = callback_Cancel
self.callback_Apply = callback_Apply
self.obj = obj_to_attach
if hasattr(obj_to_attach,'Attacher'):
self.attacher = obj_to_attach.Attacher
elif hasattr(obj_to_attach,'AttacherType'):
self.attacher = Part.AttachEngine(obj_to_attach.AttacherType)
else:
movable = True
if not hasattr(self.obj, "Placement"):
movable = False
if 'Hidden' in self.obj.getEditorMode("Placement") or 'ReadOnly' in self.obj.getEditorMode("Placement"):
movable = False
if not movable:
if self.callback_Cancel:
self.callback_Cancel()
raise ValueError(_translate('AttachmentEditor',"Object {name} is neither movable nor attachable, can't edit attachment",None)
.format(name= self.obj.Label))
self.obj_is_attachable = False
self.attacher = Part.AttachEngine()
mb = QtGui.QMessageBox()
mb.setIcon(mb.Icon.Warning)
mb.setText(_translate('AttachmentEditor',
"{obj} is not attachable. You can still use attachment editor dialog to align the object, but the attachment won't be parametic."
,None)
.format(obj= obj_to_attach.Label))
mb.setWindowTitle(_translate('AttachmentEditor',"Attachment",None))
btnAbort = mb.addButton(QtGui.QMessageBox.StandardButton.Abort)
btnOK = mb.addButton(_translate('AttachmentEditor',"Continue",None),QtGui.QMessageBox.ButtonRole.ActionRole)
mb.setDefaultButton(btnOK)
mb.exec_()
if mb.clickedButton() is btnAbort:
if self.callback_Cancel:
self.callback_Cancel()
raise CancelError()
import os
self.form=uic.loadUi(os.path.dirname(__file__) + os.path.sep + 'TaskAttachmentEditor.ui')
# self.form.setWindowIcon(QtGui.QIcon(':/icons/PartDesign_InternalExternalGear.svg'))
self.form.setWindowTitle(_translate('AttachmentEditor',"Attachment",None))
self.refLines = [self.form.lineRef1,
self.form.lineRef2,
self.form.lineRef3,
self.form.lineRef4]
self.refButtons = [self.form.buttonRef1,
self.form.buttonRef2,
self.form.buttonRef3,
self.form.buttonRef4]
self.superPlacementEdits = [self.form.superplacementX,
self.form.superplacementY,
self.form.superplacementZ,
self.form.superplacementYaw,
self.form.superplacementPitch,
self.form.superplacementRoll]
self.block = False
for i in range(len(self.refLines)):
QtCore.QObject.connect(self.refLines[i], QtCore.SIGNAL('textEdited(QString)'), lambda txt, i=i: self.lineRefChanged(i,txt))
for i in range(len(self.refLines)):
QtCore.QObject.connect(self.refButtons[i], QtCore.SIGNAL('clicked()'), lambda i=i: self.refButtonClicked(i))
for i in range(len(self.superPlacementEdits)):
QtCore.QObject.connect(self.superPlacementEdits[i], QtCore.SIGNAL('valueChanged(double)'), lambda val, i=i: self.superplacementChanged(i,val))
QtCore.QObject.connect(self.form.checkBoxFlip, QtCore.SIGNAL('clicked()'), self.checkBoxFlipClicked)
QtCore.QObject.connect(self.form.listOfModes, QtCore.SIGNAL('itemSelectionChanged()'), self.modeSelected)
if self.create_transaction:
self.obj.Document.openTransaction(_translate('AttachmentEditor',"Edit attachment of {feat}",None).format(feat= self.obj.Name))
self.readParameters()
if len(self.attacher.References) == 0 and take_selection:
sel = GetSelectionAsLinkSubList()
for i in range(len(sel))[::-1]:
if sel[i][0] is obj_to_attach:
sel.pop(i)
self.attacher.References = sel
if len(self.attacher.References) == 0:
self.i_active_ref = 0
self.auto_next = True
else:
self.i_active_ref = -1
self.auto_next = False
Gui.Selection.addObserver(self)
self.updatePreview()
self.updateRefButtons()
self.tv = TempoVis(self.obj.Document)
self.tv.hide_all_dependent(self.obj)
self.tv.show(self.obj)
self.tv.show([obj for (obj,subname) in self.attacher.References])
# task dialog handling
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok) | int(QtGui.QDialogButtonBox.Cancel)| int(QtGui.QDialogButtonBox.Apply)
def clicked(self,button):
if button == QtGui.QDialogButtonBox.Apply:
if self.obj_is_attachable:
self.writeParameters()
self.updatePreview()
if self.callback_Apply:
self.callback_Apply()
def accept(self):
if self.obj_is_attachable:
self.writeParameters()
if self.create_transaction:
self.obj.Document.commitTransaction()
self.cleanUp()
Gui.Control.closeDialog()
if self.callback_OK:
self.callback_OK()
def reject(self):
if self.create_transaction:
self.obj.Document.abortTransaction()
self.cleanUp()
Gui.Control.closeDialog()
if self.callback_Cancel:
self.callback_Cancel()
#selectionObserver stuff
def addSelection(self,docname,objname,subname,pnt):
i = self.i_active_ref
if i < 0:
#not selecting any reference
return
if i > 0 and self.auto_next:
prevref = LinkFromStr( self.refLines[i-1].text(), self.obj.Document )
if prevref[0].Name == objname and subname == '':
# whole object was selected by double-clicking
# its subelement was already written to line[i-1], so we decrease i to overwrite the lineRefChanged
i -= 1
if i > len(self.refLines)-1:
# all 4 references have been selected, finish
assert(self.auto_next)
self.i_active_ref = -1
self.updateRefButtons()
return
if i > -1:
# assign the selected reference
if objname == self.obj.Name:
self.form.message.setText(_translate('AttachmentEditor',"Ignored. Can't attach object to itself!",None))
return
if App.getDocument(docname).getObject(objname) in getAllDependent(self.obj):
self.form.message.setText(_translate('AttachmentEditor',"{obj1} depends on object being attached, can't use it for attachment",None).format(obj1= objname))
return
self.refLines[i].setText( StrFromLink(App.getDocument(docname).getObject(objname), subname) )
self.lineRefChanged(i,'')
if self.auto_next:
i += 1
self.i_active_ref = i
self.updateRefButtons()
# slots
def superplacementChanged(self, index, value):
if self.block:
return
plm = self.attacher.SuperPlacement
pos = plm.Base
if index==0:
pos.x = Q(self.form.superplacementX.text()).getValueAs(mm)
if index==1:
pos.y = Q(self.form.superplacementY.text()).getValueAs(mm)
if index==2:
pos.z = Q(self.form.superplacementZ.text()).getValueAs(mm)
if index >= 0 and index <= 2:
plm.Base = pos
rot = plm.Rotation;
(yaw, pitch, roll) = rot.toEuler()
if index==3:
yaw = Q(self.form.superplacementYaw.text()).getValueAs(deg)
if index==4:
pitch = Q(self.form.superplacementPitch.text()).getValueAs(deg)
if index==5:
roll = Q(self.form.superplacementRoll.text()).getValueAs(deg)
if index >= 3 and index <= 5:
rot = App.Rotation(yaw,pitch,roll)
plm.Rotation = rot
self.attacher.SuperPlacement = plm
self.updatePreview()
def checkBoxFlipClicked(self):
if self.block:
return
self.attacher.Reverse = self.form.checkBoxFlip.isChecked()
self.updatePreview()
def lineRefChanged(self, index, value):
if self.block:
return
# not parsing links here, because doing it in updatePreview will display error message
self.updatePreview()
def refButtonClicked(self, index):
if self.block:
return
if self.i_active_ref == index:
#stop selecting
self.i_active_ref = -1
else:
#start selecting
self.i_active_ref = index
self.auto_next = False
self.updateRefButtons()
def modeSelected(self):
if self.block:
return
self.attacher.Mode = self.getCurrentMode()
self.updatePreview()
#internal methods
def writeParameters(self):
'Transfer from the dialog to the object'
self.attacher.writeParametersToFeature(self.obj)
def readParameters(self):
'Transfer from the object to the dialog'
if self.obj_is_attachable:
self.attacher.readParametersFromFeature(self.obj)
plm = self.attacher.SuperPlacement
try:
old_selfblock = self.block
self.block = True
self.form.superplacementX.setText ((plm.Base.x * mm).UserString)
self.form.superplacementY.setText ((plm.Base.y * mm).UserString)
self.form.superplacementZ.setText ((plm.Base.z * mm).UserString)
self.form.superplacementYaw.setText ((plm.Rotation.toEuler()[0] * deg).UserString)
self.form.superplacementPitch.setText((plm.Rotation.toEuler()[1] * deg).UserString)
self.form.superplacementRoll.setText ((plm.Rotation.toEuler()[2] * deg).UserString)
self.form.checkBoxFlip.setChecked(self.attacher.Reverse)
strings = StrListFromRefs(self.attacher.References)
if len(strings) < len(self.refLines):
strings.extend(['']*(len(self.refLines) - len(strings)))
for i in range(len(self.refLines)):
self.refLines[i].setText(strings[i])
finally:
self.block = old_selfblock
def parseAllRefLines(self):
self.attacher.References = RefsFromStrList([le.text() for le in self.refLines], self.obj.Document)
def updateListOfModes(self):
'''needs suggestor to have been called, and assigned to self.last_sugr'''
try:
old_selfblock = self.block
self.block = True
list_widget = self.form.listOfModes
list_widget.clear()
sugr = self.last_sugr
# add valid modes
for m in sugr['allApplicableModes']:
item = QtGui.QListWidgetItem()
txt = self.attacher.getModeInfo(m)['UserFriendlyName']
item.setText(txt)
item.setData(self.KEYmode,m)
item.setData(self.KEYon,True)
if m == sugr['bestFitMode']:
f = item.font()
f.setBold(True)
item.setFont(f)
list_widget.addItem(item)
item.setSelected(self.attacher.Mode == m)
# add potential modes
for m in sugr['reachableModes'].keys():
item = QtGui.QListWidgetItem()
txt = self.attacher.getModeInfo(m)['UserFriendlyName']
listlistrefs = sugr['reachableModes'][m]
if len(listlistrefs) == 1:
listrefs_userfriendly = [self.attacher.getRefTypeInfo(t)['UserFriendlyName'] for t in listlistrefs[0]]
txt = _translate('AttachmentEditor',"{mode} (add {morerefs})",None).format(mode= txt,
morerefs= u"+".join(listrefs_userfriendly))
else:
txt = _translate('AttachmentEditor',"{mode} (add more references)",None).format(mode= txt)
item.setText(txt)
item.setData(self.KEYmode,m)
item.setData(self.KEYon,True)
if m == sugr['bestFitMode']:
f = item.font()
f.setBold(True)
item.setFont(f)
#disable this item
f = item.flags()
f = f & ~(QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable)
item.setFlags(f)
list_widget.addItem(item)
# re-scan the list to fill in tooltips
for item in list_widget.findItems('', QtCore.Qt.MatchContains):
m = item.data(self.KEYmode)
on = item.data(self.KEYon)
mi = self.attacher.getModeInfo(m)
cmb = []
for refstr in mi['ReferenceCombinations']:
refstr_userfriendly = [self.attacher.getRefTypeInfo(t)['UserFriendlyName'] for t in refstr]
cmb.append(u", ".join(refstr_userfriendly))
tip = _translate('AttachmentEditor',"{docu}\n\nReference combinations:\n{combinations}",None).format(docu=mi['BriefDocu'], combinations= u"\n".join(cmb) )
item.setToolTip(tip)
finally:
self.block = old_selfblock
def updateRefButtons(self):
try:
old_selfblock = self.block
self.block = True
for i in range(len(self.refButtons)):
btn = self.refButtons[i]
btn.setCheckable(True)
btn.setChecked(self.i_active_ref == i)
typ = _translate('AttachmentEditor',"Reference{i}",None).format(i= str(i+1))
if self.last_sugr is not None:
typestr = self.last_sugr['references_Types']
if i < len(typestr):
typ = self.attacher.getRefTypeInfo(typestr[i])['UserFriendlyName']
btn.setText(_translate('AttachmentEditor',"Selecting...",None) if self.i_active_ref == i else typ)
finally:
self.block = old_selfblock
def getCurrentMode(self):
list_widget = self.form.listOfModes
sel = list_widget.selectedItems()
if len(sel) == 1:
if sel[0].data(self.KEYon):
return str(sel[0].data(self.KEYmode)) # data() returns unicode, which confuses attacher
# nothing selected in list. Return suggested
if self.last_sugr is not None:
if self.last_sugr['message'] == 'OK':
return self.last_sugr['bestFitMode']
# no suggested mode. Return current, so it doesn't change
return self.attacher.Mode
def updatePreview(self):
new_plm = None
try:
self.parseAllRefLines()
self.last_sugr = self.attacher.suggestModes()
if self.last_sugr['message'] == 'LinkBroken':
raise ValueError(_translate('AttachmentEditor',"Failed to resolve links. {err}",None).format(err= self.last_sugr['error']))
self.updateListOfModes()
self.attacher.Mode = self.getCurrentMode()
new_plm = self.attacher.calculateAttachedPlacement(self.obj.Placement)
if new_plm is None:
self.form.message.setText(_translate('AttachmentEditor',"Not attached",None))
else:
self.form.message.setText( _translate('AttachmentEditor',"Attached with mode {mode}",None)
.format( mode= self.attacher.getModeInfo(self.getCurrentMode())['UserFriendlyName'] ) )
if PlacementsFuzzyCompare(self.obj.Placement, new_plm) == False:
# assign only if placement changed. this avoids touching the object
# when entering and extiting dialog without changing anything
self.obj.Placement = new_plm
except Exception as err:
self.form.message.setText(_translate('AttachmentEditor',"Error: {err}",None).format(err= err.message))
if new_plm is not None:
self.form.groupBox_superplacement.setTitle(_translate('AttachmentEditor',"Extra placement:",None))
else:
self.form.groupBox_superplacement.setTitle(_translate('AttachmentEditor',"Extra placement (inactive - not attached):",None))
def cleanUp(self):
'''stuff that needs to be done when dialog is closed.'''
Gui.Selection.removeObserver(self)
self.tv.restore()

View File

@ -0,0 +1,385 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PartDesignGui::TaskDatumParameters</class>
<widget class="QWidget" name="PartDesignGui::TaskDatumParameters">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>271</width>
<height>604</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="message">
<property name="text">
<string>Selection accepted</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="buttonRef1">
<property name="text">
<string>Reference 1</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineRef1"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="buttonRef2">
<property name="text">
<string>Reference 2</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineRef2"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="buttonRef3">
<property name="text">
<string>Reference 3</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineRef3"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QPushButton" name="buttonRef4">
<property name="text">
<string>Reference 4</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineRef4"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Attachment mode:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listOfModes">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_superplacement">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Mirror of superPlacement property. Extra placement is expressed in local space of object being attached.</string>
</property>
<property name="title">
<string>Extra placement:</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="labelOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>X:</string>
</property>
<property name="buddy">
<cstring>labelOffset</cstring>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelOffset2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Y:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::InputField" name="superplacementY" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>5</height>
</size>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelOffset3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Z:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::InputField" name="superplacementZ" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>5</height>
</size>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelYaw">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Yaw:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="labelPitch">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Pitch:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelRoll">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Roll:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::InputField" name="superplacementX" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>5</height>
</size>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="Gui::InputField" name="superplacementYaw">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="Gui::InputField" name="superplacementPitch">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="Gui::InputField" name="superplacementRoll">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxFlip">
<property name="text">
<string>Flip sides</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header>Gui/InputField.h</header>
</customwidget>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
<customwidget>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>buttonRef1</tabstop>
<tabstop>lineRef1</tabstop>
<tabstop>buttonRef2</tabstop>
<tabstop>lineRef2</tabstop>
<tabstop>buttonRef3</tabstop>
<tabstop>lineRef3</tabstop>
<tabstop>buttonRef4</tabstop>
<tabstop>lineRef4</tabstop>
<tabstop>listOfModes</tabstop>
<tabstop>superplacementX</tabstop>
<tabstop>superplacementY</tabstop>
<tabstop>superplacementZ</tabstop>
<tabstop>superplacementYaw</tabstop>
<tabstop>superplacementPitch</tabstop>
<tabstop>superplacementRoll</tabstop>
<tabstop>checkBoxFlip</tabstop>
</tabstops>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>true</bool>
</property>
</designerdata>
</ui>

View File

@ -0,0 +1,110 @@
#/***************************************************************************
# * Copyright (c) Victor Titov (DeepSOIC) *
# * (vv.titov@gmail.com) 2016 *
# * *
# * 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 *
# * *
# ***************************************************************************/
import FreeCAD as App
if App.GuiUp:
import FreeCADGui as Gui
from FrozenClass import FrozenClass
from DepGraphTools import getAllDependencies, getAllDependent, isContainer
class TempoVis(FrozenClass):
'''TempoVis - helper object to save visibilities of objects before doing
some GUI editing, hiding or showing relevant stuff during edit, and
then restoring all visibilities after editing.
Constructors:
TempoVis(document): creates a new TempoVis. Supplying document is mandatory. Objects not belonging to the document can't be modified via TempoVis.'''
def __define_attributes(self):
self.data = {} # dict. key = ("Object","Property"), value = original value of the property
self.document = None
self.restore_on_delete = False
self._freeze()
def __init__(self, document):
self.__define_attributes()
self.document = document
def modifyVPProperty(self, doc_obj_or_list, prop_name, new_value):
'''modifyVPProperty(self, doc_obj_or_list, prop_name, new_value): modifies
prop_name property of ViewProvider of doc_obj_or_list, and remembers
original value of the property. Original values will be restored upon
TempoVis deletion, or call to restore().'''
if App.GuiUp:
if type(doc_obj_or_list) is not list:
doc_obj_or_list = [doc_obj_or_list]
for doc_obj in doc_obj_or_list:
if doc_obj.Document is not self.document: #ignore objects from other documents
raise ValueError("Document object to be modified does not belong to document TempoVis was made for.")
oldval = getattr(doc_obj.ViewObject, prop_name)
setattr(doc_obj.ViewObject, prop_name, new_value)
#assert(getattr(doc_obj.ViewObject, prop_name)==new_value)
if not self.data.has_key((doc_obj.Name,prop_name)):
self.data[(doc_obj.Name,prop_name)] = oldval
self.restore_on_delete = True
def show(self, doc_obj_or_list):
'''show(doc_obj_or_list): shows objects (sets their Visibility to True). doc_obj_or_list can be a document object, or a list of document objects'''
self.modifyVPProperty(doc_obj_or_list, "Visibility", True)
def hide(self, doc_obj_or_list):
'''hide(doc_obj_or_list): hides objects (sets their Visibility to False). doc_obj_or_list can be a document object, or a list of document objects'''
self.modifyVPProperty(doc_obj_or_list, "Visibility", False)
def hide_all_dependent(self, doc_obj):
'''hide_all_dependent(doc_obj): hides all objects that depend on doc_obj. Groups, Parts and Bodies are not hidden by this.'''
self.hide( [o for o in getAllDependent(doc_obj) if not isContainer(o)])
def show_all_dependent(self, doc_obj):
'''show_all_dependent(doc_obj): shows all objects that depend on doc_obj. This method is probably useless.'''
self.show( getAllDependent(doc_obj) )
def hide_all_dependencies(self, doc_obj):
'''hide_all_dependencies(doc_obj): hides all objects that doc_obj depends on (directly and indirectly).'''
self.hide( getAllDependencies(doc_obj) )
def show_all_dependencies(self, doc_obj):
'''show_all_dependencies(doc_obj): shows all objects that doc_obj depends on (directly and indirectly). This method is probably useless.'''
self.show( getAllDependencies(doc_obj) )
def restore(self):
'''restore(): restore all ViewProvider properties modified via TempoVis to their original values. Called automatically when instance is destroyed, unless it was called explicitly.'''
for obj_name, prop_name in self.data:
setattr(self.document.getObject(obj_name).ViewObject, prop_name, self.data[(obj_name, prop_name)])
self.restore_on_delete = False
def forget(self):
'''forget(): resets TempoVis'''
self.data = {}
self.restore_on_delete = False
def __del__(self):
if self.restore_on_delete:
self.restore()

View File

@ -15,3 +15,16 @@ INSTALL(
DESTINATION
Mod/Part
)
INSTALL(
FILES
AttachmentEditor/__init__.py
AttachmentEditor/Commands.py
AttachmentEditor/DepGraphTools.py
AttachmentEditor/FrozenClass.py
AttachmentEditor/TaskAttachmentEditor.py
AttachmentEditor/TaskAttachmentEditor.ui
AttachmentEditor/TempoVis.py
DESTINATION
Mod/Part/AttachmentEditor
)

View File

@ -126,7 +126,6 @@ PyMODINIT_FUNC initPartGui()
Py_INCREF(pAttachEngineTextsModule);
PyModule_AddObject(partGuiModule, "AttachEngineResources", pAttachEngineTextsModule);
PartGui::SoBrepFaceSet ::initClass();
PartGui::SoBrepEdgeSet ::initClass();
PartGui::SoBrepPointSet ::initClass();
@ -182,6 +181,13 @@ PyMODINIT_FUNC initPartGui()
CreatePartCommands();
CreateSimplePartCommands();
CreateParamPartCommands();
try{
Py::Object ae = Base::Interpreter().runString_returnObject("__import__('AttachmentEditor.Commands').Commands");
Py::Module(partGuiModule).setAttr(std::string("AttachmentEditor"),ae);
} catch (Base::PyException &err){
err.ReportException();
}
// register preferences pages
(void)new Gui::PrefPageProducer<PartGui::DlgSettingsGeneral> ( QT_TRANSLATE_NOOP("QObject","Part design") );