vedo.assembly

Submodule for managing groups of vedo objects

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3import numpy as np
  4
  5try:
  6    import vedo.vtkclasses as vtk
  7except ImportError:
  8    import vtkmodules.all as vtk
  9
 10import vedo
 11
 12__docformat__ = "google"
 13
 14__doc__ = """
 15Submodule for managing groups of vedo objects
 16
 17![](https://vedo.embl.es/images/basic/align4.png)
 18"""
 19
 20__all__ = [
 21    "Group",
 22    "Assembly",
 23    "procrustes_alignment",
 24]
 25
 26
 27#################################################
 28def procrustes_alignment(sources, rigid=False):
 29    """
 30    Return an ``Assembly`` of aligned source meshes with the `Procrustes` algorithm.
 31    The output ``Assembly`` is normalized in size.
 32
 33    The `Procrustes` algorithm takes N set of points and aligns them in a least-squares sense
 34    to their mutual mean. The algorithm is iterated until convergence,
 35    as the mean must be recomputed after each alignment.
 36
 37    The set of average points generated by the algorithm can be accessed with
 38    ``algoutput.info['mean']`` as a numpy array.
 39
 40    Arguments:
 41        rigid : bool
 42            if `True` scaling is disabled.
 43
 44    Examples:
 45        - [align4.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align4.py)
 46
 47        ![](https://vedo.embl.es/images/basic/align4.png)
 48    """
 49
 50    group = vtk.vtkMultiBlockDataGroupFilter()
 51    for source in sources:
 52        if sources[0].npoints != source.npoints:
 53            vedo.logger.error("sources have different nr of points")
 54            raise RuntimeError()
 55        group.AddInputData(source.polydata())
 56    procrustes = vtk.vtkProcrustesAlignmentFilter()
 57    procrustes.StartFromCentroidOn()
 58    procrustes.SetInputConnection(group.GetOutputPort())
 59    if rigid:
 60        procrustes.GetLandmarkTransform().SetModeToRigidBody()
 61    procrustes.Update()
 62
 63    acts = []
 64    for i, s in enumerate(sources):
 65        poly = procrustes.GetOutput().GetBlock(i)
 66        mesh = vedo.mesh.Mesh(poly)
 67        mesh.SetProperty(s.GetProperty())
 68        if hasattr(s, "name"):
 69            mesh.name = s.name
 70        acts.append(mesh)
 71    assem = Assembly(acts)
 72    assem.transform = procrustes.GetLandmarkTransform()
 73    assem.info["mean"] = vedo.utils.vtk2numpy(procrustes.GetMeanPoints().GetData())
 74    return assem
 75
 76
 77#################################################
 78class Group(vtk.vtkPropAssembly):
 79    """Form groups of generic objects (not necessarily meshes)."""
 80    def __init__(self, objects=()):
 81        """Form groups of generic objects (not necessarily meshes)."""
 82
 83        vtk.vtkPropAssembly.__init__(self)
 84
 85        self.name = ""
 86        self.created = ""
 87        self.trail = None
 88        self.trail_points = []
 89        self.trail_segment_size = 0
 90        self.trail_offset = None
 91        self.shadows = []
 92        self.info = {}
 93        self.rendered_at = set()
 94        self.transform = None
 95        self.scalarbar = None
 96
 97        for a in vedo.utils.flatten(objects):
 98            if a:
 99                self.AddPart(a)
100
101        self.PickableOff()
102
103    def __iadd__(self, obj):
104        """
105        Add an object to the group
106        """
107        if not vedo.utils.is_sequence(obj):
108            obj = [obj]
109        for a in obj:
110            if a:
111                self.AddPart(a)
112        return self
113
114    def unpack(self):
115        """Unpack the group into its elements"""
116        elements = []
117        self.InitPathTraversal()
118        parts = self.GetParts()
119        parts.InitTraversal()
120        for i in range(parts.GetNumberOfItems()):
121            ele = parts.GetItemAsObject(i)
122            elements.append(ele)
123
124        # gr.InitPathTraversal()
125        # for _ in range(gr.GetNumberOfPaths()):
126        #     path  = gr.GetNextPath()
127        #     print([path])
128        #     path.InitTraversal()
129        #     for i in range(path.GetNumberOfItems()):
130        #         a = path.GetItemAsObject(i).GetViewProp()
131        #         print([a])
132
133        return elements
134
135    def clear(self):
136        """Remove all parts"""
137        for a in self.unpack():
138            self.RemovePart(a)
139        return self
140
141    def on(self):
142        """Switch on visibility"""
143        self.VisibilityOn()
144        return self
145
146    def off(self):
147        """Switch off visibility"""
148        self.VisibilityOff()
149        return self
150
151    def pickable(self, value=None):
152        """Set/get the pickability property of an object."""
153        if value is None:
154            return self.GetPickable()
155        self.SetPickable(value)
156        return self
157
158    def draggable(self, value=None):
159        """Set/get the draggability property of an object."""
160        if value is None:
161            return self.GetDragable()
162        self.SetDragable(value)
163        return self
164
165
166    def pos(self, x=None, y=None):
167        """Set/Get object position."""
168        if x is None:  # get functionality
169            return np.array(self.GetPosition())
170
171        if y is None:  # assume x is of the form (x,y)
172            x, y = x
173        self.SetPosition(x, y)
174        return self
175
176    def shift(self, ds):
177        """Add a shift to the current object position."""
178        p = np.array(self.GetPosition())
179
180        self.SetPosition(p + ds)
181        return self
182
183    def bounds(self):
184        """
185        Get the object bounds.
186        Returns a list in format [xmin,xmax, ymin,ymax].
187        """
188        return self.GetBounds()
189
190    def diagonal_size(self):
191        """Get the length of the diagonal"""
192        b = self.GetBounds()
193        return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2)
194
195
196    def show(self, **options):
197        """
198        Create on the fly an instance of class ``Plotter`` or use the last existing one to
199        show one single object.
200
201        This method is meant as a shortcut. If more than one object needs to be visualised
202        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
203
204        Returns the ``Plotter`` class instance.
205        """
206        return vedo.plotter.show(self, **options)
207
208
209#################################################
210class Assembly(vedo.base.Base3DProp, vtk.vtkAssembly):
211    """
212    Group many objects and treat them as a single new object.
213    """
214    def __init__(self, *meshs):
215        """
216        Group many objects and treat them as a single new object,
217        keeping track of internal transformations.
218
219        Examples:
220            - [gyroscope1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope1.py)
221
222            ![](https://vedo.embl.es/images/simulations/39766016-85c1c1d6-52e3-11e8-8575-d167b7ce5217.gif)
223        """
224        vtk.vtkAssembly.__init__(self)
225        vedo.base.Base3DProp.__init__(self)
226
227        if len(meshs) == 1:
228            meshs = meshs[0]
229        else:
230            meshs = vedo.utils.flatten(meshs)
231
232        self.actors = meshs
233
234        if meshs and hasattr(meshs[0], "top"):
235            self.base = meshs[0].base
236            self.top = meshs[0].top
237        else:
238            self.base = None
239            self.top = None
240
241        scalarbars = []
242        for a in meshs:
243            if isinstance(a, vtk.vtkProp3D):  # and a.GetNumberOfPoints():
244                self.AddPart(a)
245            if hasattr(a, "scalarbar") and a.scalarbar is not None:
246                scalarbars.append(a.scalarbar)
247
248        if len(scalarbars) > 1:
249            self.scalarbar = Group(scalarbars)
250        elif len(scalarbars) == 1:
251            self.scalarbar = scalarbars[0]
252
253        self.pipeline = vedo.utils.OperationNode(
254            "Assembly", parents=meshs, comment=f"#meshes {len(meshs)}", c="#f08080",
255        )
256        ###################################################################
257
258    def _repr_html_(self):
259        """
260        HTML representation of the Assembly object for Jupyter Notebooks.
261        
262        Returns:
263            HTML text with the image and some properties.
264        """
265        import io
266        import base64
267        from PIL import Image
268
269        library_name = "vedo.assembly.Assembly"
270        help_url = "https://vedo.embl.es/docs/vedo/assembly.html"
271
272        arr = self.thumbnail(zoom=1.1, elevation=-60)
273
274        im = Image.fromarray(arr)
275        buffered = io.BytesIO()
276        im.save(buffered, format="PNG", quality=100)
277        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
278        url = "data:image/png;base64," + encoded
279        image = f"<img src='{url}'></img>"
280
281        # statisitics
282        bounds = "<br/>".join(
283            [
284                vedo.utils.precision(min_x,4) + " ... " + vedo.utils.precision(max_x,4)
285                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
286            ]
287        )
288
289        help_text = ""
290        if self.name:
291            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
292        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 
293        if self.filename:
294            dots = ""
295            if len(self.filename) > 30:
296                dots = "..."
297            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
298        
299        all = [
300            "<table>",
301            "<tr>", 
302            "<td>", image, "</td>",
303            "<td style='text-align: center; vertical-align: center;'><br/>", help_text,
304            "<table>",
305            "<tr><td><b> nr. of objects </b></td><td>" + str(self.GetNumberOfPaths()) + "</td></tr>",
306            "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>",
307            "<tr><td><b> diagonal size </b></td><td>" + vedo.utils.precision(self.diagonal_size(), 5) + "</td></tr>",
308            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
309            "</table>",
310            "</table>",
311        ]
312        return "\n".join(all)
313
314    def __add__(self, obj):
315        """
316        Add an object to the assembly
317        """
318        if isinstance(obj, vtk.vtkProp3D):
319            self.AddPart(obj)
320
321        self.actors.append(obj)
322
323        if hasattr(obj, "scalarbar") and obj.scalarbar is not None:
324            if self.scalarbar is None:
325                self.scalarbar = obj.scalarbar
326                return self
327
328            def unpack_group(scalarbar):
329                if isinstance(scalarbar, Group):
330                    return scalarbar.unpack()
331                else:
332                    return scalarbar
333
334            if isinstance(self.scalarbar, Group):
335                self.scalarbar += unpack_group(obj.scalarbar)
336            else:
337                self.scalarbar = Group(
338                    [unpack_group(self.scalarbar), unpack_group(obj.scalarbar)]
339                )
340        self.pipeline = vedo.utils.OperationNode(
341            "add mesh", parents=[self, obj], c="#f08080",
342        )
343        return self
344    
345    def __contains__(self, obj):
346        """Allows to use ``in`` to check if an object is in the Assembly."""
347        return obj in self.actors
348
349    def clone(self):
350        """Make a clone copy of the object."""
351        newlist = []
352        for a in self.actors:
353            newlist.append(a.clone())
354        return Assembly(newlist)
355
356    def unpack(self, i=None, transformed=False):
357        """Unpack the list of objects from a ``Assembly``.
358
359        If `i` is given, get `i-th` object from a ``Assembly``.
360        Input can be a string, in this case returns the first object
361        whose name contains the given string.
362
363        Examples:
364            - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)
365        """
366        if transformed:
367            actors = []
368            for a in self.actors:
369                actors.append(a.clone(transformed=True))
370        else:
371            actors = self.actors
372
373        if i is None:
374            return actors
375        elif isinstance(i, int):
376            return actors[i]
377        elif isinstance(i, str):
378            for m in actors:
379                if i in m.name:
380                    return m
class Group(vtkmodules.vtkRenderingCore.vtkPropAssembly):
 79class Group(vtk.vtkPropAssembly):
 80    """Form groups of generic objects (not necessarily meshes)."""
 81    def __init__(self, objects=()):
 82        """Form groups of generic objects (not necessarily meshes)."""
 83
 84        vtk.vtkPropAssembly.__init__(self)
 85
 86        self.name = ""
 87        self.created = ""
 88        self.trail = None
 89        self.trail_points = []
 90        self.trail_segment_size = 0
 91        self.trail_offset = None
 92        self.shadows = []
 93        self.info = {}
 94        self.rendered_at = set()
 95        self.transform = None
 96        self.scalarbar = None
 97
 98        for a in vedo.utils.flatten(objects):
 99            if a:
100                self.AddPart(a)
101
102        self.PickableOff()
103
104    def __iadd__(self, obj):
105        """
106        Add an object to the group
107        """
108        if not vedo.utils.is_sequence(obj):
109            obj = [obj]
110        for a in obj:
111            if a:
112                self.AddPart(a)
113        return self
114
115    def unpack(self):
116        """Unpack the group into its elements"""
117        elements = []
118        self.InitPathTraversal()
119        parts = self.GetParts()
120        parts.InitTraversal()
121        for i in range(parts.GetNumberOfItems()):
122            ele = parts.GetItemAsObject(i)
123            elements.append(ele)
124
125        # gr.InitPathTraversal()
126        # for _ in range(gr.GetNumberOfPaths()):
127        #     path  = gr.GetNextPath()
128        #     print([path])
129        #     path.InitTraversal()
130        #     for i in range(path.GetNumberOfItems()):
131        #         a = path.GetItemAsObject(i).GetViewProp()
132        #         print([a])
133
134        return elements
135
136    def clear(self):
137        """Remove all parts"""
138        for a in self.unpack():
139            self.RemovePart(a)
140        return self
141
142    def on(self):
143        """Switch on visibility"""
144        self.VisibilityOn()
145        return self
146
147    def off(self):
148        """Switch off visibility"""
149        self.VisibilityOff()
150        return self
151
152    def pickable(self, value=None):
153        """Set/get the pickability property of an object."""
154        if value is None:
155            return self.GetPickable()
156        self.SetPickable(value)
157        return self
158
159    def draggable(self, value=None):
160        """Set/get the draggability property of an object."""
161        if value is None:
162            return self.GetDragable()
163        self.SetDragable(value)
164        return self
165
166
167    def pos(self, x=None, y=None):
168        """Set/Get object position."""
169        if x is None:  # get functionality
170            return np.array(self.GetPosition())
171
172        if y is None:  # assume x is of the form (x,y)
173            x, y = x
174        self.SetPosition(x, y)
175        return self
176
177    def shift(self, ds):
178        """Add a shift to the current object position."""
179        p = np.array(self.GetPosition())
180
181        self.SetPosition(p + ds)
182        return self
183
184    def bounds(self):
185        """
186        Get the object bounds.
187        Returns a list in format [xmin,xmax, ymin,ymax].
188        """
189        return self.GetBounds()
190
191    def diagonal_size(self):
192        """Get the length of the diagonal"""
193        b = self.GetBounds()
194        return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2)
195
196
197    def show(self, **options):
198        """
199        Create on the fly an instance of class ``Plotter`` or use the last existing one to
200        show one single object.
201
202        This method is meant as a shortcut. If more than one object needs to be visualised
203        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
204
205        Returns the ``Plotter`` class instance.
206        """
207        return vedo.plotter.show(self, **options)

Form groups of generic objects (not necessarily meshes).

Group(objects=())
 81    def __init__(self, objects=()):
 82        """Form groups of generic objects (not necessarily meshes)."""
 83
 84        vtk.vtkPropAssembly.__init__(self)
 85
 86        self.name = ""
 87        self.created = ""
 88        self.trail = None
 89        self.trail_points = []
 90        self.trail_segment_size = 0
 91        self.trail_offset = None
 92        self.shadows = []
 93        self.info = {}
 94        self.rendered_at = set()
 95        self.transform = None
 96        self.scalarbar = None
 97
 98        for a in vedo.utils.flatten(objects):
 99            if a:
100                self.AddPart(a)
101
102        self.PickableOff()

Form groups of generic objects (not necessarily meshes).

def unpack(self):
115    def unpack(self):
116        """Unpack the group into its elements"""
117        elements = []
118        self.InitPathTraversal()
119        parts = self.GetParts()
120        parts.InitTraversal()
121        for i in range(parts.GetNumberOfItems()):
122            ele = parts.GetItemAsObject(i)
123            elements.append(ele)
124
125        # gr.InitPathTraversal()
126        # for _ in range(gr.GetNumberOfPaths()):
127        #     path  = gr.GetNextPath()
128        #     print([path])
129        #     path.InitTraversal()
130        #     for i in range(path.GetNumberOfItems()):
131        #         a = path.GetItemAsObject(i).GetViewProp()
132        #         print([a])
133
134        return elements

Unpack the group into its elements

def clear(self):
136    def clear(self):
137        """Remove all parts"""
138        for a in self.unpack():
139            self.RemovePart(a)
140        return self

Remove all parts

def on(self):
142    def on(self):
143        """Switch on visibility"""
144        self.VisibilityOn()
145        return self

Switch on visibility

def off(self):
147    def off(self):
148        """Switch off visibility"""
149        self.VisibilityOff()
150        return self

Switch off visibility

def pickable(self, value=None):
152    def pickable(self, value=None):
153        """Set/get the pickability property of an object."""
154        if value is None:
155            return self.GetPickable()
156        self.SetPickable(value)
157        return self

Set/get the pickability property of an object.

def draggable(self, value=None):
159    def draggable(self, value=None):
160        """Set/get the draggability property of an object."""
161        if value is None:
162            return self.GetDragable()
163        self.SetDragable(value)
164        return self

Set/get the draggability property of an object.

def pos(self, x=None, y=None):
167    def pos(self, x=None, y=None):
168        """Set/Get object position."""
169        if x is None:  # get functionality
170            return np.array(self.GetPosition())
171
172        if y is None:  # assume x is of the form (x,y)
173            x, y = x
174        self.SetPosition(x, y)
175        return self

Set/Get object position.

def shift(self, ds):
177    def shift(self, ds):
178        """Add a shift to the current object position."""
179        p = np.array(self.GetPosition())
180
181        self.SetPosition(p + ds)
182        return self

Add a shift to the current object position.

def bounds(self):
184    def bounds(self):
185        """
186        Get the object bounds.
187        Returns a list in format [xmin,xmax, ymin,ymax].
188        """
189        return self.GetBounds()

Get the object bounds. Returns a list in format [xmin,xmax, ymin,ymax].

def diagonal_size(self):
191    def diagonal_size(self):
192        """Get the length of the diagonal"""
193        b = self.GetBounds()
194        return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2)

Get the length of the diagonal

def show(self, **options):
197    def show(self, **options):
198        """
199        Create on the fly an instance of class ``Plotter`` or use the last existing one to
200        show one single object.
201
202        This method is meant as a shortcut. If more than one object needs to be visualised
203        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
204
205        Returns the ``Plotter`` class instance.
206        """
207        return vedo.plotter.show(self, **options)

Create on the fly an instance of class Plotter or use the last existing one to show one single object.

This method is meant as a shortcut. If more than one object needs to be visualised please use the syntax show(mesh1, mesh2, volume, ..., options).

Returns the Plotter class instance.

class Assembly(vedo.base.Base3DProp, vtkmodules.vtkRenderingCore.vtkAssembly):
211class Assembly(vedo.base.Base3DProp, vtk.vtkAssembly):
212    """
213    Group many objects and treat them as a single new object.
214    """
215    def __init__(self, *meshs):
216        """
217        Group many objects and treat them as a single new object,
218        keeping track of internal transformations.
219
220        Examples:
221            - [gyroscope1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope1.py)
222
223            ![](https://vedo.embl.es/images/simulations/39766016-85c1c1d6-52e3-11e8-8575-d167b7ce5217.gif)
224        """
225        vtk.vtkAssembly.__init__(self)
226        vedo.base.Base3DProp.__init__(self)
227
228        if len(meshs) == 1:
229            meshs = meshs[0]
230        else:
231            meshs = vedo.utils.flatten(meshs)
232
233        self.actors = meshs
234
235        if meshs and hasattr(meshs[0], "top"):
236            self.base = meshs[0].base
237            self.top = meshs[0].top
238        else:
239            self.base = None
240            self.top = None
241
242        scalarbars = []
243        for a in meshs:
244            if isinstance(a, vtk.vtkProp3D):  # and a.GetNumberOfPoints():
245                self.AddPart(a)
246            if hasattr(a, "scalarbar") and a.scalarbar is not None:
247                scalarbars.append(a.scalarbar)
248
249        if len(scalarbars) > 1:
250            self.scalarbar = Group(scalarbars)
251        elif len(scalarbars) == 1:
252            self.scalarbar = scalarbars[0]
253
254        self.pipeline = vedo.utils.OperationNode(
255            "Assembly", parents=meshs, comment=f"#meshes {len(meshs)}", c="#f08080",
256        )
257        ###################################################################
258
259    def _repr_html_(self):
260        """
261        HTML representation of the Assembly object for Jupyter Notebooks.
262        
263        Returns:
264            HTML text with the image and some properties.
265        """
266        import io
267        import base64
268        from PIL import Image
269
270        library_name = "vedo.assembly.Assembly"
271        help_url = "https://vedo.embl.es/docs/vedo/assembly.html"
272
273        arr = self.thumbnail(zoom=1.1, elevation=-60)
274
275        im = Image.fromarray(arr)
276        buffered = io.BytesIO()
277        im.save(buffered, format="PNG", quality=100)
278        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
279        url = "data:image/png;base64," + encoded
280        image = f"<img src='{url}'></img>"
281
282        # statisitics
283        bounds = "<br/>".join(
284            [
285                vedo.utils.precision(min_x,4) + " ... " + vedo.utils.precision(max_x,4)
286                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
287            ]
288        )
289
290        help_text = ""
291        if self.name:
292            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
293        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 
294        if self.filename:
295            dots = ""
296            if len(self.filename) > 30:
297                dots = "..."
298            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
299        
300        all = [
301            "<table>",
302            "<tr>", 
303            "<td>", image, "</td>",
304            "<td style='text-align: center; vertical-align: center;'><br/>", help_text,
305            "<table>",
306            "<tr><td><b> nr. of objects </b></td><td>" + str(self.GetNumberOfPaths()) + "</td></tr>",
307            "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>",
308            "<tr><td><b> diagonal size </b></td><td>" + vedo.utils.precision(self.diagonal_size(), 5) + "</td></tr>",
309            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
310            "</table>",
311            "</table>",
312        ]
313        return "\n".join(all)
314
315    def __add__(self, obj):
316        """
317        Add an object to the assembly
318        """
319        if isinstance(obj, vtk.vtkProp3D):
320            self.AddPart(obj)
321
322        self.actors.append(obj)
323
324        if hasattr(obj, "scalarbar") and obj.scalarbar is not None:
325            if self.scalarbar is None:
326                self.scalarbar = obj.scalarbar
327                return self
328
329            def unpack_group(scalarbar):
330                if isinstance(scalarbar, Group):
331                    return scalarbar.unpack()
332                else:
333                    return scalarbar
334
335            if isinstance(self.scalarbar, Group):
336                self.scalarbar += unpack_group(obj.scalarbar)
337            else:
338                self.scalarbar = Group(
339                    [unpack_group(self.scalarbar), unpack_group(obj.scalarbar)]
340                )
341        self.pipeline = vedo.utils.OperationNode(
342            "add mesh", parents=[self, obj], c="#f08080",
343        )
344        return self
345    
346    def __contains__(self, obj):
347        """Allows to use ``in`` to check if an object is in the Assembly."""
348        return obj in self.actors
349
350    def clone(self):
351        """Make a clone copy of the object."""
352        newlist = []
353        for a in self.actors:
354            newlist.append(a.clone())
355        return Assembly(newlist)
356
357    def unpack(self, i=None, transformed=False):
358        """Unpack the list of objects from a ``Assembly``.
359
360        If `i` is given, get `i-th` object from a ``Assembly``.
361        Input can be a string, in this case returns the first object
362        whose name contains the given string.
363
364        Examples:
365            - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)
366        """
367        if transformed:
368            actors = []
369            for a in self.actors:
370                actors.append(a.clone(transformed=True))
371        else:
372            actors = self.actors
373
374        if i is None:
375            return actors
376        elif isinstance(i, int):
377            return actors[i]
378        elif isinstance(i, str):
379            for m in actors:
380                if i in m.name:
381                    return m

Group many objects and treat them as a single new object.

Assembly(*meshs)
215    def __init__(self, *meshs):
216        """
217        Group many objects and treat them as a single new object,
218        keeping track of internal transformations.
219
220        Examples:
221            - [gyroscope1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope1.py)
222
223            ![](https://vedo.embl.es/images/simulations/39766016-85c1c1d6-52e3-11e8-8575-d167b7ce5217.gif)
224        """
225        vtk.vtkAssembly.__init__(self)
226        vedo.base.Base3DProp.__init__(self)
227
228        if len(meshs) == 1:
229            meshs = meshs[0]
230        else:
231            meshs = vedo.utils.flatten(meshs)
232
233        self.actors = meshs
234
235        if meshs and hasattr(meshs[0], "top"):
236            self.base = meshs[0].base
237            self.top = meshs[0].top
238        else:
239            self.base = None
240            self.top = None
241
242        scalarbars = []
243        for a in meshs:
244            if isinstance(a, vtk.vtkProp3D):  # and a.GetNumberOfPoints():
245                self.AddPart(a)
246            if hasattr(a, "scalarbar") and a.scalarbar is not None:
247                scalarbars.append(a.scalarbar)
248
249        if len(scalarbars) > 1:
250            self.scalarbar = Group(scalarbars)
251        elif len(scalarbars) == 1:
252            self.scalarbar = scalarbars[0]
253
254        self.pipeline = vedo.utils.OperationNode(
255            "Assembly", parents=meshs, comment=f"#meshes {len(meshs)}", c="#f08080",
256        )
257        ###################################################################

Group many objects and treat them as a single new object, keeping track of internal transformations.

Examples:

def clone(self):
350    def clone(self):
351        """Make a clone copy of the object."""
352        newlist = []
353        for a in self.actors:
354            newlist.append(a.clone())
355        return Assembly(newlist)

Make a clone copy of the object.

def unpack(self, i=None, transformed=False):
357    def unpack(self, i=None, transformed=False):
358        """Unpack the list of objects from a ``Assembly``.
359
360        If `i` is given, get `i-th` object from a ``Assembly``.
361        Input can be a string, in this case returns the first object
362        whose name contains the given string.
363
364        Examples:
365            - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)
366        """
367        if transformed:
368            actors = []
369            for a in self.actors:
370                actors.append(a.clone(transformed=True))
371        else:
372            actors = self.actors
373
374        if i is None:
375            return actors
376        elif isinstance(i, int):
377            return actors[i]
378        elif isinstance(i, str):
379            for m in actors:
380                if i in m.name:
381                    return m

Unpack the list of objects from a Assembly.

If i is given, get i-th object from a Assembly. Input can be a string, in this case returns the first object whose name contains the given string.

Examples:
def procrustes_alignment(sources, rigid=False):
29def procrustes_alignment(sources, rigid=False):
30    """
31    Return an ``Assembly`` of aligned source meshes with the `Procrustes` algorithm.
32    The output ``Assembly`` is normalized in size.
33
34    The `Procrustes` algorithm takes N set of points and aligns them in a least-squares sense
35    to their mutual mean. The algorithm is iterated until convergence,
36    as the mean must be recomputed after each alignment.
37
38    The set of average points generated by the algorithm can be accessed with
39    ``algoutput.info['mean']`` as a numpy array.
40
41    Arguments:
42        rigid : bool
43            if `True` scaling is disabled.
44
45    Examples:
46        - [align4.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align4.py)
47
48        ![](https://vedo.embl.es/images/basic/align4.png)
49    """
50
51    group = vtk.vtkMultiBlockDataGroupFilter()
52    for source in sources:
53        if sources[0].npoints != source.npoints:
54            vedo.logger.error("sources have different nr of points")
55            raise RuntimeError()
56        group.AddInputData(source.polydata())
57    procrustes = vtk.vtkProcrustesAlignmentFilter()
58    procrustes.StartFromCentroidOn()
59    procrustes.SetInputConnection(group.GetOutputPort())
60    if rigid:
61        procrustes.GetLandmarkTransform().SetModeToRigidBody()
62    procrustes.Update()
63
64    acts = []
65    for i, s in enumerate(sources):
66        poly = procrustes.GetOutput().GetBlock(i)
67        mesh = vedo.mesh.Mesh(poly)
68        mesh.SetProperty(s.GetProperty())
69        if hasattr(s, "name"):
70            mesh.name = s.name
71        acts.append(mesh)
72    assem = Assembly(acts)
73    assem.transform = procrustes.GetLandmarkTransform()
74    assem.info["mean"] = vedo.utils.vtk2numpy(procrustes.GetMeanPoints().GetData())
75    return assem

Return an Assembly of aligned source meshes with the Procrustes algorithm. The output Assembly is normalized in size.

The Procrustes algorithm takes N set of points and aligns them in a least-squares sense to their mutual mean. The algorithm is iterated until convergence, as the mean must be recomputed after each alignment.

The set of average points generated by the algorithm can be accessed with algoutput.info['mean'] as a numpy array.

Arguments:
  • rigid : bool if True scaling is disabled.
Examples: