#################################################################################################### # # PyValentina - A Python implementation of Valentina Pattern Drafting Software # 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 val XML file format and is designed so as to decouple the XML details and the calculation API. The purpose of each XmlObjectAdaptator sub-classes is to serve as a bidirectional adaptor between the XML format and the API. """ #################################################################################################### import logging from pathlib import Path from lxml import etree from Valentina.Geometry.Vector import Vector2D from Valentina.Pattern.Measurement import Measurements, Measurement from Valentina.Pattern.Pattern import Pattern from Valentina.Xml.Objectivity import (IntAttribute, FloatAttribute, StringAttribute, XmlObjectAdaptator) from Valentina.Xml.XmlFile import XmlFileMixin from .Measurements import VitFile import Valentina.Pattern.Calculation as Calculation #################################################################################################### _module_logger = logging.getLogger(__name__) #################################################################################################### class CalculationMixin: __attributes__ = ( IntAttribute('id'), ) __calculation__ = None ############################################## def __repr__(self): return '{} {}'.format(self.__class__.__name__, self.to_dict()) ############################################## def to_calculation(self, pattern): raise NotImplementedError ############################################## @classmethod def from_calculation(calculation): raise NotImplementedError #################################################################################################### class CalculationTypeMixin(CalculationMixin): ############################################## def to_xml(self): return XmlObjectAdaptator.to_xml(self, type=self.__type__) #################################################################################################### class LinePropertiesMixin: __attributes__ = ( StringAttribute('line_color', 'lineColor'), StringAttribute('line_style', 'typeLine'), ) __COLORS__ = ( 'black', 'blue', 'cornflowerblue', 'darkBlue', 'darkGreen', 'darkRed', 'darkviolet', 'deeppink', 'deepskyblue', 'goldenrod', 'green', 'lightsalmon', 'lime', 'mediumseagreen', 'orange', 'violet', 'yellow', ) __LINE_STYLE__ = ( 'dashDotDotLine', 'dashDotLine', 'dashLine', 'dotLine', 'hair', # should be solid 'none', ) #################################################################################################### # angle # angle1 # angle2 # arc # axisP1 # axisP2 # axisType # baseLineP1 # baseLineP2 # basePoint # c1Center # c1Radius # c2Center # c2Radius # cCenter # center # color # cRadius # crossPoint # curve # curve1 # curve2 # dartP1 # dartP2 # dartP3 # duplicate # firstArc # firstPoint # hCrossPoint # id # idObject # length # length1 # length2 # lineColor # mx # mx1 # mx2 # my # my1 # my2 # name # name1 # name2 # object (group) # p1Line # p1Line1 # p1Line2 # p2Line # p2Line1 # p2Line2 # point1 # point2 # point3 # point4 # pShoulder # pSpline # radius # radius1 # radius2 # rotationAngle # secondArc # secondPoint # spline # splinePath # suffix # tangent # thirdPoint # tool # type # typeLine # vCrossPoint # visible (group) # x # y class XyMixin: __attributes__ = ( StringAttribute('x'), StringAttribute('y'), ) class FirstSecondPointMixin: __attributes__ = ( IntAttribute('first_point', 'firstPoint'), IntAttribute('second_point', 'secondPoint'), ) class FirstSecondThirdPointMixin(FirstSecondPointMixin): __attributes__ = ( IntAttribute('third_point', 'thirdPoint'), ) class BasePointMixin: __attributes__ = ( IntAttribute('base_point', 'basePoint'), ) class Line1Mixin: __attributes__ = ( IntAttribute('point1_line1', 'p1Line1'), IntAttribute('point2_line1', 'p2Line1'), ) class Line2Mixin: __attributes__ = ( IntAttribute('point1_line2', 'p1Line2'), IntAttribute('point2_line2', 'p2Line2'), ) class Line12Mixin(Line1Mixin, Line2Mixin): pass class LengthMixin: __attributes__ = ( StringAttribute('length'), ) class AngleMixin: __attributes__ = ( StringAttribute('angle'), ) class LengthAngleMixin(LengthMixin, AngleMixin): pass #################################################################################################### class PointMixin(CalculationTypeMixin): __tag__ = 'point' __attributes__ = ( StringAttribute('name'), FloatAttribute('mx'), FloatAttribute('my'), ) ############################################## def to_calculation(self, pattern): kwargs = self.to_dict(exclude=('mx', 'my')) # id' kwargs['label_offset'] = Vector2D(self.mx, self.my) return self.__calculation__(pattern, **kwargs) ############################################## @classmethod def from_calculation(cls, calculation): kwargs = cls.get_dict(calculation, exclude=('mx', 'my')) label_offset = calculation.label_offset kwargs['mx'] = label_offset.x kwargs['my'] = label_offset.y return cls(**kwargs) #################################################################################################### class PointLinePropertiesMixin(PointMixin, LinePropertiesMixin): pass #################################################################################################### class AlongLinePoint(PointLinePropertiesMixin, FirstSecondPointMixin, LengthMixin, XmlObjectAdaptator): # __type__ = 'alongLine' __calculation__ = Calculation.AlongLinePoint #################################################################################################### class BissectorPoint(PointLinePropertiesMixin, FirstSecondThirdPointMixin, LengthMixin, XmlObjectAdaptator): # __type__ = 'bisector' # __calculation__ = Calculation.BissectorPoint #################################################################################################### # __type__ = 'curveIntersectAxis' # # __type__ = 'cutArc' # # __type__ = 'cutSpline' # # __type__ = 'cutSplinePath' # #################################################################################################### class EndLinePoint(PointLinePropertiesMixin, BasePointMixin, LengthAngleMixin, XmlObjectAdaptator): # __type__ = 'endLine' __calculation__ = Calculation.EndLinePoint #################################################################################################### class HeightPoint(PointLinePropertiesMixin, BasePointMixin, Line1Mixin, XmlObjectAdaptator): # __type__ = 'height' # __calculation__ = Calculation.HeightPoint #################################################################################################### class LineIntersectPoint(PointMixin, Line12Mixin, XmlObjectAdaptator): # __type__ = 'lineIntersect' __calculation__ = Calculation.LineIntersectPoint #################################################################################################### class LineIntersectAxisPoint(PointLinePropertiesMixin, BasePointMixin, Line1Mixin, AngleMixin, XmlObjectAdaptator): # __type__ = 'lineIntersectAxis' # __calculation__ = Calculation.LineIntersectAxisPoint #################################################################################################### class NormalPoint(PointLinePropertiesMixin, FirstSecondPointMixin, LengthAngleMixin, XmlObjectAdaptator): # __type__ = 'normal' __calculation__ = Calculation.NormalPoint #################################################################################################### # __type__ = 'pointFromArcAndTangent' # # __type__ = 'pointFromCircleAndTangent' # # __type__ = 'pointOfContact' # #################################################################################################### class PointOfIntersection(PointMixin, FirstSecondPointMixin, XmlObjectAdaptator): # __type__ = 'pointOfIntersection' __calculation__ = Calculation.PointOfIntersection #################################################################################################### # __type__ = 'pointOfIntersectionArcs' # # __type__ = 'pointOfIntersectionCircles' # # __type__ = 'pointOfIntersectionCurves' # #################################################################################################### class ShoulderPoint(PointLinePropertiesMixin, Line1Mixin, LengthMixin, XmlObjectAdaptator): # __type__ = 'shoulder' # __calculation__ = Calculation.ShoulderPoint __attributes__ = ( IntAttribute('shoulder_point', 'pShoulder'), ) #################################################################################################### class SinglePoint(PointMixin, XyMixin, XmlObjectAdaptator): # __type__ = 'single' __calculation__ = Calculation.SinglePoint #################################################################################################### # __type__ = 'triangle' # # __type__ = 'trueDarts' # #################################################################################################### class Point: # We cannot use a metaclass to auto-register due to XmlObjectAdaptator (right ?) __TYPES__ = { 'alongLine': AlongLinePoint, 'bisector': None, 'curveIntersectAxis': None, 'cutArc': None, 'cutSpline': None, 'cutSplinePath': None, 'endLine': EndLinePoint, 'height': None, 'lineIntersect': LineIntersectPoint, 'lineIntersectAxis': None, 'normal': NormalPoint, 'pointFromArcAndTangent': None, 'pointFromCircleAndTangent': None, 'pointOfContact': None, 'pointOfIntersection': PointOfIntersection, 'pointOfIntersectionArcs': None, 'pointOfIntersectionCircles': None, 'pointOfIntersectionCurves': None, 'shoulder': None, 'single': SinglePoint, 'triangle': None, 'trueDarts': None, } #################################################################################################### class Line(CalculationMixin, LinePropertiesMixin, FirstSecondPointMixin, XmlObjectAdaptator): # __tag__ = 'line' __calculation__ = Calculation.Line ############################################## def to_calculation(self, pattern): return self.__calculation__(pattern, **self.to_dict()) # exclude=('id') ############################################## @classmethod def from_calculation(cls, calculation): kwargs = cls.get_dict(calculation) return cls(**kwargs) #################################################################################################### class SplineMixin(CalculationTypeMixin): __tag__ = 'spline' #################################################################################################### class SimpleInteractiveSpline(SplineMixin, XmlObjectAdaptator): # __type__ = 'simpleInteractive' __attributes__ = ( IntAttribute('first_point', 'point1'), IntAttribute('second_point', 'point4'), StringAttribute('length1'), StringAttribute('length2'), StringAttribute('angle1'), StringAttribute('angle2'), StringAttribute('line_color', 'color'), ) __calculation__ = Calculation.SimpleInteractiveSpline ############################################## def to_calculation(self, pattern): return self.__calculation__(pattern, **self.to_dict()) # exclude=('id') ############################################## @classmethod def from_calculation(cls, calculation): kwargs = cls.get_dict(calculation) return cls(**kwargs) #################################################################################################### # # # # # # # # # # # # # # # #################################################################################################### class Spline: # We cannot use a metaclass to auto-register due to XmlObjectAdaptator (right ?) __TYPES__ = { 'cubicBezier': None, 'cubicBezierPath': None, 'pathInteractive': None, 'simpleInteractive': SimpleInteractiveSpline, } #################################################################################################### # # # __ARC_TYPE__ = ( # 'arcWithLength', # 'simple', # ) # # __ELLIPSE_TYPE__ = ( # 'simple', # ) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # __OPERATION_TYPE__ = ( # 'flippingByAxis', # 'flippingByLine', # 'moving', # 'rotation', # ) #################################################################################################### class CalculationDispatcher: _logger = _module_logger.getChild('CalculationDispatcher') __TAGS__ = { 'arc': None, 'ellipse': None, 'line': Line, 'operation': None, 'point': Point, 'spline': Spline, } ############################################## def __init__(self): self._mapping = {} 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 ValFile(XmlFileMixin): _logger = _module_logger.getChild('ValFile') _calculation_dispatcher = CalculationDispatcher() ############################################## def __init__(self, path=None): # Fixme: if path is None: path = '' XmlFileMixin.__init__(self, path) self._vit_file = None self._pattern = None # Fixme: # if path is not None: if path != '': self._read() ############################################## def Write(self, path, vit_file, pattern): # Fixme: 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): # # # # 0.4.0 # cm # # # # # # # # #
# # # 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) unit = self._get_xpath_element(tree, 'unit').text pattern = Pattern(self._vit_file.measurements, unit) self._pattern = pattern elements = self._get_xpath_element(tree, 'draw/calculation') for element in elements: try: xml_calculation = self._calculation_dispatcher.from_xml(element) xml_calculation.to_calculation(pattern) # calculation = # pattern.add(calculation) except NotImplementedError: self._logger.warning('Not implemented calculation\n' + str(etree.tostring(element))) pattern.eval() ############################################## def write(self, path=None): root = etree.Element('pattern') root.append(etree.Comment('Pattern created with PyValentina (https://github.com/FabriceSalvaire/PyValentina)')) 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) 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) # 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: # ElementTree.write() ? f.write(etree.tostring(root, pretty_print=True))