#
##
## 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/.
##
"""Common file type specifications
This module defines the :class:`Filetype` class, which holds the basic
information about one of the common file types used by pyFormex. The user
can define more file types if needed. All file types are collected in
a single dict :attr:`Filetypes` using a simple mnemonic string as key.
Furthermore this module defines some functions to help in the use of
file types, e.g. in dialogs.
"""
import re
from pyformex.path import Path
from pyformex.metaclass import RegistryMeta
[docs]class Filetype(metaclass=RegistryMeta):
"""A class for holding file types and the related filename patterns.
Parameters
----------
key: str
A short and unique mnemonic string, by preference lower case,
that wil be used as lookup key for this Filetype definition
in the global :attr:`Filetypes` collection.
text: str
A textual description of the file type.
*suffixes: sequence of str
All remaining parameters are file suffixes that should be used
to filter files that are supposed to be of this type. Any number
of suffixes is allowed. If None are provided, all files will match
the file type.
Examples
--------
>>> for key in Filetype.dict: print(f"{key} = '{Filetype[key]}'")
all = 'All files (*)'
ccx = 'CalCuliX files (*.dat *.inp)'
dcm = 'DICOM images (*.dcm)'
dxf = 'AutoCAD DXF files (*.dxf)'
dxftext = 'Converted AutoCAD files (*.dxftext)'
flavia = 'flavia results (*.flavia.msh *.flavia.res)'
gts = 'GTS files (*.gts)'
gz = 'Compressed files (*.gz *.bz2)'
html = 'Web pages (*.html)'
icon = 'Icons (*.xpm)'
img = 'Images (*.png *.jpg *.jpeg *.eps *.gif *.bmp)'
inp = 'Abaqus or CalCuliX input files (*.inp)'
neu = 'Gambit Neutral files (*.neu)'
obj = 'Wavefront OBJ files (*.obj)'
off = 'Geomview object files (*.off)'
pgf = 'pyFormex geometry files (*.pgf)'
ply = 'Stanford Polygon File Format files (*.ply)'
png = 'PNG images (*.png)'
poly = 'Polygon files (*.off *.obj *.ply)'
postproc = 'Postproc scripts (*.post.py)'
project = 'pyFormex Projects (*.pzf *.hdf5 *.pyf)'
pyformex = 'pyFormex scripts (*.py)'
pyf = 'pyFormex projects (*.pyf)'
python = 'Python files (*.py)'
pzf = 'pyFormex zip files (*.pzf)'
smesh = 'Tetgen surface mesh files (*.smesh)'
stl = 'STL files (*.stl)'
stlb = 'Binary STL files (*.stl)'
surface = 'Surface models (*.gts *.stl *.off *.obj *.ply)'
tetsurf = 'Tetgen surface (*.smesh)'
tetgen = 'Tetgen files (*.poly *.smesh *.ele *.face *.edge *.node *.neigh)'
text = 'Text files (*.txt *.out *.rst *.md *.py *.html)'
video = 'Video (*.mp4)'
vtk = 'VTK types (*.vtk *.vtp)'
vtp = 'vtkPolyData file (*.vtp)'
geometry = 'Geometry files (*.gts *.inp *.neu *.obj *.off *.pgf *.ply\
*.pzf *.stl *.vtk *.vtp)'
"""
def __init__(self, key, text, *suffixes):
self.text = text
self.suff = suffixes
# @classmethod
# def fromdesc(clas, desc):
@classmethod
def combine_suffixes(clas, *args):
suff = set()
for arg in args:
if arg in clas.dict:
suff |= set(clas[arg].suffixes())
else:
suff |= {arg}
return sorted(suff)
[docs] def suffixes(self, compr=False):
"""Return a list of file suffixes for the Filetype.
Parameters
----------
compr: bool
If True, the file suffixes for compressed files of this type
are automatically added.
Examples
--------
>>> Filetype['pgf'].suffixes()
['pgf']
>>> Filetype['pgf'].suffixes(compr=True)
['pgf', 'pgf.gz', 'pgf.bz2']
>>> Filetype['all'].suffixes()
[]
"""
suff = list(self.suff)
if compr:
compr_types = Filetype['gz'].suff
suff.extend([f"{s}.{c}" for s in suff for c in compr_types])
return suff
[docs] def patterns(self, compr=False):
"""Return a list with the file patterns matching the Filetype.
Parameters
----------
compr: bool
If True, the file suffixes for compressed files of this type
are automatically added.
Examples
--------
>>> Filetype['pgf'].patterns()
['*.pgf']
>>> Filetype['pgf'].patterns(compr=True)
['*.pgf', '*.pgf.gz', '*.pgf.bz2']
>>> Filetype['all'].patterns()
['*']
"""
if self.suff:
return [f"*.{s}" for s in self.suffixes(compr)]
else:
return ['*']
[docs] def desc(self, compr=False):
"""Create a filetype descriptor compatible with Qt Widgets.
Parameters
----------
compr: bool
If True, the file patterns for compressed files are automatically
added.
Returns
-------
str
A string that can be directly used in the Qt File Dialog
widgets to filter the selectable files. This string has the
format ``file type text (*.ext1 *.ext2)``.
Examples
--------
>>> Filetype['img'].desc()
'Images (*.png *.jpg *.jpeg *.eps *.gif *.bmp)'
>>> fileDescriptor('inp')
'Abaqus or CalCuliX input files (*.inp)'
>>> fileDescriptor('doc')
'DOC files (*.doc)'
>>> fileDescriptor('*.inp')
'*.inp'
>>> fileDescriptor('pgf',compr=True)
'pyFormex geometry files (*.pgf *.pgf.gz *.pgf.bz2)'
"""
return f"{self.text} ({' '.join(self.patterns(compr))})"
__str__ = desc
# The builtin file types. Only types that are relevant for pyFormex
# should be added here. Users can of course add their own types.
Filetype('all', 'All files')
Filetype('ccx', 'CalCuliX files', 'dat', 'inp')
Filetype('dcm', 'DICOM images', 'dcm')
Filetype('dxf', 'AutoCAD DXF files', 'dxf')
Filetype('dxftext', 'Converted AutoCAD files', 'dxftext')
Filetype('flavia', 'flavia results', 'flavia.msh', 'flavia.res')
Filetype('gts', 'GTS files', 'gts')
Filetype('gz', 'Compressed files', 'gz', 'bz2')
Filetype('html', 'Web pages', 'html')
Filetype('icon', 'Icons', 'xpm')
Filetype('img', 'Images', 'png', 'jpg', 'jpeg', 'eps', 'gif', 'bmp')
Filetype('inp', 'Abaqus or CalCuliX input files', 'inp')
Filetype('neu', 'Gambit Neutral files', 'neu')
Filetype('obj', 'Wavefront OBJ files', 'obj')
Filetype('off', 'Geomview object files', 'off')
Filetype('pgf', 'pyFormex geometry files', 'pgf')
Filetype('ply', 'Stanford Polygon File Format files', 'ply')
Filetype('png', 'PNG images', 'png')
Filetype('poly', 'Polygon files', 'off', 'obj', 'ply')
Filetype('postproc', 'Postproc scripts', 'post.py')
Filetype('project', 'pyFormex Projects', 'pzf', 'hdf5', 'pyf')
Filetype('pyformex', 'pyFormex scripts', 'py')
Filetype('pyf', 'pyFormex projects', 'pyf')
Filetype('python', 'Python files', 'py')
Filetype('pzf', 'pyFormex zip files', 'pzf')
Filetype('smesh', 'Tetgen surface mesh files', 'smesh')
Filetype('stl', 'STL files', 'stl')
Filetype('stlb', 'Binary STL files', 'stl') # Use only for output
Filetype('surface', 'Surface models', 'gts', 'stl', 'off', 'obj', 'ply')
Filetype('tetsurf', 'Tetgen surface', 'smesh')
Filetype('tetgen', 'Tetgen files', 'poly', 'smesh', 'ele', 'face',
'edge', 'node', 'neigh')
Filetype('text', 'Text files', 'txt', 'out', 'rst', 'md', 'py', 'html')
Filetype('video', 'Video', 'mp4')
Filetype('vtk', 'VTK types', 'vtk', 'vtp')
Filetype('vtp', 'vtkPolyData file', 'vtp')
Filetype('geometry', 'Geometry files', *Filetype.combine_suffixes(
'pzf', 'pgf', 'surface', 'poly', 'vtk', 'inp', 'neu'))
[docs]def fileDescriptor(ftype, compr=False):
"""Return a description of the specified file type(s).
The description is in a format understood by the Qt QFileDialog widget.
Parameters
----------
ftype: str or list of str
The file type (or types) for which a description is requested.
The case of the string(s) is ignored: it is converted to lower case.
Returns
-------
str or list of str
The file description(s) corresponding with the specified file type(s).
The return value(s) depend(s) on the value of the input string(s) in the
the following way (see Examples below):
- if it is a key in the :attr:`file_description` dict, the
corresponding value is returned;
- if it is a string of only alphanumerical characters: it is interpreted
as a file extension and the corresponding return value is
``FTYPE files (*.ftype)``;
- any other string is returned as as: this allows the user to compose
his filters himself.
Examples
--------
>>> fileDescriptor('img')
'Images (*.png *.jpg *.jpeg *.eps *.gif *.bmp)'
>>> fileDescriptor(['stl','all'])
['STL files (*.stl)', 'All files (*)']
>>> fileDescriptor('inp')
'Abaqus or CalCuliX input files (*.inp)'
>>> fileDescriptor('doc')
'DOC files (*.doc)'
>>> fileDescriptor('Video (*.mp4 *.ogv)')
'Video (*.mp4 *.ogv)'
>>> fileDescriptor('pgf',compr=True)
'pyFormex geometry files (*.pgf *.pgf.gz *.pgf.bz2)'
"""
if isinstance(ftype, list):
return [fileDescriptor(f, compr) for f in ftype]
ltype = ftype.lower()
if ltype in Filetype.dict:
ret = Filetype[ltype].desc(compr)
elif ftype.isalnum():
ret = f"{ftype.upper()} files (*.{ftype})"
else:
ret = ftype
return ret
[docs]def fileTypesFromFilter(fdesc):
"""Extract the filetypes from a file type descriptor.
A file type descriptor is a string consisting of an initial part
followed by a second part enclosed in parentheses. The second part
is a space separated list of glob patterns. An example
file descriptor is ``file type text (\\*.ext1 \\*.ext2)``.
This is the format as returned by :meth:`Filetype.desc`.
Parameters
----------
fdesc: str
A file descriptor string.
compr: bool,optional
If True, the compressed file types are automatically added.
Returns
-------
desc: str
The file type description text.
ext: list of str
A list of the matching extensions (without dot) for this type.
An empty string means that any extension is accepted.
Examples
--------
>>> fileTypesFromFilter(Filetype['img'].desc())
['png', 'jpg', 'jpeg', 'eps', 'gif', 'bmp']
>>> fileTypesFromFilter(Filetype['pgf'].desc(compr=True))
['pgf', 'pgf.gz', 'pgf.bz2']
>>> fileTypesFromFilter('* *.png')
['', 'png']
>>> fileTypesFromFilter('Images (*.png *.jpg *.jpeg)')
['png', 'jpg', 'jpeg']
"""
m = re.compile(r'.*\((.*)\).*').match(fdesc)
ext = m.groups()[0] if m else fdesc
return [e.lstrip('*').lstrip('.') for e in ext.split(' ')]
[docs]def setFiletypeFromFilter(filename, fdesc):
"""Make sure a filename has an acceptable suffix.
Parameters
----------
filename: :class:`~path.Path`
The filename to check and set the suffix.
fdesc: str
A file descriptor string.
Returns
-------
Path
If `filename` had a suffix included in `accept`, returns the
input filename unaltered. Else returns the filename with a dot
and the first suffix from `accepted` appeded to it.
Examples
--------
>>> setFiletypeFromFilter('image01.jpg', 'Images (*.png *.jpg *.jpeg)')
Path('image01.jpg')
>>> setFiletypeFromFilter('image01', 'Images (*.png *.jpg *.jpeg)')
Path('image01.png')
"""
filename = Path(filename)
okext = fileTypesFromFilter(fdesc)
if not ('' in okext or filename.filetype() in okext):
filename = filename.with_suffix('.'+okext[0])
return filename
### End