Source code for apps.BifMesh

#
##
##  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 inputLongitudinalSeeds(): """Interactive input of the longitudinal meshing seeds. nseeds = number of seeds in zone0 and zone1 zonesizes are the zone sizes in percentages transition zone is seeded using an approximated geometrical series """ # TODO: change the seeding to a branch by branch handling?? global dialog SA = [None]*3 def drawLongitudinalSeeding(H, sat): for i, at, col in zip(range(3), sat, ('blue', 'green', 'red')): spl = H[2*i][0] pts = spl.pointsAt(spl.atLength(at)) SA[i] = pf.draw(pts, color=col, marksize=7, flat=True, alpha=1, undraw=SA[i]) pf.zoomAll() def show(): """Show the current seeds""" global dialog pf.undraw(SA) if not dialog.validate(): return res = dialog.results if not res: return sat = [] for i in range(3): # branches nseeds = eval(res[f'nseeds{i}']) # TODO: sec. risk: change!! zonesizes = eval(res[f'ratios{i}']) if sum(zonesizes) > 1.: raise ValueError("The sum of zone lengths has to be < 1.") na, nb = nseeds za, zb = zonesizes seeds = pf.at.seed3(za/na, zb/nb, na, nb) sat.append(seeds) drawLongitudinalSeeding(long_splines, sat) return sat def accept(): """Accept the current seeds""" sat = show() if sat: pf.PF['branch_seeds'] = sat dialog.accept() def help(): pf.showInfo( "Set the seeding parameters for the 3 branches. Each branch is " "divided in 3 parts: close to the bifurcation, far from the " "bifurcation and the central part between these two. Only the " "data for the first two need to be entered. The 3rd (central)" "is calculated as transition.") pf.clear() long_splines = pf.PF['long_splines'] pf.draw(long_splines, linewidth=1, color='darkgray', flat=True, alpha=1) pf.drawNumbers(pf.Coords([long_splines[i][0].coords[-1] for i in [0, 2, 4]])) nseeds = '[5, 3 ]' ratios = '[0.3, 0.4]' dialog = pf.Dialog(caption=_name+' seeds', items=[ _I('nseeds0', nseeds), _I('ratios0', ratios), _I('nseeds1', nseeds), _I('ratios1', ratios), _I('nseeds2', nseeds), _I('ratios2', ratios), ], actions=[ ('Cancel',), ('Show', show), ('Accept', accept), ('Help', help)]) dialog.move(100, 100) dialog.getResults()
[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 inputMeshingParameters(): """Dialog for input of the meshing parameters. """ dialog = pf.Dialog(caption=_name, store=_name+'_data', items=[ _I('n_longit', 2, min=1, max=16, tooltip="Number of hex elements in longitudinal direction of " "a block"), _I('n_circum', 3, min=1, max=16, tooltip="Number of hex elements over the circumference of a 1/4 " "section"), _I('s_radial', '[0.0, 0.6, 1.0]', tooltip="Number of hex elements radially from inner pattern to " "boundary layer. It can be an integer or a list of seeds in the " "range 0.0 to 1.0"), _I('s_boundary', '[0.0,0.4, 0.8,1.0]', tooltip="Number of hex elements radially in the boundary layer. " "It can be an integer or a list of seeds in the range 0.0 to 1.0"), ]) res = dialog.getResults() if res: pf.PF['mesh_block_params'] = res
[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, }
[docs]def inputDrawOptions(): """Input the drawing options""" _draw_options.update(pf.PF.get(_draw_options_name, {})) res = pf.askItems(list(_draw_options.items())) if res: _draw_options.update(res) pf.PF[_draw_options_name] = res
_slice_data = { 'nslice': [10, 10, 10], }
[docs]def inputSlicingParameters(): """Input the slicing parameters""" _slice_data.update(pf.PF.get(_slice_data_name, {})) res = pf.askItems(list(_slice_data.items())) if res: _slice_data.update(res) pf.PF[_slice_data_name] = res
[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
[docs]def inputCentralPoint(): """Fix the central point of the bifurcation""" drawSurface() pf.perspective(False) obj = d2.drawObject2D('point', npoints=1, zvalue=0.) obj.specular = 0. if obj is not None: pf.PF['central_point'] = obj drawCentralPoint()
[docs]def inputControlLines(): """Enter six polyline paths in counterclockwise direction.""" branch = [] BA = [] pf.perspective(False) for i in range(6): print("Input Branch %s" % i) if i % 2 == 0: coords = None else: coords = branch[i-1].coords[-1:] obj = d2.drawObject2D(mode='polyline', npoints=-1, coords=coords, zvalue=0.) # obj.specular = 0. pf.removeHighlight() # WHY is bbox='last' or zoomAll needed here if obj is not None: BA.append(pf.draw(obj, color='blue', flat=True)) pf.zoomAll() branch.append(obj) else: break if len(branch) == 6: pf.removeHighlight() pf.undraw(BA) createBranches(branch) pf.zoomAll() else: pf.warning("Incorrect definition of helper lines")
####################### 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