Newer
Older
####################################################################################################
#
# 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/>.
#
####################################################################################################
"""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 .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:
__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 to implement a dispatcher for calculations."""
_logger = _module_logger.getChild('CalculationDispatcher')
__TAGS__ = {
'arc': None,
'ellipse': None,
'line': Line,
'operation': None,
'point': Point,
'spline': Spline,
}
##############################################
# Fixme: could be done in class definition
self._mapping = {} # used for Calculation -> XML
self._init_mapper()
##############################################
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)
##############################################
def from_xml(self, element):
tag_cls = self.__TAGS__[element.tag]
if hasattr(tag_cls, '__TYPES__'):
cls = tag_cls.__TYPES__[element.attrib['type']]
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 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()
####################################################################################################
_logger = _module_logger.getChild('ValFileReader')
##############################################
XmlFileMixin.__init__(self, path)
self.root = None
self.attribute = {}
self.vit_file = None
self.pattern = None
##############################################
@property
def measurements(self):
if self.vit_file is not None:
return self.vit_file.measurements
else:
return None
##############################################
# <!--Pattern created with Valentina v0.6.0.912b (https://valentinaproject.bitbucket.io/).-->
# <version>0.7.10</version>
# <unit>cm</unit>
# <description/>
# <notes/>
# <patternName>pattern name</patternName>
# <patternNumber>pattern number</patternNumber>
# <company>company/Designer name</company>
# <patternLabel>
# <line alignment="0" bold="true" italic="false" sfIncrement="4" text="%author%"/>
# </patternLabel>
# <patternMaterials/>
# <measurements>measurements.vit</measurements>
# <previewCalculations/>
# <draw name="...">
# <calculation/>
# <modeling/>
# <details/>
# <groups/>
# </draw>
# </pattern>
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 = 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))
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
##############################################
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)
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
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)
##############################################
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)
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:
fh.write(etree.tostring(root, pretty_print=True))