Newer
Older
####################################################################################################
#
# 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 <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import logging
from lxml import etree
from ArithmeticInterval import Interval2D
from .Measurement import VitParser
from Valentina.Geometry.Line2D import Line2D
from Valentina.Geometry.Vector2D import Vector2D
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
# {'type': 'trueDarts', 'name1': 'A34', 'dartP2': '139', 'dartP1': '141',
# 'my2': '-3.87275', 'point2': '146', 'dartP3': '140', 'id': '144', 'mx2': '0.794387',
# 'my1': '-2.44561', 'name2': 'A35', 'point1': '145', 'baseLineP2': '63', 'mx1': '-3.64071',
# 'baseLineP1': '68'}
####################################################################################################
def vector_to_interval2d(vector):
x, y = vector.x, vector.y
return Interval2D((x, x), (y, y))
####################################################################################################
class Operation:
_logger = _module_logger.getChild('Operation')
# Improvements:
# XML <-> Python
# for each calculation
# provide mapping between xml and Python attributes
# and type: string, int, float, string expression
# Mixin classes
__ARC_TYPE__ = (
'arcWithLength', # to be implemented
'simple', # to be implemented
)
__ELLIPSE_TYPE__ = (
'simple', # to be implemented
)
__OPERATION_TYPE__ = (
'flippingByAxis', # to be implemented
'flippingByLine', # to be implemented
'moving', # to be implemented
'rotation', # to be implemented
)
##############################################
@staticmethod
def from_xml(element, pattern):
if element.tag == 'point':
return Point.from_xml(element, pattern)
elif element.tag == 'line':
return Line.from_xml(element, pattern)
elif element.tag == 'spline':
return Curve.from_xml(element, pattern)
elif element.tag == 'spline':
return Curve.from_xml(element, pattern)
# elif element.tag == 'operation': # Fixme: name clash
# return Operation.from_xml(element, pattern)
# elif element.tag == 'arc':
# return Arc.from_xml(element, pattern)
# elif element.tag == 'ellipse':
# return Ellipse.from_xml(element, pattern)
else:
return Operation(pattern, element.attrib['id'])
##############################################
@staticmethod
def xml_attributes(element, pattern, attributes, keys):
attrib = element.attrib
kwargs = {key:attrib.get(attribute, None) for key, attribute in zip(keys, attributes)}
kwargs['pattern'] = pattern
return kwargs
##############################################
@property
def id(self):
return self._id
@property
def pattern(self):
return self._pattern
return self.__class__.__name__ + ' {0._id}'.format(self)
self._logger.info('Eval {}'.format(self))
self.eval_internal()
##############################################
def eval_internal(self):
pass
####################################################################################################
class Pattern:
_logger = _module_logger.getChild('Pattern')
def __init__(self, measurements):
self._measurements = measurements
self._evaluator = measurements.evaluator
@property
def measurements(self):
return self._measurements
##############################################
@property
def evaluator(self):
return self._evaluator
##############################################
@property
def operations(self):
return self._operations
##############################################
if operation is not None:
self._operations.append(operation)
self._operation_dict[operation.id] = operation
##############################################
def get_operation(self, id_):
return self._operation_dict[id_]
##############################################
def get_point(self, name):
return self._points[name]
##############################################
def eval(self):
self._logger.info('Eval all operations')
for operation in self._operations:
if isinstance(operation, Point):
self._evaluator.add_point(operation)
operation.eval()
elif isinstance(operation, Curve):
operation.eval() # for control points
##############################################
def dump(self):
print("\nDump operations:")
if isinstance(operation, Point):
print(operation, operation.vector)
else:
print(operation)
##############################################
def bounding_box(self):
interval = None
for operation in self._operations:
if isinstance(operation, Point):
interval_point = vector_to_interval2d(operation.vector)
if interval is None:
interval = interval_point
else:
interval |= interval_point
elif isinstance(operation, Curve):
interval |= vector_to_interval2d(operation.control_point1)
interval |= vector_to_interval2d(operation.control_point2)
return interval
####################################################################################################
class LineProperties:
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
__COLORS__ = (
'black',
'blue',
'cornflowerblue',
'darkBlue',
'darkGreen',
'darkRed',
'darkviolet',
'deeppink',
'deepskyblue',
'goldenrod',
'green',
'lightsalmon',
'lime',
'mediumseagreen',
'orange',
'violet',
'yellow',
)
# Fixme: line style ?
__LINE_TYPE__ = (
'dashDotDotLine',
'dashDotLine',
'dashLine',
'dotLine',
'hair', # should be solid
'none',
)
##############################################
def __init__(self, line_type=None, line_color=None):
self._line_type = line_type
self._line_color = line_color
##############################################
@property
def line_color(self):
return self._line_color
@property
def line_type(self):
return self._line_type
####################################################################################################
class Point(Operation):
__POINT_TYPES__ = (
'alongLine',
'bisector', # to be implemented
'curveIntersectAxis', # to be implemented
'cutArc', # to be implemented
'cutSpline', # to be implemented
'cutSplinePath', # to be implemented
'endLine',
'height', # to be implemented
'lineIntersect',
'lineIntersectAxis', # to be implemented
'normal',
'pointFromArcAndTangent', # to be implemented
'pointFromCircleAndTangent', # to be implemented
'pointOfContact', # to be implemented
'pointOfIntersection',
'pointOfIntersectionArcs', # to be implemented
'pointOfIntersectionCircles', # to be implemented
'pointOfIntersectionCurves', # to be implemented
'shoulder', # to be implemented
'single',
'triangle', # to be implemented
'trueDarts', # to be implemented
)
self._mx = float(mx) # Fixme: pass type in xml_attributes?
self._my = float(my)
self._vector = None
##############################################
@property
def name(self):
return self._name
@property
def mx(self):
return self._mx
@property
def my(self):
return self._my
@property
def vector(self):
return self._vector
##############################################
@staticmethod
if element.tag != 'point':
raise ValueError
type_ = element.attrib['type']
if type_ == 'single':
return SinglePoint.from_xml(element, pattern)
return AlongLinePoint.from_xml(element, pattern)
return EndLinePoint.from_xml(element, pattern)
return LineIntersectPoint.from_xml(element, pattern)
return NormalPoint.from_xml(element, pattern)
return PointOfIntersectionPoint.from_xml(element, pattern)
##############################################
def _post_eval_internal(self):
self._logger.info('{0._name} {0._vector}'.format(self))
####################################################################################################
class SinglePoint(Point):
##############################################
# super(SinglePoint, self).__init__(id_, name, mx, my, line_type, line_color)
Point.__init__(self, pattern, id_, name, mx, my)
self._x = Expression(x, pattern.evaluator)
self._y = Expression(y, pattern.evaluator)
##############################################
@property
def x(self):
return self._x
@property
def y(self):
return self._y
##############################################
@staticmethod
# {'y': '1.0', 'mx': '0.1', 'type': 'single', 'my': '0.2', 'id': '1', 'name': 'A0', 'x': '0.7'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'name', 'x', 'y', 'mx', 'my'),
('id_', 'name', 'x', 'y', 'mx', 'my'))
return SinglePoint(**kwargs)
##############################################
def __repr__(self):
return self.__class__.__name__ + ' {0._name} = ({0._x}, {0._y})'.format(self)
##############################################
def eval_internal(self):
self._vector = Vector2D(self._x.value, self._y.value)
self._post_eval_internal()
####################################################################################################
class AlongLinePoint(Point, LineProperties):
##############################################
first_point, second_point, length,
mx=0, my=0,
line_type=None, line_color=None,
# super(AlongLinePoint, self).__init__(id_, name, mx, my, line_type, line_color)
Point.__init__(self, pattern, id_, name, mx, my)
self._first_point = pattern.get_operation(first_point)
self._second_point = pattern.get_operation(second_point)
self._length = Expression(length, pattern.evaluator)
##############################################
@property
def first_point(self):
return self._first_point
@property
def second_point(self):
return self._second_point
@property
def length(self):
return self._length
##############################################
@staticmethod
# {'lineColor': 'black', 'firstPoint': '138', 'id': '141', 'mx': '-4.2484', 'typeLine': 'none',
# 'my': '1.01162', 'name': 'A33', 'length': 'Line_A30_A32', 'type': 'alongLine', 'secondPoint': '68'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'name', 'firstPoint', 'secondPoint', 'length', 'mx', 'my', 'lineColor', 'typeLine'),
('id_', 'name', 'first_point', 'second_point', 'length', 'mx', 'my', 'line_color', 'line_type'))
##############################################
def __repr__(self):
return self.__class__.__name__ + ' {0._name} = ({0._first_point.name}, {0._second_point.name}, {0._length})'.format(self)
##############################################
def eval_internal(self):
vector = self._second_point.vector - self._first_point.vector
self._pattern.evaluator.set_current_segment(vector)
self._vector = self._first_point.vector + vector.to_normalised()*self._length.value
self._pattern.evaluator.unset_current_segment()
self._post_eval_internal()
####################################################################################################
class EndLinePoint(Point, LineProperties):
##############################################
base_point, angle, length,
mx=0, my=0,
line_type=None, line_color=None,
# super(EndLinePoint, self).__init__(id_, name, mx, my, line_type, line_color)
Point.__init__(self, pattern, id_, name, mx, my)
self._base_point = pattern.get_operation(base_point)
self._angle = Expression(angle, pattern.evaluator)
self._length = Expression(length, pattern.evaluator)
##############################################
@property
def base_point(self):
return self._base_point
@property
def length(self):
return self._length
@property
def angle(self):
return self._angle
##############################################
@staticmethod
# {'basePoint': '1', 'name': 'A1', 'id': '2', 'angle': '0', 'length': 'waist_circ/2+10',
# 'typeLine': 'dashDotLine', 'my': '0.264583', 'type': 'endLine', 'mx': '0.132292', 'lineColor': 'black'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'name', 'basePoint', 'angle', 'length', 'mx', 'my', 'lineColor', 'typeLine'),
('id_', 'name', 'base_point', 'angle', 'length', 'mx', 'my', 'line_color', 'line_type'))
return EndLinePoint(**kwargs)
##############################################
def __repr__(self):
return self.__class__.__name__ + ' {0._name} = ({0._base_point.name}, {0._angle}, {0._length})'.format(self)
##############################################
def eval_internal(self):
self._vector = self._base_point._vector + Vector2D.from_angle(self._angle.value)*self._length.value
self._post_eval_internal()
####################################################################################################
class LineIntersectPoint(Point, LineProperties):
##############################################
point1_line1, point2_line1, point1_line2, point2_line2,
mx=0, my=0,
line_type=None, line_color=None,
# super(LineIntersectPoint, self).__init__(id_, name, mx, my, line_type, line_color)
Point.__init__(self, pattern, id_, name, mx, my)
self._point1_line1 = pattern.get_operation(point1_line1)
self._point2_line1 = pattern.get_operation(point2_line1)
self._point1_line2 = pattern.get_operation(point1_line2)
self._point2_line2 = pattern.get_operation(point2_line2)
##############################################
@property
def point1_line1(self):
return self._point1_line1
@property
def point2_line1(self):
return self._point2_line1
@property
def point1_line2(self):
return self._point1_line2
@property
def point2_line2(self):
return self._point2_line2
##############################################
@staticmethod
# {'type': 'lineIntersect', 'p2Line1': '32', 'mx': '0.132292', 'p1Line2': '10',
# 'p2Line2': '11', 'p1Line1': '27', 'id': '39', 'my': '0.264583', 'name': 'Cp'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'name', 'p1Line1', 'p2Line1', 'p1Line2', 'p2Line2', 'mx', 'my', 'lineColor', 'typeLine'),
('id_', 'name', 'point1_line1', 'point2_line1', 'point1_line2', 'point2_line2', 'mx', 'my', 'line_color', 'line_type'))
##############################################
def __repr__(self):
return self.__class__.__name__ + ' {0._name} = ({0._point1_line1.name}, {0._point2_line1.name}, {0._point1_line2.name}, {0._point2_line2.name})'.format(self)
##############################################
def eval_internal(self):
line1 = Line2D(self._point1_line1.vector, self._point2_line1.vector)
line2 = Line2D(self._point1_line2.vector, self._point2_line2.vector)
self._vector = line1.intersection(line2)
self._post_eval_internal()
####################################################################################################
class NormalPoint(Point, LineProperties):
##############################################
first_point, second_point, angle, length,
mx=0, my=0,
line_type=None, line_color=None,
# super(NormalPoint, self).__init__(id_, name, mx, my, line_type, line_color)
Point.__init__(self, pattern, id_, name, mx, my)
self._first_point = pattern.get_operation(first_point)
self._second_point = pattern.get_operation(second_point)
self._angle = Expression(angle, pattern.evaluator)
self._length = Expression(length, pattern.evaluator)
##############################################
@property
def first_point(self):
return self._first_point
@property
def second_point(self):
return self._second_point
@property
def angle(self):
return self._angle
@property
def length(self):
return self._length
##############################################
@staticmethod
# {'my': '-4.18524', 'secondPoint': '63', 'name': 'A36', 'angle': '0', 'length': '0.5',
# 'firstPoint': '138', 'typeLine': 'hair', 'type': 'normal', 'mx': '-1.57131', 'id': '147', 'lineColor': 'black'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'name', 'firstPoint', 'secondPoint', 'angle', 'length', 'mx', 'my', 'lineColor', 'typeLine'),
('id_', 'name', 'first_point', 'second_point', 'angle', 'length', 'mx', 'my', 'line_color', 'line_type'))
##############################################
def __repr__(self):
return self.__class__.__name__ + ' {0._name} = ({0._first_point.name}, {0._second_point.name}, {0._angle}, {0._length})'.format(self)
##############################################
def eval_internal(self):
vector = self._second_point.vector - self._first_point.vector
self._pattern.evaluator.set_current_segment(vector)
direction = vector.to_normalised()
direction = direction.rotate_counter_clockwise_90()
angle = self._angle.value
if angle:
direction = direction.rotate_counter_clockwise(angle)
self._vector = self._first_point.vector + direction*self._length.value
self._pattern.evaluator.unset_current_segment()
self._post_eval_internal()
####################################################################################################
class PointOfIntersectionPoint(Point):
##############################################
def __init__(self, pattern, id_, name,
first_point, second_point,
# super(PointOfIntersectionPoint, self).__init__(id_, name, mx, my)
Point.__init__(self, pattern, id_, name, mx, my)
self._first_point = pattern.get_operation(first_point)
self._second_point = pattern.get_operation(second_point)
##############################################
@property
def first_point(self):
return self._first_point
@property
def second_point(self):
return self._second_point
##############################################
@staticmethod
# {'id': '71', 'secondPoint': '56', 'type': 'pointOfIntersection', 'firstPoint': '59', 'name': 'Nc', 'mx': '0.132292', 'my': '0.264583'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'name', 'firstPoint', 'secondPoint', 'mx', 'my'),
('id_', 'name', 'first_point', 'second_point', 'mx', 'my'))
##############################################
def __repr__(self):
return self.__class__.__name__ + ' {0._name} = ({0._first_point.name}, {0._second_point.name})'.format(self)
##############################################
def eval_internal(self):
self._vector = Vector2D(self._first_point.vector.x, self._second_point.vector.y)
self._post_eval_internal()
####################################################################################################
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
class Line(Operation, LineProperties):
##############################################
def __init__(self, pattern, id_,
first_point, second_point,
line_type=None, line_color=None,
):
Operation.__init__(self, pattern, id_)
LineProperties.__init__(self, line_type, line_color)
self._first_point = pattern.get_operation(first_point)
self._second_point = pattern.get_operation(second_point)
##############################################
@property
def first_point(self):
return self._first_point
@property
def second_point(self):
return self._second_point
##############################################
@staticmethod
def from_xml(element, pattern):
# {'typeLine': 'hair', 'lineColor': 'black', 'firstPoint': '74', 'secondPoint': '72', 'id': '76'}
kwargs = Operation.xml_attributes(element, pattern,
('id', 'firstPoint', 'secondPoint', 'lineColor', 'typeLine'),
('id_', 'first_point', 'second_point', 'line_color', 'line_type'))
return Line(**kwargs)
##############################################
def __repr__(self):
return self.__class__.__name__ + ' ({0._first_point.name}, {0._second_point.name})'.format(self)
##############################################
def eval_internal(self):
pass
####################################################################################################
class Curve(Operation, LineProperties):
__SPLINE_TYPE__ = (
'cubicBezier', # to be implemented
'cubicBezierPath', # to be implemented
'pathInteractive', # to be implemented
'simpleInteractive',
)
##############################################
def __init__(self, pattern, id_,
first_point, second_point,
angle1, length1,
angle2, length2,
line_type=None, line_color=None,
):
Operation.__init__(self, pattern, id_)
LineProperties.__init__(self, line_type, line_color)
self._first_point = pattern.get_operation(first_point)
self._second_point = pattern.get_operation(second_point)
self._angle1 = Expression(angle1, pattern.evaluator)
self._length1 = Expression(length1, pattern.evaluator)
self._angle2 = Expression(angle2, pattern.evaluator)
self._length2 = Expression(length2, pattern.evaluator)
self._control_point1 = None # Fixme: not yet computed
self._control_point2 = None
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
##############################################
@property
def first_point(self):
return self._first_point
@property
def second_point(self):
return self._second_point
@property
def angle1(self):
return self._angle1
@property
def length1(self):
return self._length1
@property
def angle2(self):
return self._angle2
@property
def length2(self):
return self._length2
@property
def control_point1(self):
return self._control_point1
@property
def control_point2(self):
return self._control_point2
##############################################
@staticmethod
def from_xml(element, pattern):
# 'type': 'simpleInteractive',
# 'length2': '8.65783', 'angle2': '85.3921',
# 'point4': '31',
# 'color': 'black',
# 'length1': '8.85757', 'angle1': '251.913',
# 'point1': '20',
# 'id': '97'
# }
kwargs = Operation.xml_attributes(element, pattern,
('id', 'point1', 'point4', 'angle1', 'length1', 'angle2', 'length2', 'color'),
('id_', 'first_point', 'second_point', 'angle1', 'length1', 'angle2', 'length2', 'line_color'))
return Curve(**kwargs)
##############################################
def __repr__(self):
return self.__class__.__name__ + ' ({0._first_point.name}, {0._second_point.name}, {0._angle1}, {0._length1}, {0._angle2}, {0._length2})'.format(self)
##############################################
def eval_internal(self):
self._control_point1 = self.first_point.vector + Vector2D.from_angle(self._angle1.value)*self._length1.value
self._control_point2 = self.second_point.vector + Vector2D.from_angle(self._angle2.value)*self._length2.value
self._logger.info("Control points : {} {}".format(self._control_point1, self._control_point2))
####################################################################################################
_logger = _module_logger.getChild('ValParser')
##############################################
def parse(self, val_path):
with open(val_path, 'rb') as f:
source = f.read()
tree = etree.fromstring(source)
measurements_path = self._get_xpath_element(tree, 'measurements').text
measurements = VitParser().parse(measurements_path)
pattern = Pattern(measurements)
elements = self._get_xpath_element(tree, 'draw/calculation')
for element in elements:
operation = Operation.from_xml(element, pattern)