#
##
## SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
## SPDX-License-Identifier: GPL-3.0-or-later
##
## This file is part of pyFormex 3.4 (Thu Nov 16 18:07:39 CET 2023)
## pyFormex is a tool for generating, manipulating and transforming 3D
## geometrical models by sequences of mathematical operations.
## Home page: https://pyformex.org
## Project page: https://savannah.nongnu.org/projects/pyformex/
## Development: https://gitlab.com/bverheg/pyformex
## Distributed under the GNU General Public License version 3 or later.
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see http://www.gnu.org/licenses/.
##
"""A collection of graphical tools for pyFormex.
This is a collection of graphical tools for the pyFormex GUI. Many of
these tools can be invoked from the Tools menu.
"""
import pyformex as pf
from pyformex.core import np, at, gt, utils, Coords, Formex, Mesh, TriSurface
from pyformex.collection import Collection
from pyformex.curve import PolyLine
from pyformex.gui import draw as gs
from pyformex.gui.draw import _T, _I
[docs]def mergedSurface(*objects):
"""Create a single surface from multiple Geometry objects
Parameters
----------
objects: list of Geometry
Any number of Geometry subclass instances. The objects that are
TriSurface or convertible to TriSurface are merged into a single
surface. The other ones are skipped with a message.
Returns
-------
TriSurface
A single TriSurface containing all the geometry objects convertable
to surface.
"""
surfaces = []
for i, obj in enumerate(objects):
if not isinstance(obj, TriSurface):
try:
obj = obj.toSurface()
except Exception:
print(type(obj))
print(f"Skipping object {i}: can not be converted to TriSurface")
continue
surfaces.append(obj)
if surfaces:
return TriSurface.concatenate(surfaces)
return TriSurface()
########################### Labels ####################################
[docs]class Label:
"""A colored string attached to a point in 3D.
Parameters
----------
point: :term:`coords_like` (3,)
The coordinates of the point where the label is attached.
text: str
The string to display at the given point
color: int
The color to display the string with. It is an index into
the current palette (pf.canvas.settings.colormap)
Notes
-----
The Label class is intended to add discrete annotations to a rendering.
It should not be used to add large annotation sets, like the numbers
of all elements or nodes. Use :func:`drawNumbers` for that purpose.
"""
def __init__(self, point, text, color=0):
self.point = Coords(point).reshape(3,)
self.text = str(text)
self.color = int(color)
def __repr__(self):
return f"Label({list(self.point)}, '{self.text}', {self.color})"
def __str__(self):
with np.printoptions(sign=' ', floatmode='fixed'):
return f"({self.color}) {np.array2string(self.point, separator=', ')}: {self.text}"
[docs] def match(self, point=None, text=None, color=None):
"""Check if Label attributes match the given values.
Returns
-------
True if the Label's attributes match all the specified arguments.
"""
return ((point is None or np.allclose(self.point, point)) and
(text is None or self.text == text) and
(color is None or self.color == color))
[docs] def print(self, prefix=''):
"""Print a Label to the console"""
gs.printc(f"{prefix}{self}", color=self.color)
[docs] def edit(self, xactions=[]):
"""Interactively edit a Label.
Pops up a Dialog to edit each of the Label's attributes.
A list of extra action tuples (buttontext, function) may
be specified to add extra action buttons on the Dialog.
The default has a Cancen and an OK button.
"""
res = gs.askItems(caption='Edit Label', items=[
_I('point', self.point, itemtype='point'),
_I('text', self.text),
_I('color', self.color),
], actions=xactions+[('Cancel',), ('OK',)])
if res:
self.__init__(**res)
return True
return False
[docs]class Labels:
"""A flexibel collection of point annotations
The Labels class can hold a collection of Label items
and provides function to interactively add, remove, or edit
the labels. It can automatically generate text and color for
new labels. Labels can also be added programmatically.
In autodraw mode all changes are dynamically
propagated to the rendering.
Parameters
----------
gentext: callable, optional
A function that returns the text for a new label. The
default will generate texts like 'point-0', 'point-1'...
gencolor: callable, optional
A function that returns the color index for a new label.
The default generates the sequence 0, 1,...
pointsize: int
The size of the point drawn where the label is attached.
gravity: str
The gravity for alignment of the string with the attachment point.
fontsize: int
The font size to use for the label text.
autodraw: bool
If True (default), the labels are (re)drawn automatically after
each add or remove operation.
autoprint: bool
If True (default), every label that is added is also printed to the
console with its color.
"""
def __init__(self, gentext=None, gencolor=None,
pointsize=10, gravity='ne', fontsize=20,
autodraw=True, autoprint=True):
if callable(gentext):
self.gentext = staticmethod(gentext)
else:
self.gentext = utils.autoName('point')
if callable(gencolor):
self.gencolor = staticmethod(gencolor)
else:
self.gencolor = utils.Counter()
self.actors = []
self.pointsize = float(pointsize)
self.gravity = str(gravity)
self.fontsize = int(fontsize)
self.autodraw = bool(autodraw)
self.autoprint = bool(autoprint)
self.clear()
[docs] def clear(self):
"""Remove all labels."""
if self.autodraw:
self.undraw()
self.labellist = np.array([], dtype=object)
self.actors = []
def __iter__(self):
"""Return an iterator for the Collection"""
return self.labellist
@property
def nlabels(self):
"""Return the number of labels"""
return len(self.labellist)
@property
def points(self):
"""Return a Coords with all label attachment points"""
return Coords([l.point for l in self.labellist])
@property
def texts(self):
"""Return a list with all label texts"""
return [l.text for l in self.labellist]
@property
def colors(self):
"""Return an int array with all labels colors"""
return np.array([l.color for l in self.labellist], dtype=at.Int)
def __str__(self):
return 'Labels:\n' + '\n'.join([f" {l}" for l in self.labellist])
def __getitem__(self, index):
return self.labellist[index]
[docs] def printc(self, header='LABELS:', indent=' '):
"""Print the list of labels in color on the console"""
if header:
print(header)
for l in self.labellist:
gs.printc(f"{indent}{l}", color=l.color)
[docs] def add(self, labels):
"""Add one or more labels
Parameters
----------
labels: Label | list[Label]
One or more :class:`Label` instances to add to the Labels.
"""
if isinstance(labels, Label):
labels = [labels]
if (isinstance(labels, list) and all(
isinstance(i, Label) for i in labels)):
self.labellist = np.append(self.labellist, labels)
if self.autodraw:
self.draw()
if self.autoprint:
for label in labels:
label.print()
else:
raise ValueError("Expected Label or list of Labels")
[docs] def genLabel(self, point, text=None, color=None):
"""Create a new Label without adding it to the collection.
Parameters: see :class:`Label`.
Returns
-------
Label
A Label with the provided attributes or where not provided,
those generated by the specified or default gentext/gencolor
function. This Label is not automatically added to the Labels
collection.
See Also
--------
add: add a Label to the Labels collection
addLabel: create a Label and add it to the collection
"""
point = Coords(point).reshape(1, 3)
text = self.gentext() if text is None else str(text)
color = self.gencolor() if color is None else int(color)
return Label(point, text, color)
[docs] def addLabel(self, point, text=None, color=None):
"""Create a new label and add it to the collection."""
label = self.genLabel(point, text, color)
self.add(label)
return label
[docs] def match(self, point=None, text=None, color=None):
"""Match labels by point, text and/or color
Matches all labels in the collection with the given parameters.
Returns
-------
bool array
A bool array holding True or False for every Label in the
collection.
See Also
--------
find: return indices of matching labels
"""
return np.array([item.match(point, text, color)
for item in self.labellist], dtype=np.bool)
[docs] def find(self, point=None, text=None, color=None):
"""Return indices fo matching labels.
Returns
-------
int array
The indices of the labels matching the provided parameters.
"""
return at.where_1d(self.match(point, text, color))
[docs] def remove(self, point=None, text=None, color=None):
"""Remove labels by point coordinates, text or color
Removes all the labels matching the provided parameters.
"""
rem = self.match(point, text, color)
self.labellist = self.labellist[~rem]
if self.autodraw:
self.draw()
[docs] def removeLabel(self, label):
"""Remove a label
Parameters
----------
label: Label
A Label to be removed.
Removed all the labels matching the provided Label's attributes.
"""
self.remove(label.point, label.text, label.color)
[docs] def draw(self):
"""Draw the collection of labels.
This keeps track of the generated scene actors, and removes the
previous ones when a new draw is done. Two actors are drawn: one
with the dots representing the points, and one with the text strings.
"""
color = self.colors
A = [
gs.draw(self.points, color=color, pointsize=self.pointsize,
ontop=True, rendertype=4, bbox='last', view=None),
gs.drawMarks(self.points, self.texts, colors=color,
gravity=self.gravity, size=self.fontsize)
]
gs.drawActor(self.actors[2:])
gs.undraw(self.actors[:2])
self.actors[:2] = A
[docs] def undraw(self):
"""Remove the labels from the rendering"""
gs.undraw(self.actors)
[docs]def labelPoints(labels=None, gentext=None, edit=False):
"""Interactively create/edit/remove point labels.
Starts a point picking mode and creates a label for every picked
point. The labels are collected in a Labels instance, can be edited
and removed, and are drawn automatically.
Parameters
----------
labels: Labels, optional
A Labels collection of labels. If provided, the labels are added to
this collection and existing labels can be edited. If not provided,
a default Labels is created.
gentext: callable, optional
Only used if no ``labels`` was provided. The created default Labels
will then use this function to generate the label text.
edit: bool, optional
If True (default), every generated label will immediately show
an edit Dialog to change its attributes. If False, generated
labels can not be changed, but may be edited afterwards.
Returns
-------
Labels
The Labels collection: either the update one that was passed in,
or a newly generated one.
Notes
-----
Points can be picked one by one or multiple at once. Picked points
are then handled one by one. If no label exists at point, a new one
is generated. If edit is True, the label (new or old) can then be
edited. The edit dialog allows to accept, change or remove the label.
When all points are handled, a new pick can be done.
If the CTRL key is depressed during picking, existing labels at the points
are removed instead of adding/editing labels.
The procedure stops when the ESC key or right mouse button is pressed.
The Labels collection is then returned. Labels.undraw() can be used
to remove them from the screen, and Labels.draw() to show them again.
Currently, if you edit the coordinates of a point thus that they do
not match a point of a regular actor, you will not be able to pick
that point again and thus to edit/remove that label.
"""
def modify_labels(self):
from pyformex.gui.qtcanvas import _PICK_SET, _PICK_ADD, _PICK_REMOVE
nonlocal labels
def remove(b):
labels.removeLabel(label)
b.parent().parent.reject()
xactions = [('Remove', remove)]
self.highlightSelection(self.picked)
points = getPoints(self.picked)
if self.mod in (_PICK_ADD, _PICK_SET):
for p in points:
ids = labels.find(p)
if len(ids) == 0 and self.mod == _PICK_ADD:
label = labels.addLabel(p)
if edit:
ids = [-1]
if edit:
for i in ids:
label = labels.labellist[i]
if label.edit(xactions=xactions):
labels.draw()
elif self.mod == _PICK_REMOVE:
for p in points:
labels.remove(p)
self.removeHighlight()
def label_points_func(self):
nonlocal labels
self.selection.keep_order = True # TODO:this should be an option of pick
# self.modify_selection()
modify_labels(self)
print("Current labels:", labels, sep='\n')
if isinstance(labels, Labels):
labels.draw()
else:
labels = Labels(gentext=gentext)
gs.pick('point', func=label_points_func)
return labels
# Label text options for labelPoints2D
def label_coords2D(P, prec):
return f" ({P.x:{prec}}, {P.y:{prec}})"
def label_dist2D(P, Q, prec):
PQ = Q-P
# return f" h={PQ.x:{prec}}, v={PQ.y:{prec}}, d={at.length(PQ[:2]):{prec}}"
return f" dist={at.length(PQ[:2]):{prec}} ({PQ.x:{prec}}, {PQ.y:{prec}})"
def label_angle2D(P, Q, R, prec):
angle = gt.rotationAngle(R-Q, P-Q, m=(0., 0., 1.), angle_spec=at.DEG)[0]
print(angle)
return f" angle={angle:{prec}}"
[docs]def labelPoints2D(query='point', *,
surface=None, missing='r', zvalue=None, npoints=-1,
labels=None, gravity='e', drawstyle=2, linewidth=2,
prec='+.4f', labeltext=None,
# every=1, keeplines=False
):
"""Interactively create and label points on a plane or a surface.
Puts the user in an interactive projective mode where each mouse click
generates a 3D point. Since the mouse click only generates two coordinates
(in a plane perpendicular to the camera axis), the third coordinate
(depth) has to be defined by projection in the direction of the camera
axis) onto a surface or a plane. This only works well visually if the camera
is in projection mode. Therefore perspective mode is switched off during
the operation (which may slightly change the rendering) and the camera is
locked. The camera is restored at the end.
The 'query' parameter defines what type of info is put in the labels
attached to the/some points. There are currently 4 modes:
- 'point': every point is labeled with an index.
- 'coords': every point is labeled with the 2D coordinates of the point.
- 'dist': every second point is labeled with the 2D distance from the
previous point.
- 'angle': after every third point the penultimate point is labeled with
the 2D angle between the 2D line segments from that point to the next
and to the previous point.
All labeled points are collected in a
:class:`~pyformex.plugins.tools.Labels` instance.
The operation ends when either a required number of labeled points is
generated, or the user stops the interactive mode by accepting (right
mouse button or ENTER key) or cancelling (ESC key).
Parameters
----------
query: str
Defines what type of information is put in the labels and how many
points are labeled. It should be one of 'point', 'coords', 'dist'
or 'angle'. See explanation above.
surface: :class:`TriSurface` | '_merged_', optional
A surface on which to draw the points. If provided, the points are
created on that surface. A special value '_merged_' will create
a merged surface of all current actors that can be converted to
surfaces. Thus it is possible to draw points on a quad Mesh or even
on the surface of a volume Mesh.
Mouse clicks outside the surface are handled according to the missing
parameter.
Note that this function does not draw the surface itself. The caller
must make sure that the proper surface is drawn beforehand to guide
the user in putting the points.
missing: 'r' | 'o' | 'e'
In surface mode, defines what to do when clicking outside the surface:
with the default 'r', no point is created; with 'o', a point is created
on a plane in front of the surface; with 'e' a ProjecionMissing is
raised.
zvalue: float, optional
The depth value of a plane perpendicular on the camera axis on which
the points will be created. It is measured from the focus
point towards the camera eye. Only used if no surface was provided.
If not provided (and neither is surface), a plane through
the camera focus is used.
npoints: int, optional
The number of points to be created. If < 0 (default), an unlimited number
of points can be created. It is stopped by clicking the right
mouse button of hitting ENTER.
labels: :class:`Labels`, optional
The Labels class where the generated points should be added. If not
provided, a new Labels instance is created.
gravity: str | None
If not None, overrides the gravity for the labels.
drawstyle: int
Type of line decorations drawn with query = 'point' or 'coords'.
linewidth: float
Line width to be used in line decorations.
prec: str
Format string to use for each of the coordinate values.
labeltext: callable | None
If provided, overrides the default label text generator. The
parameters passed to the callable depend on the query mode: for
'point' and 'coords', it is (P, prec), where P is the point in camera
coodinates and prec is the prec argument; for 'dist' mode, two
points are passed (P, Q, prec) and for 'angle' mode three points
(P, Q, R, prec).
Returns
-------
labels: :class:`Labels`
Returns the passed or created Labels instance, with the generated
points added to it. The 3D global coordinates of the points can be
obtained from labels.points.
See Also
--------
:func:`pyformex.plugins.tools_menu.labelPoints2DDialog`: provides a
dialog to set and remember some of the parameters for this function
and then calls this function with the global Labels collection of the
Tools menu.
"""
if isinstance(labels, Labels):
labels.draw()
else:
labels = Labels()
if gravity is not None:
labels.gravity = str(gravity)
color = 0 if labels.colors.size == 0 else labels.colors.max() + 1
if not labeltext:
labeltext = {
'point': None,
'coords': label_coords2D,
'dist': label_dist2D,
'angle': label_angle2D,
}[query]
if query != 'coords':
drawstyle = 0
def connectPoints(P, Q, style=1):
"""Draw connection between two points"""
nonlocal color, labels, cs
PP = P.toCS(cs)
QQ = Q.toCS(cs)
RR = Coords([QQ.x, PP.y, 0.])
# SS = Coords([QQ.x, QQ.y, 0.])
R = RR.fromCS(cs)
# S = SS.fromCS(cs)
if abs(style) == 1:
F = Formex([[P, Q]]).setProp(color)
elif abs(style) == 2:
F = Formex([[P, R], [R, Q]]).setProp(color)
elif abs(style) == 3:
F = PolyLine([P, (Q.x, P.y, P.z), (Q.x, Q.y, P.z), Q]).setProp(color)
A = gs.draw(F, color='prop', linewidth=linewidth, ontop=True)
# labels.actors.append(A)
return A
def add_point(self):
nonlocal surface, color, labels, cs
P = self.drawn
if surface:
P = P.projectOnSurface(
surface, dir=pf.canvas.camera.axis, missing=missing)
if P.shape[0] == 0:
return # No point is created
self.drawing = Coords.concatenate([self.drawing, P])
if query in ['point', 'coords']:
P = self.drawing[-1]
if drawstyle:
connectPoints(cs.o, P, drawstyle)
PP = P.toCS(cs)
text = None if labeltext is None else labeltext(PP, prec)
labels.addLabel(P, text=text, color=color)
color += 1
else:
if query == 'dist' and len(self.drawing) % 2 == 0:
P, Q = self.drawing[-2:]
drawn.append(connectPoints(P, Q))
PP, QQ = P.toCS(cs), Q.toCS(cs)
text = labeltext(PP, QQ, prec)
labels.addLabel(0.5*(P+Q), text=text, color=color)
color += 1
elif query == 'angle' and len(self.drawing) % 3 == 0:
P, Q, R = self.drawing[-3:]
drawn.extend([connectPoints(P, Q), connectPoints(Q, R)])
PP, QQ, RR = P.toCS(cs), Q.toCS(cs), R.toCS(cs)
text = labeltext(PP, QQ, RR, prec)
labels.addLabel(Q, text=text, color=color)
color += 1
drawn = []
canvas = pf.canvas
cam = canvas.camera
_save_perspective = cam.perspective
gs.perspective(False)
cam.lock() # Needed to ensure unproject
cs = cam.coordsys()
if surface == '_merged_':
surface = mergedSurface(*[a.object for a in canvas.actors])
if isinstance(surface, TriSurface):
# Use a z value guaranteeing the closest point to the viewer
zvalue = surface.center().toCS(cs)[2] + 0.5 * surface.dsize()
else:
surface = None
if zvalue is None:
zvalue = 0.
front = Coords((0., 0., zvalue)).fromCS(cs)
zplane = cam.project(front).reshape(3)[2]
canvas.idraw(zplane=zplane, func=add_point, mouseline=False)
canvas.removeHighlight()
cam.unlock()
gs.perspective(_save_perspective)
labels.actors.append(drawn)
return labels
########################### Planes ####################################
class Plane():
def __init__(self, points, normal=None, size=(1.0, 1.0)):
pts = Coords(points)
if pts.shape == (3,) and normal is not None:
P = pts
n = Coords(normal)
if n.shape != (3,):
raise ValueError("normal does not have correct shape")
elif pts.shape == (3, 3,):
P = pts.centroid()
n = np.cross(pts[1]-pts[0], pts[2]-pts[0])
else:
raise ValueError("points has incorrect shape (%s)" % str(pts.shape))
s = Coords(size)
self.P = P
self.n = n
self.s = s
def point(self):
return self.P
def normal(self):
return self.n
def size(self):
return self.s
def bbox(self):
return self.P.bbox()
def __str__(self):
return 'P:%s n:%s s:%s' % (list(self.P), list(self.n),
(list(self.s[0]), list(self.s[1])))
def actor(self, **kargs):
from pyformex.opengl import actors
actor = actors.PlaneActor(n=self.n, P=self.P, **kargs)
return actor
################# Report information about picked objects ################
[docs]def getPoints(K):
"""Return all points in the Collection as a Coords"""
points = []
if K.obj_type == 'point':
for k in K.keys():
v = K[k]
o = pf.canvas.actors[k].object
points.append(o.points()[v])
return Coords.concatenate(points)
def report(K):
if K is not None and hasattr(K, 'obj_type'):
if K.obj_type == 'actor':
return reportActors(K)
elif K.obj_type == 'element':
return reportElements(K)
elif K.obj_type == 'point':
return reportPoints(K)
elif K.obj_type == 'edge':
return reportEdges(K)
elif K.obj_type == 'prop':
return reportProps(K)
elif K.obj_type == 'partition':
return reportPartitions(K)
return ''
[docs]def indent_str(s, n):
"""Indent a multiline string"""
return '\n'.join([' '*n + line for line in s.split('\n')])
def reportActors(K):
def _format(actor):
if hasattr(actor.object, 'report'):
return indent_str(actor.object.report(), 2)
else:
return f" {actor.getType()}"
s = "Actor report\n"
v = K.get(-1, [])
s += f"Actors {v}\n"
s += '\n'.join([f" Actor {k}:" + _format(pf.canvas.actors[k]) for k in v])
return s
def reportElements(K):
s = "Element report\n"
for k in K.keys():
v = K[k]
obj = pf.canvas.actors[k].object
s += f"Actor {k} ({obj.__class__.__name__}); Elements {v}\n"
for p in v:
if isinstance(obj, Formex):
s += at.stringar(f" Element {p}: ", obj.coords[p])
elif isinstance(obj, Mesh):
e = obj.elems[p]
s += f" Element {p}: {e}\n"
s += at.stringar(" Coords: ", obj.coords[e])
if p != v[-1]:
s += '\n'
return s
def reportPoints(K):
s = "Point report\n"
for k in K.keys():
v = K[k]
A = pf.canvas.actors[k]
s += "Actor %s (type %s); Points %s\n" % (k, A.getType(), v)
x = A.object.points()
for p in v:
s += " Point %s: %s\n" % (p, x[p])
return s
def reportProps(K):
s = "Property report\n"
for k in K.keys():
v = K[k]
A = pf.canvas.actors[k]
t = A.getType()
s += "Actor %s (type %s); Props %s\n" % (k, t, v)
for p in v:
w = np.where(A.object.prop == p)[0]
s += " Elements with prop %s: %s\n" % (p, w)
return s
def reportEdges(K):
s = "Edge report\n"
for k in K.keys():
v = K[k]
A = pf.canvas.actors[k]
s += "Actor %s (type %s); Edges %s\n" % (k, A.getType(), v)
e = A.edges()
for p in v:
s += " Edge %s: %s\n" % (p, e[p])
def reportPartitions(K):
s = "Partition report\n"
for k in K.keys():
P = K[k][0]
A = pf.canvas.actors[k]
t = A.getType()
for l in P.keys():
v = P[l]
s += "Actor %s (type %s); Partition %s; Elements %s\n" % (k, t, l, v)
if t == 'Formex':
e = A
elif t == 'TriSurface':
e = A._elems
for p in v:
s += " Element %s: %s\n" % (p, e[p])
return s
[docs]def getObjectItems(obj, items, mode):
"""Get the specified items from object."""
if mode == 'actor':
return [obj[i].object for i in items if hasattr(obj[i], 'object')]
elif mode in ['element', 'partition']:
if hasattr(obj, 'object') and hasattr(obj.object, 'select'):
return obj.object.select(items)
elif mode == 'point':
if hasattr(obj, 'points'):
return obj.object.points()[items]
return None
[docs]def getCollection(K):
"""Returns a collection."""
if K.obj_type == 'actor':
return [pf.canvas.actors[int(i)].object for i in K.get(-1, [])
if hasattr(pf.canvas.actors[int(i)], 'object')]
elif K.obj_type in ['element', 'point']:
return [getObjectItems(pf.canvas.actors[k], K[k], K.obj_type)
for k in K.keys()]
elif K.obj_type == 'partition':
return [getObjectItems(pf.canvas.actors[k], K[k][0][prop], K.obj_type)
for k in K.keys() for prop in K[k][0].keys()]
else:
return None
[docs]def growCollection(K, **kargs):
"""Grow the collection with n frontal rings.
K should be a collection of elements.
This should work on any objects that have a growSelection method, like
a Mesh.
"""
if K.obj_type == 'element':
for k in K.keys():
o = pf.canvas.actors[k].object
print(o)
if hasattr(o, 'growSelection'):
K[k] = o.growSelection(K[k], **kargs)
else:
utils.warn(f"Can not grow selection on type {type(o)}")
[docs]def partitionCollection(K):
"""Partition the collection according to node adjacency.
The actor numbers will be connected to a collection of property numbers,
e.g. 0 [1 [4,12] 2 [6,20]], where 0 is the actor number, 1 and 2 are the
property numbers and 4, 12, 6 and 20 are the element numbers.
"""
sel = getCollection(K)
if len(sel) == 0:
print("Nothing to partition!")
return
if K.obj_type == 'actor':
actor_numbers = K.get(-1, [])
K.clear()
for i in actor_numbers:
K.add(np.arange(sel[int(i)].nelems()), i)
prop = 1
j = 0
for i in K.keys():
p = sel[j].partitionByConnection() + prop
print("Actor %s partitioned in %s parts" % (i, p.max()-p.min()+1))
C = Collection()
C.set(np.transpose(np.asarray([p, K[i]])))
K[i] = C
prop += p.max()-p.min()+1
j += 1
K.obj_type = 'partition'
[docs]def getPartition(K, prop):
""" Remove all partitions with property not in prop."""
for k in K.keys():
for p in K[k][0].keys():
if p not in prop:
K[k][0].remove(K[k][0][p], p)
[docs]def exportObjects(obj, name, single=False):
"""Export a list of objects under the given name.
If obj is a list, and single=True, each element of the list is exported
as a single item. The items will be given the names name-0, name-1, etc.
Else, the obj is exported as is under the name.
"""
from pyformex.script import export
if single and isinstance(obj, list):
export(dict([(f"name-{i}", v) for i, v in enumerate(obj)]))
else:
export({name: obj})
[docs]def actorDialog(actorids):
"""Create an actor dialog for the specified actors (by index)
"""
print("actorDialog %s" % actorids)
actors = [pf.canvas.actors[i] for i in actorids]
res = gs.askItems([_T("actor_%s" % i, [
_I('name', str(a.name)),
_I('type', str(a.getType()), readonly=True),
_I('visible', bool(a.visible)),
_I('alpha', float(a.alpha)),
_I('objcolor', str(a.objcolor), itemtype='color'),
]) for i, a in zip(actorids, actors)])
print(res)
# End