####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2017 Fabrice Salvaire
#
# 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 .
#
####################################################################################################
"""This module implements the Valentina val XML file format.
"""
####################################################################################################
import logging
from pathlib import Path
from lxml import etree
from Patro.Common.Xml.XmlFile import XmlFileMixin
from Patro.Pattern.Pattern import Pattern
from .Measurement import VitFile
from .VitFormat import (
Point,
Line,
Spline,
ModelingPoint,
ModelingSpline,
Detail,
DetailData,
DetailPatternInfo,
DetailGrainline,
DetailNode,
)
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
# Last valentina version supported
VAL_VERSION = '0.7.10'
####################################################################################################
class Modeling:
"""Class to implement a modeling mapper."""
##############################################
def __init__(self):
self._id_map = {}
##############################################
def __getitem__(self, id):
return self._id_map[id]
##############################################
def add(self, item):
self._id_map[item.id] = item
####################################################################################################
class Dispatcher:
"""Baseclass to dispatch XML to Python class."""
__TAGS__ = {}
##############################################
def from_xml(self, element):
tag_cls = self.__TAGS__[element.tag]
if tag_cls is not None:
return tag_cls(element)
else:
raise NotImplementedError
####################################################################################################
class CalculationDispatcher(Dispatcher):
"""Class to implement a dispatcher for calculations."""
_logger = _module_logger.getChild('CalculationDispatcher')
__TAGS__ = {
'arc': None,
'ellipse': None,
'line': Line,
'operation': None,
'point': Point,
'spline': Spline,
}
##############################################
def __init__(self):
# Fixme: could be done in class definition
self._mapping = {} # used for Calculation -> XML
self._init_mapper()
##############################################
def _register_mapping(self, xml_cls):
operation_cls = xml_cls.__operation__
if operation_cls:
self._mapping[xml_cls] = operation_cls
self._mapping[operation_cls] = xml_cls
##############################################
def _init_mapper(self):
for tag_cls in self.__TAGS__.values():
if tag_cls is not None:
if hasattr(tag_cls, '__TYPES__'):
for xml_cls in tag_cls.__TYPES__.values():
if xml_cls is not None:
self._register_mapping(xml_cls)
else:
self._register_mapping(tag_cls)
##############################################
def from_xml(self, element):
tag_cls = self.__TAGS__[element.tag]
if hasattr(tag_cls, '__TYPES__'):
cls = tag_cls.__TYPES__[element.attrib['type']]
else:
cls = tag_cls
if cls is not None:
return cls(element)
else:
raise NotImplementedError
##############################################
def from_operation(self, operation):
return self._mapping[operation.__class__].from_operation(operation)
####################################################################################################
class ModelingDispatcher(Dispatcher):
"""Class to implement a dispatcher for modeling."""
__TAGS__ = {
'point': ModelingPoint,
'spline': ModelingSpline,
}
####################################################################################################
class DetailDispatcher(Dispatcher):
"""Class to implement a dispatcher for detail."""
__TAGS__ = {
'grainline': DetailGrainline,
'patternInfo': DetailPatternInfo,
'data': DetailData,
}
####################################################################################################
_calculation_dispatcher = CalculationDispatcher()
_modeling_dispatcher = ModelingDispatcher()
_detail_dispatcher = DetailDispatcher()
####################################################################################################
class ValFileReaderInternal(XmlFileMixin):
"""Class to read val file."""
_logger = _module_logger.getChild('ValFileReader')
##############################################
def __init__(self, path):
XmlFileMixin.__init__(self, path)
self.root = None
self.attribute = {}
self.vit_file = None
self.pattern = None
self.read()
##############################################
@property
def measurements(self):
if self.vit_file is not None:
return self.vit_file.measurements
else:
return None
##############################################
def read(self):
#
#
#
# 0.7.10
# cm
#
#
# pattern name
# pattern number
# company/Designer name
#
#
#
#
# measurements.vit
#
#
#
#
#
#
#
#
#
self._logger.info('Read Valentina file "{}"'.format(self.path))
self.root = self.parse()
self.read_attributes()
self.read_measurements()
# patternLabel
# patternMaterials
# increments
# previewCalculations
self.pattern = Pattern(self.measurements, self.attribute['unit'])
for piece in self.get_xpath_elements(self.root, 'draw'):
self.read_piece(piece)
##############################################
def read_measurements(self):
measurements_path = self.get_xpath_element(self.root, 'measurements').text
if measurements_path is not None:
measurements_path = Path(measurements_path)
if not measurements_path.exists():
measurements_path = self.path.parent.joinpath(measurements_path)
if not measurements_path.exists():
raise NameError("Cannot find {}".format(measurements_path))
self.vit_file = VitFile(measurements_path)
else:
self.vit_file = None
##############################################
def read_attributes(self):
required_attributes = (
'unit',
)
optional_attributes = (
'description',
'notes',
'patternName',
'patternNumber',
'company',
)
attribute_names = list(required_attributes) + list(optional_attributes)
self.attribute = {name:self.get_text_element(self.root, name) for name in attribute_names}
for name in required_attributes:
if self.attribute[name] is None:
raise NameError('{} is undefined'.format(name))
##############################################
def read_piece(self, piece):
piece_name = piece.attrib['name']
self._logger.info('Create scope "{}"'.format(piece_name))
scope = self.pattern.add_scope(piece_name)
sketch = scope.sketch
for element in self.get_xpath_element(piece, 'calculation'):
try:
xml_calculation = _calculation_dispatcher.from_xml(element)
operation = xml_calculation.to_operation(sketch)
self._logger.info('Add operation {}'.format(operation))
except NotImplementedError:
self._logger.warning('Not implemented calculation\n' + str(etree.tostring(element)))
sketch.eval()
modeling = Modeling()
for element in self.get_xpath_element(piece, 'modeling'):
xml_modeling_item = _modeling_dispatcher.from_xml(element)
modeling.add(xml_modeling_item)
self._logger.info('Modeling {}'.format(xml_modeling_item))
# details = []
for detail_element in self.get_xpath_element(piece, 'details'):
self.read_detail(scope, modeling, detail_element)
# details.append(xml_detail)
##############################################
def read_detail(self, scope, modeling, detail_element):
xml_detail = Detail(modeling, detail_element)
self._logger.info('Detail {}'.format(xml_detail))
for element in detail_element:
if element.tag == 'nodes':
for node in element:
xml_node = DetailNode(node)
xml_detail.append_node(xml_node)
else:
xml_modeling_item = _detail_dispatcher.from_xml(element)
# Fixme: xml_detail. = xml_modeling_item
print(xml_modeling_item)
for node, modeling_item in xml_detail.iter_on_nodes():
# print(node.object_id, '->', modeling_item, '->', modeling_item.object_id)
print(node, '->\n', modeling_item, '->\n', scope.sketch.get_operation(modeling_item.object_id))
####################################################################################################
class ValFileReader:
"""Class to read val file."""
##############################################
def __init__(self, path):
self._internal = ValFileReaderInternal(path)
##############################################
@property
def measurements(self):
return self._internal.measurements
@property
def pattern(self):
return self._internal.pattern
####################################################################################################
class ValFileWriter:
"""Class to write val file."""
_logger = _module_logger.getChild('ValFileWriter')
##############################################
def __init__(self, path, vit_file, pattern):
self._path = str(path)
self._vit_file = vit_file
self._pattern = pattern
root = self._build_xml_tree()
self._write(root)
##############################################
def _build_xml_tree(self):
root = etree.Element('pattern')
root.append(etree.Comment('Pattern created with Patro (https://github.com/FabriceSalvaire/Patro)'))
etree.SubElement(root, 'version').text = self.VAL_VERSION
etree.SubElement(root, 'unit').text = self._pattern.unit
etree.SubElement(root, 'author')
etree.SubElement(root, 'description')
etree.SubElement(root, 'notes')
measurements = etree.SubElement(root, 'measurements')
if self._vit_file is not None:
measurements.text = str(self._vit_file.path)
etree.SubElement(root, 'increments')
for scope in self._pattern.scopes:
draw_element = etree.SubElement(root, 'draw')
draw_element.attrib['name'] = scope.name
calculation_element = etree.SubElement(draw_element, 'calculation')
modeling_element = etree.SubElement(draw_element, 'modeling')
details_element = etree.SubElement(draw_element, 'details')
# group_element = etree.SubElement(draw_element, 'groups')
for operation in scope.sketch.operations:
xml_calculation = _calculation_dispatcher.from_operation(operation)
# print(xml_calculation)
# print(xml_calculation.to_xml_string())
calculation_element.append(xml_calculation.to_xml())
return root
##############################################
def _write(self, root):
with open(self._path, 'wb') as fh:
# ElementTree.write() ?
fh.write(etree.tostring(root, pretty_print=True))