#
##
## SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
## SPDX-License-Identifier: GPL-3.0-or-later
##
## This file is part of pyFormex 3.5 (Thu Feb 8 19:11:13 CET 2024)
## 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/.
##
"""Vascular Bifurcation Sweeping Mesher
"""
import numpy as np
import pyformex as pf
from pyformex import olist
from pyformex.plugins import draw2d as d2
from pyformex.gui.widgets import _I
from pyformex.gui.menus import PluginMenu
# keys for global data
_name = pf.Path(__file__).stem
_slice_data_name = _name + '_slice_data'
_draw_options_name = _name + '_draw_options'
_surface = None
##################### Mesher Functions ####################################
[docs]def structuredQuadMeshGrid(sgx=3, sgy=3, isopquad=None):
"""Returns nodes (2D) and elems of a structured quadrilateral grid.
nodes and elements are both ordered first vertically (y) and then
horizontally (x). This function is the equivalent of
simple.rectangularGrid but on the mesh level.
"""
sg = pf.simple.regularGrid([0., 0.], [1., 1.], [sgx, sgy], swapaxes=True)
sgc = sgy+1
esg = np.array([np.array([[i, i+sgc, i+1+sgc, i+1] for i in range(sgc-1)])
+ sgc*j for j in range(sgx)])
if isopquad == 'quad16':
# control points for the hex64 applied to a basic struct hex grid
return (sg.reshape(-1, 2), esg.reshape(-1, 4),
pf.simple.regularGrid([0., 0.], [1., 1.], [3, 3],
swapaxes=True).reshape(-1, 2))
else:
return sg.reshape(-1, 2), esg.reshape(-1, 4)
[docs]def structuredHexMeshGrid(dx, dy, dz, isophex='hex64'):
"""Build a structured hexahedral grid
with nodes and elements both numbered in a structured way:
first along z, then along y,and then along x.
The resulting hex cells are oriented along z.
This function is the equivalent of simple.rectangularGrid but for a mesh.
Additionally, dx,dy,dz can be either integers or div (1D list or array).
In case of list/array, first and last numbers should be 0.0 and 1.0 if
the desired grid has to be inside the region 0.,0.,0. to 1.,1.,1.
TODO: include other optons to get the control points for other
isoparametric transformation for hex.
"""
sgx, sgy, sgz = dx, dy, dz
if not isinstance(dx, int):
sgx = len(dx)-1
if not isinstance(dy, int):
sgy = len(dy)-1
if not isinstance(dz, int):
sgz = len(dz)-1
n3 = pf.simple.regularGrid([0., 0., 0.], [1., 1., 1.], [sgx, sgy, sgz],
swapaxes=True)
if not isinstance(dx, int):
n3[..., 0] = np.array(dx).reshape(-1, 1, 1)
if not isinstance(dy, int):
n3[..., 1] = np.array(dy).reshape(-1, 1)
if not isinstance(dz, int):
n3[..., 2] = np.array(dz).reshape(-1)
nyz = (sgy+1)*(sgz+1)
xh0 = np.array([0, nyz, nyz+sgz+1, 0+sgz+1])
xh0 = np.concatenate([xh0, xh0+1]) # first cell
hz = np.array([xh0+j for j in range(sgz)]) # z column
hzy = np.array([hz+(sgz+1)*j for j in range(sgy)]) # zy 2D rectangle
hzyx = np.array([hzy+nyz*k for k in range(sgx)]).reshape(-1, 8) # zyx 3D
if isophex == 'hex64':
# control points for the hex64 applied to a basic struct hex grid
return (pf.Coords(n3.reshape(-1, 3)),
hzyx.reshape(-1, 8),
pf.simple.regularGrid([0., 0., 0.], [1., 1., 1.], [3, 3, 3],
swapaxes=True).reshape(-1, 3))
else:
return pf.Coords(n3.reshape(-1, 3)), hzyx.reshape(-1, 8)
[docs]def findBisectrixUsingPlanes(cpx, centx):
"""returns a bisectrix-points at each point of a Polygon
(unit vector of the bisectrix). All the bisectrix-points are on the
side of centx (inside the Polygon), regardless to the concavity or
convexity of the angle, thus avoiding the problem of collinear or
concave segments. The points will point toward the centx if the centx
is offplane. It uses the lines from intersection of 2 planes.
"""
cx = np.concatenate([cpx, [cpx[0]]], axis=0)
cx = np.concatenate([cx[:-1], cx[1:]], axis=-1).reshape(-1, 2, 3)
# pf.draw(Formex(cx))
# pf.drawNumbers(Formex(cx))
# pf.drawNumbers(Formex(cx[0]))
if centx is None:
centx = cx.reshape(-1, 3).mean(axis=0)
# pf.draw(Formex(centx))
nx0 = []
for i in range(cx.shape[0]):
v0 = cx[i, 1]-cx[i, 0]
z0 = np.cross(v0, centx-cx[i, 0])
nx0.append(pf.at.normalize(np.cross(z0, v0)))
c1 = cx[:, ::-1]
nx1 = []
for i in range(c1.shape[0]):
v0 = c1[i, 1]-c1[i, 0]
z0 = np.cross(v0, centx-c1[i, 0])
nx1.append(pf.at.normalize(np.cross(z0, v0)))
nx1 = np.roll(nx1, 1, 0)
nx = 0.5*(nx0+nx1)
nx = pf.at.normalize(nx) # new added
# [pf.draw(Formex([[cpx[i], cpx[i]+nx[i]]])) for i in range(cpx[:].shape[0])]
return nx
[docs]def cpBoundaryLayer(BS, centr, issection0=False, bl_rel=0.2):
"""it takes n points of a nearly circular section
(for the isop transformation, n should be 24, 48 etc)
and find the control points needed for the boundary layer.
The center of the section has to be given separately.
-issection0 needs to be True only for the section-0 of each branch
of a bifurcation, which has to share the control points with the other
branches. So it must be False for all other sections and single vessels.
This implementation for the bl (separated from the inner lumen)
is needed to ensure an optimal mesh quality at the boundary layer
in terms of angular skewness, needed for WSS calculation."""
if BS.shape[0] % 2:
raise ValueError("BE CAREFUL: the number of points along each circular "
"section need to be even to split a vessel in 2 halves "
"with the same connectivity!")
bllength = np.length(BS-centr).reshape(-1, 1)*bl_rel
# unit vectors similar to bisectrix but obtained as intersection of planes
blvecn = findBisectrixUsingPlanes(BS, centr)
# pf.draw(Formex(centr))
# print bisbs
if issection0:
# inside the bifurcation center the 3 half sections need to touch
# each others on 1 single line. Thus, because the control points
# on this line (the bifurcation axis) needs to be the same, these
# 2 special points are defined differently, but only at the first
# section of each branches.
blvecn[0] = pf.at.normalize(centr-BS[0])
midp = BS.shape[0] // 2 # 12 if 24 control points, 24 with 48 cp.
blvecn[midp] = pf.at.normalize(centr-BS[midp])
blvec = blvecn*bllength
cpblayer = np.array([BS+blvec*i for i in [1., 2/3., 1/3., 0.]])
cpblayer = np.swapaxes(cpblayer, 0, 1)
# pf.draw(Formex(cpblayer[:].reshape(-1, 3)))
# drawNumbers(Formex(cpblayer[0]))
r4 = np.concatenate([i*3+np.arange(4) for i in np.arange(cpblayer.shape[0]//3)])
r4[-1] = 0
r4 = r4.reshape(-1, 4)
# control points of the boundaary layer,
# points on the border with the inner part of the lumen
return cpblayer[r4].reshape(-1, 16, 3), cpblayer[:, 0]
[docs]def cpQuarterLumen(lumb, centp, edgesq=0.75, diag=0.6*2**0.5, verbos=False):
"""control points for 1 quarter of lumen mapped in quad regions.
lumb is a set of points on a quarter of section.
centp is the center of the section.
The number of poin I found that edgesq=0.75, diag=0.6*2**0.5 give the
better mapping. Also possible edgesq=0.4, diag=0.42*2**0.5.
Currently, it is not perfect if the section is not planar.
"""
arcp = lumb.copy()
arcsh = arcp.shape[0]-1
xcp1, xcp3 = centp+(arcp[[arcsh, 0]]-centp)*edgesq
xcp2 = centp+(arcp[arcsh//2]-centp)*diag
nc0 = np.array([centp, xcp1, xcp2, xcp3]) # new coord0
grid16 = pf.simple.regularGrid([0., 0., 0], [1., 1., 0.], [3, 3, 0],
swapaxes=True).reshape(-1, 3) # grid
ncold = grid16[[0, 12, 15, 3]] # old coord
fx = arcsh//6
sc = np.array([1./fx, 1./fx, 0.])
grid16 = pf.Formex([grid16]).replicm((fx, fx)).scale(sc)[:]
# TODO: use Coords.isopar
from pyformex.plugins.isopar import Isopar
gridint = Isopar('quad4', nc0, ncold).transform(grid16) # 4 internal grids
xa0 = pf.Coords.interpolate(
pf.Coords(ncold[[3]]), pf.Coords(ncold[[2]]), div=3*fx)
xa1 = pf.Coords.interpolate(
pf.Coords(ncold[[2]]), pf.Coords(ncold[[1]]), div=3*fx)
xa = np.concatenate([xa0, xa1[1:]], axis=0)
xa = Isopar('quad4', nc0, ncold).transform(xa).reshape(-1, 3)
xar3 = np.concatenate([i*3+np.arange(4) for i in np.arange(2*fx)])
gridext = np.array(list(zip(xa[xar3], arcp[xar3])))
gridext = pf.Coords.interpolate(
pf.Coords(gridext[:, 0]), pf.Coords(gridext[:, 1]), div=3)
gridext = np.swapaxes(gridext, 0, 1).reshape(2*fx, 16, 3)
if verbos:
print(f"---one Quarter of section is submapped in {gridint.shape[0]} "
f"internal and {gridext.shape[0]} transitional quad regions---")
# gridG=concatenate([gridint, gridext], axis=0)
# print '---one Quarter of section is submapped in %d quad regions---'%gridG.shape[0]
# [pf.draw(Formex(fo).setProp(i)) for i, fo in enumerate(gridG) ]
# for i in gridG:
# di= [drawNumbers(Formex(i))]
# pf.zoomAll()
# pf.undraw(di)
# exit()
return gridint, gridext
[docs]def visualizeSubmappingQuadRegion(sqr, timewait=None):
"""visualilze the control points (-1,16,3) in each submapped region
and check the quality of the region (which will be inherited by the
mesh crossectionally)
"""
sqr3 = pf.simple.regularGrid([0., 0., 0.], [1., 1., 0.], [3, 3, 0],
swapaxes=True).reshape(-1, 3) # base old coords
sqrn3, sqre3 = structuredQuadMeshGrid(3, 3) # quad mesh to map
for i, f in enumerate(sqr):
sqr0 = pf.Formex(sqrn3).isopar('quad16', f, sqr3)[:].reshape(-1, 3)
sqr0 = pf.Formex(sqr0[sqre3])
pf.draw(pf.Formex(f).setProp(i+1))
pf.draw(sqr0.setProp(i+1))
di = [pf.drawNumbers(pf.Formex(f))]
pf.zoomAll()
# check quality of G0: if not good enough, change the parameters of
# cpQuarterLumen(quartsec[0], oc, edgesq= ..., diag= ...)
if timewait:
pf.sleep(timewait)
pf.undraw(di)
[docs]def cpOneSection(hc, oc=None, isBranchingSection=False, verbos=False):
"""What is this
hc is a numbers of points on the boundary line of 1 almost circular section.
oc is the center point of the section.
It returns 3 groups of control points: for the inner part,
for the transitional part and for the boundary layer of one single section
"""
# if the center is not given, it is calculated from the first
# and the half points of the section
if oc is None:
oc = (hc[0]+hc[hc.shape[0]//2])*0.5
# create control points for the boundary layer of 1 full section.
if verbos:
if isBranchingSection:
print("--BRANCHING SECTION:section located at the center "
"of the bifurcation")
cpbl, hlum = cpBoundaryLayer(hc, centr=oc, issection0=isBranchingSection)
# split the inner lumen in quarters and check if the isop can be applied
if hlum.shape[0] % 24 != 0:
raise ValueError("The number of points along each circular section "
"should be a multiple of 24 in order to allow mapping!")
npq = hlum.shape[0] // 4
hlum1 = np.concatenate([hlum, [hlum[0]]], axis=0)
quartsec = [hlum1[npq*i:npq*(i+1)+1] for i in range(4)] # split in quarters
# created control points of each quarter
cpis, cpts = [], []
for q in quartsec:
i = cpQuarterLumen(q, oc)
cpis.append(i[0]) # control points of the inner part of a section
cpts.append(i[1]) # control points of the transitional part of a section
# visualizeSubmappingQuadRegion(cpss, timewait=None)#
return (np.array(cpis).reshape(-1, 16, 3),
np.array(cpts).reshape(-1, 16, 3),
np.array(cpbl))
[docs]def cpAllSections(HC, OC, start_end_branching=[False, False]):
"""control points of all sections
divided in 3 groups of control points: for the inner part,
for the transitional part and for the boundary layer.
if start_end_branching is [True,True] the first and the last section
are considered bifurcation sections and therefore meshed differently.
"""
isBranching = np.zeros([HC.shape[0]], dtype=bool)
isBranching[[0, -1]] = start_end_branching # first section is branching
# print isBranching
cpain, cpatr, cpabl = [], [], []
for hc, oc, isBr in zip(HC, OC, isBranching):
i = cpOneSection(hc, oc, isBranchingSection=isBr)
cpain.append(i[0]), cpatr.append(i[1]), cpabl.append(i[2])
cpain, cpatr, cpabl = [np.array(i) for i in [cpain, cpatr, cpabl]]
print(f"# sections = {cpain.shape[0]}, # inner quad reg = {cpain.shape[1]},"
f" # trans quad reg = {cpatr.shape[1]}, # boundary-layer quad reg = "
f"{cpabl.shape[1]}")
if start_end_branching == [True, True]:
print('--this vessel BIFURCATES both at FIRST AND LAST section')
if start_end_branching == [True, False]:
print('--this vessel BIFURCATES at FIRST section')
if start_end_branching == [False, True]:
print('--this vessel BIFURCATES at LAST section')
if start_end_branching == [False, False]:
print('--this vessel DOES NOT BIFURCATE')
# [visualizeSubmappingQuadRegion(i) for i in cpain]
# [visualizeSubmappingQuadRegion(i) for i in cpatr]
# [visualizeSubmappingQuadRegion(i) for i in cpabl]
return cpain, cpatr, cpabl
# FIRST STEP ----- FROM SPLINE-PTS to CONTROL-POINTS-QUAD16---------------
# cpAin, cpAtr, cpAbl=cpAllSections(HC, OC, [False, False])
# control points of all sections grouped in inner, trans and boundary layer.
# Each contains number of long_slice, number of hex-reg, 16, 3.
# [visualizeSubmappingQuadRegion(i) for i in cpAin]
# pause()
[docs]def cpStackQ16toH64(cpq16):
"""sweeping trick: from sweeping sections longitudinally to mapping hex64:
ittakes -1,16,3 (cp of the quad16) and groups them in -1,64,3
(cp of the hex63) but slice after slice: [0,1,2,3],[1,2,3,4],[2,3,4,5],...
It is a trick to use the hex64 for sweeping along an arbitrary number
of sections.
"""
cpqindex = np.concatenate([i+np.arange(4) for i in np.arange(
cpq16.shape[0]-3)]).reshape(-1, 4)
cpq16t = np.swapaxes(cpq16[cpqindex], 1, 2)
shcp = cpq16t.shape
return cpq16t.reshape([shcp[0], shcp[1], 64, 3])
# SECOND STEP ----- FROM CONTROL-POINTS-QUAD16 to CONTROL-POINTS-HEX64---------
# hex_cp=[cpStackQ16toH64(i) for i in [cpAin, cpAtr, cpAbl] ]
# control points for hex64 divided in 3 groups: central, transition,
# and boundary layer. The stacking uses the TRICK for sweeping.
#
# THIRD STEP ----- specifing mesh_blocks parameters and build 3 mesh blocks
# ncirc=3
# nlong=2
# ntr=2#int or div
# nbl=[0., 0.4, 0.7, 0.8, 0.9, 1.]#int or div
# in_block=structuredHexMeshGrid(nlong, ncirc,ncirc, isophex='hex64')
# tr_block=structuredHexMeshGrid(nlong, ncirc,ntr, isophex='hex64')
# bl_block=structuredHexMeshGrid(nlong, ncirc,nbl, isophex='hex64')
[docs]def mapHexLong(mesh_block, cpvr):
"""map a structured mesh (n_block, e_block, cp_block are in mesh_block)
into a volume defined by the control points cpvr (# regions longitudinally,
# regions in 1 cross sectionsm, 64, 3 ).
cp_block are the control points of the mesh block.
It returns nodes and elements.
Nodes are repeated in subsequently mapped regions !
TRICK: in order to make the mapping working for an arbitrary number of
sections the following trick is used: of the whole mesh_block,
only the part located between the points 1--2 is meshed and mapped
between 2 slices only. Thus, the other parts 0--1 and 2--3 are not
mapped. To do so, the first and the last slice need to be meshed
separately: n_start 0--1 and n_end 2--3.
"""
n_block, e_block, cp_block = mesh_block
n_start, n_body, n_end = (
n_block.scale([1./3., 1., 1.]).translate([i/3., 0., 0.])
for i in range(3))
cp_start, cp_body, cp_end = cpvr[0], cpvr.reshape(-1, 64, 3), cpvr[-1]
n = [[pf.Coords(n_tract).isopar('hex64', cpi, cp_block) for cpi in cp_tract]
for n_tract, cp_tract in zip([n_start, n_body, n_end],
[cp_start, cp_body, cp_end])]
n = np.concatenate(n, axis=0)
return n, e_block
[docs]def mapQuadLong(mesh_block, cpvr):
"""
TRICK: as above
"""
n_block, e_block, cp_block = mesh_block
n_start, n_body, n_end = (
n_block.scale([1./3., 1., 1.]).translate([i/3., 0., 0.])
for i in range(3))
cp_body = cpvr.reshape(-1, 16, 3)
cp_start = cp_body[:8]
cp_end = cp_body[-8:]
n = [[pf.Coords(n_tract).isopar('quad16', cpi, cp_block)
for cpi in cp_tract]
for n_tract, cp_tract in zip([n_start, n_body, n_end],
[cp_start, cp_body, cp_end])]
n = np.concatenate(n, axis=0)
return n, e_block
##################### Functions ####################################
[docs]def getData(*args):
"""Return global data"""
try:
res = [pf.PF[a] for a in args]
if len(res) == 1:
res = res[0]
return res
except Exception:
print(args)
pf.error("I could not find all the data")
raise
[docs]def getDataItem(data, item):
"""Return an item from a dict"""
items = getData(data)
return items.get(item, None)
[docs]def drawOption(item):
"""Return draw option"""
return _draw_options[item]
[docs]def sliceData(item):
"""Return slice data"""
return _slice_data[item]
def vecbisectrix(p0, p1, cx):
return pf.at.normalize(pf.at.normalize(p0-cx) + pf.at.normalize(p1-cx))
[docs]def longestItem(geomlist):
"""Return the geometric part with the most elements."""
return geomlist[np.argmax([m.nelems() for m in geomlist])]
[docs]def largestSubMesh(M):
"""Return the largest part with single prop value."""
p = M.propSet()
n = np.array([(M.prop == i).sum() for i in p])
i = n.argmax()
return M.selectProp(p[i])
[docs]def selectPart(M, x0, x1):
"""
Select part of section between x0 and x1
section is a list of Mesh objects.
All meshes are clipped to the parts between planes through x0 and x1
perpendicular to the line x0-x1.
The mesh with the largest remaining part is returned.
"""
# meshlist = [ m.clipAtPlane(x1,x0-x1).clipAtPlane(x0,x1-x0) for m in meshlist ]
# return meshToPolyLine(longestItem(meshlist))
M = M.clipAtPlane(x1, x0-x1, nodes='all').clipAtPlane(x0, x1-x0, nodes='all')
if M.prop is not None:
M = largestSubMesh(M)
return meshToPolyLine2(M)
[docs]def cutPart(M, x0, x1):
"""Cut part of section at plane thru x0 and return part between x0 and x1"""
M = M.clipAtPlane(x1, x0-x1, nodes='all')
meshlist = M.splitProp()
meshlist = [meshToPolyLine2(m) for m in meshlist]
# meshlist = [ p.cutWithPlane(x0,x1-x0,side='+') for p in meshlist ]
pf.draw(meshlist, flat=True, alpha=1, linewidth=3)
# print erf
meshlist = olist.flatten(meshlist)
if len(meshlist) == 1:
pl = meshlist[0]
elif len(meshlist) == 2:
p1, p0 = meshlist
pl = pf.PolyLine(pf.Coords.concatenate([p0.coords, p1.coords]))
else:
print([p.nelems() for p in meshlist])
for p, c in zip(meshlist, pf.canvas.settings.colormap):
pf.draw(p, color=c)
pl = longestItem(meshlist)
return pl
[docs]def getPart(section, x0, x1, cutat=-1):
"""Return a cut or a selected part, depending on cutat parameter."""
if cutat == 0:
# cut at plane x0
return cutPart(section, x0, x1)
elif cutat == 1:
# cut at plane x1
return cutPart(section, x1, x0)
else:
# select parts between x0 and x1 only
return selectPart(section, x0, x1)
[docs]def connectPolyLines(plist):
"""Connect a set of PolyLines into a single one.
The PolyLines are connected in order of the distance between the
endpoints.
"""
pass
[docs]def meshList(coords, elems):
"""Create a list of Meshes from intersection lines"""
return [pf.Mesh(coords, e) for e in elems]
[docs]def meshToPolyLine1(m):
"""Convert a 2-plex mesh with ordered segments to a PolyLine."""
pts = m.elems[:, 0]
if m.elems[-1, -1] != m.elems[0, 0]:
pts = np.concatenate([pts, m.elems[-1:, -1]])
return pf.PolyLine(m.coords[pts])
[docs]def meshToPolyLine2(m2):
"""Convert a 2-plex mesh with ordered segments to a PolyLine."""
# order line elements from boundary points'
# Remove degenerate and doubles
m2 = m2.fuse().compact()
# m2 = pf.Mesh(m2.coords,m2.elems.removeDegenerate().removeDoubles())
m2 = pf.Mesh(m2.coords, m2.elems.removeDegenerate().removeDuplicate())
# Split in connected loops
parts = m2.elems.chained()
prop = np.concatenate([[i]*p.nelems() for i, p in enumerate(parts)])
elems = np.concatenate(parts, axis=0)
m2 = pf.Mesh(m2.coords, elems, prop=prop)
pts = m2.elems[:, 0]
if m2.elems[-1, -1] != m2.elems[0, 0]:
pts = np.concatenate([pts, m2.elems[-1:, -1]])
return pf.PolyLine(m2.coords[pts], closed=False)
else:
return pf.PolyLine(m2.coords[pts], closed=True)
[docs]def slicer(S, s0, s1, cutat=-1, visual=False):
"""Cut a surface with a series of planes.
- S: surface
- s0,s1: Coords arrays with the same number of points
Each pair of corresponding points of s0 and s1 defines a plane parallel
to the z-axis
"""
S = pf.TriSurface(S)
if not isinstance(s1, pf.Coords):
s0 = s0.coords
if not isinstance(s1, pf.Coords):
s1 = s1.coords
v1 = s1-s0 # direction of the knife
zdir = pf.at.unitVector(2) # direction of the z axis
ncut = np.cross(zdir, v1) # normals on the cut planes
sections = [S.intersectionWithPlane(p0, n0) for p0, n0 in zip(s0, ncut)]
if visual:
pf.clear()
pf.draw(sections, color='magenta')
sections = [getPart(s, x0, x1, cutat=cutat)
for s, x0, x1 in zip(sections, s0, s1)]
if visual:
pf.clear()
pf.draw(sections, color='cyan')
# Orient all PolyLines so that first points is at max. z-value
for i, s in enumerate(sections):
if s.coords[0, 2] < s.coords[-1, 2]:
# print "Reversing PL %s" % i
sections[i] = s.reverse()
# [draw([s.coords[-1] for s in sections], marksize=1) ]
return sections
[docs]def sliceBranch(S, cp, s0, s1, cl, nslices):
"""Slice a single branch of the bifurcation
- `S`: the bifurcation surface, oriented parallel to xy.
- `cp` : the center of the bifurcation.
- `s0`, `s1`: the control polylines along the branch.
- `cl`: the centerline of the branch.
- `nslices`: the number of slices used to approximate the branch
surface.
"""
visual = drawOption('visual')
cl = cl.approx(nseg=nslices)
s0 = s0.approx(nseg=nslices)
s1 = s1.approx(nseg=nslices)
h0 = slicer(S, s0, cl, cutat=-1, visual=visual)
if visual:
pf.clear()
pf.draw(h0, color='black')
h1 = slicer(S, cl, s1, cutat=-1, visual=visual)
# if visual:
# pf.draw(h0,color='red')
# pf.draw(h1,color='blue')
return [h0, h1]
[docs]def sliceIt():
"""Slice a surface using the provided slicing data
"""
S, cp = getData('surface', 'central_point')
try:
hl = pf.PF['control_lines']
cl = pf.PF['center_lines']
except Exception:
centerlines()
hl = pf.PF['control_lines']
cl = pf.PF['center_lines']
nslice = sliceData('nslice')
with pf.busyCursor():
h = [sliceBranch(S, cp, hl[2*i], hl[2*i+1], cl[i], nslice[i])
for i in range(3)]
# corrections of some points of the cross-PolyLines because sometimes,
# due to the cutting, different pts appear at the connections between
# semi-branches (0,1) and branches (at center-splines top and bottom)
# merge the points between half branches
for ibr in [0, 1, 2]:
# point first and last of each half cross section
pfl0 = np.array([hi.coords[[0, -1]] for hi in h[ibr][0]])
pfl1 = np.array([hi.coords[[0, -1]] for hi in h[ibr][1]])
temp = (pfl0 + pfl1)*0.5
for i in range(len(temp)):
h[ibr][0][i].coords[[0, -1]] = h[ibr][1][i].coords[[0, -1]] = temp[i]
# mergethe 3 points on the top and the 3 points on the bottom
# (at the bif center)
t0 = np.array([h[ibr][sbr][0].coords[0] for ibr in [0, 1, 2]
for sbr in [0, 1]]).mean(axis=0)
t1 = np.array([h[ibr][sbr][0].coords[-1] for ibr in [0, 1, 2]
for sbr in [0, 1]]).mean(axis=0)
for ibr in [0, 1, 2]:
for sbr in [0, 1]:
h[ibr][sbr][0].coords[0] = t0
h[ibr][sbr][0].coords[-1] = t1
pf.PF['cross_sections'] = olist.flatten(h)
# to recover from overwriting when creating the outer cross sections
pf.PF['cross_sections_backup'] = olist.flatten(h)
pf.clear()
drawCenterLines()
drawCrossSections()
def splineIt():
# print 'how many splines circumferentially? with tangency between branches ?'
# res = askItems([
# ['niso', 12],
# ['with_tangence', True]
# ])
#
# niso= (res['niso'])
# smoothconnections=res['with_tangence']
niso = 12
smoothconnections = True
nslice = sliceData('nslice')
cs = getData('cross_sections')
# cross splines
spc = [[ci.approx(nseg=niso) for ci in c] for c in cs]
pf.PF['cross_splines'] = spc
pf.clear()
drawCrossSplines()
# axis spline
[hi0, hi1, hi2, hi3, hi4, hi5] = [np.array([h.coords for h in hi])
for hi in spc]
clv = [pf.PolyLine((hi[:, 0]+hi[:, -1])*0.5) for hi in [hi0, hi2, hi4]]
axis = [pf.BezierSpline(c.coords, curl=1./3.).approx(nseg=nsl)
for c, nsl in zip(clv, nslice)]
pf.PF['axis_splines'] = axis
drawAxisSplines()
# longitudinal splines
# 2 options: with or without continuity of the tangent at the connections
# between contiguous splines, except for the center-Top and center-Bottom
if smoothconnections: # with continuity
# 6 splines at the center-Top and center-Bottom
TBspl = [pf.BezierSpline([ci.coords[j] for ci in spc[i]], curl=1./3.)
for j, i in zip([0, niso, 0, niso, 0, niso], range(6))]
# prolonged sections, to give correct tangence
prolspc = [[spc[i][1]]+spc[j]
for i, j in [[5, 0], [2, 1], [1, 2], [4, 3], [3, 4], [0, 5]]]
# all splines between Top and Bottom splines
mspl = [[pf.BezierSpline([ci.coords[j] for ci in prolspc[i]], curl=1./3.)
for j in range(1, niso)] for i in range(6)]
spl = []
for i in range(6): # 6 semi-branches
smspl = [TBspl[i]] # splines of 1 semi-branch
for ispl in mspl[i]:
coordb = np.append(
ispl.coords, [[0., 0., 0.]]*2, axis=0).reshape(-1, 3, 3)[1:]
# factor to move the last control point=amount of tangence
# between contiguous splines
sct = 1.
coordb[0, 1] = (coordb[0, 1]-coordb[0, 0])*sct+coordb[0, 0]
smspl = smspl+[pf.BezierSpline(coords=coordb[:, 0],
control=coordb[:-1, [1, 2]])]
spl.append(smspl)
else: # without continuity
spl = [[pf.BezierSpline([ci.coords[j] for ci in c], curl=1./3.)
for j in range(niso+1)] for c in spc]
pf.PF['long_splines'] = spl
drawLongSplines()
dialog = None
[docs]def seedLongSplines(H, at, curvedSection=True, nPushedSections=6,
napproxlong=40, napproxcut=40):
"""Takes the Longitudinal Splines (6 semi-braches, 12 longitudinal_splines)
and return the control points on them (n_long, n_cross=24)
and on the centerlines (n_long).
"""
# nplong = [at[0].shape[0]-1, at[1].shape[0]-1, at[2].shape[0] -1]
# add 1 splines at each semibranch:
# for each half, the first curve of its other half
# other half: if i even -> i+1; if i uneven -> i-1
B = []
for i in range(6):
j = i-1 if i % 2 else i+1
B.append([spl.pointsAt(spl.atLength(at[i//2]))
for spl in H[i] + [H[j][0]]])
pf.PF['B'] = B
def BezierCurve(X):
"""Create a Bezier curve between 4 points"""
ns = (X.shape[0]-1) // 3
ip = 3*np.arange(ns+1)
P = X[ip]
ip = 3*np.arange(ns)
ic = np.column_stack([ip+1, ip+2]).ravel()
C = X[ic].reshape(-1, 2, 3)
return pf.BezierSpline(P, control=C, closed=False)
def nearestPoints2D(pt0, pt1):
"""P0 and P1 and 2D arryas.
It takes the closest point of 2 arrays of points and finds the 2
closest points. It returns the 2 indices.
"""
if pt0.shape[1] != 2:
raise ValueError("only for 2D arras (no z)")
np = (pt0.reshape(-1, 1, 2)-pt1.reshape(1, -1, 2)) # create a matrix!!!
npl = (np[:, :, 0]**2+np[:, :, 1]**2)**0.5
nearest = np.where(npl == npl.min())
return nearest[0][0], nearest[1][0]
def cutLongSplinesWithCurvedProfile(sideA, sideB, curvA, curvB, npb):
# create 2D cutting curved profiles given 3 points
ssh = sideA.shape[0]
hsh = ssh // 2 # half point
s12 = (sideA[0] + sideB[0])*0.5
cutProfilePts = np.array([sideA[hsh], s12, s12, sideB[hsh]])
# [draw(BezierCurve(cutProfilePts[:, i]), alpha=1, flat=True)
# for i in range(1, sideA[0].shape[0]-1)]
cutProfile2D = np.array([BezierCurve(cutProfilePts[:, i]).subPoints(
npb)[:, :2] for i in range(1, sideA[0].shape[0]-1)])
# cuts long splines with curved profiles
cutcurvA, cutcurvB = [], []
for ind in range(ssh):
hlongA, hlongB = curvA[ind], curvB[ind]
iA, iB = [0], [0]
for il in range(0, cutProfile2D.shape[0]):
iA.append(nearestPoints2D(hlongA[:, :2], cutProfile2D[il])[0])
iB.append(nearestPoints2D(hlongB[:, :2], cutProfile2D[il])[0])
iA, iB = np.append(iA, -1), np.append(iB, -1)
# draw(Formex(hlongA[iA]), marksize=3, flat=True, alpha=1)
# draw(Formex(hlongB[iB]), marksize=3, flat=True, alpha=1.)
cutcurvA.append(hlongA[iA])
cutcurvB.append(hlongB[iB])
return (np.array(cutcurvA).reshape(ssh, -1, 3),
np.array(cutcurvB).reshape(ssh, -1, 3))
if curvedSection:
TB = [cutLongSplinesWithCurvedProfile(B[i], B[i+1], H[i], H[i+1],
napproxcut) for i in [0, 2, 4]]
TB = TB[0]+TB[1]+TB[2]
if nPushedSections > 0:
# this part pushes 'nPushedSections' sections closer
# to the bifurcation center!
cv = np.arange(nPushedSections+2)
mcv = np.array(cv, dtype=float)/cv[-1]
for tbi, bi in zip(TB, B):
for i in cv:
tbi[:, i] = (tbi[:, i]*mcv[i] + bi[:, i]*(1.-mcv[i]))
B = TB
B = [np.swapaxes(b, 0, 1) for b in B]
cent012 = [(b[:, 0]+b[:, -1])*0.5 for b in [B[0], B[2], B[4]]]
lum012 = [np.concatenate([B[i][:, ::-1], B[i+1][:, 1:-1]], axis=1)
for i in [0, 2, 4]]
return list(zip(lum012, cent012))
def seedLongitudinalSplines():
# res= askItems([
# ('curvedSection', True),
# ['nPushedSections', 3],
# ['napprox', 60],
# ])
# curvedsecs=res['curvedSection']
# numpushed=res['nPushedSections']
# splineapprox=res['napprox']
curvedsecs = False # True
numpushed = 3
splineapprox = 60
try:
seeds = pf.PF['branch_seeds']
except Exception:
pf.warning("You need to set the longitudinal seeds first")
return
seededBif = seedLongSplines(
pf.PF['long_splines'], seeds, curvedSection=curvedsecs,
nPushedSections=numpushed, napproxlong=splineapprox,
napproxcut=splineapprox)
pf.PF['seededBif'] = seededBif
return
for i in range(3):
drawSeededBranch(seededBif[i][0], seededBif[i][1], propbranch=i+1)
[docs]def meshBranch(HC, OC, nlong, ncirc, ntr, nbl):
"""Convenient function: from sections and centerlines to parametric volume
mesh and outer surface mesh.
"""
cpAin, cpAtr, cpAbl = cpAllSections(HC, OC, [True, True])
hex_cp = [cpStackQ16toH64(i) for i in [cpAin, cpAtr, cpAbl]]
in_block = structuredHexMeshGrid(nlong, ncirc, ncirc, isophex='hex64')
tr_block = structuredHexMeshGrid(nlong, ncirc, ntr, isophex='hex64')
bl_block = structuredHexMeshGrid(nlong, ncirc, nbl, isophex='hex64')
in_mesh, tr_mesh, bl_mesh = [
mapHexLong(v_block, v_cp) for v_block, v_cp in zip(
[in_block, tr_block, bl_block], hex_cp)]
M = [m[0][:, m[1]].reshape(-1, 8, 3) for m in [in_mesh, tr_mesh, bl_mesh]]
M = [pf.Formex(m).toMesh() for m in M]
# M=[correctHexMeshOrientation(m) for m in M ]
# crate quad mesh on the external surface
nq, eq = structuredQuadMeshGrid(nlong, ncirc)
nq = pf.Coords(np.column_stack([nq, np.ones([len(nq)])]))
gnq, eq = mapHexLong([nq, eq, bl_block[2]], hex_cp[2]) # group of nodes
xsurf = gnq[:, eq].reshape(-1, 4, 3)
return M, xsurf
def getMeshingParameters():
return pf.PF.get('mesh_block_params', {
'n_longit': 2,
'n_circum': 3,
's_radial': '[0.0, 0.6, 1.0]',
's_boundary': '[0.0,0.4, 0.8,1.0]'
})
[docs]def sweepingMesher():
"""Sweeping hexahedral bifurcation mesher
Creates a hexahedral mesh inside the branches of the bifurcation.
Currently only the lumen mesh is included in this GPL3 version.
"""
# Get the domain
res = pf.askItems(caption='Bifmesh domain selection', items=[
_I('domain', 'Lumen', 'radio', choices=['Lumen', ]),
])
if not res:
return
domain = res['domain']
print("Meshing %s" % domain)
# Get the meshing parameters
res = getMeshingParameters()
longp, longc = res['n_longit'], res['n_circum']
longr, longbl = eval(res['s_radial']), eval(res['s_boundary'])
# Create the meshes
with pf.busyCursor():
Vmesh, Smesh = [], []
bifbranches = pf.PF['seededBif']
for branch in bifbranches:
vmesh, smesh = meshBranch(branch[0], branch[1], longp, longc,
longr, longbl)
Vmesh.extend(vmesh)
Smesh.extend(smesh)
if domain == 'Lumen':
## m=[]
## [ m.extend(i) for i in Vmesh ]
## n, e=mergeMeshes(m)
M = pf.FEModel(Vmesh)
pf.PF['CFD_lumen_model'] = M
pf.PF['inner_surface_mesh'] = Smesh
pf.clear()
drawLumenMesh()
if domain == 'Wall':
pf.PF['outer_surface_mesh'] = Smesh
pf.clear()
for smesh in pf.PF['outer_surface_mesh']:
pf.draw(pf.Formex(smesh), linewidth=3, color='red')
def writeMeshFile():
cfd = pf.PF['CFD_lumen_model']
n, E = cfd.coords, cfd.elems
# all elements had negative volume!
cfdmesh = pf.Mesh(n, np.concatenate(E)).reverse()
types = ['pgf', 'neu', 'inp']
fn = pf.askFilename(pf.cfg['workdir'], types)
if fn:
ftype = fn.ftype
print("Exporting surface model to %s (%s)" % (fn, ftype))
if ftype == 'pgf':
kargs = dict(sep=' ', mode='w', shortlines=False)
elif ftype == 'inp':
kargs = dict(eltype='C3D8R')
else:
kargs = {}
with pf.busyCursor():
if ftype == 'pgf':
pf.writeGeometry(fn, [cfdmesh], **kargs)
def surfMesh():
spc = getData('cross_splines')
Fspc = [[ci.toFormex() for ci in c] for c in spc]
surf = [[pf.connect([Fi, Fi, Fj, Fj], nodid=[0, 1, 1, 0])
for Fi, Fj in zip(f[:-1], f[1:])] for f in Fspc]
pf.draw(surf)
pf.PF['surface_mesh'] = surf
################## Create center line ##########################
[docs]def divideControlLines():
"""Divide the control lines."""
br = getData('branch')
try:
slice_data = pf.PF[_slice_data]
print("1", slice_data)
except Exception:
slice_data = _slice_data
print("2", slice_data)
npcent = slice_data['nslice']
cl = [br[i].approx(nseg=npcent[i//2]) for i in range(len(br))]
pf.PF['control_lines'] = cl
if drawOption('visual'):
drawControlLines()
[docs]def center2D(x, x0, x1):
"""Find the center of the section x between the points x0 and x1"""
x[:, 2] = 0. # clear the z-coordinates
i = x.distanceFromPoint(x0).argmin()
j = x.distanceFromPoint(x1).argmin()
xm = 0.5*(x[i]+x[j])
return xm
[docs]def centerline2D(S, s0, s1):
"""Find the centerline of a tubular surface.
- `S`: a tubular surface
- `s0`,`s1`: helper polylines on opposing sides of the surface.
"""
sections = slicer(S, s0, s1, visual=False)
## if drawOption('visual'):
## pf.draw(sections,color='red', alpha=1, flat=True)
pf.draw(sections, color='black', alpha=1, flat=True)
cl = pf.PolyLine([center2D(s.coords, x0, x1)
for s, x0, x1 in zip(sections, s0.coords, s1.coords)])
return cl
[docs]def extendedCenterline(cl, s0, s1, cp):
"""Extend the center line to the central point.
cl: center line
s0,s1: helper lines
cp: central point.
Each center line gets two extra points at the start:
the central point and a point on the bisectrix at half distance.
"""
vb = vecbisectrix(s0.coords[0], s1.coords[0], cp)
d0 = cl.coords[0].distanceFromPoint(cp)
return pf.PolyLine(pf.Coords.concatenate([cp, cp+vb*d0/2., cl.coords]))
def centerlines():
S, cp = getData('surface', 'central_point')
try:
hl = pf.PF['control_lines']
except Exception:
divideControlLines()
hl = pf.PF['control_lines']
with pf.busyCursor():
cl = [centerline2D(S, hl[2*i], hl[2*i+1]) for i in range(3)]
cl = [extendedCenterline(cl[i], hl[2*i], hl[2*i+1], cp) for i in range(3)]
pf.PF['center_lines'] = cl
# if drawOption('visual'):
drawCenterLines()
_draw_options = {
'visual': False,
'numbers': False,
'fill_cross': False,
'fill_surf': False,
}
_slice_data = {
'nslice': [10, 10, 10],
}
[docs]def createBranches(branch):
"""Create the branch control lines for the input lines."""
# roll the first part to the end
branch = branch[1:]+branch[:1]
# reverse the uneven branches
for i in range(1, 6, 2):
branch[i] = branch[i].reverse()
print("Branch control lines:")
for i in range(6):
print(" %s, %s" % divmod(i, 2))
print(branch[i].coords)
pf.PF['branch'] = branch
drawHelperLines()
#############################################################
## Menu steps ##
def importGeometry():
from pyformex.gui.menus.Geometry import importSurface
importSurface(pf.cfg['datadir'] / 'bifurcation.off.gz')
pm = pf.pmgr()
S = pm.get_sel(clas=pf.TriSurface, single=True)
if S:
pf.PF['surface'] = pm.selection[0]
def positionGeometry():
pass
####################### DRAWING ######################################
color_half_branch = ['red', 'cyan', 'green', 'magenta', 'blue', 'yellow']
def drawSurface(clear=True):
if clear:
pf.clear()
pf.view('front')
pf.smooth()
S = pf.PF['surface']
pf.draw(S, color='red', alpha=0.3)
pf.zoomAll()
def drawHelperLines():
branch = pf.PF['branch']
for i in range(3):
pf.draw(branch[2*i:2*i+2], color=['red', 'green', 'blue'][i],
flat=True, alpha=1, linewidth=3)
def drawControlLines():
hl = pf.PF['control_lines']
pf.draw(hl, color='red', flat=True, alpha=1, linewidth=3)
if drawOption('numbers'):
for h in hl:
pf.drawNumbers(h.coords)
def drawCentralPoint():
cp = pf.PF['central_point']
print("Central Point = %s" % cp)
pf.draw(cp, bbox='last', color='black', marksize=8,
flat=True, alpha=1, ontop=True)
def drawCenterLines():
pf.transparent(True)
cl = pf.PF['center_lines']
pf.draw(cl, color='blue', flat=True, alpha=1, linewidth=3)
if drawOption('numbers'):
for li in cl:
pf.drawNumbers(li.coords)
def drawCrossSections():
pf.transparent(True)
cs = pf.PF['cross_sections']
# draw(cs[0:6:2],color='red')
# draw(cs[1:6:2],color='blue')
pf.draw(pf.Mesh.concatenate([i.toMesh() for i in olist.flatten(cs[0:6:2])]),
color='red', flat=True, alpha=1, linewidth=3)
pf.draw(pf.Mesh.concatenate([i.toMesh() for i in olist.flatten(cs[1:6:2])]),
color='blue', flat=True, alpha=1, linewidth=3)
def drawSeededBranch(branchsections, branchcl, propbranch=0):
for sec in branchsections:
pf.draw(pf.PolyLine(sec, closed=True), flat=True, alpha=1, linewidth=3)
def drawOuterCrossSections():
cs = pf.PF['cross_sections']
pf.draw(cs[0:6:2], color='red', linewidth=4, flat=True, alpha=1)
pf.draw(cs[1:6:2], color='blue', linewidth=4, flat=True, alpha=1)
def drawSurfaceMesh():
for smesh in pf.PF['inner_surface_mesh']:
pf.draw(pf.Formex(smesh), linewidth=3, color='green')
def drawLumenMesh():
lumen_model = pf.PF['CFD_lumen_model']
n, el = lumen_model.coords, lumen_model.elems
for i, e in enumerate(el):
pf.draw(pf.Mesh(coords=n, elems=e).getBorderMesh().setProp(i+1))
def drawCrossSplines():
sp = getData('cross_splines')
if drawOption('fill_cross'):
for s in sp:
pf.draw(pf.Formex([si.coords for si in s]), color='black',
flat=True, alpha=1)
else:
for s, c in zip(sp, color_half_branch):
pf.draw(s, color=c, flat=True, alpha=1)
if drawOption('numbers'):
for s in sp:
for si in s:
pf.drawNumbers(si.coords)
def drawLongSplines():
sp = getData('long_splines')
for s, c in zip(sp, color_half_branch):
pf.draw(s, color=c, flat=True, alpha=1)
def drawAxisSplines():
sp = getData('axis_splines')
pf.draw(sp, color='black', flat=True, alpha=1)
# drawNumbers( pf.Formex( [i.coords[-1] for i in sp] ).scale(1.1) )
def drawSurfMesh():
surf = getData('surface_mesh')
pf.draw(surf, color='black')
[docs]def drawCSys(ax=pf.Formex([[[1., 0., 0.]], [[0., 1., 0.]], [[0., 0., 1.]],
[[0., 0., 0.]]]), color='black'):
"""draws the coordinate system with origin in ax[0]
and directions determined by the 3 points in ax[1:4]
"""
assex = np.array([ax[3], ax[0]])
assey = np.array([ax[3], ax[1]])
assez = np.array([ax[3], ax[2]])
for asse in [assex, assey, assez]:
pf.draw(pf.Formex(asse.reshape(1, 2, 3)), color=color)
pf.drawNumbers(pf.Formex([assex[1], assey[1], assez[1]]))
def flyThru():
cl = pf.PF['axis_splines']
for li in cl:
pf.flyAlong(li, upvector=[0., 0., 1.])
def drawAll():
drawSurface()
drawHelperLines()
drawCentralPoint()
drawCenterLines()
#############################################################################
######### Create a menu with interactive tasks #############
stepwise = False
def nextStep(msg):
global stepwise
if stepwise:
ans = pf.ask(msg, ['Quit', 'Continue', 'Step'])
if ans == 'Continue':
stepwise = False
return ans != 'Quit'
else:
return True
def example():
global stepwise
stepwise = True
pf.clear()
pf.wireframe()
pf.view('front')
if not nextStep("This example guides you through the subsequent steps "
"to create a hexahedral mesh in a bifurcation. At each "
"step you can opt to execute a single step, continue "
"the whole procedure, or quit the example.\n\n"
"1. Input the bifurcation surface model"):
return
examplefile = pf.cfg['datadir'] / 'bifurcation.off.gz'
print(examplefile)
pf.PF['surface'] = pf.TriSurface.read(examplefile)
drawSurface()
if not nextStep('2. Create the central point of the bifurcation'):
return
cp = pf.Coords([-1.92753589, 0.94010758, -0.1379855])
pf.PF['central_point'] = cp
pf.smooth()
pf.transparent(True)
drawCentralPoint()
if not nextStep("3. Create the helper lines for the mesher. "
"This step is best done with perspective off."):
return
pf.setDrawOptions({'bbox': 'last'})
pf.perspective(False)
C = [[-33.93232346, 7.50834751, 0.],
[-1.96555257, 6.90520096, 0.],
[19.08426476, 10.4637661, 0.],
[19.14457893, 1.2959373, 0.],
[2.61836171, 0.87373471, 0.],
[19.08426476, -1.59916639, 0.],
[19.02395058, -12.6970644, 0.],
[-1.84492326, -6.30370998, 0.],
[-34.29421234, -4.61489964, 0.]]
C = pf.Coords(C)
pf.drawNumbers(C)
C = C.reshape(3, 3, 3)
branch = []
for i in range(3):
for j in range(2):
branch.append(pf.PolyLine(C[i, j:j+2]))
createBranches(branch)
if not nextStep("Notice the order of the input points!\n\n"
"4. Create the Center Lines"):
return
inputSlicingParameters()
centerlines()
if not nextStep('5. Slice the bifurcation'):
return
sliceIt()
if not nextStep('6. Create Spline Mesh'):
return
pf.perspective(True)
splineIt()
if not nextStep('7. Seed the longitudinal splines'):
return
inputLongitudinalSeeds()
seedLongitudinalSplines()
if not nextStep('8. Run the sweeping hex mesher'):
return
sweepingMesher()
pf.setDrawOptions({'bbox': 'auto'})
if not nextStep('9. Write mesh to file'):
return
writeMeshFile()
[docs]def updateData(data, newdata):
"""Update the input data fields with new data values"""
if newdata:
for d in data:
v = newdata.get(d[0], None)
if v is not None:
d[1] = v
def resetData():
pass
def resetDraw():
pf.clear()
pf.smooth()
pf.transparent(False)
pf.lights(True)
pf.setDrawOptions({'bbox': 'last'})
pf.linewidth(2)
def resetApp():
pf.resetGUI()
resetData()
resetDraw()
##########################################################################
######### Create a menu with interactive tasks #############
menu_items = [
("&Run through example", example),
("---", None),
("&1. Import Bifurcation Geometry", importGeometry),
("&2. Input Central Point", inputCentralPoint),
("&3. Input Helper Lines", inputControlLines),
("&4a. Input Slicing Parameters", inputSlicingParameters),
("&4b. Create Center Lines", centerlines),
("&5. Slice the bifurcation", sliceIt),
("&6. Create Spline Mesh", splineIt),
("&7a. Input Longitudinal Seeds", inputLongitudinalSeeds),
("&7b. Seed Longitudinal Splines", seedLongitudinalSplines),
("&8a. Input Meshing Parameters", inputMeshingParameters),
("&8b. Sweeping Mesher", sweepingMesher),
("&9. Write mesh to file", writeMeshFile),
("---", None),
("&Create Surface Mesh", surfMesh),
("---", None),
("&Draw", [
("&All", drawAll),
("&Surface", drawSurface),
("&Helper Lines", drawHelperLines),
("&Control Lines", drawControlLines),
("&Central Point", drawCentralPoint),
("&Center Lines2D", drawCenterLines),
("&Center Lines3D", drawAxisSplines),
("&Cross Sections", drawCrossSections),
("&Cross Splines", drawCrossSplines),
("&Long Splines", drawLongSplines),
# ("&SurfaceMesh", drawSurfaceMesh),
("&Surface Spline_Mesh", drawSurfMesh),
("Set Draw Options", inputDrawOptions),
]),
("---", None),
("&Fly Along Center Lines", flyThru),
("---", None),
('Reset App', resetApp),
]
def menu_setup(menu):
resetApp()
## app main function ##
def run():
# register the menu
PluginMenu('BifMesh', modname='BifMesh', package='pyformex.apps',
menuctrl='inline').show()
pf.showInfo("The BifMesh menu is ready")
if __name__ == '__draw__':
run()
# End