def __init__(self, filename=None, access='wr', convert=True, signature=_signature_, compression=5, binary=True, data={}, **kargs): """Create a new project.""" if 'create' in kargs: utils.warn( "The create=True argument should be replaced with access='w'") if 'legacy' in kargs: utils.warn("The legacy=True argument has become superfluous") self.filename = filename self.access = access self.signature = str(signature) self.gzip = compression if compression in range(1, 10) else 0 self.mode = 'b' if binary or compression > 0 else '' TrackedDict.__init__(self) if filename and os.path.exists(filename) and 'r' in self.access: # read existing contents self.load(convert) self.hits = 0 if data: self.update(data) if filename and access == 'w': # destroy existing contents self.save() pf.debug("INITIAL hits = %s" % self.hits, pf.DEBUG.PROJECT)
def uncompress(self): """Uncompress a compressed project file. The project file is read, and if successfull, is written back in uncompressed format. This allows to make conversions of the data inside. """ f = self.readHeader() print(self.format,self.gzip) if f: if self.gzip: try: pyf = gzip.GzipFile(self.filename,'r',self.gzip,f) except: self.gzip = 0 if self.gzip: fn = self.filename.replace('.pyf','_uncompressed.pyf') fu = open(fn,'w'+self.mode) h = self.header_data() h['gzip'] = 0 fu.write("%s\n" % h) while True: x = pyf.read() if x: fu.write(x) else: break fu.close() print("Uncompressed %s to %s" % (self.filename,fn)) else: utils.warn("The contents of the file does not appear to be compressed.") f.close()
def uncompress(self): """Uncompress a compressed project file. The project file is read, and if successfull, is written back in uncompressed format. This allows to make conversions of the data inside. """ f = self.readHeader() print(self.format, self.gzip) if f: if self.gzip: try: pyf = gzip.GzipFile(self.filename, 'r', self.gzip, f) except: self.gzip = 0 if self.gzip: fn = self.filename.replace('.pyf', '_uncompressed.pyf') fu = open(fn, 'w' + self.mode) h = self.header_data() h['gzip'] = 0 fu.write("%s\n" % h) while True: x = pyf.read() if x: fu.write(x) else: break fu.close() print("Uncompressed %s to %s" % (self.filename, fn)) else: utils.warn("warn_project_compression") f.close()
def regularGrid(x0, x1, nx, swapaxes=None): """Create a regular grid of points between two points x0 and x1. Parameters: - `x0`: n-dimensional float (usually 1D, 2D or 3D). - `x1`: n-dimensional float with same dimension as `x0`. - `nx`: n-dimensional int with same dimension as `x0` and `x1`. The space between `x0` and `x1` is divided in `nx[i]` equal parts along the axis i. - `swapaxes`: bool. If False(default), the points are number first in the direction of the 0 axis, then the next axis,... If True, numbering starts in the direction of the highest axis. This is the legacy behavior. Returns a rectangular grid of n-dimensional coordinates in an array with shape ( nx[0]+1, nx[1]+1, ..., ndim ). Example: >>> regularGrid(0.,1.,4) array([[ 0. ], [ 0.25], [ 0.5 ], [ 0.75], [ 1. ]]) >>> regularGrid((0.,0.),(1.,1.),(3,2)) """ if swapaxes is None: # We do not use a decorator utils.warning, because # this function gets called during startup (initialization of elements) utils.warn("warn_regular_grid") swapaxes = False x0 = np.asarray(x0).ravel() x1 = np.asarray(x1).ravel() nx = np.asarray(nx).ravel() if x0.size != x1.size or nx.size != x0.size: raise ValueError("Expected equally sized 1D arrays x0,x1,nx") if any(nx < 0): raise ValueError("nx values should be >= 0") # First construct a grid with integer coordinates ndim = x0.size shape = np.append(tuple(nx + 1), ndim) if swapaxes: # we can just use numpy.indices ind = np.indices(nx + 1) else: # we need to reverse the axes for numpy.indices ind = np.indices(nx[::-1] + 1)[::-1] ind = ind.reshape((ndim, -1)) # And a grid with the complementary indices nx[nx == 0] = 1 jnd = nx.reshape((ndim, -1)) - ind ind = ind.transpose() jnd = jnd.transpose() return ((x0 * jnd + x1 * ind) / nx).reshape(shape)
def exit(all=False): """Exit from the current script or from pyformex if no script running.""" if len(pf.scriptlock) > 0: if all: utils.warn("warn_exit_all") pass else: # This is the only exception we can use in script mode # to stop the execution raise SystemExit
def globalInterpolationCurve(Q,degree=3,strategy=0.5): """Create a global interpolation NurbsCurve. Given an ordered set of points Q, the globalInterpolationCurve is a NURBS curve of the given degree, passing through all the points. Returns: A NurbsCurve through the given point set. The number of control points is the same as the number of input points. .. warning:: Currently there is the limitation that two consecutive points should not coincide. If they do, a warning is shown and the double points will be removed. The procedure works by computing the control points that will produce a NurbsCurve with the given points occurring at predefined parameter values. The strategy to set this values uses a parameter as exponent. Different values produce (slighly) different curves. Typical values are: 0.0: equally spaced (not recommended) 0.5: centripetal (default, recommended) 1.0: chord length (often used) """ from plugins.curve import PolyLine # set the knot values at the points nc = Q.shape[0] n = nc-1 # chord length d = PolyLine(Q).lengths() if (d==0.0).any(): utils.warn("Your point set appears to contain double points. Currently I cannot handle that. I will skip the doubles and try to go ahead.") Q = concatenate([Q[d!=0.0],Q[-1:]],axis=0) d = PolyLine(Q).lengths() if (d==0.0).any(): raise ValueError,"Double points in the data set are not allowed" # apply strategy d = d ** strategy d = d.cumsum() d /= d[-1] u = concatenate([[0.], d]) #print "u = ",u U,A = nurbs.curveGlobalInterpolationMatrix(Q,u,degree) #print "U = ",U #print "A = ",A P = linalg.solve(A,Q) #print "P = ",P return NurbsCurve(P,knots=U,degree=degree)
def load(self,try_resolve=False,quiet=False): """Load a project from file. The loaded definitions will update the current project. """ f = self.readHeader(quiet) if self.format < highest_format: if not quiet: print("Format looks like %s" % self.format) utils.warn('warn_old_project') with f: try: if not quiet: print("Unpickling gzip") pyf = gzip.GzipFile(fileobj=f,mode='rb') p = pickle_load(pyf,try_resolve) pyf.close() except: if not quiet: print("Unpickling clear") p = pickle_load(f,try_resolve) self.update(p)
def view(v,wait=True): """Show a named view, either a builtin or a user defined. This shows the current scene from another viewing angle. Switching views of a scene is much faster than redrawing a scene. Therefore this function is prefered over :func:`draw` when the actors in the scene remain unchanged and only the camera viewpoint changes. Just like :func:`draw`, this function obeys the drawing lock mechanism, and by default it will restart the lock to retard the next draing operation. """ pf.GUI.drawlock.wait() if v != 'last': angles = pf.canvas.view_angles.get(v) if not angles: utils.warn("A view named '%s' has not been created yet" % v) return pf.canvas.setCamera(None, angles) setView(v) pf.canvas.update() if wait: pf.GUI.drawlock.lock()
def exportDxf(filename, coll): """Export a collection of dxf parts a DXF file coll is a list of dxf objects Currently, only dxf objects of type 'Line' and 'Arc' can be exported. """ dxf = DxfExporter(filename) dxf.entities() for ent in coll: print(type(ent), ent) if isinstance(ent, curve.Line): dxf.line(ent.coords) elif isinstance(ent, curve.Arc): dxf.arc(ent.getCenter(), ent.radius, ent.getAngles()) elif isinstance(ent, curve.PolyLine): dxf.polyline(ent.coords) else: utils.warn('warn_dxf_export', data=type(ent)) dxf.endSection() dxf.close()
def drawText(text,pos,**kargs): """Show a text at position pos. Draws a text at a given position. The position can be either a 2D canvas position, specified in pixel coordinates (int), or a 3D position, specified in global world coordinates (float). In the latter case the text will be displayed on the canvas at the projected world point, and will move with that projection, while keeping the text unscaled and oriented to the viewer. The 3D mode is especially useful to annotate parts of the geometry with a label. Parameters: - `text`: string to be displayed. - `pos`: (2,) int or (3,) float: canvas or world position. - any other parameters are passed to :class:`opengl.textext.Text`. """ utils.warn("warn_drawText") A = actors.Text(text, pos, **kargs) drawActor(A) return A
def drawImage(image,w=0,h=0,x=-1,y=-1,color=colors.white,ontop=False): """Draws an image as a viewport decoration. Parameters: - `image`: a QImage or any data that can be converted to a QImage, e.g. the name of a raster image file. See also the :func:`loadImage` function. - `w`,`h`: width and height (in pixels) of the displayed image. If the supplied image has a different size, it will be rescaled. A value <= 0 will be replaced with the corresponding actual size of the image. - `x`,`y`: position of the lower left corner of the image. If negative, the image will be centered on the current viewport. - `color`: the color to mix in (AND) with the image. The default (white) will make all pixels appear as in the image. - `ontop`: determines whether the image will appear as a background (default) or at the front of the 3D scene (as on the camera glass). Returns the Decoration drawn. Note that the Decoration has a fixed size (and position) on the canvas and will not scale when the viewport size is changed. The :func:`bgcolor` function can be used to draw an image that completely fills the background. """ utils.warn("warn_drawImage_changed") from pyformex.plugins.imagearray import qimage2numpy from pyformex.opengl.decors import Rectangle image = qimage2numpy(image, resize=(w, h), indexed=False) w, h = image.shape[:2] if x < 0: x = (pf.canvas.width() - w) // 2 if y < 0: y = (pf.canvas.height() - h) // 2 R = Rectangle(x, y, x+w, y+h, color=color, texture=image, ontop=ontop) decorate(R) return R
def load(self, try_resolve=False, quiet=False): """Load a project from file. The loaded definitions will update the current project. """ f = self.readHeader(quiet) if self.format < highest_format: if not quiet: print("Format looks like %s" % self.format) utils.warn('warn_old_project') with f: try: if not quiet: print("Unpickling gzip") pyf = gzip.GzipFile(fileobj=f, mode='rb') p = pickle_load(pyf, try_resolve) pyf.close() except: if not quiet: print("Unpickling clear") p = pickle_load(f, try_resolve) self.update(p)
def readDXF(filename): """Read a DXF file and extract the recognized entities. `filename`: name of a .DXF file. Returns a multiline string with one line for each recognized entity, in a format that can directly be used by :func:`convertDXF`. This function requires the external program `dxfparser` which comes with the pyFormex distribution. It currently recognizes entities of type 'Arc', 'Line', 'Polyline', 'Vertex'. """ print(filename) if utils.hasExternal('dxfparser'): cmd = 'pyformex-dxfparser %s 2>/dev/null' % filename print(cmd) P = utils.command(cmd, shell=True) if P.sta == 0: return P.out else: return '' else: utils.warn('warn_dxf_noparser') return ''
def __init__(self,filename=None,access='wr',convert=True,signature=_signature_,compression=5,binary=True,data={},**kargs): """Create a new project.""" if 'create' in kargs: utils.warn("The create=True argument should be replaced with access='w'") if 'legacy' in kargs: utils.warn("The legacy=True argument has become superfluous") self.filename = filename self.access = access self.signature = str(signature) self.gzip = compression if compression in range(1,10) else 0 self.mode = 'b' if binary or compression > 0 else '' TrackedDict.__init__(self) if filename and os.path.exists(filename) and 'r' in self.access: # read existing contents self.load(convert) self.hits = 0 if data: self.update(data) if filename and access=='w': # destroy existing contents self.save() pf.debug("INITIAL hits = %s" % self.hits,pf.DEBUG.PROJECT)
def qimage2numpy(image, resize=(0, 0), order='RGBA', flip=True, indexed=None, expand=None): """Transform an image to a Numpy array. Parameters: - `image`: a QImage or any data that can be converted to a QImage, e.g. the name of an image file, in any of the formats supported by Qt. The image can be a full color image or an indexed type. Only 32bit and 8bit images are currently supported. - `resize`: a tuple of two integers (width,height). Positive value will force the image to be resized to this value. - `order`: string with a permutation of the characters 'RGBA', defining the order in which the colors are returned. Default is RGBA, so that result[...,0] gives the red component. Note however that QImage stores in ARGB order. You may also specify a subset of the 'RGBA' characters, in which case you will only get some of the color components. An often used value is 'RGB' to get the colors without the alpha value. - `flip`: boolean: if True, the image scanlines are flipped upside down. This is practical because image files are usually stored in top down order, while OpenGL uses an upwards positive direction, requiring a flip to show the image upright. - `indexed`: True, False or None. - If True, the result will be an indexed image where each pixel color is an index into a color table. Non-indexed image data will be converted. - If False, the result will be a full color array specifying the color of each pixel. Indexed images will be converted. - If None (default), no conversion is done and the resulting data are dependent on the image format. In all cases both a color and a colortable will be returned, but the latter will be None for non-indexed images. - `expand`: deprecated, retained for compatibility Returns: - if `indexed` is False: an int8 array with shape (height,width,4), holding the 4 components of the color of each pixel. Order of the components is as specified by the `order` argument. Indexed image formats will be expanded to a full color array. - if `indexed` is True: a tuple (colors,colortable) where colors is an (height,width) shaped int array of indices into the colortable, which is an int8 array with shape (ncolors,4). - if `indexed` is None (default), a tuple (colors,colortable) is returned, the type of which depend on the original image format: - for indexed formats, colors is an int (height,width) array of indices into the colortable, which is an int8 array with shape (ncolors,4). - for non-indexed formats, colors is a full (height,width,4) array and colortable is None. """ if expand is not None: utils.warn("depr_qimage2numpy_arg") indexed = not expand image = resizeImage(image, *resize) if indexed: image = image.convertToFormat(QImage.Format_Indexed8) h, w = image.height(), image.width() if image.format() in (QImage.Format_ARGB32_Premultiplied, QImage.Format_ARGB32, QImage.Format_RGB32): buf = image.bits() if not pf.options.pyside: buf = buf.asstring(image.numBytes()) ar = np.frombuffer(buf, dtype='ubyte', count=image.numBytes()).reshape(h, w, 4) idx = ['BGRA'.index(c) for c in order] ar = ar[..., idx] ct = None elif image.format() == QImage.Format_Indexed8: ct = np.array(image.colorTable(), dtype=np.uint32) #print("IMAGE FORMAT is INDEXED with %s colors" % ct.shape[0]) ct = ct.view(np.uint8).reshape(-1, 4) idx = ['BGRA'.index(c) for c in order] ct = ct[..., idx] buf = image.bits() if not pf.options.pyside: buf = buf.asstring(image.numBytes()) ar = np.frombuffer(buf, dtype=np.uint8) if ar.size % h == 0: ar = ar.reshape(h, -1) if ar.shape[1] > w: # The QImage buffer always has a size corresponding # with a width that is a multiple of 8. Here we strip # off the padding pixels of the QImage buffer to the # reported width. ar = ar[:, :w] if ar.size != w * h: # This should no longer happen, since we have now adjusted # the numpy buffer width to the correct image width. pf.warning( "Size of image data (%s) does not match the reported dimensions: %s x %s = %s" % (ar.size, w, h, w * h)) else: raise ValueError("qimage2numpy only supports 32bit and 8bit images") # Put upright as expected if flip: ar = np.flipud(ar) # Convert indexed to nonindexed if requested if indexed is False and ct is not None: ar = ct[ar] ct = None # Return only full colors if requested if indexed is False: return ar else: return ar, ct
def remesh(self, elementsizemode='edgelength', edgelength=None, area=None, areaarray=None, aspectratio=None, excludeprop=None, includeprop=None, preserveboundary=False, conformal='border', options=''): """Remesh a TriSurface. Returns the remeshed TriSurface. If the TriSurface has property numbers the property numbers will be inherited by the remeshed surface. Parameters: - `elementsizemode`: str: metric that is used for remeshing. `edgelength`, `area` and `areaarray` allow to specify a global target edgelength, area and areaarray (area at each node), respectively. - `edgelength`: float: global target triangle edgelength - `area`: float: global target triangle area - `areaarray`: array of float: nodal target triangle area - `aspectratio`: float: upper threshold for aspect ratio (default=1.2) - `includeprop`: either a single integer, or a list/array of integers. Only the regions with these property number(s) will be remeshed. This option is not compatible with `exludeprop`. - `excludeprop`: either a single integer, or a list/array of integers. The regions with these property number(s) will not be remeshed. This option is not compatible with `includeprop`. - `preserveboundary`: if True vmtk tries to keep the shape of the border. - `conformal`: None, `border` or `regionsborder`. If there is a border (ie. the surface is open) conformal=`border` preserves the border line (both points and connectivity); conformal=`regionsborder` preserves both border line and lines between regions with different property numbers. Detected BUGS: - preserveboundary = True With VMTK 1.3 and VTK6 there is a bug: if surface has a boundary (is not closed) and you want preserveboundary=False you can an error: python: /build/vtk6-E5SYwm/vtk6-6.3.0+dfsg1/Common/Core/vtkDataArrayTemplate.h:191: T vtkDataArrayTemplate<T>::GetValue(vtkIdType) [with T = int; vtkIdType = long long int]: Assertion `id >= 0 && id < this->Size' failed. Aborted As a workaround, you can - either close the surface (so there is no boundary, and preserveboundary will be ignored) - or add some extensions (with different property number) to the borders and use preserveboundary=True After remeshing you can clip the added parts using their property number. """ if includeprop is not None: if excludeprop is not None: raise ValueError('you cannot use both excludeprop and includeprop') else: ps = self.propSet() mask = in1d(ar1=ps, ar2=checkArray1D(includeprop)) if sum(mask) == 0: utils.warn("warn_vmtk_includeprop", data=(includeprop, ps)) return self excludeprop = ps[~mask] if conformal == 'border' or conformal == 'regionsborder': if elementsizemode == 'areaarray': raise ValueError( 'conformal (regions)border and areaarray cannot be used together (yet)!' ) #conformalBorder alters the node list. Afterwards, the nodes do not correspond with pointdata. if self.isClosedManifold() == False: if conformal == 'regionsborder': if self.propSet() is not None: if len(self.propSet()) > 1: return TriSurface.concatenate([ remesh(s2, elementsizemode=elementsizemode, edgelength=edgelength, area=area, areaarray=None, aspectratio=aspectratio, excludeprop=excludeprop, preserveboundary=preserveboundary, conformal='border') for s2 in self.splitProp() ]) added = TriSurface( self.getBorderMesh().convert('line3').setType('tri3')) s1 = self + added.setProp(-1) + added.setProp( -2) #add triangles on the border s1 = s1.fuse().compact().renumber( ) #this would mix up the areaarray!! excludeprop1 = array([-1, -2]) if excludeprop is not None: excludeprop1 = append(excludeprop1, asarray(excludeprop).reshape(-1)) return remesh(s1, elementsizemode=elementsizemode, edgelength=edgelength, area=area, areaarray=None, aspectratio=aspectratio, excludeprop=excludeprop1, preserveboundary=preserveboundary, conformal=None).cselectProp([-1, -2]).compact() else: if conformal is not None: raise ValueError( 'conformal should be either None, border or regionsborder') from pyformex.plugins.vtk_itf import writeVTP, checkClean, readVTKObject tmp = utils.tempFile(suffix='.vtp').name tmp1 = utils.tempFile(suffix='.vtp').name fielddata, celldata, pointdata = {}, {}, {} cmd = 'vmtk vmtksurfaceremeshing -ifile %s -ofile %s' % (tmp, tmp1) if elementsizemode == 'edgelength': if edgelength is None: self.getElemEdges() E = Mesh(self.coords, self.edges, eltype='line2') edgelength = E.lengths().mean() cmd += ' -elementsizemode edgelength -edgelength %f' % edgelength elif elementsizemode == 'area': if area is None: area = self.areas().mean() cmd += ' -elementsizemode area -area %f' % area elif elementsizemode == 'areaarray': if not checkClean(self): raise ValueError( "Mesh is not clean: vtk will alter the node numbering and the areaarray will not correspond to the node numbering. To clean: mesh.fuse().compact().renumber()" ) cmd += ' -elementsizemode areaarray -areaarray nodalareas ' pointdata['nodalareas'] = areaarray if aspectratio is not None: cmd += ' -aspectratio %f' % aspectratio if excludeprop is not None: cmd += ' -exclude ' + ' '.join([ '%d' % i for i in checkArray1D(excludeprop, kind='i', allow=None) ]) if preserveboundary: cmd += ' -preserveboundary 1' if self.prop is not None: cmd += ' -entityidsarray prop' print("Writing temp file %s" % tmp) cmd += ' %s' % options writeVTP(mesh=self, fn=tmp, pointdata=pointdata) print("Remeshing with command\n %s" % cmd) P = utils.command(cmd) os.remove(tmp) if P.sta: print("An error occurred during the remeshing.") print(P.out) return None [coords, cells, polys, lines, verts], fielddata, celldata, pointdata = readVTKObject(tmp1) S = TriSurface(coords, polys) if self.prop is not None: S = S.setProp(celldata['prop']) os.remove(tmp1) return S
def playScript(scr, name=None, filename=None, argv=[], pye=False): """Play a pyformex script scr. scr should be a valid Python text. There is a lock to prevent multiple scripts from being executed at the same time. This implies that pyFormex scripts can currently not be recurrent. If name is specified, set the global variable pyformex.scriptName to it when the script is started. If filename is specified, set the global variable __file__ to it. """ utils.warn('print_function') global starttime global exitrequested # (We only allow one script executing at a time!) # and scripts are non-reentrant if len(pf.scriptlock) > 0: print("!!Not executing because a script lock has been set: %s" % pf.scriptlock) return 1 scriptLock('__auto/script__') exitrequested = False if pf.GUI: pf.GUI.startRun() # Read the script, if a file was specified if not isinstance(scr, (str, unicode)): # scr should be an open file/stream if filename is None: filename = scr.name scr = scr.read() + '\n' # Get the globals g = Globals() if filename: g.update({'__file__': filename}) g.update({'argv': argv}) # BV: Should we continue support for this? if pye: n = (len(scr) + 1) // 2 scr = utils.mergeme(scr[:n], scr[n:]) # Now we can execute the script using these collected globals pf.scriptName = name exitall = False if pf.DEBUG.MEM: memu = memUsed() vmsiz = vmSize() pf.debug("MemUsed = %s; vmSize = %s" % (memu, vmsiz), pf.DEBUG.MEM) if filename is None: filename = '<string>' # Execute the code #starttime = time.clock() try: pf.interpreter.locals.update(g) pf.interpreter.runsource(scr, filename, 'exec') except SystemExit: print("EXIT FROM SCRIPT") finally: # honour the exit function if 'atExit' in g: atExit = g['atExit'] try: atExit() except: pf.debug('Error while calling script exit function', pf.DEBUG.SCRIPT) if pf.cfg['autoglobals']: if pf.console: g = pf.console.interpreter.locals autoExport(g) scriptRelease('__auto/script__') # release the lock if pf.GUI: pf.GUI.stopRun() pf.debug("MemUsed = %s; vmSize = %s" % (memUsed(), vmSize()), pf.DEBUG.MEM) pf.debug( "Diff MemUsed = %s; diff vmSize = %s" % (memUsed() - memu, vmSize() - vmsiz), pf.DEBUG.MEM) if exitall: pf.debug("Calling quit() from playscript", pf.DEBUG.SCRIPT) quit() return 0
## """Operations on triangulated surfaces using GTS functions. This module provides access to GTS from insisde pyFormex. """ from __future__ import absolute_import, division, print_function import pyformex as pf from pyformex.coords import * from pyformex import utils import os from pyformex.multi import multitask if not utils.hasExternal('gts-bin'): utils.warn("error_no_gts_bin") if not utils.hasExternal('gts-extra'): utils.warn("error_no_gts_extra") # # gts commands used: # in Debian package: stl2gts gts2stl gtscheck # not in Debian package: gtssplit gtscoarsen gtsrefine gtssmooth gtsinside # def read_gts_intersectioncurve(fn): import re from pyformex.formex import Formex RE = re.compile("^VECT 1 2 0 2 0 (?P<data>.*)$")