Commit ef584c10 authored by Fabrice Salvaire's avatar Fabrice Salvaire

SVG: fixed arc

parent 126ab648
...@@ -48,6 +48,8 @@ __all__ = [ ...@@ -48,6 +48,8 @@ __all__ = [
#################################################################################################### ####################################################################################################
import logging
import math import math
from math import fabs, sqrt, radians, pi, cos, sin # , degrees from math import fabs, sqrt, radians, pi, cos, sin # , degrees
...@@ -63,6 +65,10 @@ from .Transformation import Transformation2D ...@@ -63,6 +65,10 @@ from .Transformation import Transformation2D
#################################################################################################### ####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class PointNotOnCircleError(ValueError): class PointNotOnCircleError(ValueError):
pass pass
...@@ -476,6 +482,8 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive): ...@@ -476,6 +482,8 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive):
""" """
_logger = _module_logger.getChild('Ellipse2D')
############################################## ##############################################
@classmethod @classmethod
...@@ -514,11 +522,16 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive): ...@@ -514,11 +522,16 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive):
radius_x2 = radius_x**2 radius_x2 = radius_x**2
radius_y2 = radius_y**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 # Ensure radii are large enough
radii_scale = point1_prime.x**2/radius_x2 + point1_prime.y**2/radius_y2 radii_scale = point1_prime.x**2/radius_x2 + point1_prime.y**2/radius_y2
if radii_scale > 1: if radii_scale > 1:
self._logger.warning('SVG Arc: radii must be scale')
radii_scale = math.sqrt(radii_scale) radii_scale = math.sqrt(radii_scale)
radius_x = radii_scale * radius_x radius_x = radii_scale * radius_x
radius_y = radii_scale * radius_y radius_y = radii_scale * radius_y
...@@ -532,19 +545,32 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive): ...@@ -532,19 +545,32 @@ class Ellipse2D(Primitive2DMixin, CenterMixin, AngularDomainMixin, Primitive):
ratio = radius_x/radius_y ratio = radius_x/radius_y
sign = -1 if large_arc == sweep else 1 sign = 1 if large_arc != sweep else -1
center_prime = sign * math.sqrt(num / den) * point1_prime.anti_normal.divide(ratio, 1/ratio) # print(point1_prime)
center = Transformation2D.Rotation(-angle) * center_prime + (point1 + point2)/2 # 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) vector1 = (point1_prime - center_prime).divide(radius_x, radius_y)
vector2 = - (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) 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: if not sweep and delta_theta > 0:
delta_theta -= 360 delta_theta -= 360
elif sweep and delta_theta < 0: elif sweep and delta_theta < 0:
delta_theta += 360 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) return cls(center, radius_x, radius_y, angle, domain)
......
...@@ -41,7 +41,7 @@ import math ...@@ -41,7 +41,7 @@ import math
from Patro.Common.Math.Functions import sign from Patro.Common.Math.Functions import sign
from .Primitive import Primitive1P, Primitive2DMixin from .Primitive import Primitive1P, Primitive2DMixin
from .Bezier import QuadraticBezier2D, CubicBezier2D from .Bezier import QuadraticBezier2D, CubicBezier2D
from .Conic import Circle2D, AngularDomain from .Conic import AngularDomain, Circle2D, Ellipse2D
from .Segment import Segment2D from .Segment import Segment2D
from .Vector import Vector2D from .Vector import Vector2D
...@@ -839,6 +839,12 @@ class ArcSegment(OnePointMixin, PathPart): ...@@ -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): def to_absolute(self):
self._point = self.stop_point self._point = self.stop_point
self._absolute = True self._absolute = True
...@@ -853,8 +859,12 @@ class ArcSegment(OnePointMixin, PathPart): ...@@ -853,8 +859,12 @@ class ArcSegment(OnePointMixin, PathPart):
@property @property
def geometry(self): def geometry(self):
# Fixme: !!! return Ellipse2D.svg_arc(
return Segment2D(self.start_point, self.stop_point) self.start_point, self.stop_point,
self._radius_x, self._radius_y,
self._angle,
self._large_arc, self._sweep,
)
#################################################################################################### ####################################################################################################
......
...@@ -289,7 +289,12 @@ class Transformation2D(Transformation): ...@@ -289,7 +289,12 @@ class Transformation2D(Transformation):
c = cos(angle) c = cos(angle)
s = sin(angle) s = sin(angle)
return cls(np.array(((c, -s), (s, c))), TransformationType.Rotation) return cls(
np.array((
(c, -s),
(s, c))),
TransformationType.Rotation,
)
############################################## ##############################################
......
...@@ -561,6 +561,14 @@ class Vector2D(Vector2DFloatBase): ...@@ -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): def divide(self, scale_x, scale_y):
"""Scale self by 1/scale""" """Scale self by 1/scale"""
obj = self.clone() obj = self.clone()
......
...@@ -403,6 +403,22 @@ class GraphicSceneScope: ...@@ -403,6 +403,22 @@ class GraphicSceneScope:
def add_segment(segment): def add_segment(segment):
add_by_method(self.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): def add_quadratic(segment):
add_by_method(self.quadratic_bezier, segment) add_by_method(self.quadratic_bezier, segment)
...@@ -426,7 +442,7 @@ class GraphicSceneScope: ...@@ -426,7 +442,7 @@ class GraphicSceneScope:
elif isinstance(segment, Path.CubicBezierSegment): elif isinstance(segment, Path.CubicBezierSegment):
add_cubic(segment) add_cubic(segment)
elif isinstance(segment, Path.ArcSegment): elif isinstance(segment, Path.ArcSegment):
add_segment(segment) add_ellipse(segment)
elif isinstance(segment, Path.StringedQuadtraticBezierSegment): elif isinstance(segment, Path.StringedQuadtraticBezierSegment):
pass pass
elif isinstance(segment, Path.StringedCubicBezierSegment): elif isinstance(segment, Path.StringedCubicBezierSegment):
......
...@@ -303,20 +303,15 @@ class QtPainter(Painter): ...@@ -303,20 +303,15 @@ class QtPainter(Painter):
############################################## ##############################################
def paint_CircleItem(self, item): def _paint_arc(self, item, center, radius_x, radius_y):
center = self.cast_position(item.position)
radius = self.length_scene_to_viewport(item.radius)
pen = self._set_pen(item)
if item.is_closed: if item.is_closed:
self._painter.drawEllipse(center, radius, radius) self._painter.drawEllipse(center, radius, radius)
else: else:
# drawArc cannot be filled ! # drawArc cannot be filled !
rectangle = QRectF( rectangle = QRectF(
center + QPointF(-radius, radius), center + QPointF(-radius_x, radius_y),
center + QPointF(radius, -radius), center + QPointF(radius_x, -radius_y),
) )
start_angle, stop_angle = [int(angle*16) for angle in (item.start_angle, item.stop_angle)] start_angle, stop_angle = [int(angle*16) for angle in (item.start_angle, item.stop_angle)]
span_angle = stop_angle - start_angle span_angle = stop_angle - start_angle
...@@ -327,6 +322,17 @@ class QtPainter(Painter): ...@@ -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): def paint_EllipseItem(self, item):
center = self.cast_position(item.position) center = self.cast_position(item.position)
...@@ -335,7 +341,8 @@ class QtPainter(Painter): ...@@ -335,7 +341,8 @@ class QtPainter(Painter):
pen = self._set_pen(item) pen = self._set_pen(item)
self._painter.drawEllipse(center, radius_x, radius_y) # Fixme: angle !!!
self._paint_arc(item, center, radius_x, radius_y)
############################################## ##############################################
......
...@@ -97,6 +97,19 @@ class SceneImporter(SvgFileInternal): ...@@ -97,6 +97,19 @@ class SceneImporter(SvgFileInternal):
def on_graphic_item(self, item): 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) state = self._dispatcher.state.clone().merge(item)
# self._logger.info('Item: {}\n{}'.format(item.id, item)) # self._logger.info('Item: {}\n{}'.format(item.id, item))
# self._logger.info('Item State:\n' + str(state)) # self._logger.info('Item State:\n' + str(state))
...@@ -115,6 +128,7 @@ class SceneImporter(SvgFileInternal): ...@@ -115,6 +128,7 @@ class SceneImporter(SvgFileInternal):
) )
transformation = self._screen_transformation * state.transform transformation = self._screen_transformation * state.transform
# transformation = state.transform
# self._logger.info('Sate Transform\n' + str(transformation)) # self._logger.info('Sate Transform\n' + str(transformation))
if isinstance(item, SvgFormat.Path): if isinstance(item, SvgFormat.Path):
# and state.stroke_dasharray is None # and state.stroke_dasharray is None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment