Files
create/src/Mod/Fem/femguiobjects/FemSelectionWidgets.py
2019-05-20 12:33:30 +02:00

563 lines
24 KiB
Python

# ***************************************************************************
# * *
# * Copyright (c) 2017 Markus Hovorka <m.hovorka@live.de> *
# * Copyright (c) 2018 Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program 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 program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
__title__ = "FreeCAD FEM select widget"
__author__ = "Markus Hovorka, Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
## @package FemSelectWidget
# \ingroup FEM
# \brief FreeCAD FEM FemSelectWidget
import FreeCAD
import FreeCADGui
import femmesh.meshtools as FemMeshTools
from PySide import QtGui
from PySide import QtCore
import FreeCADGui as Gui
class _Selector(QtGui.QWidget):
def __init__(self):
super(_Selector, self).__init__()
self._references = []
self._register = dict()
addBtn = QtGui.QPushButton(self.tr("Add"))
delBtn = QtGui.QPushButton(self.tr("Remove"))
addBtn.clicked.connect(self._add)
delBtn.clicked.connect(self._del)
btnLayout = QtGui.QHBoxLayout()
btnLayout.addWidget(addBtn)
btnLayout.addWidget(delBtn)
self._model = QtGui.QStandardItemModel()
self._view = SmallListView()
self._view.setModel(self._model)
self._helpTextLbl = QtGui.QLabel()
self._helpTextLbl.setWordWrap(True)
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self._helpTextLbl)
mainLayout.addLayout(btnLayout)
mainLayout.addWidget(self._view)
self.setLayout(mainLayout)
def references(self):
return [entry for entry in self._references if entry[1]]
def setReferences(self, references):
self._references = []
self._updateReferences(references)
def setHelpText(self, text):
self._helpTextLbl.setText(text)
@QtCore.Slot()
def _add(self):
selection = self.getSelection()
self._updateReferences(selection)
@QtCore.Slot()
def _del(self):
selected = self._view.selectedIndexes()
for index in selected:
identifier = self._model.data(index)
obj, sub = self._register[identifier]
refIndex = self._getIndex(obj)
entry = self._references[refIndex]
newSub = tuple((x for x in entry[1] if x != sub))
self._references[refIndex] = (obj, newSub)
self._model.removeRow(index.row())
def _updateReferences(self, selection):
for obj, subList in selection:
index = self._getIndex(obj)
for sub in subList:
entry = self._references[index]
if sub not in entry[1]:
self._addToWidget(obj, sub)
newEntry = (obj, entry[1] + (sub,))
self._references[index] = newEntry
def _addToWidget(self, obj, sub):
identifier = "%s::%s" % (obj.Name, sub)
item = QtGui.QStandardItem(identifier)
self._model.appendRow(item)
self._register[identifier] = (obj, sub)
def _getIndex(self, obj):
for i, entry in enumerate(self._references):
if entry[0] == obj:
return i
self._references.append((obj, tuple()))
return len(self._references) - 1
def getSelection(self):
raise NotImplementedError()
class BoundarySelector(_Selector):
def __init__(self):
super(BoundarySelector, self).__init__()
self.setWindowTitle(self.tr("Select Faces/Edges/Vertexes"))
self.setHelpText(self.tr(
"To add references select them in the 3D view and then"
" click \"Add\"."))
def getSelection(self):
selection = []
for selObj in Gui.Selection.getSelectionEx():
if selObj.HasSubObjects:
item = (selObj.Object, tuple(selObj.SubElementNames))
selection.append(item)
return selection
class SolidSelector(_Selector):
def __init__(self):
super(SolidSelector, self).__init__()
self.setWindowTitle(self.tr("Select Solids"))
self.setHelpText(self.tr(
"Select elements part of the solid that shall be added"
" to the list. To than add the solid click \"Add\"."))
def getSelection(self):
selection = []
for selObj in Gui.Selection.getSelectionEx():
solids = set()
for sub in self._getObjects(selObj.Object, selObj.SubElementNames):
s = self._getSolidOfSub(selObj.Object, sub)
if s is not None:
solids.add(s)
if solids:
item = (selObj.Object, tuple(solids))
selection.append(item)
return selection
def _getObjects(self, obj, names):
objects = []
shape = obj.Shape
for n in names:
if n.startswith("Face"):
objects.append(shape.Faces[int(n[4:]) - 1])
elif n.startswith("Edge"):
objects.append(shape.Edges[int(n[4:]) - 1])
elif n.startswith("Vertex"):
objects.append(shape.Vertexes[int(n[6:]) - 1])
elif n.startswith("Solid"):
objects.append(shape.Solids[int(n[5:]) - 1])
return objects
def _getSolidOfSub(self, obj, sub):
foundSolids = set()
if sub.ShapeType == "Solid":
for solidId, solid in enumerate(obj.Shape.Solids):
if sub.isSame(solid):
foundSolids.add("Solid" + str(solidId + 1))
elif sub.ShapeType == "Face":
for solidId, solid in enumerate(obj.Shape.Solids):
if(self._findSub(sub, solid.Faces)):
foundSolids.add("Solid" + str(solidId + 1))
elif sub.ShapeType == "Edge":
for solidId, solid in enumerate(obj.Shape.Solids):
if(self._findSub(sub, solid.Edges)):
foundSolids.add("Solid" + str(solidId + 1))
elif sub.ShapeType == "Vertex":
for solidId, solid in enumerate(obj.Shape.Solids):
if(self._findSub(sub, solid.Vertexes)):
foundSolids.add("Solid" + str(solidId + 1))
if len(foundSolids) == 1:
return iter(foundSolids).next()
return None
def _findSub(self, sub, subList):
for i, s in enumerate(subList):
if s.isSame(sub):
return True
return False
class SmallListView(QtGui.QListView):
def sizeHint(self):
return QtCore.QSize(50, 50)
class GeometryElementsSelection(QtGui.QWidget):
def __init__(self, ref, eltypes=[], multigeom=True):
super(GeometryElementsSelection, self).__init__()
# init ui stuff
FreeCADGui.Selection.clearSelection()
self.selection_mode_solid = False
self.sel_server = None
self.obj_notvisible = []
self.initElemTypes(eltypes)
self.allow_multiple_geom_types = multigeom
# print(self.allow_multiple_geom_types)
self.initUI()
# set references and fill the list widget
self.references = []
if ref:
self.tuplereferences = ref
self.get_references()
self.rebuild_list_References()
def initElemTypes(self, eltypes):
self.sel_elem_types = eltypes
# FreeCAD.Console.PrintMessage(
# 'Selection of: {} is allowed.\n'.format(self.sel_elem_types)
# )
self.sel_elem_text = ''
for e in self.sel_elem_types:
self.sel_elem_text += (e + ', ')
self.sel_elem_text = self.sel_elem_text.rstrip(', ')
# FreeCAD.Console.PrintMessage('Selection of: ' + self.sel_elem_text + ' is allowed.\n')
self.selection_mode_std_print_message = (
"Single click on a " + self.sel_elem_text + " will add it to the list"
)
self.selection_mode_solid_print_message = (
"Single click on a Face or Edge which belongs "
"to one Solid will add the Solid to the list"
)
def initUI(self):
# auch ArchPanel ist coded ohne ui-file
# title
self.setWindowTitle(self.tr("Geometry reference selector for a ") + self.sel_elem_text)
# button
self.pushButton_Add = QtGui.QPushButton(self.tr("Add"))
# label
self._helpTextLbl = QtGui.QLabel()
self._helpTextLbl.setWordWrap(True)
self._helpTextLbl.setText(self.tr(
"Click on \"Add\" and select geometric elements to add them to the list."
" If no geometry is added to the list, all remaining ones are used."
" The following geometry elements are allowed to select: ") + self.sel_elem_text)
# list
self.list_References = QtGui.QListWidget()
# radiobutton down the list
self.lb_selmod = QtGui.QLabel()
self.lb_selmod.setText(self.tr("Selection mode"))
self.rb_standard = QtGui.QRadioButton(self.tr(self.sel_elem_text.lstrip('Solid, ')))
self.rb_solid = QtGui.QRadioButton(self.tr("Solid"))
self.rb_standard.setChecked(True)
self.rb_solid.setChecked(False)
# radio butoon layout
rbtnLayout = QtGui.QHBoxLayout()
rbtnLayout.addWidget(self.lb_selmod)
rbtnLayout.addWidget(self.rb_standard)
rbtnLayout.addWidget(self.rb_solid)
# main layout
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self._helpTextLbl)
mainLayout.addWidget(self.pushButton_Add)
mainLayout.addWidget(self.list_References)
if 'Solid' in self.sel_elem_types:
mainLayout.addLayout(rbtnLayout)
self.setLayout(mainLayout)
# signals and slots
self.list_References.itemSelectionChanged.connect(self.select_clicked_reference_shape)
self.list_References.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.list_References.connect(
self.list_References,
QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
self.references_list_right_clicked
)
QtCore.QObject.connect(
self.pushButton_Add,
QtCore.SIGNAL("clicked()"),
self.add_references
)
QtCore.QObject.connect(
self.rb_standard,
QtCore.SIGNAL("toggled(bool)"),
self.choose_selection_mode_standard
)
QtCore.QObject.connect(
self.rb_solid,
QtCore.SIGNAL("toggled(bool)"),
self.choose_selection_mode_solid
)
def get_references(self):
for ref in self.tuplereferences:
for elem in ref[1]:
self.references.append((ref[0], elem))
def get_item_text(self, ref):
return (ref[0].Name + ':' + ref[1])
def get_allitems_text(self):
items = []
for ref in self.references:
items.append(self.get_item_text(ref))
return sorted(items)
def rebuild_list_References(self, current_row=0):
self.list_References.clear()
for listItemName in self.get_allitems_text():
self.list_References.addItem(listItemName)
if current_row > self.list_References.count() - 1: # first row is 0
current_row = self.list_References.count() - 1
if self.list_References.count() > 0:
self.list_References.setCurrentItem(self.list_References.item(current_row))
def select_clicked_reference_shape(self):
self.setback_listobj_visibility()
if self.sel_server:
FreeCADGui.Selection.removeObserver(self.sel_server)
self.sel_server = None
if not self.sel_server:
if not self.references:
return
currentItemName = str(self.list_References.currentItem().text())
for ref in self.references:
if self.get_item_text(ref) == currentItemName:
# print('found: shape: ' + ref[0].Name + ' element: ' + ref[1])
if not ref[0].ViewObject.Visibility:
self.obj_notvisible.append(ref[0])
ref[0].ViewObject.Visibility = True
FreeCADGui.Selection.clearSelection()
ref_sh_type = ref[0].Shape.ShapeType
if ref[1].startswith('Solid') \
and (ref_sh_type == 'Compound' or ref_sh_type == 'CompSolid'):
# selection of Solids of Compounds or CompSolids is not possible
# because a Solid is no Subelement
# since only Subelements can be selected
# we're going to select all Faces of said Solids
# the method getElement(element)doesn't return Solid elements
solid = FemMeshTools.get_element(ref[0], ref[1])
if not solid:
return
faces = []
for fs in solid.Faces:
# find these faces in ref[0]
for i, fref in enumerate(ref[0].Shape.Faces):
if fs.isSame(fref):
fref_elstring = 'Face' + str(i + 1)
if fref_elstring not in faces:
faces.append(fref_elstring)
for f in faces:
FreeCADGui.Selection.addSelection(ref[0], f)
else:
# Selection of all other element types is supported
FreeCADGui.Selection.addSelection(ref[0], ref[1])
def setback_listobj_visibility(self):
'''set back Visibility of the list objects
'''
FreeCADGui.Selection.clearSelection()
for obj in self.obj_notvisible:
obj.ViewObject.Visibility = False
self.obj_notvisible = []
def references_list_right_clicked(self, QPos):
self.contextMenu = QtGui.QMenu()
menu_item_remove_selected = self.contextMenu.addAction("Remove selected geometry")
menu_item_remove_all = self.contextMenu.addAction("Clear list")
if not self.references:
menu_item_remove_selected.setDisabled(True)
menu_item_remove_all.setDisabled(True)
self.connect(
menu_item_remove_selected,
QtCore.SIGNAL("triggered()"),
self.remove_selected_reference
)
self.connect(
menu_item_remove_all,
QtCore.SIGNAL("triggered()"),
self.remove_all_references
)
parentPosition = self.list_References.mapToGlobal(QtCore.QPoint(0, 0))
self.contextMenu.move(parentPosition + QPos)
self.contextMenu.show()
def remove_selected_reference(self):
if not self.references:
return
currentItemName = str(self.list_References.currentItem().text())
currentRow = self.list_References.currentRow()
for ref in self.references:
if self.get_item_text(ref) == currentItemName:
self.references.remove(ref)
self.rebuild_list_References(currentRow)
def remove_all_references(self):
self.references = []
self.rebuild_list_References()
def choose_selection_mode_standard(self, state):
self.selection_mode_solid = not state
if self.sel_server and not self.selection_mode_solid:
FreeCAD.Console.PrintMessage(self.selection_mode_std_print_message + '\n')
def choose_selection_mode_solid(self, state):
self.selection_mode_solid = state
if self.sel_server and self.selection_mode_solid:
FreeCAD.Console.PrintMessage(self.selection_mode_solid_print_message + '\n')
def add_references(self):
'''Called if Button add_reference is triggered'''
# in constraints EditTaskPanel the selection is active as soon as the taskpanel is open
# here the addReference button EditTaskPanel has to be triggered to start selection mode
self.setback_listobj_visibility()
FreeCADGui.Selection.clearSelection()
# start SelectionObserver and parse the function to add the References to the widget
if self.selection_mode_solid: # print message on button click
print_message = self.selection_mode_solid_print_message
else:
print_message = self.selection_mode_std_print_message
if not self.sel_server:
# if we do not check, we would start a new SelectionObserver
# on every click on addReference button
# but close only one SelectionObserver on leaving the task panel
self.sel_server = FemSelectionObserver(self.selectionParser, print_message)
def selectionParser(self, selection):
print('selection: {} {} {}'.format(
selection[0].Shape.ShapeType,
selection[0].Name,
selection[1]
))
if hasattr(selection[0], "Shape") and selection[1]:
elt = selection[0].Shape.getElement(selection[1])
ele_ShapeType = elt.ShapeType
if self.selection_mode_solid and 'Solid' in self.sel_elem_types:
# in solid selection mode use edges and faces for selection of a solid
# adapt selection variable to hold the Solid
solid_to_add = None
if ele_ShapeType == 'Edge':
found_edge = False
for i, s in enumerate(selection[0].Shape.Solids):
for e in s.Edges:
if elt.isSame(e):
if not found_edge:
solid_to_add = str(i + 1)
else:
FreeCAD.Console.PrintMessage(
'Edge belongs to more than one solid\n'
)
solid_to_add = None
found_edge = True
elif ele_ShapeType == 'Face':
found_face = False
for i, s in enumerate(selection[0].Shape.Solids):
for e in s.Faces:
if elt.isSame(e):
if not found_face:
solid_to_add = str(i + 1)
else:
FreeCAD.Console.PrintMessage(
'Face belongs to more than one solid\n'
)
solid_to_add = None
found_face = True
if solid_to_add:
selection = (selection[0], 'Solid' + solid_to_add)
ele_ShapeType = 'Solid'
FreeCAD.Console.PrintMessage(
'selection variable adapted to hold the Solid: {} {} {}\n'
.format(selection[0].Shape.ShapeType, selection[0].Name, selection[1])
)
else:
return
if ele_ShapeType in self.sel_elem_types:
if (self.selection_mode_solid and ele_ShapeType == 'Solid') \
or self.selection_mode_solid is False:
if selection not in self.references:
# only equal shape types are allowed to add
if self.allow_multiple_geom_types is False:
if self.has_equal_references_shape_types(ele_ShapeType):
self.references.append(selection)
self.rebuild_list_References(
self.get_allitems_text().index(self.get_item_text(selection))
)
else:
# selected shape will not added to the list
FreeCADGui.Selection.clearSelection()
else: # multiple shape types are allowed to add
self.references.append(selection)
self.rebuild_list_References(
self.get_allitems_text().index(self.get_item_text(selection))
)
else:
# selected shape will not added to the list
FreeCADGui.Selection.clearSelection()
message = (
'{} is in reference list already!\n'
.format(self.get_item_text(selection))
)
FreeCAD.Console.PrintMessage(message)
QtGui.QMessageBox.critical(None, "Geometry already in list", message)
else:
# selected shape will not added to the list
FreeCADGui.Selection.clearSelection()
message = ele_ShapeType + ' is not allowed to add to the list!\n'
FreeCAD.Console.PrintMessage(message)
QtGui.QMessageBox.critical(None, "Wrong shape type", message)
def has_equal_references_shape_types(self, ref_shty=''):
for ref in self.references:
# the method getElement(element) does not return Solid elements
r = FemMeshTools.get_element(ref[0], ref[1])
if not r:
FreeCAD.Console.PrintError(
'Problem in retrieving element: {} \n'.format(ref[1])
)
continue
FreeCAD.Console.PrintLog(
' ReferenceShape : {}, {}, {} --> {}'
.format(r.ShapeType, ref[0].Name, ref[0].Label, ref[1])
)
if not ref_shty:
ref_shty = r.ShapeType
if r.ShapeType != ref_shty:
message = 'Multiple shape types are not allowed in the reference list.\n'
FreeCAD.Console.PrintMessage(message)
QtGui.QMessageBox.critical(None, "Multiple ShapeTypes not allowed", message)
return False
return True
class FemSelectionObserver:
'''selection observer especially for the needs of geometry reference selection of FEM'''
def __init__(self, parseSelectionFunction, print_message=''):
self.parseSelectionFunction = parseSelectionFunction
FreeCADGui.Selection.addObserver(self)
FreeCAD.Console.PrintMessage(print_message + "!\n")
def addSelection(self, docName, objName, sub, pos):
selected_object = FreeCAD.getDocument(docName).getObject(objName) # get the obj objName
self.added_obj = (selected_object, sub)
# on double click on a vertex of a solid sub is None and obj is the solid
self.parseSelectionFunction(self.added_obj)