Skip to content
Pattern.py 10.4 KiB
Newer Older
####################################################################################################
#
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# 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 <http://www.gnu.org/licenses/>.
#
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
"""This module implements the val XML file format.
####################################################################################################

import logging
from Patro.Common.Xml.XmlFile import XmlFileMixin
Fabrice Salvaire's avatar
Fabrice Salvaire committed
from Patro.Pattern.Pattern import Pattern
from .Measurements import VitFile
Fabrice Salvaire's avatar
Fabrice Salvaire committed
from .VitFormat import (
    Point,
    Line,
    Spline,
    ModelingPoint,
    ModelingSpline,
    Detail,
    DetailData,
    DetailPatternInfo,
    DetailGrainline,
    DetailNode,
)

####################################################################################################

_module_logger = logging.getLogger(__name__)

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Baseclass to dispatch XML to Python class."""

    __TAGS__ = {}

    ##############################################

    def from_xml(self, element):

        tag_class = self.__TAGS__[element.tag]
        if tag_class is not None:
            return tag_class(element)
        else:
            raise NotImplementedError

####################################################################################################

class CalculationDispatcher:

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """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):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # Fixme: could be done in class definition
        self._mapping = {} # used for Calculation -> XML
        self._init_mapper()

    ##############################################

    def _register_mapping(self, xml_class):

        calculation_class = xml_class.__calculation__
        if calculation_class:
            self._mapping[xml_class] = calculation_class
            self._mapping[calculation_class] = xml_class

    ##############################################

    def _init_mapper(self):

        for tag_class in self.__TAGS__.values():
            if tag_class is not None:
                if hasattr(tag_class, '__TYPES__'):
                    for xml_class in tag_class.__TYPES__.values():
                        if xml_class is not None:
                            self._register_mapping(xml_class)
                else:
                    self._register_mapping(tag_class)

    ##############################################

    def from_xml(self, element):
        tag_class = self.__TAGS__[element.tag]
        if hasattr(tag_class, '__TYPES__'):
            cls = tag_class.__TYPES__[element.attrib['type']]
        else:
            cls = tag_class
        if cls is not None:
            return cls(element)
        else:
            raise NotImplementedError

    ##############################################

    def from_calculation(self, calculation):
        return self._mapping[calculation.__class__].from_calculation(calculation)

####################################################################################################

class Modeling:

    ##############################################

    def __init__(self):
        self._items = []
        self._id_map = {}

    ##############################################

    def __getitem__(self, id):
        return self._id_map[id]

    ##############################################

    def append(self, item):
        self._items.append(item)
        self._id_map[item.id] = item

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class ModelingDispatcher(Dispatcher):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __TAGS__ = {
        'point': ModelingPoint,
        'spline': ModelingSpline,
        }

####################################################################################################

class DetailDispatcher(Dispatcher):

    __TAGS__ = {
        'grainline': DetailGrainline,
        'patternInfo': DetailPatternInfo,
        'data': DetailData,
        }

####################################################################################################

class ValFile(XmlFileMixin):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class to read/write val file."""

    _logger = _module_logger.getChild('ValFile')

    _calculation_dispatcher = CalculationDispatcher()
    _modeling_dispatcher = ModelingDispatcher()
    _detail_dispatcher = DetailDispatcher()
    ##############################################

    def __init__(self, path=None):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # Fixme: path
        XmlFileMixin.__init__(self, path)
        self._vit_file = None
        self._pattern = None
        # if path is not None:
        if path != '':
            self._read()

    ##############################################

    def Write(self, path, vit_file, pattern):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # Fixme: write
        self._vit_file = vit_file
        self._pattern = pattern
        self.write(path)

    ##############################################

    @property
    def measurements(self):
        return self._vit_file.measurements

    @property
    def pattern(self):
        return self._pattern

    ##############################################

    def _read(self):

        # <?xml version='1.0' encoding='UTF-8'?>
        # <pattern>
        #     <!--Pattern created with Valentina (http://www.valentina-project.org/).-->
        #     <version>0.4.0</version>
        #     <unit>cm</unit>
        #     <author/>
        #     <description/>
        #     <notes/>
        #     <measurements/>
        #     <increments/>
        #     <draw name="Pattern piece 1">
        #         <calculation/>
        #         <modeling/>
        #         <details/>
        #         <groups/>
        #     </draw>
        # </pattern>

        tree = self._parse()

        measurements_path = Path(self._get_xpath_element(tree, 'measurements').text)
        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)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        unit = self._get_xpath_element(tree, 'unit').text

        pattern = Pattern(self._vit_file.measurements, unit)
        for element in self._get_xpath_element(tree, 'draw/calculation'):
                xml_calculation = self._calculation_dispatcher.from_xml(element)
                xml_calculation.to_calculation(pattern)
            except NotImplementedError:
                self._logger.warning('Not implemented calculation\n' +  str(etree.tostring(element)))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        pattern.eval()

        modeling = Modeling()
        for element in self._get_xpath_element(tree, 'draw/modeling'):
            xml_modeling_item = self._modeling_dispatcher.from_xml(element)
            modeling.append(xml_modeling_item)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            # print(xml_modeling_item)

        details = []
        for detail_element in self._get_xpath_element(tree, 'draw/details'):
            xml_detail = Detail(modeling, detail_element)
            details.append(xml_detail)
            print(xml_detail)
            for element in detail_element:
                if element.tag == 'nodes':
                    for node in element:
                        xml_node = DetailNode(node)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                        # print(xml_node)
                        xml_detail.append_node(xml_node)
                else:
                    xml_modeling_item = self._detail_dispatcher.from_xml(element)
                    # Fixme: xml_detail. = xml_modeling_item
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                    # 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', pattern.get_calculation(modeling_item.object_id))

    ##############################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def write(self, path=None):

        root = etree.Element('pattern')
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        root.append(etree.Comment('Pattern created with Patro (https://github.com/FabriceSalvaire/Patro)'))
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        etree.SubElement(root, 'version').text = '0.4.0'
        etree.SubElement(root, 'unit').text = self._pattern.unit
        etree.SubElement(root, 'author')
        etree.SubElement(root, 'description')
        etree.SubElement(root, 'notes')
        etree.SubElement(root, 'measurements').text = str(self._vit_file.path)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        etree.SubElement(root, 'increments')

        draw_element = etree.SubElement(root, 'draw') # Fixme:
        draw_element.attrib['name'] = 'Pattern piece 1' # Fixme:

        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 calculation in self._pattern.calculations:
            xml_calculation = self._calculation_dispatcher.from_calculation(calculation)
            # print(xml_calculation)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            # print(xml_calculation.to_xml_string())
            calculation_element.append(xml_calculation.to_xml())

        if path is None:
            path = self.path
        with open(str(path), 'wb') as f:
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            # ElementTree.write() ?
            f.write(etree.tostring(root, pretty_print=True))