Commit ef584c10 by Fabrice Salvaire

SVG: fixed arc

parent 126ab648
......@@ -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)
......
......@@ -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,
)
####################################################################################################
......
......@@ -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,
)
##############################################
......
......@@ -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()
......
......@@ -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):
......
......@@ -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)
##############################################
......
......@@ -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
......
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