def run(): clear() linewidth(2) flat() F = Formex([[[1., 0., 0.]], [[1., 1., 0.]]]).rosette(4, 90.) draw(F) drawNumbers(F) zoomAll() setDrawOptions(bbox=None) showDoc() pts = F.coords.reshape(-1, 3) draw(simple.circle(2, 4), color=yellow, linewidth=4) for degree, c in zip(list(range(1, 4)), [black, red, green]): N = NurbsCurve(pts, degree=degree, closed=True) draw(N, color=c) drawThePoints(N, 16, color=c) for w, c in zip([sqrt(2.), sqrt(2.) / 2., 0.25, 0.], [blue, cyan, magenta, white]): wts = array([1., w] * 4).reshape(8, 1) pts4 = Coords4(pts) pts4.deNormalize(wts) pts4 = Coords4(concatenate([pts4, pts4[:1]], axis=0)) N = NurbsCurve(pts4, degree=2, closed=False, blended=False) draw(N, color=c) drawThePoints(N, 16, color=c)
def splitArgs(args, mask=None, nproc=-1, close=False): """Split data blocks over multiple processors. Parameters: - `args`: a list or tuple of data blocks. All items in the list that need to be split should be arrays with the same first dimension. - `mask`: list of bool, with same length as `args`. It flags which items in the `args` list are to be split. If not specified, all array type items will be split. - `nproc`: number of processors intended. If negative (default), it is set equal to the number of processors detected. - `close`: bool. If True, the elements where the arrays are split are included in both blocks delimited by the element. Returns a list of `nproc` tuples. Each tuple contains the same number of items as the input `args` and in the same order, whereby the (nonmasked) arrays are replaced by a slice of the array along its first axis, and the masked and non-array items are replicated as is. This function uses :meth:`arraytools.splitar` for the splitting of the arrays. Example: >>> splitArgs([np.arange(5),'abcde'],nproc=3) [(array([0, 1]), 'abcde'), (array([2]), 'abcde'), (array([3, 4]), 'abcde')] >>> for i in splitArgs([np.eye(5),'=>',np.arange(5)],nproc=3): ... print("%s %s %s" % i) [[ 1. 0. 0. 0. 0.] [ 0. 1. 0. 0. 0.]] => [0 1] [[ 0. 0. 1. 0. 0.]] => [2] [[ 0. 0. 0. 1. 0.] [ 0. 0. 0. 0. 1.]] => [3 4] >>> for i in splitArgs([np.eye(5),'=>',np.arange(5)],mask=[1,0,0],nproc=3): ... print("%s %s %s" % i) [[ 1. 0. 0. 0. 0.] [ 0. 1. 0. 0. 0.]] => [0 1 2 3 4] [[ 0. 0. 1. 0. 0.]] => [0 1 2 3 4] [[ 0. 0. 0. 1. 0.] [ 0. 0. 0. 0. 1.]] => [0 1 2 3 4] """ if nproc < 0: nproc = cpu_count() if mask is None: mask = [isinstance(a, np.ndarray) for a in args] split = [ splitar(a, nproc, close) if m else [a] * nproc for a, m in zip(args, mask) ] return list(zip(*split))
def createCircles(n): """Create a set of BezierSpline curves. The curves are transformations of a unit circle. They are non-uniformously scaled to yield ellipses, and then rotated and translated. """ C = circle() t = arange(n + 1) / float(n) CL = [C.scale([1., a, 0.]) for a in 0.5 + arange(n + 1) / float(n)] CL = [Ci.rot(a, 2) for Ci, a in zip(CL, arange(n + 1) / float(n) * 45.)] CL = [Ci.trl(2, a) for Ci, a in zip(CL, arange(n + 1) / float(n) * 4.)] return CL
def cutSelection(): """Cut the selection with a plane.""" FL = selection.check() FLnot = [F for F in FL if F.nplex() not in [2, 3]] if FLnot: warning( "Currently I can only cut Formices with plexitude 2 or 3.\nPlease change your selection." ) return dsize = bbox(FL).dsize() if dsize > 0.: esize = 10**(niceLogSize(dsize) - 5) else: esize = 1.e-5 res = askItems([ ['Point', (0.0, 0.0, 0.0)], ['Normal', (1.0, 0.0, 0.0)], ['New props', [1, 2, 2, 3, 4, 5, 6]], ['Side', 'positive', 'radio', ['positive', 'negative', 'both']], ['Tolerance', esize], ], caption='Define the cutting plane') if res: P = res['Point'] N = res['Normal'] atol = res['Tolerance'] p = res['New props'] side = res['Side'] if side == 'both': G = [ F.cutWithPlane(P, N, side=side, atol=atol, newprops=p) for F in FL ] draw(G[0]) G_pos = [g[0] for g in G] G_neg = [g[1] for g in G] export(dict([('%s/pos' % n, g) for n, g in zip(selection, G_pos)])) export(dict([('%s/neg' % n, g) for n, g in zip(selection, G_neg)])) selection.set(['%s/pos' % n for n in selection] + ['%s/neg' % n for n in selection]) selection.draw() else: selection.changeValues([ F.cutWithPlane(P, N, side=side, atol=atol, newprops=p) for F in FL ]) selection.drawChanges()
def setDebug(): options = [ o for o in dir(pf.DEBUG) if o[0] != '_' ] options.remove('ALL') options.remove('NONE') values = [ getattr(pf.DEBUG, o) for o in options ] items = [ _I(o, bool(pf.options.debuglevel & v)) for o, v in zip(options, values) ] res = draw.askItems(items) if res: print(res) debug = 0 for o, v in zip(options, values): if res[o]: debug |= v print("debuglevel = %s" % debug) pf.options.debuglevel = debug
def drawnObject(points, mode='point'): """Return the geometric object resulting from draw2D points""" minor = None if '_' in mode: mode, minor = mode.split('_') closed = minor == 'closed' if mode == 'point': return points elif mode == 'polyline': if points.ncoords() < 2: return None closed = obj_params.get('closed', None) return PolyLine(points, closed=closed) elif mode == 'curve' and points.ncoords() > 1: curl = obj_params.get('curl', None) closed = obj_params.get('closed', None) return BezierSpline(points, curl=curl, closed=closed) elif mode == 'nurbs': degree = obj_params.get('degree', None) if points.ncoords() <= degree: return None closed = obj_params.get('closed', None) return NurbsCurve(points, degree=degree, closed=closed) elif mode == 'circle' and points.ncoords() % 3 == 0: R, C, N = triangleCircumCircle(points.reshape(-1, 3, 3)) circles = [circle(r=r, c=c, n=n) for r, c, n in zip(R, C, N)] if len(circles) == 1: return circles[0] else: return circles else: return None
def odict(self): """Return the currently selected items as a dictionary. Returns an OrderedDict with the currently selected objects in the order of the selection.names. """ return OrderedDict(zip(self.names, self.check(warn=False)))
def testInside(S, P, method, nproc, atol): """Test which of the points P are inside surface S""" print("Testing %s points against %s faces" % (P.nelems(), S.nelems())) bb = bboxIntersection(S, P) drawBbox(bb, color=array(red), linewidth=2) P = Coords(P).points() t = timer.Timer() if method == 'vtk' and not utils.hasModule('vtk'): warning("You need to install python-vtk!") return if nproc == 1: ind = inside(S, P, method, atol) else: datablocks = splitar(P, nproc) datalen = [0] + [d.shape[0] for d in datablocks] shift = array(datalen[:-1]).cumsum() #print("METH %s" % method) tasks = [(inside, (S, d, method, atol)) for d in datablocks] ind = multitask(tasks, nproc) ind = concatenate([ i+s for i, s in zip(ind, shift)]) print("%sinside: %s points / %s faces: found %s inside points in %s seconds" % (method, P.shape[0], S.nelems(), len(ind), t.seconds())) if len(ind) > 0: draw(P[ind], color=green, marksize=3, ontop=True, nolight=True, bbox='last')
def add(self, name, strict=True, skipconfig=True): """Add a new filename to the front of the menu. This function is used to add app/scripts to the history menus. By default, only legal pyFormex apps or scripts can be added, and scripts from the user config will not be added. Setting strict and or skipconfig to False will skip the filter(s). """ if strict: if self.mode == 'app': appname = self.fullAppName(name) app = apps.load(appname, strict=strict) if app is None: print("%s is not a pyFormex app!" % appname) return else: if not utils.is_pyFormex(name): return if skipconfig: # This is here to skip the startup script if os.path.dirname( os.path.abspath(name)) == pf.cfg['userconfdir']: return files = self.files olist.toFront(files, name) if self.max > 0 and len(files) > self.max: files = files[:self.max] while len(self.my_actions) < len(files): self.my_actions.append(self.addAction(name)) for a, f in zip(self.my_actions, self.files): a.setText(f)
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 = zeros([HC.shape[0]], dtype=bool) isBranching[[ 0, -1 ]] = start_end_branching #if first section is a branching section #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 = [array(i) for i in [cpain, cpatr, cpabl]] print( '# sections= %d, # inner quad reg = %d, # trans quad reg = %d, # boundary-layer quad reg = %d' % (cpain.shape[0], cpain.shape[1], cpatr.shape[1], 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
def setELoad(): """Pick the edges with load condition.""" global edge_load, nsteps, step if not checkModel(): return removeHighlight() edge_load = askItems([ ('step', step), ('x', edge_load['x'], { 'text': 'x-load' }), ('y', edge_load['y'], { 'text': 'y-load' }), ]) if edge_load: K = pickEdges() for k in K.keys(): v = K[k] elems, edges = v // 4, v % 4 print(k, elems, edges) for el, edg in zip(elems, edges): for label in 'xy': if edge_load[label] != 0.: PDB.elemProp(set=el, group=k, tag="Step-%s" % step, eload=EdgeLoad(edge=edg, label=label, value=edge_load[label])) nsteps = max(nsteps, step)
def rename(oldnames, newnames): """Rename the global variables in oldnames to newnames.""" g = pf.PF for oldname, newname in zip(oldnames, newnames): if oldname in g: g[newname] = g[oldname] del g[oldname]
def drawLongitudinalSeeding(H, at): for i, j in zip([0, 2, 4], range(3)):#semibranch,at l0=H[i][0].subPoints(10)#0,2,4,napproxlong draw(divPolyLine(l0, at[j][0]), color='green', marksize=7, flat=True, alpha=1) draw(divPolyLine(l0, at[j][1]), color='yellow', marksize=7, flat=True, alpha=1) draw(divPolyLine(l0, at[j][2]), color='red', marksize=7, flat=True, alpha=1) zoomAll()
def human(self, symbol='xyz'): """Return a human representation""" mon = self.atoms(symbol) mon = [ str(c) + '*' + m if c != 1 else m for c, m in zip(self.coeff, mon) ] return ' + '.join(mon).replace('*1', '').replace('+ -', '-')
def __init__(self, actions=[], function=None, menu=None, toolbar=None, icons=None, text=None): """Create an new action list, empty by default. A list of strings can be passed to initialize the actions. If a menu and/or toolbar are passed, a button is added to them for each string in the action list. If a function is passed, it will be called with the string as parameter when the item is triggered. If no icon names are specified, they are taken equal to the action names. Icons will be taken from the installed icon directory. If you want to specify other icons, use the add() method. """ self.actions = [] self.function = function self.menu = menu self.toolbar = toolbar if icons is None: icons = actions icons = [utils.findIcon(i) for i in icons] if text is None: text = actions for name, icon, txt in zip(actions, icons, text): self.add(name, icon, txt)
def vmtkDistanceOfPoints(self, X, tryfixsign=True, nproc=1): """Find the distances of points X to the TriSurface self. - `X` is a (nX,3) shaped array of points. - `nproc`: number of parallel processes to use. On multiprocessor machines this may be used to speed up the processing. If <= 0 , the number of processes will be set equal to the number of processors, to achieve a maximal speedup. Retuns a tuple of vector and signed scalar distances for all points. The signed distance is positive if the distance vector and the surface normal have negative dot product, i.e. if X is outer with respect to self. """ if nproc < 1: nproc = multi.cpu_count() if nproc == 1: S = TriSurface( X, arange(X.shape[0]).reshape(-1, 1) * ones(3, dtype=int).reshape(1, -1)) return vmtkDistanceOfSurface(self, S, tryfixsign) else: datablocks = multi.splitar(X, nproc, close=False) print('-----distance of %d points from %d triangles with %d proc' % (len(X), self.nelems(), nproc)) tasks = [(vmtkDistanceOfPoints, (self, d, tryfixsign, 1)) for d in datablocks] ind = multi.multitask(tasks, nproc) vdist, sdist = list(zip(*ind)) return concatenate(vdist), concatenate(sdist)
def writeElems(fil, elems, type, name=None, eid=None, eofs=0, nofs=0): """Write element group of given type. elems is the list with the element node numbers. The elements are added to the named element set. The eofs and nofs specify offsets for element and node numbers. If eid is specified, it contains the element numbers increased with eofs. """ out = type if name is not None: out += ' nom = %s' % name fil.write('%s\n' % out) nn = elems.shape[1] if nn < 5: fmt = 'M%d' + nn * ' N%d' + '\n' else: fl = nn // 4 fmt = 'M%d' + fl * (4 * ' N%d' + '\n') if nn % 4 != 0: fmt += (nn % 4) * ' N%d' + '\n' if eid is None: eid = arange(elems.shape[0]) else: eid = asarray(eid) for i, e in zip(eid + eofs, elems + nofs): fil.write(fmt % ((i, ) + tuple(e))) fil.write('FINSF\n') fil.write('%\n')
def readMesh(fn): """Read a flavia mesh file. Returns a list of Meshes if succesful. """ fil = open(fn, 'r') meshes = [] for line in fil: if line.startswith('#'): continue elif line.startswith('Mesh'): s = shlex.split(line.lower()) s = dict(zip(s[0::2], s[1::2])) print(s) ndim = int(s['dimension']) nplex = int(s['nnode']) eltype = element_type_translation[s['elemtype']][nplex] print("eltype = %s, ndim = %s" % (eltype, ndim)) elif line.startswith('Coordinates'): coords = readCoords(fil, ndim) print("Coords %s" % str(coords.shape)) elif line.startswith('Elements'): elems, props = readElems(fil, nplex) print("Elements %s %s" % (elems.shape, props.shape)) meshes.append((elems, props)) else: print(line) elems, props = [m[0] for m in meshes], [m[1] for m in meshes] maxnod = max([e.max() for e in elems]) coords = coords[:maxnod + 1] return coords, elems, props, ndim
def drawCrossSplines(): sp = getData('cross_splines') if drawOption('fill_cross'): [draw(Formex([si.coords for si in s]), color='black', flat=True, alpha=1) for s in sp] else: [draw(s, color=c, flat=True, alpha=1) for s, c in zip(sp, color_half_branch)] if drawOption('numbers'): [[drawNumbers(si.coords) for si in s] for s in sp]
def __init__(self, dic, name, parent=None, *args): QtCore.QAbstractItemModel.__init__(self, parent, *args) self.dic = dic self.name = name keys = dic.keys() vals = dic.values() typs = [str(type(v)) for v in vals] self.items = list(zip(keys, typs, vals))
def printbbox(self): """Print the bbox of the current selection.""" objects = self.check() if objects: for n, o in zip(self.names, objects): bb = o.bbox() print("* %s (%s): bbox [%s, %s]" % (n, o.__class__.__name__, bb[0], bb[1])) if len(self.names) > 1: print("** Overal bbox: [%s, %s]" % (bb[0], bb[1]))
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] export({'cross_splines':spc}) clear() drawCrossSplines() # axis spline [hi0, hi1, hi2, hi3, hi4, hi5]=[array([h.coords for h in hi]) for hi in spc] clv=[ PolyLine( (hi[:, 0]+hi[:, -1])*0.5 ) for hi in [hi0, hi2, hi4] ] axis = [BezierSpline(c.coords, curl=1./3.).approx(nseg=nsl) for c, nsl in zip(clv, nslice)] export({'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)==True:##with continuity TBspl = [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))]#6 splines at the center-Top and center-Bottom prolspc=[[spc[i][1]]+spc[j] for i, j in [[5, 0], [2, 1], [1, 2], [4, 3], [3, 4], [0, 5] ] ]#prolonged sections, to give correct tangence mspl = [ [BezierSpline([ci.coords[j] for ci in prolspc[i]], curl=1./3.) for j in range(1, niso)] for i in range(6)]#all splines between Top and Bottom splines spl=[] for i in range(6):#6 semi-branches smspl=[TBspl[i]]#splines of 1 semi-branch for ispl in mspl[i]: ### problem with revision revision 1513 solved #coordb= ispl.coords[1:] #remove the last piece (only needed for the tangence)#valid up to revision -r 1512 coordb=append(ispl.coords, [[0., 0., 0.]]*2, axis=0).reshape(-1, 3, 3)[1:]#from revision -r 1513 sct=1.# factor to move the last control point=amount of tangence between contiguous splines coordb[0, 1]=(coordb[0, 1]-coordb[0, 0])*sct+coordb[0, 0] smspl=smspl+[BezierSpline(coords=coordb[:, 0], control=coordb[:-1, [1, 2]])] spl.append(smspl) else: ##without continuity spl = [[BezierSpline([ci.coords[j] for ci in c], curl=1./3.) for j in range(niso+1)] for c in spc] export({'long_splines':spl}) drawLongSplines()
def classify(appdir, pkg, nmax=0): """Classify the files in submenus according to keywords. """ class failed(object): """A class to allow failing examples in the catalog""" _status = 'failed' all_apps = sorted(apps.detect(appdir)) kat = ['all', 'status', 'level', 'topics', 'techniques'] cat = dict([(k, set()) for k in kat]) cat['status'] = ['failed', 'checked', 'unchecked'] cat['level'] = ['beginner', 'normal', 'advanced'] print(cat) col = {} if nmax > 9: # Do not exagerate! # split the full collection in alphabetical groups of length nmax lbl, grp = splitAlpha(all_apps, nmax) cat['all'] = lbl for l, g in zip(lbl, grp): col['all/' + l] = g for i, appname in enumerate(all_apps): #col['all'].update([appname]) try: fullname = str(pkg) + '.' + appname app = apps.load(fullname, strict=True) if app is None: raise RuntimeError("App '%s' has no run method" % fullname) except: app = failed print("Failed to load app '%s'" % fullname) for k in kat: if hasattr(app, '_' + k): v = getattr(app, '_' + k) if isinstance(v, list): v = [vi.lower() for vi in v] else: v = v.lower() if k in ['status', 'level']: v = [v] else: cat[k].update(v) for i in v: ki = '%s/%s' % (k, i) if not ki in col.keys(): col[ki] = set() col[ki].update([appname]) sortSets(cat) sortSets(col) return all_apps, kat, cat, col
def setProp(self, prop=None, blocks=None): """Create or destroy the property array for the Geometry. A property array is a rank-1 integer array with dimension equal to the number of elements in the Geometry. Each element thus has its own property number. These numbers can be used for any purpose. They play an import role when creating new geometry: new elements inherit the property number of their parent element. Properties are also preserved on most geometrical transformations. Because elements with different property numbers can be drawn in different colors, the property numbers are also often used to impose color. Parameters: - `prop`: a single integer value or a list/array of integer values. If the number of passed values is less than the number of elements, they wil be repeated. If you give more, they will be ignored. The special value 'range' will set the property numbers equal to the element number. A value None (default) removes the properties from the Geometry. - `blocks`: a single integer value or a list/array of integer values. If the number of passed values is less than the length of `prop`, they wil be repeated. If you give more, they will be ignored. Every prop will be repeated the corresponding number of times specified in blocks. Returns the Geometry with the new properties inserted or with the properties deleted (if argument is None). Notice that unlike most other Geometry methods, this method changes (and returns) the existing object. """ if prop is None: self.prop = None else: if isinstance(prop, str): if prop == 'range': prop = np.arange(self.nelems()) else: prop = np.array(prop) if blocks is not None: if isinstance(blocks, int): blocks = [blocks] blocks = np.resize(blocks, prop.ravel().shape) prop = np.concatenate([ np.resize(p, b) for p, b in zip(prop, blocks) ]).astype(Int) self.prop = self.toProp(prop) return self
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 """ if not isinstance(S, TriSurface): S = TriSurface(S) if not isinstance(s0, Coords): s0 = s0.coords if not isinstance(s1, Coords): s1 = s1.coords v1 = s1-s0 # direction of the knife zdir = unitVector(2) # direction of the z axis ncut = cross(zdir, v1) # normals on the cut planes sections = [ S.intersectionWithPlane(p0, n0) for p0, n0 in zip(s0, ncut)] if visual: clear() draw(sections, color='magenta') sections = [ getPart(s, x0, x1, cutat=cutat) for s, x0, x1 in zip(sections, s0, s1)] if visual: clear() 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
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'): ## draw(sections,color='red', alpha=1, flat=True) draw(sections, color='black', alpha=1, flat=True) cl = PolyLine([center2D(s.coords, x0, x1) for s, x0, x1 in zip(sections, s0.coords, s1.coords)]) return cl
def __init__(self, name, dic=None, parent=None, *args): QtCore.QAbstractItemModel.__init__(self, parent, *args) if dic is None: dic = gobals() self.dic = dic self.name = name self.obj = dic.get(name, None) keys = dir(self.obj) vals = [str(getattr(self.obj, k)) for k in keys] isdict = [isinstance(self.obj, dict) for k in keys] has_dict = [hasattr(self.obj, '__dict__') for k in keys] has_class = [getattr(self.obj, '__class__') for k in keys] self.items = list(zip(keys, vals, isdict, has_dict, has_class))
def getElems(self, sets): """Return the definitions of the elements in sets. sets should be a list of element sets with length equal to the number of element groups. Each set contains element numbers local to that group. As the elements can be grouped according to plexitude, this function returns a list of element arrays matching the element groups in self.elems. Some of these arrays may be empty. """ return [ e[s] for e, s in zip(self.elems, sets) ]
def connectCurves(curve1, curve2, n): """Connect two curves to form a surface. curve1, curve2 are plex-2 Formices with the same number of elements. The two curves are connected by a surface of quadrilaterals, with n elements in the direction between the curves. """ if curve1.nelems() != curve2.nelems(): raise ValueError("Both curves should have same number of elements") # Create the interpolations curves = interpolate(curve1, curve2, n).split(curve1.nelems()) quads = [connect([c1, c1, c2, c2], nodid=[0, 1, 1, 0]) for c1, c2 in zip(curves[:-1], curves[1:])] return Formex.concatenate(quads)
def getWireAxes(self): """Return the wire axes as curves. The return value is two lists of curves (PolyLines), representing the individual wire axes for each wire direction. """ from pyformex import connectivity M = self.F.toMesh() ML = [M.selectProp(i) for i in [1, 3]] wires = [connectivity.connectedLineElems(Mi.elems) for Mi in ML] wireaxes = [[Formex(Mi.coords[wi]) for wi in wiresi] for Mi, wiresi in zip(ML, wires)] wireaxes = [[w.toCurve() for w in wi] for wi in wireaxes] return wireaxes