pyFormex example scripts¶
Warning
This document still needs some cleanup!
Sometimes you learn quicker from studying an example than from reading a tutorial or user guide. To help you we have created this collection of annotated examples. Beware that the script texts presented in this document may differ slightly from the corresponding example coming with the pyFormex distribution.
WireStent¶
To get acquainted with the modus operandi of pyFormex,
the WireStent.py script is studied step by step. The lines are numbered
for easy referencing, but are not part of the script itself.
  1#   *** pyformex ***
  2##
  3##  SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
  4##  SPDX-License-Identifier: GPL-3.0-or-later
  5##
  6##  This file is part of pyFormex 3.4  (Thu Nov 16 18:07:39 CET 2023)
  7##  pyFormex is a tool for generating, manipulating and transforming 3D
  8##  geometrical models by sequences of mathematical operations.
  9##  Home page: https://pyformex.org
 10##  Project page: https://savannah.nongnu.org/projects/pyformex/
 11##  Development: https://gitlab.com/bverheg/pyformex
 12##  Distributed under the GNU General Public License version 3 or later.
 13##
 14##  This program is free software: you can redistribute it and/or modify
 15##  it under the terms of the GNU General Public License as published by
 16##  the Free Software Foundation, either version 3 of the License, or
 17##  (at your option) any later version.
 18##
 19##  This program is distributed in the hope that it will be useful,
 20##  but WITHOUT ANY WARRANTY; without even the implied warranty of
 21##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 22##  GNU General Public License for more details.
 23##
 24##  You should have received a copy of the GNU General Public License
 25##  along with this program.  If not, see http://www.gnu.org/licenses/.
 26##
 27"""Wirestent.py
 28
 29A pyFormex script to generate a geometrical model of a wire stent.
 30
 31This version is for inclusion in the pyFormex documentation.
 32"""
 33
 34from formex import *
 35
 36class DoubleHelixStent:
 37    """Constructs a double helix wire stent.
 38
 39    A stent is a tubular shape such as used for opening obstructed
 40    blood vessels. This stent is made frome sets of wires spiraling
 41    in two directions.
 42    The geometry is defined by the following parameters:
 43      L  : approximate length of the stent
 44      De : external diameter of the stent 
 45      D  : average stent diameter
 46      d  : wire diameter
 47      be : pitch angle (degrees)
 48      p  : pitch  
 49      nx : number of wires in one spiral set
 50      ny : number of modules in axial direction
 51      ds : extra distance between the wires (default is 0.0 for
 52           touching wires)
 53      dz : maximal distance of wire center to average cilinder
 54      nb : number of elements in a strut (a part of a wire between two
 55           crossings), default 4
 56    The stent is created around the z-axis. 
 57    By default, there will be connectors between the wires at each
 58    crossing. They can be switched off in the constructor.
 59    The returned formex has one set of wires with property 1, the
 60    other with property 3. The connectors have property 2. The wire
 61    set with property 1 is winding positively around the z-axis.
 62    """
 63    def __init__(self,De,L,d,nx,be,ds=0.0,nb=4,connectors=True):
 64        """Create the Wire Stent."""
 65        D = De - 2*d - ds
 66        r = 0.5*D
 67        dz = 0.5*(ds+d)
 68        p = math.pi*D*tand(be)
 69        nx = int(nx)
 70        ny = int(round(nx*L/p))  # The actual length may differ a bit from L
 71        # a single bumped strut, oriented along the x-axis
 72        bump_z=lambda x: 1.-(x/nb)**2
 73        base = Formex(pattern('1')).replic(nb,1.0).bump1(2,[0.,0.,dz],bump_z,0)
 74        # scale back to size 1.
 75        base = base.scale([1./nb,1./nb,1.])
 76        # NE and SE directed struts
 77        NE = base.shear(1,0,1.)
 78        SE = base.reflect(2).shear(1,0,-1.)
 79        NE.setProp(1)
 80        SE.setProp(3)
 81        # a unit cell of crossing struts
 82        cell1 = (NE+SE).rosette(2,180)
 83        # add a connector between first points of NE and SE
 84        if connectors:
 85            cell1 += Formex([[NE[0][0],SE[0][0]]],2)
 86        # and create its mirror
 87        cell2 = cell1.reflect(2)
 88        # and move both to appropriate place
 89        self.cell1 = cell1.translate([1.,1.,0.])
 90        self.cell2 = cell2.translate([-1.,-1.,0.])
 91        # the base pattern cell1+cell2 now has size [-2,-2]..[2,2]
 92        # Create the full pattern by replication
 93        dx = 4.
 94        dy = 4.
 95        F = (self.cell1+self.cell2).replic2(nx,ny,dx,dy)
 96        # fold it into a cylinder
 97        self.F = F.translate([0.,0.,r]).cylindrical(
 98            dir=[2,0,1],scale=[1.,360./(nx*dx),p/nx/dy])
 99        self.ny = ny
100
101    def all(self):
102        """Return the Formex with all bar elements."""
103        return self.F
104
105
106if __name__ == "draw":
107
108    # show an example
109
110    wireframe()
111    reset()
112
113    D = 10.
114    L = 80.
115    d = 0.2
116    n = 12
117    b = 30.
118    res = askItems([['Diameter',D],
119                    ['Length',L],
120                    ['WireDiam',d],
121                    ['NWires',n],
122                    ['Pitch',b]])
123
124    if not res:
125        exit()
126        
127    D = float(res['Diameter'])
128    L = float(res['Length'])
129    d = float(res['WireDiam'])
130    n = int(res['NWires'])
131    if (n % 2) != 0:
132        warning('Number of wires must be even!')
133        exit()
134    b = float(res['Pitch'])
135
136    H = DoubleHelixStent(D,L,d,n,b).all()
137    clear()
138    draw(H,view='iso')
139    
140    # and save it in a lot of graphics formats
141    if ack("Do you want to save this image (in lots of formats) ?"):
142        for ext in [ 'bmp', 'jpg', 'pbm', 'png', 'ppm', 'xbm', 'xpm',
143                     'eps', 'ps', 'pdf', 'tex' ]: 
144            image.save('WireStent.'+ext)
145
146# End
As all pyFormex scripts, it starts with a comments line holding the word
pyformex (line 1). This is followed more comments lines specifying the
copyright and license notices. If you intend to distribute your scripts, you should give these certainly special consideration.
Next is a documentation string explaining
the purpose of the script (lines 25-30).
The script then starts by importing all definitions from
other modules required to run the WireStent.py
script (line 32).
Subsequently, the class DoubleHelixStent is defined which
allows the simple use of the geometrical model in other scripts for e.g.
parametric, optimization and finite element analyses of braided wire stents.
Consequently, the latter scripts do not have to contain the wire stent geometry
building and can be condensed and conveniently arranged. The definition of the
class starts with a documentation string, explaining
its aim and functioning (lines 34-60).
The constructor __init__ of the DoubleHelixStent class requires 8
arguments (line 61):
- stent external diameter  (mm). (mm).
- stent length  (mm). (mm).
- wire diameter  (mm). (mm).
- Number of wires in one spiral set, i.e. wires with the same orientation,  (-). (-).
- Pitch angle  ( ( ). ).
- Extra radial distance between the crossing wires  (mm). By default, (mm). By default, is [0.0]mm for crossing wires, corresponding with a centre line
distance between two crossing wires of exactly is [0.0]mm for crossing wires, corresponding with a centre line
distance between two crossing wires of exactly . .
- Number of elements in a strut, i.e. part of a wire between two crossings,  (-). As every base element is a straight line, multiple elements are
required to approximate the curvature of the stent wires. The default value of 4
elements in a strut is a good assumption. (-). As every base element is a straight line, multiple elements are
required to approximate the curvature of the stent wires. The default value of 4
elements in a strut is a good assumption.
- If - connectors=True, extra elements are created at the positions where there is physical contact between the crossing wires. These elements are required to enable contact between these wires in finite element analyses.
The virtual construction of the wire stent structure is defined by the following sequence of four operations: (i) Creation of a nearly planar base module of two crossing wires; (ii) Extending the base module with a mirrored and translated copy; (iii) Replicating the extended base module in both directions of the base plane; and (iv) Rolling the nearly planar grid into the cylindrical stent structure, which is easily parametric adaptable.
Creating the base module¶
(lines 63-71)
Depending on the specified arguments in the constructor, the mean stent diameter
 , the average stent radius
, the average stent radius  , the
, the bump or curvature of the
wires  , the pitch
, the pitch  and the number of base modules in the
axial direction
 and the number of base modules in the
axial direction  are calculated with the following script. As the wire
stent structure is obtained by braiding, the wires have an undulating course and
the
 are calculated with the following script. As the wire
stent structure is obtained by braiding, the wires have an undulating course and
the bump dz corresponds to the amplitude of the wave. If no extra distance
 is specified, there will be exactly one wire diameter between the
centre lines of the crossing wires. The number of modules in the axial direction
 is specified, there will be exactly one wire diameter between the
centre lines of the crossing wires. The number of modules in the axial direction
 is an integer, therefore, the actual length of the stent model might
differ slightly from the specified, desired length
 is an integer, therefore, the actual length of the stent model might
differ slightly from the specified, desired length  . However, this
difference has a negligible impact on the numerical results.
. However, this
difference has a negligible impact on the numerical results.
Of now, all parameters to describe the stent geometry are specified and
available to start the construction of the wire stent. Initially a simple Formex
is created using the pattern()-function: a straigth line segment of length
1 oriented along the X-axis (East or  -direction). The
-direction). The replic()-functionality replicates this line segment  times with step 1 in the
X-direction (
 times with step 1 in the
X-direction ( -direction). Subsequently, these
-direction). Subsequently, these  line
segments form a new Formex which is given a one-dimensional
 line
segments form a new Formex which is given a one-dimensional bump with the
bump1()-function. The Formex undergoes a deformation in the Z-direction
( -direction), forced by the point
-direction), forced by the point [0,0,dz]. The bump
intensity is specified by the quadratic bump_z function and varies along the
X-axis ( -axis). The creation of this single bumped strut, oriented
along the X-axis is summarized in the next script and depicted in figures
A straight line segment, The line segment with replications and A bumped line segment,.
-axis). The creation of this single bumped strut, oriented
along the X-axis is summarized in the next script and depicted in figures
A straight line segment, The line segment with replications and A bumped line segment,.
 
A straight line segment¶
 
The line segment with replications¶
 
A bumped line segment¶
The single bumped strut (base) is rescaled homothetically in the XY-plane to
size one with the scale()-function. Subsequently, the shear()-functionality generates a new NE Formex by skewing the base Formex in
the Y-direction ( -direction) with a
-direction) with a skew factor of  in
the YX-plane. As a result, the Y-coordinates of the
 in
the YX-plane. As a result, the Y-coordinates of the base Formex are altered
according to the following rule:  . Similarly a
. Similarly a
SE Formex is generated by a shear() operation on a mirrored copy of the
base Formex. The base copy, mirrored in the direction of the XY-plane
(perpendicular to the  -axis), is obtained by the
-axis), is obtained by the reflect()
command. Both Formices are given a different property number by the
setProp()-function, visualised by the different color codes in Figure
Unit cell of crossing wires and connectors This number can be used as an entry in a database, which holds some
sort of property. The Formex and the database are two seperate entities, only
linked by the property numbers. The rosette()-function creates a unit cell
of crossing struts by  rotational replications with an angular step of
[180]:math:deg around the Z-axis (the original Formex is the first of the
 rotational replications with an angular step of
[180]:math:deg around the Z-axis (the original Formex is the first of the
 replicas). If specified in the constructor, an additional Formex with
property
 replicas). If specified in the constructor, an additional Formex with
property  connects the first points of the
 connects the first points of the NE and SE Formices.
(lines 72-83)
 
Rescaled bumped strut¶
 
Mirrored and skewed bumped strut¶
 
Unit cell of crossing wires and connectors¶
Extending the base module¶
Subsequently, a mirrored copy of the base cell is generated (Figure
Mirrored unit cell). Both Formices are
translated to their appropriate side by side position with the translate()-option and form the complete extended base module with 4 by 4 dimensions as
depicted in Figure Completed base module.
Furthermore, both Formices are defined as an
attribute of the DoubleHelixStent class by the self-statement,
allowing their use after every DoubleHelixStent initialisation. Such further
use is impossible with local variables, such as for example the NE and
SE Formices.
(lines 84-89)
 
Mirrored unit cell¶
 
Completed base module¶
Full nearly planar pattern¶
The fully nearly planar pattern is obtained by copying the base module in two
directions and shown in Figure Full planar topology. replic2() generates this
pattern with  and
 and  replications with steps
 replications with steps  and
 and
 in respectively, the default X- and Y-direction.
 in respectively, the default X- and Y-direction.
(lines 90-93)
 
Full planar topology¶
 
Orthogonal view of the full planar topology¶
Cylindrical stent structure¶
Finally the full pattern is translated over the stent radius  in
Z-direction and transformed to the cylindrical stent structure by a coordinate
transformation with the Z-coordinates as distance
 in
Z-direction and transformed to the cylindrical stent structure by a coordinate
transformation with the Z-coordinates as distance  , the X-coordinates
as angle
, the X-coordinates
as angle  and the Y-coordinates as height
 and the Y-coordinates as height  . The
. The
scale()-operator rescales the stent structure to the correct circumference
and length. The resulting stent geometry is depicted in Figure Cylindrical stent.
(lines 94-96)
In addition to the stent initialization, the DoubleHelixStent class script
contains a function all() representing the complete stent Formex.
Consequently, the DoubleHelixStent class has four attributes: the Formices
cell1, cell2 and all; and the number  .
(lines 97-100)
.
(lines 97-100)
 
Cylindrical stent¶
 
Orthogonal view of the cylindrical stent¶
Parametric stent geometry¶
An inherent feature of script-based modeling is the possibility of easily
generating lots of variations on the original geometry. This is a huge advantage
for parametric analyses and illustrated in figures
Stent variant with De=16, nx=6, \beta=25: these wire
stents are all created with the same script, but with other values of the
parameters  ,
,  and
 and  . As the script for building
the wire stent geometry is defined as a the
. As the script for building
the wire stent geometry is defined as a the DoubleHelixStent class in the
(WireStent.py) script, it can easily be imported for e.g. this purpose.
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
 
Stent variant with  ¶
¶
#   *** pyformex ***
##
##  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/.
##
from examples.WireStent import DoubleHelixStent
for De in [16.,32.]:
    for nx in [6,10]:
        for beta in [25,50]:
            stent = DoubleHelixStent(De,40.,0.22,nx,beta).all()
            draw(stent,view='iso')
            pause()
            clear()
            
Obviously, generating such parametric wire stent geometries with classical CAD methodologies is feasible, though probably (very) time consuming. However, as provides a multitude of features (such as parametric modeling, finite element pre- and postprocessing, optimization strategies, etcetera) in one single consistent environment, it appears to be the obvious way to go when studying the mechanical behavior of braided wire stents.
Operating on surface meshes¶
Besides being used for creating geometries, also offers interesting possibilities for executing specialized operations on surface meshes, usually STL type triangulated meshes originating from medical scan (CT) images. Some of the algorithms developed were included in .
Unroll stent¶
A stent is a medical device used to reopen narrowed arteries. The vast majority of stents are balloon-expandable, which means that the metal structure is deployed by inflating a balloon, located inside the stent. Figure Triangulated mesh of a Cypher® stent shows an example of such a stent prior to expansion (balloon not shown). The 3D surface is obtained by micro CT and consists of triangles.
 
Triangulated mesh of a Cypher® stent¶
The structure of such a device can be quite complex and difficult to analyse. The same functions offers for creating geometries can also be employed to investigate triangulated meshes. A simple unroll operation of the stent gives a much better overview of the complete geometrical structure and allows easier analysis (see figure Result of the unroll operation).
F = F.toCylindrical().scale([1.,2*radius*pi/360,1.])
 
Result of the unroll operation¶
The unrolled geometry can then be used for further investigations. An important
property of such a stent is the circumference of a single stent cell. The
clip() method can be used to isolate a single stent cell. In order to obtain
a line describing the stent cell, the function intersectionLinesWithPlane()
has been used. The result can be seen in figures Part of the intersection with a plane.
 
Part of the intersection with a plane¶
Finally, one connected circumference of a stent cell is selected
(figure Circumference of a stent cell)
and the length() function returns its length, which
is 9.19 mm.
 
Circumference of a stent cell¶
 
  