diff --git a/Patro/GeometryEngine/Conic.py b/Patro/GeometryEngine/Conic.py index 2a2ad0446da14a2c4e25327058b1df65b05c8c77..62f94352c761aad3325fed9b509e960292173e25 100644 --- a/Patro/GeometryEngine/Conic.py +++ b/Patro/GeometryEngine/Conic.py @@ -48,6 +48,8 @@ __all__ = [ #################################################################################################### +import logging + import math from math import fabs, sqrt, radians, pi, cos, sin # , degrees @@ -63,6 +65,10 @@ from .Transformation import Transformation2D #################################################################################################### +_module_logger = logging.getLogger(__name__) + +#################################################################################################### + class PointNotOnCircleError(ValueError): pass @@ -476,6 +482,8 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive): """ + _logger = _module_logger.getChild('Ellipse2D') + ############################################## @classmethod @@ -514,11 +522,16 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive): radius_x2 = radius_x**2 radius_y2 = radius_y**2 - point1_prime = Transformation2D.Rotation(angle) * (point1 - point2)/2 + # We define a new referential with the origin is set to the middle of P1 — P2 + origin_prime = (point1 + point2)/2 + + # P1 is exprimed in this referential where the ellipse major axis line up with the x axis + point1_prime = Transformation2D.Rotation(-angle) * (point1 - point2)/2 # Ensure radii are large enough radii_scale = point1_prime.x**2/radius_x2 + point1_prime.y**2/radius_y2 if radii_scale > 1: + self._logger.warning('SVG Arc: radii must be scale') radii_scale = math.sqrt(radii_scale) radius_x = radii_scale * radius_x radius_y = radii_scale * radius_y @@ -532,19 +545,32 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive): ratio = radius_x/radius_y - sign = -1 if large_arc == sweep else 1 - center_prime = sign * math.sqrt(num / den) * point1_prime.anti_normal.divide(ratio, 1/ratio) - center = Transformation2D.Rotation(-angle) * center_prime + (point1 + point2)/2 + sign = 1 if large_arc != sweep else -1 + # print(point1_prime) + # print(point1_prime.anti_normal) + # print(ratio) + # print(point1_prime.anti_normal.scale(ratio, 1/ratio)) + sign *= -1 # Fixme: solve mirroring artefacts for y-axis pointing to the top + center_prime = sign * math.sqrt(num / den) * point1_prime.anti_normal.scale(ratio, 1/ratio) + + center = Transformation2D.Rotation(angle) * center_prime + origin_prime vector1 = (point1_prime - center_prime).divide(radius_x, radius_y) vector2 = - (point1_prime + center_prime).divide(radius_x, radius_y) theta = cls.__vector_cls__(1, 0).angle_with(vector1) - delta_theta = vector1.angle_with(vector2) % 360 + delta_theta = vector1.angle_with(vector2) + # if theta < 0: + # theta = 180 + theta + # if delta_theta < 0: + # delta_theta = 180 + delta_theta + delta_theta = delta_theta % 360 + # print('theta', theta, delta_theta) if not sweep and delta_theta > 0: delta_theta -= 360 elif sweep and delta_theta < 0: delta_theta += 360 - domain = domain=AngularDomain(theta, theta + delta_theta) + # print('theta', theta, delta_theta, theta + delta_theta) + domain = domain = AngularDomain(theta, theta + delta_theta) return cls(center, radius_x, radius_y, angle, domain) diff --git a/Patro/GeometryEngine/Path.py b/Patro/GeometryEngine/Path.py index 6ba28d05bb55ae0ca1612c5fdbb477aa2a557999..90fb0433de26cb0b1853ce28839e336556255337 100644 --- a/Patro/GeometryEngine/Path.py +++ b/Patro/GeometryEngine/Path.py @@ -41,7 +41,7 @@ import math from Patro.Common.Math.Functions import sign from .Primitive import Primitive1P, Primitive2DMixin from .Bezier import QuadraticBezier2D, CubicBezier2D -from .Conic import Circle2D, AngularDomain +from .Conic import AngularDomain, Circle2D, Ellipse2D from .Segment import Segment2D from .Vector import Vector2D @@ -839,6 +839,12 @@ class ArcSegment(OnePointMixin, PathPart): ############################################## + def __repr__(self): + template = '{0}(@{1._index} {1._point} rx={1._radius_x} ry={1._radius_y} a={1._angle} la={1._large_arc} s={1._sweep})' + return template.format(self.__class__.__name__, self) + + ############################################## + def to_absolute(self): self._point = self.stop_point self._absolute = True @@ -853,8 +859,12 @@ class ArcSegment(OnePointMixin, PathPart): @property def geometry(self): - # Fixme: !!! - return Segment2D(self.start_point, self.stop_point) + return Ellipse2D.svg_arc( + self.start_point, self.stop_point, + self._radius_x, self._radius_y, + self._angle, + self._large_arc, self._sweep, + ) #################################################################################################### diff --git a/Patro/GeometryEngine/Transformation.py b/Patro/GeometryEngine/Transformation.py index 79d4047540040df38bf9319e78fc78b427b9b3e6..f81279ed3248ac6b42a96fb1399b3a5582169235 100644 --- a/Patro/GeometryEngine/Transformation.py +++ b/Patro/GeometryEngine/Transformation.py @@ -289,7 +289,12 @@ class Transformation2D(Transformation): c = cos(angle) s = sin(angle) - return cls(np.array(((c, -s), (s, c))), TransformationType.Rotation) + return cls( + np.array(( + (c, -s), + (s, c))), + TransformationType.Rotation, + ) ############################################## diff --git a/Patro/GeometryEngine/Vector.py b/Patro/GeometryEngine/Vector.py index 1c80d1c3ef376e133b31e8067b534ce08e1f64d5..e50863995f8ea69efebbd5862da5975ca17d69a1 100644 --- a/Patro/GeometryEngine/Vector.py +++ b/Patro/GeometryEngine/Vector.py @@ -561,6 +561,14 @@ class Vector2D(Vector2DFloatBase): ############################################## + def scale(self, scale_x, scale_y): + """Scale self by scale""" + obj = self.clone() + obj._v *= np.array((scale_x, scale_y)) + return obj + + ############################################## + def divide(self, scale_x, scale_y): """Scale self by 1/scale""" obj = self.clone() diff --git a/Patro/GraphicEngine/GraphicScene/Scene.py b/Patro/GraphicEngine/GraphicScene/Scene.py index 40b97f7d9de27d4e7e7449476c98e7f71ac35bb0..c42447e6c9f9dcc96804b8a6df7ce64c94a8a700 100644 --- a/Patro/GraphicEngine/GraphicScene/Scene.py +++ b/Patro/GraphicEngine/GraphicScene/Scene.py @@ -403,6 +403,22 @@ class GraphicSceneScope: def add_segment(segment): add_by_method(self.segment, segment) + def add_ellipse(segment): + # add_segment(Segment.Segment2D(*segment.points)) + ellipse = segment.geometry + # print(ellipse, ellipse.domain) + arc_item = self.ellipse( + ellipse.center, + ellipse.radius_x, + ellipse.radius_y, + ellipse.angle, + path_style, + start_angle=ellipse.domain.start, + stop_angle=ellipse.domain.stop, + user_data=segment, + ) + items.append(arc_item) + def add_quadratic(segment): add_by_method(self.quadratic_bezier, segment) @@ -426,7 +442,7 @@ class GraphicSceneScope: elif isinstance(segment, Path.CubicBezierSegment): add_cubic(segment) elif isinstance(segment, Path.ArcSegment): - add_segment(segment) + add_ellipse(segment) elif isinstance(segment, Path.StringedQuadtraticBezierSegment): pass elif isinstance(segment, Path.StringedCubicBezierSegment): diff --git a/Patro/GraphicEngine/Painter/QtPainter.py b/Patro/GraphicEngine/Painter/QtPainter.py index 5825c36b39ed1d6c9337d87a1604b9ee7dac9e91..135dec9495eabef7f461ed5bbc4791888a50155a 100644 --- a/Patro/GraphicEngine/Painter/QtPainter.py +++ b/Patro/GraphicEngine/Painter/QtPainter.py @@ -303,20 +303,15 @@ class QtPainter(Painter): ############################################## - def paint_CircleItem(self, item): - - center = self.cast_position(item.position) - radius = self.length_scene_to_viewport(item.radius) - - pen = self._set_pen(item) + def _paint_arc(self, item, center, radius_x, radius_y): if item.is_closed: self._painter.drawEllipse(center, radius, radius) else: # drawArc cannot be filled ! rectangle = QRectF( - center + QPointF(-radius, radius), - center + QPointF(radius, -radius), + center + QPointF(-radius_x, radius_y), + center + QPointF(radius_x, -radius_y), ) start_angle, stop_angle = [int(angle*16) for angle in (item.start_angle, item.stop_angle)] span_angle = stop_angle - start_angle @@ -327,6 +322,17 @@ class QtPainter(Painter): ############################################## + def paint_CircleItem(self, item): + + center = self.cast_position(item.position) + radius = self.length_scene_to_viewport(item.radius) + + pen = self._set_pen(item) + + self._paint_arc(item, center, radius, radius) + + ############################################## + def paint_EllipseItem(self, item): center = self.cast_position(item.position) @@ -335,7 +341,8 @@ class QtPainter(Painter): pen = self._set_pen(item) - self._painter.drawEllipse(center, radius_x, radius_y) + # Fixme: angle !!! + self._paint_arc(item, center, radius_x, radius_y) ############################################## diff --git a/examples/file-format/svg/test-svg-import.py b/examples/file-format/svg/test-svg-import.py index 9aba8d480561d7d667d5e6b1d1360b1c559cf139..81fe22480f60f0acba2e7c132916d8cd9f4a81f7 100644 --- a/examples/file-format/svg/test-svg-import.py +++ b/examples/file-format/svg/test-svg-import.py @@ -97,6 +97,19 @@ class SceneImporter(SvgFileInternal): def on_graphic_item(self, item): + # d=" + # m 31.589881,269.68673 + # a 5,10 0 0 1 7.061333, 0.48142 + # 5,10 0 0 1 -0.229465, 14.12342 + # 5,10 0 0 1 -7.062064, -0.43644 + # 5,10 0 0 1 0.20697 , -14.1248 + # L 35,277.00003 + # Z" + # if item.id == 'ellipse': + # print(item) + # for part in item.path_data: + # print(part) + state = self._dispatcher.state.clone().merge(item) # self._logger.info('Item: {}\n{}'.format(item.id, item)) # self._logger.info('Item State:\n' + str(state)) @@ -115,6 +128,7 @@ class SceneImporter(SvgFileInternal): ) transformation = self._screen_transformation * state.transform + # transformation = state.transform # self._logger.info('Sate Transform\n' + str(transformation)) if isinstance(item, SvgFormat.Path): # and state.stroke_dasharray is None