Source code for gui.menus.Geometry

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""Geometry Menu

The Geometry menu is the major interactive menu to handle all kinds of
geometry in pyFormex. It includes import from and export to a vast choice
of file formats. There are some simple interactive geometry creators.
"""
import numpy as np
import pyformex as pf
from pyformex.gui.projectmgr import projectmanager as pmgr
from pyformex.gui.annotations import Annotations
from pyformex.plugins import partition
from pyformex.plugins import sectionize
pg = pf.gui.guicore
_I = pg._I
_C = pg._C


[docs]def shrinkRedraw(): """Toggle the shrink mode""" pg.setShrink() pmgr().draw_sel()
def set_selection(clas=pf.Geometry): if isinstance(clas, str): clas = getattr(pf, clas) pm = pmgr(show=True) pm.ask_sel(clas=clas) pm.draw_sel() ##################### read and write ##########################
[docs]def importGeometry(path=None, ftype=None, compr=False, multi=False, target=None, select=True, draw=True): """Import geometry from file(s). Pops up a :class:`FileDialog` to select one (or more) geometry file(s) to be imported. Parameters ---------- path: :term:`path_like` The initially selected file name in the dialog. If not provided, the dialog starts in the current directory with no selected path. ftype: :term:`file_types` One or more file type strings. The popup dialog will contain a filter for each of these file types. Only the files matching the selected filte will be shown and selectable in the FileDialog. If not provided, it is set to a list of all known geometry file types in pyFormex. compr: bool If True, compressed files (with .gz or .bz2 suffices) of the file types in ftype will also be selectable. target: class If a file type allows returning different Geometry classes, the prefered target can be set. multi: bool If True, the FileDialog will allow to select multiple files and they will all be imported at once. select: bool If True (default), the imported geometry becomes the current selection in the Geometry menu. draw: bool If True (default) and ``select`` is also True, the selection is drawn after importing. """ from pyformex.gui.dialogs import FileDialog if ftype is None: ftype = ['geometry', 'all'] elif isinstance(ftype, list): pass else: ftype = [ftype] # if ftype == ['poly']: # extra = [_I('mplex', 4)] # else: mode = 'exist' if not multi else 'multi' extra = [] dia = FileDialog(path=path, filter=ftype, mode=mode, extra=extra, compr=compr) res = dia.getResults() if not res: return {} files = res.pop('filename') pm = pf.pmgr() return pm.readGeometry(files, target=target, select=select, draw=draw, **res)
def importPgf(): importGeometry(ftype='pgf', compr=True) def importPzf(_canvas=True, _camera=True): # Drawing is postponed because we may need to set canvas layout res = importGeometry(ftype='pzf', compr=False, draw=False) draw_args = {} if '_canvas' in res: if _canvas or pg.ack("The PZF file contains canvas and camera settings." " Do you want to apply them?"): pf.GUI.viewports.loadConfig(res['_canvas']) pf.GUI.viewports.update() draw_args = {'view': None} # Keep the camera view elif '_camera' in res: if _camera or pg.ack("The PZF file contains camera settings." "Do you want to apply them?"): pf.canvas.initCamera(res['_camera']) pf.canvas.update() draw_args = {'view': None} # Keep the camera view print(pf.canvas.camera.angles) pmgr().draw_sel(clear=False, **draw_args) def importPoly(): importGeometry(ftype=['poly']) def importSurface(path=None, multi=False): importGeometry(path=path, ftype=['surface', 'pgf', 'vtk', 'all'], compr=True, multi=multi, target='surface') def importObj(): importGeometry(ftype='obj') def importNeu(): importGeometry(ftype='neu') def importInp(): importGeometry(ftype='inp') def importTetgen(): importGeometry(ftype='tetgen') def importAny(multi=False): importGeometry(ftype=['geometry', 'all'], multi=multi) def importSurfaceMulti(): importSurface(multi=True) def importAnyMulti(): importAny(multi=True)
[docs]def importModel(*filenames): """Read one or more element meshes into pyFormex. Models are composed of nodes and elems stored on a .mesh file. One or more filenames can be specified. If none is given, the user will be asaked. """ if not filenames: filenames = pg.askFilename(".", "*.mesh", mode='multi') if not filenames: return with pg.busyCursor(): for f in filenames: fn = pf.Path(f) d = pf.fileread.readMeshFile(fn) modelname = fn.stem pf.PF.update({modelname: d}) M = pf.fileread.extractMeshes(d) names = ["%s-%d" % (modelname, i) for i in range(len(M))] pf.PF.update2(names, M)
[docs]def readInp(fn=None): """Read an Abaqus .inp file and convert to pyFormex .mesh. """ if fn is None: fn = pg.askFilename(".", "*.inp", mode='multi') if not fn: return with pg.busyCursor(): for f in fn: pf.fileread.convertInp(f)
[docs]def exportGeometry(ftype, single=False, compr=True, clas=None, **kargs): """Write geometry to file. Parameters ---------- ftype: str The output file format. This is the filename extension in lower case and without a leading dot. single: bool If True, only a single object can be written to the file. compr: bool If True, transparent compression is supported for the format. clas: class | tuple of class Allowed Geometry types for this format. """ pm = pmgr() pm.ask_sel(clas=clas, single=single) if not pm.selection: return print(f"Exporting {pm.selection}") cur = pf.cfg['workdir'] res = pg.askFile(cur, filter=ftype, compr=compr, **kargs) if res: # convert widget data to writeGeometry parameters if ftype == 'pzf': env = res.pop('environ') if env == 'Camera': res['_camera'] = True elif env == 'Canvas': res['_canvas'] = True fn = res.pop('filename') print(f"Writing geometry file {fn}") nobj = pf.writeGeometry(fn, pm.sel_dict(), compr=compr, **res) print(f"Objects written: {nobj}")
def exportPzf(): exportGeometry('pzf', single=False, clas=None, extra=[ _I('environ', choices=['None', 'Camera', 'Canvas'], itemtype='hradio', text='Add environment',), ]) def exportPyf(): exportGeometry('pyf', single=False, clas=None, extra=[ _I('environ', choices=['None', 'Camera', 'Canvas'], itemtype='hradio', text='Add environment',), ]) def exportPgf(): kargs = { 'compr': True, 'compression': 4, 'fmode': 'binary', } exportGeometry('pgf', single=False, clas=pf.Geometry, **kargs) def exportOff(): exportGeometry('off', single=True, clas=(pf.Mesh, pf.Polygons)) def exportObj(): exportGeometry('obj', single=True, clas=(pf.Mesh, pf.Polygons)) def exportPly(): exportGeometry('ply', single=True, clas=(pf.Mesh, pf.Polygons)) def exportNeu(): exportGeometry('neu', single=True, clas=pf.Mesh) def exportStl(): exportGeometry('stl', single=True, clas=pf.TriSurface, extra=[ _I('binary', True), _I('color', 'red', itemtype='color'), _I('alpha', 0.5), ]) def exportGts(): exportGeometry('gts', single=True, clas=pf.TriSurface) def exportSurf(): exportGeometry(['smesh', 'vtp', 'vtk'], single=True, clas=pf.TriSurface, compr=False) def exportInp(): exportGeometry(['inp'], single=True, clas=pf.Mesh, extra=[ _I('eltype', 'ELTYPE', text='Abaqus element type (required)'), ]) def exportMesh(): exportGeometry(['*'], single=True, clas=pf.Mesh, extra=[ _I('writer', 'meshio', itemtype='info'), ]) # TODO: this should be merged with export_surface def exportWebgl(): from pyformex.plugins import webgl F = pmgr(show=True).check_sel(single=True) if F: fn = pg.askFilename(pf.cfg['workdir'], 'stl') if fn: print("Exporting surface model to %s" % fn) with pg.busyCursor(): webgl.surface2webgl(F, fn)
[docs]def convertGeometryFile(): """Convert pyFormex geometry file to latest format.""" cur = pf.cfg['workdir'] fn = pg.askFilename(cur, filter=['pgf', 'all']) if fn: from pyformex.geomfile import GeometryFile print(f"Converting geometry file {fn}" f"to version {GeometryFile._version_}") GeometryFile(fn).rewrite()
##################### properties ##########################
[docs]def setProp(prop=None): """Set the property of the current selection. prop should be a single integer value or None. If None is given, a value will be asked from the user. If a negative value is given, the property is removed. If a selected object does not have a setProp method, it is ignored. """ pm = pmgr() objects = pm.get_sel() if objects: if prop is None: res = pg.askItems(caption='Set Property Value', items=[ _I('method', choices=['single', 'list', 'object', 'element', 'none']), _I('value', 0, tooltip='A single value given to all elements'), _I('list', [0, 1], tooltip='A list of values, repeated if needed'), ], enablers=[ ('method', 'single', 'value'), ('method', 'object', 'value'), ('method', 'list', 'list'), ]) if res: if res['method'] == 'single': prop = res['value'] elif res['method'] == 'list': prop = res['list'] elif res['method'] == 'object': prop = res['value'] elif res['method'] == 'element': prop = 'range' else: prop = None for o in objects: if hasattr(o, 'setProp'): o.setProp(prop) if res['method'] == 'object': prop += 1 pm.draw_sel()
[docs]def delProp(): """Delete the property of the current selection. Resets the `prop` attribute of all selected objects to None. """ pm = pmgr() objects = pm.get_sel() if objects: for o in objects: if hasattr(o, 'prop'): o.prop = None pm.draw()
[docs]def editAttrib(obj): """Edit the attributes of an object.""" items = [] for key, value in obj.attrib.items(): print(f"{key=}, {value=}") if key == 'name': item = _I(key, value) elif key in ('color', 'bkcolor'): if isinstance(value, str) and value.startswith('fld:'): item = _I(key, value) else: item = _I(key, value, itemtype='color') elif key in ('alpha', 'bkalpha'): item = _I(key, value, itemtype='fslider', min=0.0, max=1.0) elif isinstance(value, bool): item = _I(key, value, itemtype='bool') else: print(f"Currently attribute '{key}' can not be changed") continue items.append(item) print(items) res = pf.askItems(caption="Edit object attributes", items=items) if res: obj.attrib(**res)
[docs]def edit_attributes(): """Edit Attributes of the current selection. Only 1 selected object """ pm = pmgr() obj = pm.get_sel(single=True) if obj: editAttrib(obj) pm.draw_sel()
##################### conversion ########################## # TODO: conversion should keep attrib and fields where possible
[docs]def convert(method, suffix=''): """Transform the selected Geometry objects with the specified method. Parameters ---------- method: func The function name of a method that converts the selected objects. Typical values are 'toFormex', 'toMesh', 'toSurface'. Objects that do not have such a method are silently ignored. suffix: str A string to be appended to the name of the object to form the name of the converted object. The default will keep the same name and thus overwrite the old objects in the project. """ pm = pmgr() if not pm.check_sel(): return names = pm.selection failed = [] converted = {} for name, obj in pm.sel_items(): if method == 'toSurface!': # Only do exactly convertible objects if obj.nplex() == 3: method = method[:-1] else: failed.append(name) continue try: conv = getattr(obj, method) converted[name + suffix] = conv() except (AttributeError, ValueError): failed.append(name) pf.PF.update(converted) names = list(converted.keys()) print(f"Converted objects: {names}") if failed: pf.printc(f"Not converted: {failed}", color='red') pm.draw_sel() pm.refresh_choices() pm.selection = names
############################################# ### Perform operations on selection ######### #############################################
[docs]def scaleSelection(): """Scale the selection.""" pm = pmgr() if pm.check_sel(): res = pg.askItems(caption='Scale Parameters', items=[ _I('uniform', True), _I('scale1', 1.0, text='Uniform scale'), _I('scale3', (1.0, 1.0, 1.0), itemtype='point', text='Non-uniform scale'), ], enablers=[ ('uniform', True, 'scale1'), ('uniform', False, 'scale3'), ]) if res: FL = pf.List(pm.sel_values) scale = res['scale1'] if res['uniform'] else res['scale3'] pm.replace_sel(FL.scale(scale=scale), remember=True)
[docs]def translateSelection(): """Translate the selection.""" pm = pmgr() if pm.check_sel(): res = pg.askItems(caption='Translation Parameters', items=[ _C('', [ _I('global', True, tooltip='If checked, translate in global axis direction') ]), _C('', [ _I('axis', 0, min=0, max=2) ]), _I('direction', (1., 0., 0.), itemtype='point'), _I('distance', 1.0), ], enablers=[ ('global', True, 'axis'), ('global', False, 'direction'), ]) if res: dir = res['axis'] if res['global'] else res['direction'] dist = res['distance'] FL = pf.List(pm.sel_values) pm.replace_sel(FL.translate(dir=dir, step=dist), remember=True)
[docs]def centerSelection(): """Center the selection.""" pm = pmgr() if pm.check_sel(): FL = pf.List(pm.sel_values) pm.replace_sel(FL.centered(), remember=True)
[docs]def rotateSelection(): """Rotate the selection.""" pm = pmgr() if pm.check_sel(): res = pg.askItems(caption='Rotation Parameters', items=[ _C('', [ _I('global', True, tooltip='If checked, rotate around global axis') ]), _C('', [ _I('axis', 0, min=0, max=2) ]), _I('direction', (1., 0., 0.), itemtype='point', tooltip='Vector along rotation axis'), _I('point', (0., 0., 0.), itemtype='point', tooltip='Point on rotation axis'), _I('angle', 90.0, tooltip='Rotation angle in degrees'), ], enablers=[ ('global', True, 'axis'), ('global', False, 'direction'), ]) if res: axis = res['axis'] if res['global'] else res['direction'] angle = res['angle'] around = res['point'] # print(type(around)) if all([x == 0.0 for x in around]): around = None FL = pf.List(pm.sel_values) pm.replace_sel(FL.rotate(angle=angle, axis=axis, around=around), remember=True)
[docs]def permuteAxes(): """Permute the global axes.""" pm = pmgr() if pm.check_sel(): res = pg.askItems(caption='Axes Permutation Parameters', items=[ _I('order', (0, 1, 2), itemtype='ivector', tooltip='The old axes to become the new x,y,z') ]) if res: order = res['order'] FL = pf.List(pm.sel_values) pm.replace_sel(FL.permuteAxes(order), remember=True)
[docs]def clipSelection(): """Clip the selection.""" pm = pmgr() if pm.check_sel(): res = pg.askItems(caption='Clip Parameters', items=[ _I('relative', True, readonly=True), _I('axis', 0, min=0, max=2), _I('min', 0.0, min=0., max=1.), _I('max', 1.0, min=0., max=1.), _I('nodes', 'all', choices=['all', 'any', 'none']), ]) if res: FL = pm.sel_values bb = pf.bbox(FL) axis = res['axis'] xmi = bb[0][axis] xma = bb[1][axis] dx = xma-xmi xc1 = xmi + res['min'] * dx xc2 = xmi + res['max'] * dx FL = [F.clip(F.test(nodes=res['nodes'], dir=axis, min=xc1, max=xc2)) for F in FL] pm.replace_sel(FL, remember=True)
[docs]def clipAtPlane(): """Clip the selection with a plane.""" FL = pf.PF._geometry.check() if not FL: return dsize = pf.bbox(FL).dsize() esize = 10 ** (pf.at.niceLogSize(dsize)-5) res = pg.askItems(caption='Define the clipping plane', items=[ _I('Point', [0.0, 0.0, 0.0], itemtype='point'), _I('Normal', [1.0, 0.0, 0.0], itemtype='point'), _I('Keep side', itemtype='radio', choices=['positive', 'negative']), _I('Nodes', itemtype='radio', choices=['all', 'any', 'none']), _I('Tolerance', esize), _I('Property', 1), ]) if res: P = res['Point'] N = res['Normal'] side = res['Keep side'] nodes = res['Nodes'] atol = res['Tolerance'] prop = res['Property'] pf.PF._geometry.remember() if side == 'positive': func = pf.TriSurface.clip else: func = pf.TriSurface.cclip FL = [func(F, F.test(nodes=nodes, dir=N, min=P, atol=atol)) for F in FL] FL = [F.setProp(prop) for F in FL] pf.PF.update(dict([('%s/clip' % n, F) for n, F in zip(pf.PM.selection, FL)])) pf.PF._geometry.set(['%s/clip' % n for n in pf.PM.selection]) pf.PF._geometry.draw()
[docs]def cutSelection(): """Cut the selection with a plane.""" pm = pmgr() if not pm.check_sel(): return names = pm.selection oknames = [n for n in names if pf.PF[n].nplex() in [2, 3]] if len(oknames) == 0: return if len(oknames) < len(names): pm.selection = names = oknames FL = pf.List(pm.sel_values) dsize = pf.bbox(FL).dsize() if dsize > 0.: esize = 10 ** (pf.at.niceLogSize(dsize)-5) else: esize = 1.e-5 res = pg.askItems(caption='Cut Plane Parameters', items=[ _I('Point', (0.0, 0.0, 0.0), itemtype='point'), _I('Normal', (1.0, 0.0, 0.0)), _I('New props', [1, 2, 2, 3, 4, 5, 6]), _I('Side', 'positive', itemtype='radio', choices=['positive', 'negative', 'both']), _I('Tolerance', esize), ]) if res: P = res['Point'] N = res['Normal'] atol = res['Tolerance'] p = res['New props'] side = res['Side'] FL = FL.cutWithPlane(P, N, side=side, atol=atol, newprops=p) if side == 'both': newdict = {} for name, parts in zip(names, FL): newdict[name+'/pos'] = parts[0] newdict[name+'/neg'] = parts[1] pf.PF.update(newdict) pm.selection = list(newdict.keys()) pm.draw_sel() else: pm.replace_sel(FL, remember=True)
############################################# ### Property functions #############################################
[docs]def splitProp(): """Split the selected object based on property values""" pm = pmgr() F = pm.get_sel(single=True) if not F: return name = pm.selection[0] partition.splitProp(F, name)
############################################# ### Create Geometry functions #############################################
[docs]def storeSelectDraw(name, obj): """Store, select and draw an objec""" print(f"STORE {name}") if name == '__auto__': name = next(pf.autoName(obj.__class__.__name__)) pf.PF[name] = obj pm = pmgr() pm.refresh() pm.list_all() pm.selection = [name] pm.list_sel() pm.draw_sel()
def convertFormex(F, totype): if totype != 'Formex': F = F.toMesh() if totype == 'TriSurface': F = pf.TriSurface(F) return F def convert_Mesh_TriSurface(F, totype): if totype == 'Formex': return F.toFormex() else: F = F.toMesh() if totype == 'TriSurface': F = F.convert('tri3').toSurface() return F base_patterns = [ 'l:1', 'l:2', 'l:12', 'l:127', ] def createGrid(): _name = 'createGrid' _data = _name + '_data' res = pg.askItems(caption=_name, store=_data, items=[ _I('name', '__auto__'), _I('object type', 'Mesh', choices=['Formex', 'Mesh', 'TriSurface']), _I('base', choices=base_patterns), _I('n1', 4, text='nx', min=1), _I('n2', 2, text='ny', min=1), _I('t1', 1., text='stepx'), _I('t2', 1., text='stepy'), _I('taper', 0), _I('bias', 0.), ]) if res: name = res.pop('name') objtype = res.pop('object type') if name == '__auto__': name = next(pf.autoName(objtype)) base = res.pop('base') F = pf.Formex(base).replic2(**res) F = convertFormex(F, objtype) storeSelectDraw(name, F) def createRectangle(): _name = 'createRectangle' _data = _name + '_data' res = pg.askItems(caption=_name, store=_data, items=[ _I('name', '__auto__'), _I('object type', 'Mesh', choices=['Formex', 'Mesh', 'TriSurface']), _I('nx', 1, min=1), _I('ny', 1, min=1), _I('b', 1., text='width'), _I('h', 1., text='height'), _I('bias', 0.), _I('diag', 'up', choices=['none', 'up', 'down', 'x-both']), ]) if res: name = res.pop('name') objtype = res.pop('object type') if name == '__auto__': name = next(pf.autoName(objtype)) F = pf.simple.rectangle(**res) F = convertFormex(F, objtype) storeSelectDraw(name, F) def createCube(): _name = 'createCube' _data = _name + '_data' levels = {'volume': 3, 'faces': 2, 'edges': 1, 'points': 0} res = pg.askItems(caption=_name, store=_data, items=[ _I('name', '__auto__'), _I('object type', 'Mesh', choices=['Formex', 'Mesh', 'TriSurface']), _I('level', choices=levels), ]) if res: name = res['name'] objtype = res.pop('object type') if name == '__auto__': name = next(pf.autoName(objtype)) F = pf.simple.Cube(levels[res['level']]) print(F) F = convert_Mesh_TriSurface(F, objtype) storeSelectDraw(name, F) def createCylinder(): # TODO: we should reverse the surface if it is Mesh/TriSurface # or better: do it in pf.simple.sphere _name = 'createCylinder' _data = _name + '_data' res = pg.askItems(caption=_name, store=_data, items=[ _I('name', '__auto__'), _I('object type', 'Mesh', choices=['Formex', 'Mesh', 'TriSurface']), _I('D', 1., text='base diameter'), _I('D1', 1., text='top diameter'), _I('L', 2., text='height'), _I('angle', 360.), _I('nl', 6, text='div_along_length', min=1), _I('nt', 12, text='div_along_circ', min=1), _I('bias', 0.), _I('diag', 'up', choices=['none', 'up', 'down', 'x-both']), ]) if res: name = res.pop('name') objtype = res.pop('object type') if name == '__auto__': name = next(pf.autoName(objtype)) F = pf.simple.cylinder(**res) F = convertFormex(F, objtype) storeSelectDraw(name, F) def createCone(): _name = 'createCone' _data = _name + '_data' res = pg.askItems(caption=_name, store=_data, items=[ _I('name', '__auto__'), _I('object type', 'Mesh', choices=['Formex', 'Mesh', 'TriSurface']), _I('r', 1., text='radius'), _I('h', 1., text='height'), _I('t', 360., text='angle'), _I('nr', 6, text='div_along_radius', min=1), _I('nt', 12, text='div_along_circ', min=1), _I('diag', 'up', text='diagonals', choices=['none', 'up', 'down']), ]) if res: name = res.pop('name') objtype = res.pop('object type') if name == '__auto__': name = next(pf.autoName(objtype)) F = pf.simple.sector(**res) F = convertFormex(F, objtype) storeSelectDraw(name, F) def createSphere(): _name = 'createSphere' _data = _name + '_data' res = pg.askItems(caption=_name, store=_data, items=[ _I('name', '__auto__'), _I('object type', 'TriSurface', choices=['Formex', 'Mesh', 'TriSurface']), _I('method', choices=['icosa', 'octa', 'geo']), _I('ndiv', 8), _I('nx', 36), _I('ny', 18), ], enablers=[ ('method', 'icosa', 'ndiv'), ('method', 'octa', 'ndiv'), ('method', 'geo', 'nx', 'ny'), ]) if res: name = res.pop('name') objtype = res.pop('object type') if name == '__auto__': name = next(pf.autoName(objtype)) method = res.pop('method') if method in ('icosa', 'octa'): F = pf.simple.sphere(res['ndiv'], base=method) print("Surface has %s vertices and %s faces" % (F.ncoords(), F.nelems())) F = convert_Mesh_TriSurface(F, objtype) else: F = pf.simple.sphere3(res['nx'], res['ny']) F = convertFormex(F, objtype) print("Surface has %s faces" % F.nelems()) storeSelectDraw(name, F) ############################################# ### Principal Axes #############################################
[docs]def showPrincipal(): """Show the principal axes.""" F = pmgr().get_sel(single=True) if not F: return # compute the axes if isinstance(F, pf.TriSurface): res = pg.ask("Does the model represent a surface or a volume?", ["Surface", "Volume"]) I = F.inertia(res == "Volume") else: I = F.inertia() C = I.ctr Iprin, Iaxes = I.principal() pf.at.printar("Center of gravity: ", C) pf.at.printar("Principal Directions: ", Iaxes) pf.at.printar("Principal Values: ", Iprin) pf.at.printar("Inertia tensor: ", I) # display the axes CS = pf.CoordSys(rot=Iaxes, trl=C) size = F.dsize() pg.drawAxes(CS, size=size, psize=0.1*size) data = pf.PF['_principal_data'] = data = (I, Iprin, Iaxes) return data
[docs]def rotatePrincipal(): """Rotate the selection according to the last shown principal axes.""" try: data = pf.PF['_principal_data'] except KeyError: data = showPrincipal() pm = pf.pmgr() FL = pm.get_sel() if FL: FL = pf.List(FL) ctr, rot = data[0].ctr, data[2] pm.replace_sel(FL.trl(-ctr).rot(rot.transpose()).trl(ctr), remember=True)
[docs]def transformPrincipal(): """Transform the selection according to the last shown principal axes. This is analog to rotatePrincipal, but positions the object at its center. """ data = pf.PF.get('_principal_data', showPrincipal()) pm = pf.pmgr() FL = pm.get_sel() if FL: ctr, rot = data[0].ctr, data[2] pm.replace_sel(FL.trl(-ctr).rot(rot.transpose()), remember=True)
# TODO: generalize for Formex + Mesh (Curve?) ################### Perform operations on Formex #######################
[docs]def concatenateSelection(): """Concatenate the selection.""" pm = pf.pmgr() FL = pm.get_sel(clas=pf.Formex) if not FL: return plexitude = np.array([F.nplex() for F in FL]) if plexitude.min() == plexitude.max(): res = pg.askItems(caption='Concatenate Formices', items=[ _I('name', pf.autoName('formex')) ]) if res: name = res['name'] pf.PF.update({name: pf.Formex.concatenate(FL)}) pm.selection = [name] pm.draw_sel() else: pf.warning('You can only concatenate Formices with the same plexitude!')
# TODO: merge with above and call concatenate
[docs]def merge(): """Merge the selected surfaces.""" pm = pf.pmgr() FL = pm.get_sel(clas=pf.TriSurface, warn=False) if len(FL) < 2: pf.warning("You should at least select two surfaces!") return S = pf.TriSurface.concatenate(FL) name = '--merged-surface--' pf.PF[name] = S pm.selection = [name] pm.draw_sel()
[docs]def partitionSelection(): """Partition the selection.""" pm = pf.pmgr() F = pm.get_sel(clas=pf.Formex, single=True) if not F: return name = pm.selection[0] print("Partitioning Formex '%s'" % name) cuts = partition.partition(F) print("Subsequent cutting planes: %s" % cuts) if pg.ack('Save cutting plane data?'): types = ['Text Files (*.txt)', 'All Files (*)'] fn = pg.askFilename(pf.cfg['workdir'], types) if fn: pf.chdir(fn) fil = open(fn, 'w') fil.write("%s\n" % cuts) fil.close()
[docs]def sectionizeSelection(): """Sectionize the selection.""" pm = pf.pmgr() F = pm.get_sel(clas=pf.Formex, single=True) if not F: return name = pm.selection[0] print("Sectionizing Formex '%s'" % name) ns, th, segments = sectionize.createSegments(F) if not ns: return sections, ctr, diam = sectionize.sectionize(F, segments, th) # print("Centers: %s" % ctr) # print("Diameters: %s" % diam) if pg.ack('Save section data?'): types = ['Text Files (*.txt)', 'All Files (*)'] fn = pg.askFilename(pf.cfg['workdir'], types) if fn: pf.chdir(fn) fil = open(fn, 'w') fil.write("%s\n" % ctr) fil.write("%s\n" % diam) fil.close() if pg.ack('Draw circles?'): circles = sectionize.drawCircles(sections, ctr, diam) ctrline = sectionize.connectPoints(ctr) if pg.ack('Draw circles on Formex ?'): sectionize.drawAllCircles(F, circles) circles = pf.Formex.concatenate(circles) circles.setProp(3) ctrline.setProp(1) pg.draw(ctrline, color='red') pf.PF.update({'circles': circles, 'ctrline': ctrline, 'flypath': ctrline}) if pg.ack('Fly through the Formex ?'): pg.flyAlong(ctrline) # if pg.ack('Fly through in smooth mode ?'): # smooth() # flytruCircles(ctr) pm.draw_sel()
def fly(): path = pf.PF.get('flypath', None) if path is not None: pg.flyAlong(path) else: pf.warning("You should define the flypath first")
[docs]def addOutline(): """Draw the outline of the current rendering""" w, h = pf.canvas.getSize() res = pg.askItems([ _I('w', w, text='Resolution width'), _I('h', h, text='Resolution height'), _I('level', 0.5, text='Isoline level'), # _I('Background color', 1), _I('nproc', 0, text='Number of processors'), ]) if not res: return G = pf.canvas.outline(size=(res['w'], res['h']), level=res['level'], nproc=res['nproc']) OA = pg.draw(G, color=pf.cfg['geometry_menu/outline_color'], view='cur', bbox='last', linewidth=pf.cfg['geometry_menu/outline_linewidth'], flat=-True, ontop=True) if OA: OA = OA.object pf.PF.update({'_outline_': OA})
#### menu settings #### def mySettings(): from pyformex.gui.menus.Settings import updateSettings res = pg.askItems( caption='Geometry menu settings', store=pf.cfg, save=False, items=[ _I('geometry_menu/numbersontop'), _I('geometry_menu/bbox', choices=['bbox', 'grid']), _I('geometry_menu/outline_linewidth', ), _I('geometry_menu/outline_color', ), _I('_save_', False, text='Save in my preferences'), ]) if res: updateSettings(res) def loadDxfMenu(): pass def toggleNumbersOntop(): Annotations.editAnnotations(ontop='') def undoChanges(): pf.PF.rollback() ########## The menu ########## # TODO: this should be made a default menufunc for all menus menu_func = menufunc annotations_menu = [ (f, Annotations.toggle, dict(checkable=True, checked=Annotations.active(f), data=f)) for f in Annotations.available] menu_items = [ ('Project Manager', (pmgr, True)), ("Import", [ (pf.Filetype['pzf'].desc(), importPzf), (pf.Filetype['pgf'].desc(), importPgf), (pf.Filetype['poly'].desc(), importPoly), (pf.Filetype['surface'].desc(), importSurface), ("Multiple surfaces", importSurfaceMulti), (pf.Filetype['tetgen'].desc(), importTetgen), (pf.Filetype['inp'].desc(), importInp), (pf.Filetype['neu'].desc(), importNeu), ("All known geometry formats", importAny), ("Multiple imports of any kind", importAnyMulti), ("Abaqus .inp", [ ("Convert Abaqus .inp file", readInp), ("Import Converted Abaqus Model", importModel), ]), # ("AutoCAD .dxf",[ # ("Import .dxf or .dxftext",importDxf), # ("Load DXF plugin menu",loadDxfMenu), # ]), ('Upgrade pyFormex Geometry File', convertGeometryFile, { 'tooltip': "Convert a pyFormex Geometry File (.pgf) to the" "latest format, overwriting the file."}), ]), ("Export", [ (pf.Filetype['pzf'].desc(), exportPzf), (pf.Filetype['pgf'].desc(), exportPgf), (pf.Filetype['off'].desc(), exportOff), (pf.Filetype['obj'].desc(), exportObj), (pf.Filetype['ply'].desc(), exportPly), (pf.Filetype['neu'].desc(), exportNeu), (pf.Filetype['stl'].desc(), exportStl), (pf.Filetype['gts'].desc(), exportGts), ("Abaqus .inp", exportInp), ("meshio", exportMesh), ("Alternate Surface Formats", exportSurf), ("Export WebGL", exportWebgl), ]), ("---", None), ("Select", [ ('Any', (set_selection, 'Geometry')), ('Formex', (set_selection, 'Formex')), ('Mesh', (set_selection, 'Mesh')), ('TriSurface', (set_selection, 'TriSurface')), ('PolyLine', (set_selection, 'PolyLine')), ('Curve', (set_selection, 'Curve')), ('NurbsCurve', (set_selection, 'NurbsCurve')), ]), ("Draw Selection", lambda: pmgr().draw_sel()), ("---", None), ("Create Object", [ ('Grid', createGrid), ('Rectangle', createRectangle), ('Cube', createCube), ('Cylinder, Cone, Truncated Cone', createCylinder), ('Circle, Sector, Cone', createCone), ('Sphere', createSphere), ]), ("Convert Objects", [ ("To Formex", (convert, 'toFormex')), ("To Mesh", (convert, 'toMesh')), ("To Surface", (convert, 'toSurface')), ("To Surface (exact)", (convert, 'toSurface!')), ## ("To PolyLine",toPolyLine), ## ("To BezierSpline",toBezierSpline), ## ("To NurbsCurve",toNurbsCurve), ]), ("Transform Objects", [ ("Scale", scaleSelection), ("Translate", translateSelection), ("Center", centerSelection), ("Rotate", rotateSelection), ("Permute Axes", permuteAxes), ("Clip", clipSelection), ("Clip At Plane", clipAtPlane), ("Cut With Plane", cutSelection), ("Undo Last Changes", undoChanges), ]), ("Property Numbers", [ ("Set", setProp), ("Delete", delProp), ("Split", splitProp), ]), ("Edit Attributes", edit_attributes), ("---", None), ("Principal", [ ("Show Principal Axes", showPrincipal), ("Rotate to Principal Axes", rotatePrincipal), ("Transform to Principal Axes", transformPrincipal), ]), # TODO: Needs to be reactivated or moved to scripts/attic # ("Formex", [ # ("Concatenate Selection", concatenateSelection), # ("Partition Selection", partitionSelection), # ("Sectionize Selection", sectionizeSelection), # ]), ("Outline", addOutline), ("Fly", fly), ("---", None), ("Annotations", annotations_menu), ("Toggle Shrink", shrinkRedraw), ("Geometry Settings", mySettings), ("---", None), ] def menu_setup(menu): menu['Export']['meshio'].setEnabled(pf.Module.has('meshio') != '') # End