Attacher: add general purpose editor UI (python)
Added as PartGui.AttachmentEditor, and Part_EditAttachment gui command
This commit is contained in:
parent
836d5b1525
commit
7f5197695d
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
100
src/Mod/Part/AttachmentEditor/Commands.py
Normal file
100
src/Mod/Part/AttachmentEditor/Commands.py
Normal 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)
|
82
src/Mod/Part/AttachmentEditor/DepGraphTools.py
Normal file
82
src/Mod/Part/AttachmentEditor/DepGraphTools.py
Normal 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
|
37
src/Mod/Part/AttachmentEditor/FrozenClass.py
Normal file
37
src/Mod/Part/AttachmentEditor/FrozenClass.py
Normal 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
|
557
src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py
Normal file
557
src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.py
Normal 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()
|
385
src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.ui
Normal file
385
src/Mod/Part/AttachmentEditor/TaskAttachmentEditor.ui
Normal 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>
|
110
src/Mod/Part/AttachmentEditor/TempoVis.py
Normal file
110
src/Mod/Part/AttachmentEditor/TempoVis.py
Normal 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()
|
||||
|
||||
|
0
src/Mod/Part/AttachmentEditor/__init__.py
Normal file
0
src/Mod/Part/AttachmentEditor/__init__.py
Normal 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
|
||||
)
|
||||
|
|
|
@ -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") );
|
||||
|
|
Loading…
Reference in New Issue
Block a user