# $Id$
##
##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 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-2018 (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


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()


    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))



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()


    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
