def write_stl_asc(fn,x): """Write a collection of triangles to an ascii .stl file. Parameters: - `fn`: file name, by preference ending with '.stl' or '.stla' - `x`: (ntriangles,3,3) shaped array with the vertices of the triangles """ if not x.shape[1:] == (4,3): raise ValueError,"Expected an (ntri,4,3) array, got %s" % x.shape pf.message("Writing ascii STL %s" % fn) with open(fn,'wb') as fil: fil.write("solid Created by %s\n" % pf.fullVersion()) for e in x: fil.write(" facet normal %s %s %s\n" % tuple(e[0])) fil.write(" outer loop\n") for p in e[1:]: fil.write(" vertex %s %s %s\n" % tuple(p)) fil.write(" endloop\n") fil.write(" endfacet\n") fil.write("endsolid\n") pf.message("Finished writing ascii STL, %s bytes" % utils.fileSize(fn))
def start_js(self,name): """Export the start of the js file. Parameters: - `name`: string: the name of the model that will be shown by default when displaying the webgl html page This function should only be executed once, before any other export functions. It is called automatically by the first call to exportScene. """ jsname = utils.changeExt(self.name, '.js') print("Exporting WebGL script to %s" % os.path.abspath(jsname)) if self.urlprefix: jsurl = self.urlprefix + jsname print("Include it in the .html page as '%s'" % jsurl) else: jsurl = jsname self.scripts.append(jsurl) self.jsfile = open(jsname, 'w') s = "// Script generated by %s\n" % pf.fullVersion() if self.jsheader: s += str(self.jsheader) s += """ var the_renderer; var the_controls; window.onload = function() { show%s(); } """% name self.jsfile.write(s)
def detectedSoftware(all=True): """Return a dict with all detected helper software""" if all: checkAllModules() checkAllExternals() system, host, release, version, arch = os.uname() soft = { 'System': OrderedDict([ ('pyFormex_version', the_version['pyformex']), ('pyFormex_installtype', pf.installtype), ('pyFormex_fullversion', pf.fullVersion()), ('pyFormex_libraries', ', '.join(Libraries())), ('pyFormex_shaders', ', '.join(Shaders())), ('Python_version', the_version['python']), ('Python_fullversion', sys.version.replace('\n', ' ')), ('System', system), ('Host', host), ('Release', release), ('Version', version), ('Arch', arch), ]), 'Modules': the_version, 'Externals': the_external, } return soft
def processReportOptions(): """Process the reporting options Returns True if at least one reporting option was found, otherwise False. """ ret = False if pf.options.docmodule is not None: for a in pf.options.docmodule: doc_module(a) # This is an all_exclusive option !! # So we immediately return return True if pf.options.experimental: sys.path.insert(1, os.path.join(pf.pyformexdir, 'experimental')) pf.options.whereami = True if pf.options.whereami: print(pf.fullVersion()) print("pyFormex executable: %s" % pf.executable) print("pyFormex installation (%s): %s " % (pf.installtype, pf.pyformexdir)) print("Python sys.path: %s" % sys.path) ret = True if pf.options.detect: print("Detecting installed helper software") print(software.reportSoftware()) ret = True if pf.options.listmodules is not None: if not pf.options.listmodules: # Set default subpackages pf.options.listmodules = [ 'core', 'lib', 'plugins', 'gui', 'opengl' ] list_modules(pf.options.listmodules) ret = True if pf.options.pytest is not None: run_pytest(pf.options.pytest) ret = True if pf.options.doctest is not None: for a in pf.options.doctest: doctest_module(a) ret = True if pf.options.remove: remove_pyFormex(pf.pyformexdir, pf.executable) # After this option, we can not continue, # so this should be the last option processed ret = True return ret
def openProject(fn=None,exist=False,access=['wr','rw','w','r'],default=None): """Open a (new or old) Project file. A dialog is presented to ask the user for a Project file name and the access modalities. The parameters help in setting sensible defaults for the user and in delimiting his options. Depending on he results of the dialog, either a new project is created or an old one is opened, or nothing is done. If a project is opened, it is returned, else the return value is None. Parameters: - `fn`: filename: if specified, the Project file dialog will start with the specified file, otherwise it will start in the current directory. - `exist`: boolean: if False (default), the user can create new project files as well as open existing ones. Use exist=True or :func:`openExistingProject` to only accept existing project files. - `access`: a list of :class:`Project` access modes to be presented to the user. - `default`: the access mode that is presented as default to the user. If not specified, the first option of `access` will be the default. """ if type(access) == str: access = [access] cur = fn if fn else '.' typ = utils.fileDescription(['pyf','all']) res = widgets.ProjectSelection(cur,typ,exist=exist,access=access,default=default,convert=True).getResult() if not res: return fn = res.fn if not fn.endswith('.pyf'): fn += '.pyf' access = res.acc compression = res.cpr convert = res.cvt signature = pf.fullVersion() # OK, we have all data, now create/open the project pf.message("Opening project %s" % fn) pf.GUI.setBusy() # loading may take a while try: proj = project.Project(fn,access=access,convert=convert,signature=signature,compression=compression) if proj.signature != signature: pf.warning("The project was written with %s, while you are now running %s. If the latter is the newer one, this should probably not cause any problems. Saving is always done in the current format of the running version. Save your project and this message will be avoided on the next reopening." % (proj.signature,signature)) except: proj = None raise finally: pf.GUI.setBusy(False) proj.hits = 0 pf.debug("START COUNTING HITS",pf.DEBUG.PROJECT) return proj
def openProject(fn=None,exist=False,access=['wr', 'rw', 'w', 'r'],default=None): """Open a (new or old) Project file. A dialog is presented to ask the user for a Project file name and the access modalities. The parameters help in setting sensible defaults for the user and in delimiting his options. Depending on he results of the dialog, either a new project is created or an old one is opened, or nothing is done. If a project is opened, it is returned, else the return value is None. Parameters: - `fn`: filename: if specified, the Project file dialog will start with the specified file, otherwise it will start in the current directory. - `exist`: boolean: if False (default), the user can create new project files as well as open existing ones. Use exist=True or :func:`openExistingProject` to only accept existing project files. - `access`: a list of :class:`Project` access modes to be presented to the user. - `default`: the access mode that is presented as default to the user. If not specified, the first option of `access` will be the default. """ if isinstance(access, str): access = [access] cur = fn if fn else '.' res = widgets.ProjectSelection(cur, ['pyf', 'all'], exist=exist, access=access, default=default, convert=True).getResults() if not res: return fn = res.fn if not fn.endswith('.pyf'): fn += '.pyf' access = res.acc compression = res.cpr convert = res.cvt signature = pf.fullVersion() # OK, we have all data, now create/open the project print("Opening project %s" % fn) pf.GUI.setBusy() # loading may take a while try: proj = project.Project(fn, access=access, convert=convert, signature=signature, compression=compression) if proj.signature != signature: pf.warning("The project was written with %s, while you are now running %s. If the latter is the newer one, this should probably not cause any problems. Saving is always done in the current format of the running version. Save your project and this message will be avoided on the next reopening." % (proj.signature, signature)) except: proj = None raise finally: pf.GUI.setBusy(False) proj.hits = 0 pf.debug("START COUNTING HITS", pf.DEBUG.PROJECT) return proj
def write_stl_bin(fn, x, color=None): """Write a binary stl. Parameters: - `x`: (ntri,4,3) float array describin ntri triangles. The first item of each triangle is the normal, the other three are the vertices. - `color`: (4,) int array with values in the range 0..255. These are the red, green, blue and alpha components of the color. This is a single color for all the triangles, and will be stored in the header of the STL file. """ x = checkArray(x, shape=(-1, 4, 3), kind='f') if color is not None: #color = checkArray(color, shape=(4,), kind='i').astype(np.uint8) color = checkArray(color, shape=(4, ), kind='u', allow='i').astype(np.uint8) def addTriangle(i): x[i].tofile(fil) fil.write('\x00\x00') print("Writing binary STL %s" % fn) ver = pf.fullVersion() if len(ver) > 50: ver = ver[:50] if color is None: color = '' else: color = "COLOR=%4s" % color.tostring() print("Adding %s to the header" % color) with open(fn, 'wb') as fil: head = "%-50s%-30s" % (ver, color) fil.write(head) ntri = x.shape[0] print("Number of triangles: %s" % ntri) np.array(ntri).astype(np.int32).tofile(fil) x = x.astype(np.float32) [addTriangle(i) for i in range(ntri)] print("Finished writing binary STL, %s bytes" % utils.fileSize(fn))
def write_stl_bin(fn,x,color=None): """Write a binary stl. Parameters: - `x`: (ntri,4,3) float array describin ntri triangles. The first item of each triangle is the normal, the other three are the vertices. - `color`: (4,) int array with values in the range 0..255. These are the red, green, blue and alpha components of the color. This is a single color for all the triangles, and will be stored in the header of the STL file. """ x = checkArray(x,shape=(-1,4,3),kind='f') if color is not None: color = checkArray(color,shape=(4,),kind='i').astype(np.uint8) def addTriangle(i): x[i].tofile(fil) fil.write('\x00\x00') pf.message("Writing binary STL %s" % fn) ver = pf.fullVersion() if len(ver) > 50: ver = ver[:50] if color is None: color = '' else: color = "COLOR=%4s" % color.tostring() pf.message("Adding %s to the header" % color) with open(fn,'wb') as fil: head = "%-50s%-30s" % (ver,color) fil.write(head) ntri = x.shape[0] pf.message("Number of triangles: %s" % ntri) np.array(ntri).astype(np.int32).tofile(fil) x = x.astype(np.float32) [ addTriangle(i) for i in range(ntri) ] pf.message("Finished writing binary STL, %s bytes" % utils.fileSize(fn))
def reportDetected(): notfound = '** Not Found **' s = "%s\n" % pf.fullVersion() s += "\nInstall type: %s\n" % pf.installtype s += "\npyFormex C libraries: %s\n" % Libraries() s += "\nPython version: %s\n" % sys.version s += "\nOperating system: %s\n" % sys.platform s += "\nDetected Python Modules:\n" the_version.sort(sorted(the_version.keys())) for k in the_version: v = the_version[k] if not v: v = notfound s += "%s (%s)\n" % (k, v) s += "\nDetected External Programs:\n" the_external.sort(sorted(the_external.keys())) for k in the_external: v = the_external[k] if not v: v = notfound s += "%s (%s)\n" % (k, v) return s
def reportDetected(): notfound = '** Not Found **' s = "%s\n" % pf.fullVersion() s += "\nInstall type: %s\n" % pf.installtype s += "\npyFormex C libraries: %s\n" % Libraries() s += "\nPython version: %s\n" % sys.version s += "\nOperating system: %s\n" % sys.platform s += "\nDetected Python Modules:\n" the_version.sort(sorted(the_version.keys())) for k in the_version: v = the_version[k] if not v: v = notfound s += "%s (%s)\n" % ( k,v) s += "\nDetected External Programs:\n" the_external.sort(sorted(the_external.keys())) for k in the_external: v = the_external[k] if not v: v = notfound s += "%s (%s)\n" % ( k,v) return s
def createWebglHtml(name,scripts=[],bgcolor='white',body='', description='WebGL model', keywords="pyFormex, WebGL", author='', title='pyFormex WebGL model',header=''): """Create a html file for a WebGL model. Returns the absolute pathname to the created HTML file. """ s = """<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="generator" content="%s"> """ % pf.fullVersion() if description: s += '<meta name="description" content="%s">\n' % description if keywords: s += '<meta name="keywords" content="%s">\n' % keywords if author: s += '<meta name="author" content="%s">\n' % author s += "<title>%s</title>\n" % title if header: s += header for scr in scripts: s += '<script type="text/javascript" src="%s"></script>\n' % scr s += """ </head> <body bgcolor='%s'> %s </body> </html> """ % (colors.WEBcolor(bgcolor),body) with open(name, 'w') as htmlfile: htmlfile.write(s) return os.path.abspath(name)
def createMultiWebGL(): """Creates a multimodel WebGL from single WebGL models""" from pyformex.gui.draw import askFilename, askNewFilename, ack, showHTML models = askFilename(filter=['html','js'],multi=True) if not models: return combined = askNewFilename(filter=['html','js'],caption="Enter name of the combined model") if not combined: return fout = open(utils.changeExt(combined,'js'),'w') print("""// Script generated by %s var the_renderer; var the_menu; window.onload = function() { show%s() } """ % (pf.fullVersion(),utils.projectName(models[0])),file=fout) body = createdBy() body += "<div style='position:absolute;top:150px;left:10px;'>" for model in models: name = utils.projectName(model) body += "<button onclick='show%s()'>Show %s</button><br />" % (name,name) print('',file=fout) print("show%s = function() {" % name,file=fout) fn = utils.changeExt(model,'js') f = open(fn,'r') s = f.readlines() f.close() while len(s) > 0: line = s.pop(0) if line.startswith("window.onload = function"): break line = s.pop(0) if line.startswith("var r = new X.renderer3D"): print("""if (the_menu != null) the_menu.destroy(); if (the_renderer != null) the_renderer.destroy(); the_renderer = new X.renderer3D(); var r = the_renderer; """,file=fout) else: raise ValueError("Invalid WebGL script %s" % fn) while len(s) > 0: line = s.pop(0) print(line,file=fout) if line.startswith("var gui = new dat.GUI"): print("the_menu = gui;",file=fout) fout.close() body += "</div>" fn = createWebglHtml( utils.changeExt(combined,'html'), scripts=[ pf.cfg['webgl/script'], pf.cfg['webgl/guiscript'], utils.changeExt(combined,'js') ], body=body ) if ack("Show the scene in your browser?"): showHTML(fn)
def startGUI(args): """Create the QT4 application and GUI. A (possibly empty) list of command line options should be provided. QT4 wil remove the recognized QT4 and X11 options. """ # This seems to be the only way to make sure the numeric conversion is # always correct # QtCore.QLocale.setDefault(QtCore.QLocale.c()) # #pf.options.debug = -1 pf.debug("Arguments passed to the QApplication: %s" % args,pf.DEBUG.INFO) pf.app = Application(args) # pf.debug("Arguments left after constructing the QApplication: %s" % args,pf.DEBUG.INFO) pf.debug("Arguments left after constructing the QApplication: %s" % '\n'.join(pf.app.arguments()),pf.DEBUG.INFO) #pf.options.debug = 0 # As far as I have been testing this, the args passed to the Qt application are # NOT acknowledged and neither are they removed!! pf.debug("Setting application attributes",pf.DEBUG.INFO) pf.app.setOrganizationName("pyformex.org") pf.app.setOrganizationDomain("pyformex.org") pf.app.setApplicationName("pyFormex") pf.app.setApplicationVersion(pf.__version__) ## pf.settings = QtCore.QSettings("pyformex.org", "pyFormex") ## pf.settings.setValue("testje","testvalue") # Quit application if last window closed pf.app.lastWindowClosed.connect(pf.app.quit) # Check if we have DRI pf.debug("Setting OpenGL format",pf.DEBUG.OPENGL) dri = hasDRI() # Check for existing pyFormex processes pf.debug("Checking for running pyFormex",pf.DEBUG.INFO) if pf.X11: windowname,running = findOldProcesses() else: windowname,running = "UNKOWN",[] pf.debug("%s,%s" % (windowname,running),pf.DEBUG.INFO) while len(running) > 0: if len(running) >= 16: print("Too many open pyFormex windows --- bailing out") return -1 pids = [ i[2] for i in running if i[2] is not None ] warning = """.. pyFormex is already running on this screen ------------------------------------------ A main pyFormex window already exists on your screen. If you really intended to start another instance of pyFormex, you can just continue now. The window might however be a leftover from a previously crashed pyFormex session, in which case you might not even see the window anymore, nor be able to shut down that running process. In that case, you would better bail out now and try to fix the problem by killing the related process(es). If you think you have already killed those processes, you may check it by rerunning the tests. """ actions = ['Really Continue','Rerun the tests','Bail out and fix the problem'] if pids: warning += """ I have identified the process(es) by their PID as:: %s If you trust me enough, you can also have me kill this processes for you. """ % pids actions[2:2] = ['Kill the running processes'] if dri: answer = draw.ask(warning,actions) else: warning += """ I have detected that the Direct Rendering Infrastructure is not activated on your system. Continuing with a second instance of pyFormex may crash your XWindow system. You should seriously consider to bail out now!!! """ answer = draw.warning(warning,actions) if answer == 'Really Continue': break # OK, Go ahead elif answer == 'Rerun the tests': windowname,running = findOldProcesses() # try again elif answer == 'Kill the running processes': killProcesses(pids) windowname,running = findOldProcesses() # try again else: return -1 # I'm out of here! # Load the splash image pf.debug("Loading the splash image",pf.DEBUG.GUI) splash = None if os.path.exists(pf.cfg['gui/splash']): pf.debug('Loading splash %s' % pf.cfg['gui/splash'],pf.DEBUG.GUI) splashimage = QtGui.QPixmap(pf.cfg['gui/splash']) splash = QtGui.QSplashScreen(splashimage) splash.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint|QtCore.Qt.SplashScreen) splash.setFont(QtGui.QFont("Helvetica",20)) splash.showMessage(pf.Version,QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop,QtCore.Qt.red) splash.show() # create GUI, show it, run it pf.debug("Creating the GUI",pf.DEBUG.GUI) desktop = pf.app.desktop() pf.maxsize = Size(desktop.availableGeometry()) size = pf.cfg.get('gui/size',(800,600)) pos = pf.cfg.get('gui/pos',(0,0)) bdsize = pf.cfg.get('gui/bdsize',(800,600)) size = MinSize(size,pf.maxsize) # Create the GUI pf.GUI = Gui(windowname, pf.cfg.get('gui/size',(800,600)), pf.cfg.get('gui/pos',(0,0)), pf.cfg.get('gui/bdsize',(800,600)), ) # set the appearance pf.debug("Setting Appearence",pf.DEBUG.GUI) pf.GUI.setAppearence() # setup the message board pf.board = pf.GUI.board pf.board.write("""%s (C) Benedict Verhegghe pyFormex comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU General Public License, version 3 or later. See Help->License or the file COPYING for details. """ % pf.fullVersion()) # Set interaction functions def show_warning(message,category,filename,lineno,file=None,line=None): """Replace the default warnings.showwarning We display the warnings using our interactive warning widget. This feature can be turned off by setting cfg['warnings/popup'] = False """ full_message = warnings.formatwarning(message,category,filename,lineno,line) pf.message(full_message) res,check = draw.showMessage(full_message,level='warning',check="Do not show this warning anymore in future sessions") if check[0]: utils.filterWarning(str(message)) utils.saveWarningFilter(str(message)) if pf.cfg['warnings/popup']: warnings.showwarning = show_warning pf.message = draw.message pf.warning = draw.warning pf.error = draw.error # setup the canvas pf.debug("Setting the canvas",pf.DEBUG.GUI) pf.GUI.processEvents() pf.GUI.viewports.changeLayout(1) pf.GUI.viewports.setCurrent(0) #pf.canvas = pf.GUI.viewports.current pf.canvas.setRenderMode(pf.cfg['draw/rendermode']) # # PYSIDE: raises (intercepted) exception on startup # draw.reset() # setup the status bar pf.debug("Setup status bar",pf.DEBUG.GUI) pf.GUI.addInputBox() pf.GUI.toggleInputBox(False) pf.GUI.addCoordsTracker() pf.GUI.toggleCoordsTracker(pf.cfg.get('gui/coordsbox',False)) pf.debug("Using window name %s" % pf.GUI.windowTitle(),pf.DEBUG.GUI) # Script menu pf.GUI.scriptmenu = appMenu.createAppMenu(mode='script',parent=pf.GUI.menu,before='help') # App menu pf.GUI.appmenu = appMenu.createAppMenu(parent=pf.GUI.menu,before='help') # BV: removed, because they are in the script/app menus, # and the file menu is alredy very loaded ## # Link History Menus also in the File menu ## parent = pf.GUI.menu.item('file') ## before = parent.item('---1') ## if pf.GUI.apphistory: ## parent.insert_menu(pf.GUI.apphistory,before) ## if pf.GUI.scripthistory: ## parent.insert_menu(pf.GUI.scripthistory,before) # Create databases createDatabases() # Plugin menus import plugins filemenu = pf.GUI.menu.item('file') pf.gui.plugin_menu = plugins.create_plugin_menu(filemenu,before='---1') # Load configured plugins, ignore if not found plugins.loadConfiguredPlugins() # show current application/file appname = pf.cfg['curfile'] pf.GUI.setcurfile(appname) # Last minute menu modifications can go here # cleanup pf.GUI.setBusy(False) # HERE pf.GUI.addStatusBarButtons() if splash is not None: # remove the splash window splash.finish(pf.GUI) pf.GUI.setBusy(False) # OR HERE pf.debug("Showing the GUI",pf.DEBUG.GUI) pf.GUI.show() # redirect standard output to board # TODO: this should disappear when we have buffered stdout # and moved this up into GUI init pf.GUI.board.redirect(pf.cfg['gui/redirect']) pf.GUI.update() if pf.cfg['gui/fortune']: sta,out = utils.runCommand(pf.cfg['fortune']) if sta == 0: draw.showInfo(out) #pf.app.setQuitOnLastWindowClosed(False) pf.app_started = True pf.GUI.processEvents() # load last project # # TODO if pf.cfg['openlastproj']: fn = pf.cfg['curproj'] if fn: try: proj = fileMenu.readProjectFile(fn) fileMenu.setProject(proj) except: # Avoid crashes from a faulty project file # TODO: should we push this up to fileMenu.readProjectFile ? # pf.message("Could not load the current project %s" % fn) # return 0
def parseArguments(args): """Process command line arguments Parameters: - args: a list of command line arguments. The arguments of the pyFormex command line can be splitted in options and remaining arguments. This function will split the options from the other arguments and store them in the variable pf.options for access throughout pyFormex. The remaining arguments are stored in pf.options.args """ import argparse parser = argparse.ArgumentParser( prog=pf.__prog__, # usage = "%(prog)s [<options>] [ [ scriptname [scriptargs] ] ...]", description=pf.Description, epilog="More info on http://pyformex.org", # formatter = optparse.TitledHelpFormatter(), ) MO = parser.add_argument # create a shorthand notation MO("--version", action='version', version=pf.fullVersion()) MO( "--gui", action="store_true", dest="gui", default=None, help= "Start the GUI (this is the default when no scriptname argument is given)", ) MO( "--nogui", action="store_false", dest="gui", default=None, help= "Do not start the GUI (this is the default when a scriptname argument is given)", ) MO( "--nocanvas", action="store_false", dest="canvas", default=True, help= "Do not add an OpenGL canvas to the GUI (this is for development purposes only!)", ) MO( "--interactive", action="store_true", dest="interactive", default=False, help= "Go into interactive mode after processing the command line parameters. This is implied by the --gui option.", ) MO( "--uselib", action="store_true", dest="uselib", default=None, help="Use the pyFormex C lib if available. This is the default.", ) MO( "--nouselib", action="store_false", dest="uselib", default=None, help="Do not use the pyFormex C-lib.", ) MO( "--config", action="store", dest="config", default=None, help= "Use file CONFIG for settings. This file is loaded in addition to the normal configuration files and overwrites their settings. Any changes will be saved to this file.", ) MO( "--nodefaultconfig", action="store_true", dest="nodefaultconfig", default=False, help= "Skip the default site and user config files. This option can only be used in conjunction with the --config option.", ) MO( "--redirect", action="store_true", dest="redirect", default=None, help= "Redirect standard output to the message board (ignored with --nogui)", ) MO( "--noredirect", action="store_false", dest="redirect", help="Do not redirect standard output to the message board.", ) MO( "--debug", action="store", dest="debug", default='', help= "Display debugging information to sys.stdout. The value is a comma-separated list of (case-insensitive) strings corresponding with the attributes of the DebugLevels class. The individual values are OR-ed together to produce a final debug value. The special value 'all' can be used to switch on all debug info.", ) MO( "--debuglevel", action="store", dest="debuglevel", type=int, default=0, help= "Display debugging info to sys.stdout. The value is an int with the bits of the requested debug levels set. A value of -1 switches on all debug info. If this option is used, it overrides the --debug option.", ) MO( "--mesa", action="store_true", dest="mesa", default=False, help= "Force the use of software 3D rendering through the mesa libs. The default is to use hardware accelerated rendering whenever possible. This flag can be useful when running pyFormex remotely on another host. The hardware accelerated version will not work over remote X.", ) MO( "--dri", action="store_true", dest="dri", default=None, help= "Use Direct Rendering Infrastructure. By default, direct rendering will be used if available.", ) MO( "--nodri", action="store_false", dest="dri", default=None, help= "Do not use the Direct Rendering Infrastructure. This may be used to turn off the direc rendering, e.g. to allow better capturing of images and movies.", ) MO( "--opengl", action="store", dest="opengl", default='2.0', help= "Force the use of a specific OpenGL version. The version should be specified as a string 'a.b'. The default is 2.0", ) MO( "--shader", action="store", dest="shader", default='', help= "Force the use of an alternate GPU shader for the OpenGL rendering. If the default selected shader does not work well for your hardware, you can use this option to try one of the alternate shaders. See 'pyformex --detect' for a list of the available shaders.", ) MO( "--newviewports", action="store_true", dest="newviewports", default=False, help= "Use the new multiple viewport canvas implementation. This is an experimental feature only intended for developers.", ) MO( "--testcamera", action="store_true", dest="testcamera", default=False, help="Print camera settings whenever they change.", ) MO( "--memtrack", action="store_true", dest="memtrack", default=False, help="Track memory for leaks. This is only for developers.", ) MO( "--fastnurbs", action="store_true", dest="fastnurbs", default=False, help="Test C library nurbs drawing: only for developers!", ) MO( "--pyside", action="store_true", dest="pyside", default=None, help="Use the PySide bindings for QT4 libraries", ) MO( "--pyqt4", action="store_false", dest="pyside", default=None, help="Use the PyQt4 bindings for QT4 libraries", ) MO( "--unicode", action="store_true", dest="unicode", default=False, help="Allow unicode filenames. Beware: this is experimental!", ) MO( "--experimental", action="store_true", dest="experimental", default=False, help= "Allow the pyformex/experimental modules to be loaded. Beware: this should only be used if you know what you are doing!", ) MO( "--listfiles", action="store_true", dest="listfiles", default=False, help="List the pyFormex Python source files and exit.", ) MO( "--listmodules", action="store", dest="listmodules", default=None, metavar='PKG', nargs='*', help= "List the Python modules in the specified pyFormex subpackage and exit. Specify 'core' to just list the modules in the pyFormex top level. Specify 'all' to list all modules. The default is to list the modules in core, lib, plugins, gui, opengl.", ) MO( "--search", action="store_true", dest="search", default=False, help= "Search the pyformex source for a specified pattern and exit. This can optionally be followed by -- followed by options for the grep command and/or '-a' to search all files in the extended search path. The final argument is the pattern to search. '-e' before the pattern will interprete this as an extended regular expression. '-l' option only lists the names of the matching files.", ) MO( "--remove", action="store_true", dest="remove", default=False, help= "Remove the pyFormex installation and exit. This option only works when pyFormex was installed from a tarball release using the supplied install procedure. If you install from a distribution package (e.g. Debian), you should use your distribution's package tools to remove pyFormex. If you run pyFormex directly from SVN sources, you should just remove the whole checked out source tree.", ) MO( "--whereami", action="store_true", dest="whereami", default=False, help="Show where the pyformex package is installed and exit.", ) MO( "--detect", action="store_true", dest="detect", default=False, help="Show detected helper software and exit.", ) MO( "--doctest", action="store", dest="doctest", default=None, metavar='MODULE', nargs='*', help= "Run the docstring tests for the specified pyFormex modules and exit. MODULE name is specified in Python syntax, relative to pyformex package (e.g. coords, plugins.curve).", ) MO( "--pytest", action="store", dest="pytest", default=None, metavar='MODULE', nargs='*', help= "Run the pytest tests for the specified pyFormex modules and exit. MODULE name is specified in Python syntax, relative to pyformex package (e.g. coords, plugins.curve).", ) MO( "--docmodule", action="store", dest="docmodule", default=None, metavar='MODULE', nargs='*', help= "Print the autogenerated documentation for module MODULE and exit. This is mostly useful during the generation of the pyFormex reference manual, as the produced result still needs to be run through the Sphinx documentation generator. MODULE is the name of a pyFormex module (Python syntax).", ) MO( '-c', "--script", action="store", dest="script", default=None, metavar='SCRIPT', help= "A pyFormex script to be executed at startup. It is executed before any specified script files. This is mostly used in --nogui mode, when the script to execute is very short.", ) MO( "args", action="store", nargs='*', metavar='FILE', help= "pyFormex script files to be executed on startup. The files should have a .py extension. Their contents will be executed as a pyFormex script. While mostly used with the --nogui option, this will also work in GUI mode.", ) pf.options = parser.parse_args(args) # TODO: maybe store pf.parser instead ?? pf.print_help = parser.print_help # Set debug level if pf.options.debug and not pf.options.debuglevel: pf.options.debuglevel = pf.debugLevel(pf.options.debug.split(',')) # Check for invalid options if pf.options.nodefaultconfig and not pf.options.config: print( "\nInvalid options: --nodefaultconfig but no --config option\nDo pyformex --help for help on options.\n" ) sys.exit() pf.debug("Options: %s" % pf.options, pf.DEBUG.ALL)
def run(argv=[]): """This is a fairly generic main() function. It is responsible for reading the configuration file(s), processing the command line options and starting the application. The basic configuration file is 'pyformexrc' located in the pyformex directory. It should always be present and be left unchanged. You can copy this file to another location if you want to make changes. By default, pyformex will try to read the following extra configuration files (in this order: default settings: <pyformexdir>/pyformexrc system-wide settings: /etc/pyformexrc user settings: $HOME/.pyformex/pyformexrc local settings $PWD/.pyformexrc Also, an extra config file can be specified in the command line. Config file settings always override previous ones. On exit, the preferences that were changed are written to the last read config file. Changed settings are those that differ from the settings in all but the last one. """ # Create a config instance pf.cfg = Config() # Fill in the pyformexdir and homedir variables # (use a read, not an update) if os.name == 'posix': homedir = os.environ['HOME'] elif os.name == 'nt': homedir = os.environ['HOMEDRIVE'] + os.environ['HOMEPATH'] pf.cfg.read("pyformexdir = '%s'\n" % pyformexdir) pf.cfg.read("homedir = '%s'\n" % homedir) # Read the defaults (before the options) defaults = os.path.join(pyformexdir, "pyformexrc") pf.cfg.read(defaults) # Process options import optparse from optparse import make_option as MO parser = optparse.OptionParser( # THE Qapp options are removed, because it does not seem to work !!! # SEE the comments in the gui.startGUI function usage="usage: %prog [<options>] [ [ scriptname [scriptargs] ] ...]", version=pf.fullVersion(), description=pf.Description, formatter=optparse.TitledHelpFormatter(), option_list=[ MO( "--gui", action="store_true", dest="gui", default=None, help= "Start the GUI (this is the default when no scriptname argument is given)", ), MO( "--nogui", action="store_false", dest="gui", default=None, help= "Do not start the GUI (this is the default when a scriptname argument is given)", ), MO( "--interactive", action="store_true", dest="interactive", default=False, help= "Go into interactive mode after processing the command line parameters. This is implied by the --gui option.", ), MO( "--dri", action="store_true", dest="dri", default=None, help= "Use Direct Rendering Infrastructure. By default, direct rendering will be used if available.", ), MO( "--nodri", action="store_false", dest="dri", default=None, help= "Do not use the Direct Rendering Infrastructure. This may be used to turn off the direc rendering, e.g. to allow better capturing of images and movies.", ), MO( "--uselib", action="store_true", dest="uselib", default=None, help= "Use the pyFormex C lib if available. This is the default.", ), MO( "--nouselib", action="store_false", dest="uselib", default=None, help="Do not use the pyFormex C-lib.", ), MO( "--commands", action="store_true", dest="commands", default=False, help= "Use the commands module to execute external commands. Default is to use the subprocess module.", ), MO( "--config", action="store", dest="config", default=None, help="Use file CONFIG for settings", ), MO( "--nodefaultconfig", action="store_true", dest="nodefaultconfig", default=False, help= "Skip the default site and user config files. This option can only be used in conjunction with the --config option.", ), MO( "--redirect", action="store_true", dest="redirect", default=None, help= "Redirect standard output to the message board (ignored with --nogui)", ), MO( "--noredirect", action="store_false", dest="redirect", help="Do not redirect standard output to the message board.", ), MO( "--debug", action="store", dest="debug", default='', help= "Display debugging information to sys.stdout. The value is a comma-separated list of (case-insensitive) strings corresponding with the attributes of the DebugLevels class. The individual values are OR-ed together to produce a final debug value. The special value 'all' can be used to switch on all debug info.", ), MO( "--debuglevel", action="store", dest="debuglevel", type="int", default=0, help= "Display debugging info to sys.stdout. The value is an int with the bits of the requested debug levels set. A value of -1 switches on all debug info. If this option is used, it overrides the --debug option.", ), MO( "--newviewports", action="store_true", dest="newviewports", default=False, help= "Use the new multiple viewport canvas implementation. This is an experimental feature only intended for developers.", ), MO( "--testmodule", action="store", dest="testmodule", default=None, help= "Run the docstring tests for module TESTMODULE. TESTMODULE is the name of the module, using . as path separator.", ), MO( "--testcamera", action="store_true", dest="testcamera", default=False, help="Print camera settings whenever they change.", ), MO( "--testexecutor", action="store_true", dest="executor", default=False, help="Test alternate executor: only for developers!", ), MO( "--memtrack", action="store_true", dest="memtrack", default=False, help="Track memory for leaks. This is only for developers.", ), MO( "--fastnurbs", action="store_true", dest="fastnurbs", default=False, help="Test C library nurbs drawing: only for developers!", ), MO( "--pyside", action="store_true", dest="pyside", default=None, help="Use the PySide bindings for QT4 libraries", ), MO( "--pyqt4", action="store_false", dest="pyside", default=None, help="Use the PyQt4 bindings for QT4 libraries", ), MO( "--opengl2", action="store_true", dest="opengl2", default=False, help= "Use the new OpenGL rendering engine. This is an experimental feature only intended for developers.", ), MO( "--listfiles", action="store_true", dest="listfiles", default=False, help="List the pyformex Python source files.", ), MO( "--search", action="store_true", dest="search", default=False, help= "Search the pyformex source for a specified pattern and exit. This can optionally be followed by -- followed by options for the grep command and/or '-a' to search all files in the extended search path. The final argument is the pattern to search. '-e' before the pattern will interprete this as an extended regular expression. '-l' option only lists the names of the matching files.", ), MO( "--remove", action="store_true", dest="remove", default=False, help= "Remove the pyFormex installation and exit. This option only works when pyFormex was installed from a tarball release using the supplied install procedure. If you install from a distribution package (e.g. Debian), you should use your distribution's package tools to remove pyFormex. If you run pyFormex directly from SVN sources, you should just remove the whole checked out source tree.", ), MO( "--whereami", action="store_true", dest="whereami", default=False, help="Show where the pyformex package is installed and exit", ), MO( "--detect", action="store_true", dest="detect", default=False, help="Show detected helper software and exit", ), ]) pf.options, args = parser.parse_args(argv) pf.print_help = parser.print_help # Set debug level if pf.options.debug and not pf.options.debuglevel: pf.options.debuglevel = pf.debugLevel(pf.options.debug.split(',')) # process options if pf.options.nodefaultconfig and not pf.options.config: print( "\nInvalid options: --nodefaultconfig but no --config option\nDo pyformex --help for help on options.\n" ) sys.exit() pf.debug("Options: %s" % pf.options, pf.DEBUG.ALL) ########## Process special options which will not start pyFormex ####### if pf.options.testmodule: for a in pf.options.testmodule.split(','): test_module(a) return if pf.options.remove: remove_pyFormex(pyformexdir, pf.bindir) return if pf.options.whereami: # or pf.options.detect : pf.options.debuglevel |= pf.DEBUG.INFO pf.debug("pyformex script started from %s" % pf.bindir, pf.DEBUG.INFO) pf.debug("I found pyFormex installed in %s " % pyformexdir, pf.DEBUG.INFO) pf.debug("Current Python sys.path: %s" % sys.path, pf.DEBUG.INFO) if pf.options.detect: print("Detecting installed helper software") utils.checkExternal() print(utils.reportDetected()) if pf.options.whereami or pf.options.detect: return ########### Read the config files #################### # These values should not be changed pf.cfg.userprefs = os.path.join(pf.cfg.userconfdir, 'pyformexrc') # Set the config files if pf.options.nodefaultconfig: sysprefs = [] userprefs = [] else: sysprefs = [pf.cfg.siteprefs] userprefs = [pf.cfg.userprefs] if os.path.exists(pf.cfg.localprefs): userprefs.append(pf.cfg.localprefs) if pf.options.config: userprefs.append(pf.options.config) if len(userprefs) == 0: # We should always have a place to store the user preferences userprefs = [pf.cfg.userprefs] pf.preffile = os.path.abspath(userprefs[-1]) # Settings will be saved here # Read all but the last as reference for f in filter(os.path.exists, sysprefs + userprefs[:-1]): pf.debug("Reading config file %s" % f, pf.DEBUG.CONFIG) pf.cfg.read(f) pf.refcfg = pf.cfg pf.debug("=" * 60, pf.DEBUG.CONFIG) pf.debug("RefConfig: %s" % pf.refcfg, pf.DEBUG.CONFIG) # Use the last as place to save preferences pf.prefcfg = Config(default=refLookup) if os.path.exists(pf.preffile): pf.debug("Reading config file %s" % pf.preffile, pf.DEBUG.CONFIG) pf.prefcfg.read(pf.preffile) pf.debug("=" * 60, pf.DEBUG.CONFIG) pf.debug("Config: %s" % pf.prefcfg, pf.DEBUG.CONFIG) # Fix incompatible changes in configuration apply_config_changes(pf.prefcfg) # Create an empty one for the session settings pf.cfg = Config(default=prefLookup) #################################################################### ## Post config initialization ## # process non-starting options dependent on config if pf.options.search or pf.options.listfiles: if len(args) > 0: opts = [a for a in args if a.startswith('-')] args = [a for a in args if not a in opts] if '-a' in opts: opts.remove('-a') extended = True else: extended = False if len(args) > 1: files = args[1:] else: files = utils.sourceFiles(relative=True, extended=extended) if pf.options.listfiles: print('\n'.join(files)) else: cmd = "grep %s '%s' %s" % (' '.join(opts), args[0], ''.join( [" '%s'" % f for f in files])) #print(cmd) os.system(cmd) return # process other options dependent on config if pf.options.pyside is None: pf.options.pyside = pf.cfg['gui/bindings'].lower() == 'pyside' # process options that override the config if pf.options.redirect is not None: pf.cfg['gui/redirect'] = pf.options.redirect delattr(pf.options, 'redirect') # avoid abuse #print "REDIRECT",pf.cfg['gui/redirect'] ################################################################### # This should probably be changed to options overriding config # Set option from config if it was not explicitely given if pf.options.uselib is None: pf.options.uselib = pf.cfg['uselib'] # Set default --nogui if first remaining argument is a pyformex script. if pf.options.gui is None: pf.options.gui = not (len(args) > 0 and utils.is_pyFormex(args[0])) if pf.options.gui: pf.options.interactive = True # If we run from an source version, we should set the proper revision # number and run the svnclean procedure. if pf.installtype in 'SG': svnclean = os.path.join(pyformexdir, 'svnclean') if os.path.exists(svnclean): try: utils.system(svnclean) except: print("Error while executing %s, we ignore it and continue" % svnclean) ###### We have the config and options all set up ############ filterWarnings() def _format_warning(message, category, filename, lineno, line=None): """Replace the default warnings.formatwarning This allows the warnings being called using a simple mnemonic string. The full message is then found from the message module. """ import messages message = messages.getMessage(message) message = """.. pyFormex Warning ================ %s `Called from:` %s `line:` %s """ % (message, filename, lineno) if line: message += "%s\n" % line return message if pf.cfg['warnings/nice']: import warnings warnings.formatwarning = _format_warning utils.checkModule('numpy', fatal=True) # Make sure pf.PF is a Project from project import Project pf.PF = Project() utils.setSaneLocale() # Set application paths pf.debug("Loading AppDirs", pf.DEBUG.INFO) import apps apps.setAppDirs() # Start the GUI if needed # Importing the gui should be done after the config is set !! if pf.options.gui: from gui import guimain pf.debug("GUI version", pf.DEBUG.INFO) res = guimain.startGUI(args) if res != 0: print("Could not start the pyFormex GUI: %s" % res) return res # EXIT # Display the startup warnings and messages if startup_warnings: if pf.cfg['startup_warnings']: pf.warning(startup_warnings) else: print(startup_warnings) if startup_messages: print(startup_messages) if pf.options.debuglevel & pf.DEBUG.INFO: # NOTE: inside an if to avoid computing the report when not printed pf.debug(utils.reportDetected(), pf.DEBUG.INFO) # # Qt4 may have changed the locale. # Since a LC_NUMERIC setting other than C may cause lots of troubles # with reading and writing files (formats become incompatible!) # we put it back to a sane setting # utils.setSaneLocale() # Initialize the libraries #import lib #lib.init_libs(pf.options.uselib,pf.options.gui) # Prepend the autorun script ar = pf.cfg.get('autorun', '') if ar and os.path.exists(ar): args[0:0] = [ar] # remaining args are interpreted as scripts and their parameters res = 0 if args: pf.debug("Remaining args: %s" % args, pf.DEBUG.INFO) from script import processArgs res = processArgs(args) if res: if pf.options.gui: pf.message("There was an error while executing a script") else: return res # EXIT else: pf.debug("stdin is a tty: %s" % sys.stdin.isatty(), pf.DEBUG.INFO) # Play script from stdin # Can we check for interactive session: stdin connected to terminal? #from script import playScript #playScript(sys.stdin) # after processing all args, go into interactive mode if pf.options.gui and pf.app: res = guimain.runGUI() ## elif pf.options.interactive: ## print("Enter your script and end with CTRL-D") ## from script import playScript ## playScript(sys.stdin) #Save the preferences that have changed savePreferences() # Exit return res
def export(self, name=None, title=None, description=None, keywords=None, author=None, createdby=False): """Export the WebGL scene. Parameters: - `name`: a string that will be used for the filenames of the HTML, JS and STL files. - `title`: an optional title to be set in the .html file. If not specified, the `name` is used. You can also set the meta tags 'description', 'keywords' and 'author' to be included in the .html file. The first two have defaults if not specified. Returns the name of the exported htmlfile. """ if name is None: name = self.name if title is None: title = '%s WebGL example, created by pyFormex' % name if description is None: description = title if keywords is None: keywords = "pyFormex, WebGL, XTK, HTML, JavaScript" s = """// Script generated by %s window.onload = function() { var r = new X.renderer3D(); r.init(); """ % pf.fullVersion() s += '\n'.join([self.format_object(o) for o in self]) if self.gui: s += self.format_gui() if self._camera: if 'position' in self._camera: s += "r.camera.position = %s;\n" % list(self._camera.position) if 'focus' in self._camera: s += "r.camera.focus = %s;\n" % list(self._camera.focus) if 'up' in self._camera: s += "r.camera.up = %s;\n" % list(self._camera.up) s += """ r.render(); }; """ jsname = utils.changeExt(name, '.js') with open(jsname, 'w') as jsfile: jsfile.write(s) print("Exported WebGL script to %s" % os.path.abspath(jsname)) # TODO: setting DOCTYTPE makes browser initial view not good # s = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> s = """<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="generator" content="%s"> <meta name="description" content="%s"> <meta name="keywords" content="%s"> """ % (pf.fullVersion(), description, keywords) if author: s += '<meta name="author" content="%s">\n' % author s += "<title>%s</title>\n" % title if self.gui: self.scripts.append(pf.cfg['webgl/guiscript']) self.scripts.append(jsname) for scr in self.scripts: s += '<script type="text/javascript" src="%s"></script>\n' % scr s += """ </head> <body>""" if createdby: if type(createdby) is int: width = ' width="%s%%"' % createdby else: width = '' s += """<div id='pyformex' style='position:absolute;top:10px;left:10px;'> <a href='http://pyformex.org' target=_blank><img src='http://pyformex.org/images/pyformex_createdby.png' border=0%s></a> </div>""" % width s += """</body> </html> """ htmlname = utils.changeExt(jsname, '.html') with open(htmlname, 'w') as htmlfile: htmlfile.write(s) print("Exported WebGL model to %s" % os.path.abspath(htmlname)) return htmlname
## """project.py Functions for managing a project in pyFormex. """ from __future__ import print_function import pyformex as pf from track import TrackedDict from pyformex import utils import os,sys import cPickle import gzip _signature_ = pf.fullVersion() module_relocations = { 'plugins.mesh' : 'mesh', 'plugins.surface' : 'plugins.trisurface', } class_relocations = { 'coords.BoundVectors' : 'plugins.alt.BoundVectors', 'coords.CoordinateSystem' : 'coordsys.CoordinateSystem', 'elements.Element':'elements.ElementType', } def find_global(module,name): """Override the import path of some classes""" pf.debug("I want to import %s from %s" % (name,module),pf.DEBUG.PROJECT)
## along with this program. If not, see http://www.gnu.org/licenses/. ## """project.py Functions for managing a project in pyFormex. """ from __future__ import print_function import pyformex as pf from track import TrackedDict from pyformex import utils import os, sys import cPickle import gzip _signature_ = pf.fullVersion() module_relocations = { 'plugins.mesh': 'mesh', 'plugins.surface': 'plugins.trisurface', } class_relocations = { 'coords.BoundVectors': 'plugins.alt.BoundVectors', 'coords.CoordinateSystem': 'coordsys.CoordinateSystem', 'elements.Element': 'elements.ElementType', } def find_global(module, name): """Override the import path of some classes"""
def export(self,name=None,title=None,description=None,keywords=None,author=None,createdby=False): """Export the WebGL scene. Parameters: - `name`: a string that will be used for the filenames of the HTML, JS and STL files. - `title`: an optional title to be set in the .html file. If not specified, the `name` is used. You can also set the meta tags 'description', 'keywords' and 'author' to be included in the .html file. The first two have defaults if not specified. Returns the name of the exported htmlfile. """ if name is None: name = self.name if title is None: title = '%s WebGL example, created by pyFormex' % name if description is None: description = title if keywords is None: keywords = "pyFormex, WebGL, XTK, HTML, JavaScript" s = """// Script generated by %s window.onload = function() { var r = new X.renderer3D(); r.init(); """ % pf.fullVersion() s += '\n'.join([self.format_object(o) for o in self ]) if self.gui: s += self.format_gui() if self._camera: if 'position' in self._camera: s += "r.camera.position = %s;\n" % list(self._camera.position) if 'focus' in self._camera: s += "r.camera.focus = %s;\n" % list(self._camera.focus) if 'up' in self._camera: s += "r.camera.up = %s;\n" % list(self._camera.up) s += """ r.render(); }; """ jsname = utils.changeExt(name,'.js') with open(jsname,'w') as jsfile: jsfile.write(s) print("Exported WebGL script to %s" % os.path.abspath(jsname)) # TODO: setting DOCTYTPE makes browser initial view not good # s = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> s = """<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="generator" content="%s"> <meta name="description" content="%s"> <meta name="keywords" content="%s"> """ % (pf.fullVersion(),description,keywords) if author: s += '<meta name="author" content="%s">\n' % author s += "<title>%s</title>\n" % title if self.gui: self.scripts.append(pf.cfg['webgl/guiscript']) self.scripts.append(jsname) for scr in self.scripts: s += '<script type="text/javascript" src="%s"></script>\n' % scr s += """ </head> <body>""" if createdby: if type(createdby) is int: width = ' width="%s%%"' % createdby else: width = '' s += """<div id='pyformex' style='position:absolute;top:10px;left:10px;'> <a href='http://pyformex.org' target=_blank><img src='http://pyformex.org/images/pyformex_createdby.png' border=0%s></a> </div>""" % width s += """</body> </html> """ htmlname = utils.changeExt(jsname,'.html') with open(htmlname,'w') as htmlfile: htmlfile.write(s) print("Exported WebGL model to %s" % os.path.abspath(htmlname)) return htmlname