Source code for plugins.export
#
##
## This file is part of pyFormex 1.0.7 (Mon Jun 17 12:20:39 CEST 2019)
## pyFormex is a tool for generating, manipulating and transforming 3D
## geometrical models by sequences of mathematical operations.
## Home page: http://pyformex.org
## Project page: http://savannah.nongnu.org/projects/pyformex/
## Copyright 2004-2019 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
## 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/.
##
"""Classes and functions for exporting geometry in various formats.
- ObjFile: wavefront .obj format
- PlyFile: .ply format
These classes do not necessarily implement all features of the specified
formats. They are usually fitted to export and sometimes imports simple
mesh format geometries.
"""
from __future__ import absolute_import, division, print_function
import sys
import pyformex as pf
import numpy as np
[docs]class ObjFile(object):
"""Export a mesh in OBJ format.
This class exports a mesh in Wavefront OBJ format
(see `<https://en.wikipedia.org/wiki/Wavefront_.obj_file>`_).
The class instantiator takes a filename as parameter.
Normally, the file name should end in '.obj', but anything is accepted.
The ObjFile instance is a context manager.
Usage::
with ObjFile(PATH_TO_OBJFILE) as fil:
fil.write(MESH)
"""
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.fil = open(self.filename, 'w')
self.fil.write("# .obj file written by %s\n" % pf.Version())
return self
def __exit__(self, *args):
self.fil.write('# End\n')
self.fil.close()
[docs] def write(self, mesh, name=None):
"""Write a mesh to file in .obj format.
`mesh` is a Mesh instance or another object having compatible
coords and elems attributes. `name` is an optional name of the object.
"""
if name is not None:
self.fil.write("o %s\n" % str(name))
for v in mesh.coords:
self.fil.write("v %s %s %s\n" % tuple(v))
# element code: p(oint), l(ine) or f(ace)
nplex = mesh.elems.shape[1]
code = {1: 'p', 2: 'l'}.get(nplex, 'f')
s = code+(' %s'*nplex)+'\n'
for e in mesh.elems+1: # .obj format starts at 1
self.fil.write(s % tuple(e))
[docs]class PlyFile(object):
"""Export a mesh in PLY format.
This class exports a mesh in Polygon File Format
(see `<https://en.wikipedia.org/wiki/PLY_(file_format)>`_).
The class instantiator takes a filename as parameter.
Normally, the file name should end in '.ply', but anything is accepted.
The PlyFile instance is a context manager.
Usage::
with PlyFile(PATH_TO_PLYFILE) as fil:
fil.write(MESH)
"""
def __init__(self, filename, binary=False):
self.filename = filename
self.binary = binary
def __enter__(self):
self.fil = open(self.filename, 'w')
if self.binary:
fmt = 'binary_' + sys.byteorder + '_endian 1.0'
else:
fmt = 'ascii 1.0'
self.fil.write("""ply
format %s
comment .ply file written by %s
""" %(fmt, pf.Version()))
return self
def __exit__(self, *args):
self.fil.close()
[docs] def write(self, mesh, comment=None, color_table= None):
"""Write a mesh to file in .ply format.
Parameters:
- `mesh`: a Mesh instance or another object having compatible
coords and elems attributes.
- `comment`: an extra comment written into the output file.
- `color_table`: BEWARE! THIS IS SUBJECT TO CHANGES!
color_table currentlyis a list of 2 elements. The first entry
is a string that can assume 2 values 'v' or 'e', to indicate
whether the color table represents nodal or element
values. The second entry is an array of shape (ncoords,3) or
(nelems,3) of interger RGB values between 0 and 255. If RGB
values are passed as float between 0 and 1, they will be
converted to RGB integers.
"""
if comment is not None:
self.fil.write("comment %s\n" % str(comment))
coords = mesh.coords.astype('object')
els = mesh.elems.astype('object')
nplex = mesh.elems.shape[1]
el_type = {2: 'edge'}.get(nplex, 'face')
vcol_type = ecol_type = ''
if el_type == 'edge':
eprop_type = """property int vertex1
property int vertex2
"""
else:
eprop_type = """property list uchar int vertex_indices
"""
if color_table is not None:
color_location, color_table = color_table
if not(np.issubclass_(color_table.dtype.type, np.integer)):
color_table = (255*color_table.clip(0., 1.)).astype(np.integer)
# float can be added but they are implemented yet
cprop_type = {}.get(color_table.dtype, 'uchar')
cprop_types = """property %s red
property %s green
property %s blue
"""%((cprop_type,)*3)
if color_location == 'v':
vcol_type = cprop_types
coords = np.hstack([coords, color_table])
if color_location == 'e':
ecol_type = cprop_types
els = np.hstack([els, color_table])
self.fil.write("""element vertex %s
property float x
property float y
property float z
%selement %s %s
%s%send_header
""" % (mesh.ncoords(), vcol_type, el_type, mesh.nelems(), eprop_type, ecol_type))
for v in coords:
self.fil.write('%s '*coords.shape[1]%tuple(v) +'\n')
code = {2: ''}.get(nplex, '%s '%nplex)
s = code+('%s '*els.shape[1])+'\n'
for e in els:
self.fil.write(s % tuple(e))
# End