Skip to content
......@@ -20,6 +20,36 @@
"""Module to implement vector.
Example of usage::
v = Vector2D(10, 20)
v = Vector2D((10, 20))
v = Vector2D([10, 20])
v = Vector2D(iterable)
v = Vector2D(v)
v.x
v.y
# array interface
v[0], v[1]
iter(v)
-v
v + v
v += v
v - v
v -= v
v * 2
2 * v
v *= 2
v / 2
v /= 2
"""
####################################################################################################
......@@ -51,17 +81,6 @@ class Vector2DBase(Primitive, Primitive2DMixin):
def __init__(self, *args):
"""
Example of usage::
Vector(1, 3)
Vector((1, 3))
Vector([1, 3])
Vector(iterable)
Vector(vector)
"""
array = self._check_arguments(args)
# call __getitem__ once
......@@ -117,7 +136,7 @@ class Vector2DBase(Primitive, Primitive2DMixin):
##############################################
def copy(self):
def clone(self):
""" Return a copy of self """
return self.__class__(self._v)
......@@ -339,6 +358,20 @@ class Vector2DFloatBase(Vector2DBase):
##############################################
@property
def permute(self):
"""Return a new vector where x and y are permuted.
"""
xp = self._v[1]
yp = self._v[0]
return self.__class__((xp, yp))
##############################################
@property
def parity(self):
......@@ -389,12 +422,12 @@ class Vector2DFloatBase(Vector2DBase):
##############################################
def is_parallel(self, other, cross=False):
def is_parallel(self, other, return_cross=False):
"""Self is parallel with other"""
cross = self.cross(other)
test = round(cross, 7) == 0
if cross:
if return_cross:
return test, cross
else:
return test
......@@ -437,10 +470,7 @@ class Vector2DFloatBase(Vector2DBase):
##############################################
def orientation_with(self, direction):
# Fixme: check all cases
# -> angle_with
def angle_with(self, direction):
"""Return the angle of self on direction"""
......@@ -449,6 +479,8 @@ class Vector2DFloatBase(Vector2DBase):
return angle_sign * math.degrees(angle)
orientation_with = angle_with
####################################################################################################
class Vector2D(Vector2DFloatBase):
......@@ -478,13 +510,13 @@ class Vector2D(Vector2DFloatBase):
##############################################
@staticmethod
def from_ellipse(x_radius, y_radius, angle):
def from_ellipse(radius_x, radius_y, angle):
"""Create the vector (x_radius*cos(angle), y_radius*sin(angle)). *angle* is in degree."""
"""Create the vector (radius_x*cos(angle), radius_y*sin(angle)). *angle* is in degree."""
angle = math.radians(angle)
x = x_radius * cos(angle)
y = y_radius * sin(angle)
x = radius_x * cos(angle)
y = radius_y * sin(angle)
return Vector2D(x, y) # Fixme: classmethod
......@@ -503,6 +535,12 @@ class Vector2D(Vector2DFloatBase):
##############################################
def __rmul__(self, scale):
"""Return a new vector equal to the self scaled by scale"""
return self.__mul__(scale)
##############################################
def __imul__(self, scale):
"""Scale self by scale"""
self._v *= scale
......@@ -523,6 +561,22 @@ 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()
obj._v /= np.array((scale_x, scale_y))
return obj
##############################################
def normalise(self):
"""Normalise the vector"""
self._v /= self.magnitude
......@@ -564,6 +618,12 @@ class NormalisedVector2D(Vector2DFloatBase):
""" Return a new vector equal to the self scaled by scale """
return self.__class__(scale * self._v) # Fixme: Vector2D ?
##############################################
def __rmul__(self, scale):
""" Return a new vector equal to the self scaled by scale """
return self.__mul__(scale)
####################################################################################################
class HomogeneousVector2D(Vector2D):
......
......@@ -23,12 +23,13 @@
"""
# Fixme: get_geometry / as argument
# position versus point
####################################################################################################
import logging
from Patro.GeometryEngine.Bezier import CubicBezier2D
from Patro.GeometryEngine.Bezier import CubicBezier2D, QuadraticBezier2D
from Patro.GeometryEngine.Conic import Circle2D, Ellipse2D, AngularDomain
from Patro.GeometryEngine.Polyline import Polyline2D
from Patro.GeometryEngine.Rectangle import Rectangle2D
......@@ -38,6 +39,7 @@ from .GraphicItemMixin import (
PathStyleItemMixin,
PositionMixin,
TwoPositionMixin,
ThreePositionMixin,
FourPositionMixin,
NPositionMixin,
StartStopAngleMixin,
......@@ -151,7 +153,7 @@ class EllipseItem(PositionMixin, StartStopAngleMixin, PathStyleItemMixin):
##############################################
def __init__(self, scene, position,
x_radius, y_radius,
radius_x, radius_y,
angle,
path_style, user_data,
start_angle=0,
......@@ -162,27 +164,27 @@ class EllipseItem(PositionMixin, StartStopAngleMixin, PathStyleItemMixin):
PositionMixin.__init__(self, position)
StartStopAngleMixin.__init__(self, start_angle, stop_angle)
self._x_radius = x_radius
self._y_radius = y_radius
self._radius_x = radius_x
self._radius_y = radius_y
self._angle = angle
##############################################
@property
def x_radius(self):
return self._x_radius
def radius_x(self):
return self._radius_x
# @x_radius.setter
# def x_radius(self, value):
# self._x_radius = value
# @radius_x.setter
# def radius_x(self, value):
# self._radius_x = value
@property
def y_radius(self):
return self._y_radius
def radius_y(self):
return self._radius_y
# @y_radius.setter
# def y_radius(self, value):
# self._y_radius = value
# @radius_y.setter
# def radius_y(self, value):
# self._radius_y = value
@property
def angle(self):
......@@ -192,7 +194,7 @@ class EllipseItem(PositionMixin, StartStopAngleMixin, PathStyleItemMixin):
def get_geometry(self):
position = self.casted_position
return Ellipse2D(position, self._x_radius, self._y_radius, self._angle)
return Ellipse2D(position, self._radius_x, self._radius_y, self._angle)
####################################################################################################
......@@ -311,3 +313,48 @@ class CubicBezierItem(FourPositionMixin, PathStyleItemMixin):
def get_geometry(self):
positions = self.casted_positions
return CubicBezier2D(*positions)
####################################################################################################
class QuadraticBezierItem(ThreePositionMixin, PathStyleItemMixin):
##############################################
def __init__(self,
scene,
position1, position2, position3,
path_style,
user_data,
):
# Fixme: curve vs path
PathStyleItemMixin.__init__(self, scene, path_style, user_data)
ThreePositionMixin.__init__(self, position1, position2, position3)
# super(CubicBezierItem, self).__init__(path_style)
# self._curve = curve
##############################################
# @property
# def curve(self):
# return self._curve
# @curve.setter
# def curve(self, value):
# self._curve = value
##############################################
def get_geometry(self):
positions = self.casted_positions
return QuadraticBezier2D(*positions)
##############################################
@property
def cubic_positions(self):
if not(hasattr(self, '_cubic_points')):
cubic = self.geometry.to_cubic()
self._cubic_points = list(cubic.points)
return self._cubic_points
......@@ -240,14 +240,13 @@ class TwoPositionMixin:
####################################################################################################
class FourPositionMixin(TwoPositionMixin):
class ThreePositionMixin(TwoPositionMixin):
##############################################
def __init__(self, position1, position2, position3, position4):
def __init__(self, position1, position2, position3):
TwoPositionMixin.__init__(self, position1, position2)
self._position3 = position3
self._position4 = position4
##############################################
......@@ -255,6 +254,22 @@ class FourPositionMixin(TwoPositionMixin):
def position3(self):
return self._position3
@property
def positions(self):
return (self._position1, self._position2, self._position3)
####################################################################################################
class FourPositionMixin(ThreePositionMixin):
##############################################
def __init__(self, position1, position2, position3, position4):
ThreePositionMixin.__init__(self, position1, position2, position3)
self._position4 = position4
##############################################
@property
def position4(self):
return self._position4
......
......@@ -113,7 +113,7 @@ class GraphicPathStyle:
def line_width_as_float(self):
line_width = self._line_width
# Fixme: use scale ?
if isinstance(line_width, float):
if isinstance(line_width, (int, float)):
return line_width
else:
line_width = line_width.replace('pt', '')
......
......@@ -73,9 +73,12 @@ class GraphicSceneScope:
'rectangle': GraphicItem.RectangleItem,
'segment': GraphicItem.SegmentItem,
'polyline': GraphicItem.PolylineItem,
'quadratic_bezier': GraphicItem.QuadraticBezierItem,
'text': GraphicItem.TextItem,
}
_logger = _module_logger.getChild('GraphicSceneScope')
##############################################
def __init__(self, transformation=None):
......@@ -101,6 +104,15 @@ class GraphicSceneScope:
##############################################
def __len__(self):
return self.number_of_items
@property
def number_of_items(self):
return len(self._items)
##############################################
def __iter__(self):
# must be an ordered item list
return iter(self._items.values())
......@@ -257,7 +269,8 @@ class GraphicSceneScope:
try: # Fixme
distance = item.distance_to_point(position)
# print('distance_to_point {:6.2f} {}'.format(distance, item))
if distance <= radius:
# Fixme: distance is None
if distance is not None and distance <= radius:
items.append((distance, item))
except NotImplementedError:
pass
......@@ -283,25 +296,22 @@ class GraphicSceneScope:
# Bezier
if isinstance(item, Bezier.QuadraticBezier2D):
# ctor = self._scene.quadratic_bezier
# raise NotImplementedError
ctor = self._scene.cubic_bezier
points = list(item.to_cubic().points)
ctor = self.quadratic_bezier
elif isinstance(item, Bezier.CubicBezierItem):
ctor = self._scene.cubic_bezier
elif isinstance(item, Bezier.CubicBezier2D):
ctor = self.cubic_bezier
# Conic
elif isinstance(item, Conic.Circle2D):
ctor = self._scene.circle
ctor = self.circle
args = [item.radius]
if item.domain:
kwargs['start_angle'] = item.domain.start
kwargs['stop_angle'] = item.domain.stop
elif isinstance(item, Conic.Ellipse2D):
ctor = self._scene.ellipse
args = [item.x_radius, item.y_radius, item.angle]
ctor = self.ellipse
args = [item.radius_x, item.radius_y, item.angle]
# Line
elif isinstance(item, Line.Line2D):
......@@ -310,38 +320,39 @@ class GraphicSceneScope:
# Path
elif isinstance(item, Path.Path2D):
raise NotImplementedError
self.add_path(item, path_style)
# Polygon
elif isinstance(item, Path.Polygon2D):
elif isinstance(item, Polygon.Polygon2D):
# Fixme: to path
raise NotImplementedError
# Polyline
elif isinstance(item, Polyline.Polyline2D):
ctor = self._scene.polyline
ctor = self.polyline
# fixme: to path
# Rectangle
elif isinstance(item, Rectangle.Rectangle2D):
ctor = self._scene.rectangle
ctor = self.rectangle
# Fixme: to path
# Segment
if isinstance(item, Segment.Segment2D):
ctor = self._scene.segment
elif isinstance(item, Segment.Segment2D):
ctor = self.segment
# Spline
elif isinstance(item, Spline.BSpline2D):
return self._add_spline(item, path_style)
return self.add_spline(item, path_style)
# Triangle
if isinstance(item, Triangle.Triangle2D):
elif isinstance(item, Triangle.Triangle2D):
# Fixme: to path
raise NotImplementedError
# Not implemented
else:
self._logger.warning('Not implemented item {}'.format(item))
raise NotImplementedError
if ctor is not None:
......@@ -351,14 +362,101 @@ class GraphicSceneScope:
##############################################
def add_spline(self, item, path_style):
# def add_quadratic_bezier(self, curve, *args, **kwargs):
# # Fixme:
# cubic = curve.to_cubic()
# return self.cubic_bezier(*cubic.points, *args, **kwargs)
##############################################
def add_spline(self, spline, path_style):
return [
self._scene.cubic_bezier(*bezier.points, path_style, user_data=item)
for bezier in item.to_bezier()
self.cubic_bezier(*bezier.points, path_style, user_data=spline)
for bezier in spline.to_bezier()
]
##############################################
def add_path(self, path, path_style):
items = []
def add_bulge(segment):
arc = segment.bulge_geometry
arc_item = self.circle(
arc.center, arc.radius,
path_style,
start_angle=arc.domain.start,
stop_angle=arc.domain.stop,
user_data=segment,
)
items.append(arc_item)
def add_by_method(method, segment):
item = method(
*segment.points,
path_style,
user_data=segment,
)
items.append(item)
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)
def add_cubic(segment):
add_by_method(self.cubic_bezier, segment)
for segment in path:
item = None
if isinstance(segment, Path.LinearSegment):
# if segment._start_radius is True:
# continue
if segment.radius is not None:
add_bulge(segment)
# if segment._closing is True:
# start_segment = path.start_segment
# add_bulge(start_segment)
# add_segment(start_segment)
add_segment(segment)
elif isinstance(segment, Path.QuadraticBezierSegment):
add_quadratic(segment)
elif isinstance(segment, Path.CubicBezierSegment):
add_cubic(segment)
elif isinstance(segment, Path.ArcSegment):
add_ellipse(segment)
elif isinstance(segment, Path.StringedQuadtraticBezierSegment):
pass
elif isinstance(segment, Path.StringedCubicBezierSegment):
pass
##############################################
# def quadratic_bezier(self, p0, p1, p2, *args, **kwargs):
# # Fixme:
# cubic = Bezier.QuadraticBezier2D(p0, p1, p2).to_cubic()
# return self.add_cubic(cubic, *args, **kwargs)
##############################################
def bezier_path(self, points, degree, *args, **kwargs):
"""Add a Bézier curve with the given control points and degree"""
......@@ -368,7 +466,6 @@ class GraphicSceneScope:
elif degree == 2:
# Fixme:
method = self.quadratic_bezier
raise NotImplementedError
elif degree == 3:
method = self.cubic_bezier
else:
......
......@@ -127,7 +127,7 @@ class ReportlabPainter(PdfPainterBase):
# self._canvas.setSubject(subject)
bounding_box = scene.bounding_box
print(bounding_box, bounding_box.x.length, bounding_box.y.length)
# print(bounding_box, bounding_box.x.length, bounding_box.y.length)
self._canvas.translate(-bounding_box.x.inf*cm, -bounding_box.y.inf*cm)
self._canvas.translate(1*cm, 1*cm) # Fixme: margin
......
......@@ -18,9 +18,14 @@
#
####################################################################################################
"""Module to implement a Qt Painter.
"""
####################################################################################################
import logging
import math
import numpy as np
......@@ -47,12 +52,13 @@ _module_logger = logging.getLogger(__name__)
class QtScene(QObject, GraphicScene):
"""Class to add Qt Object features to GraphicScene ."""
_logger = _module_logger.getChild('QtScene')
##############################################
def __init__(self):
QObject.__init__(self)
GraphicScene.__init__(self)
......@@ -60,6 +66,8 @@ class QtScene(QObject, GraphicScene):
class QtPainter(Painter):
"""Class to implement a Qt painter."""
__STROKE_STYLE__ = {
None: None, # Fixme: ???
StrokeStyle.NoPen: Qt.NoPen,
......@@ -94,7 +102,6 @@ class QtPainter(Painter):
self._show_grid = True
# self._paper = paper
# self._translation = QPointF(0, 0)
# self._scale = 1
......@@ -153,12 +160,12 @@ class QtPainter(Painter):
def paint(self, painter):
self._logger.info('paint')
self._logger.info('Start painting')
self._painter = painter
if self._show_grid:
self._paint_grid()
super().paint()
self._logger.info('Paint done')
##############################################
......@@ -249,6 +256,7 @@ class QtPainter(Painter):
xinf, xsup = area.x.inf, area.x.sup
yinf, ysup = area.y.inf, area.y.sup
length = min(area.x.length, area.y.length)
color = QColor('black')
brush = QBrush(color)
......@@ -256,7 +264,9 @@ class QtPainter(Painter):
self._painter.setPen(pen)
self._painter.setBrush(Qt.NoBrush)
step = 10
step = max(10**int(math.log10(length)), 10)
small_step = step // 10
self._logger.info('Grid of {}/{} for {:.1f} mm'.format(step, small_step, length))
self._paint_axis_grid(xinf, xsup, yinf, ysup, True, step)
self._paint_axis_grid(yinf, ysup, xinf, xsup, False, step)
......@@ -266,9 +276,8 @@ class QtPainter(Painter):
self._painter.setPen(pen)
self._painter.setBrush(Qt.NoBrush)
step = 1
self._paint_axis_grid(xinf, xsup, yinf, ysup, True, step)
self._paint_axis_grid(yinf, ysup, xinf, xsup, False, step)
self._paint_axis_grid(xinf, xsup, yinf, ysup, True, small_step)
self._paint_axis_grid(yinf, ysup, xinf, xsup, False, small_step)
##############################################
......@@ -294,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
......@@ -318,21 +322,31 @@ class QtPainter(Painter):
##############################################
def paint_EllipseItem(self, item):
def paint_CircleItem(self, item):
center = self.cast_position(item.position)
x_radius = self.length_scene_to_viewport(item.x_radius)
y_radius = self.length_scene_to_viewport(item.y_radius)
radius = self.length_scene_to_viewport(item.radius)
pen = self._set_pen(item)
self._painter.drawEllipse(center, x_radius, y_radius)
self._paint_arc(item, center, radius, radius)
##############################################
def paint_CubicBezierItem(self, item):
def paint_EllipseItem(self, item):
vertices = self.cast_item_positions(item)
center = self.cast_position(item.position)
radius_x = self.length_scene_to_viewport(item.radius_x)
radius_y = self.length_scene_to_viewport(item.radius_y)
pen = self._set_pen(item)
# Fixme: angle !!!
self._paint_arc(item, center, radius_x, radius_y)
##############################################
def _paint_cubic(self, item, vertices):
pen = self._set_pen(item)
path = QPainterPath()
......@@ -363,6 +377,20 @@ class QtPainter(Painter):
##############################################
def paint_QuadraticBezierItem(self, item):
vertices = [self.cast_position(position) for position in item.cubic_positions]
self._paint_cubic(item, vertices)
##############################################
def paint_CubicBezierItem(self, item):
vertices = self.cast_item_positions(item)
self._paint_cubic(item, vertices)
##############################################
def paint_ImageItem(self, item):
vertices = self.cast_item_positions(item)
......@@ -416,6 +444,10 @@ class QtPainter(Painter):
class ViewportArea:
"""Class to implement a viewport."""
_logger = _module_logger.getChild('ViewportArea')
##############################################
def __init__(self):
......@@ -433,11 +465,24 @@ class ViewportArea:
##############################################
def __bool__(self):
return self._scene is not None
##############################################
@classmethod
def _to_np_array(cls, *args):
if len(args) == 1:
args = args[0]
return np.array(args, dtype=np.float)
##############################################
@classmethod
def _point_to_np(cls, point):
return cls._to_np_array(point.x(), point.y())
##############################################
@property
def viewport_size(self):
return self._viewport_size
......@@ -447,7 +492,7 @@ class ViewportArea:
def viewport_size(self, geometry):
# self._width = geometry.width()
# self._height = geometry.height()
self._viewport_size = np.array((geometry.width(), geometry.height()), dtype=np.float)
self._viewport_size = self._to_np_array(geometry.width(), geometry.height())
if self:
self._update_viewport_area()
......@@ -459,6 +504,8 @@ class ViewportArea:
@scene.setter
def scene(self, value):
if not isinstance(value, GraphicScene):
raise ValueError
self._scene = value
@property
......@@ -476,6 +523,7 @@ class ViewportArea:
@property
def scale_px_by_mm(self):
# Fixme: assume unit is mm
return self._scale
@property
......@@ -499,8 +547,8 @@ class ViewportArea:
def _update_viewport_area(self):
offset = self._viewport_size / 2 * self.scale_mm_by_px
x, y = list(self._center)
dx, dy = list(offset)
x, y = self._center
dx, dy = offset
self._area = Interval2D(
(x - dx, x + dx),
......@@ -509,7 +557,7 @@ class ViewportArea:
# Fixme: QPointF ???
self._translation = - QPointF(self._area.x.inf, self._area.y.sup)
print('_update_viewport_area', self._center, self.scale_mm_by_px, self._area)
# self._logger.debug('_update_viewport_area', self._center, self.scale_mm_by_px, self._area)
##############################################
......@@ -520,7 +568,9 @@ class ViewportArea:
# scale = min(width_scale, height_scale)
# scale [px/mm]
axis_scale = self._viewport_size / np.array(self.scene_area.size, dtype=np.float)
# Add 2% to scene for margin
margin_scale = 1 + 2 / 100
axis_scale = self._viewport_size / (self._to_np_array(self.scene_area.size) * margin_scale)
axis = axis_scale.argmin()
scale = axis_scale[axis]
......@@ -529,7 +579,6 @@ class ViewportArea:
##############################################
def zoom_at(self, center, scale):
self._center = center
self._scale = scale
self._update_viewport_area()
......@@ -540,7 +589,7 @@ class ViewportArea:
# Fixme: AttributeError: 'NoneType' object has no attribute 'center'
if self:
center = np.array(self.scene_area.center, dtype=np.float)
center = self._to_np_array(self.scene_area.center)
scale, axis = self._compute_scale_to_fit_scene()
self.zoom_at(center, scale)
......@@ -556,31 +605,36 @@ class ViewportArea:
##############################################
def length_scene_to_viewport(self, length):
return length * self._scale
##############################################
def viewport_to_scene(self, position):
point = QPointF(position.x(), -position.y())
point /= self._scale
point -= self._translation
return np.array((point.x(), point.y()), dtype=np.float)
return self._point_to_np(point)
##############################################
def pan_delta_to_scene(self, position):
def length_scene_to_viewport(self, length):
return length * self._scale
# Fixme:
point = QPointF(position.x(), position.y())
# point /= self._scale
return np.array((point.x(), point.y()), dtype=np.float)
##############################################
def length_viewport_to_scene(self, length):
return length / self._scale
##############################################
def pan_delta_to_scene(self, position):
point = self._point_to_np(position)
point *= self.scale_mm_by_px
return point
####################################################################################################
class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
"""Class to implement a painter as Qt Quick item"""
_logger = _module_logger.getChild('QtQuickPaintedSceneItem')
##############################################
......@@ -590,6 +644,7 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
QQuickPaintedItem.__init__(self, parent)
QtPainter.__init__(self)
# Setup backend rendering
self.setAntialiasing(True)
# self.setRenderTarget(QQuickPaintedItem.Image) # high quality antialiasing
self.setRenderTarget(QQuickPaintedItem.FramebufferObject) # use OpenGL
......@@ -600,7 +655,7 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
def geometryChanged(self, new_geometry, old_geometry):
print('geometryChanged', new_geometry, old_geometry)
# self._logger.info('geometryChanged', new_geometry, old_geometry)
self._viewport_area.viewport_size = new_geometry
# if self._scene:
# self._update_transformation()
......@@ -609,7 +664,6 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
##############################################
# def _update_transformation(self):
# area = self._viewport_area.area
# self.translation = - QPointF(area.x.inf, area.y.sup)
# self.scale = self._viewport_area.scale_px_by_mm # QtPainter
......@@ -632,6 +686,11 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
##############################################
def length_viewport_to_scene(self, length):
return self._viewport_area.length_viewport_to_scene(length)
##############################################
sceneChanged = Signal()
@Property(QtScene, notify=sceneChanged)
......@@ -641,7 +700,7 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
@scene.setter
def scene(self, scene):
if self._scene is not scene:
print('QtQuickPaintedSceneItem set scene', scene)
# self._logger.info('QtQuickPaintedSceneItem set scene', scene)
self._logger.info('set scene') # Fixme: don't print ???
self._scene = scene
self._viewport_area.scene = scene
......@@ -691,7 +750,7 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
@Slot(QPointF, float)
def zoom_at(self, position, zoom):
print('zoom_at', position, zoom)
# print('zoom_at', position, zoom)
scene_position = self._viewport_area.viewport_to_scene(position)
self._viewport_area.zoom_at(scene_position, zoom)
self.update()
......@@ -714,24 +773,22 @@ class QtQuickPaintedSceneItem(QQuickPaintedItem, QtPainter):
##############################################
@Slot(QPointF)
def item_at(self, position):
# Fixme: 1 = 1 cm
# as f of zoom ?
radius = 0.3
def item_at(self, position, radius_px=10):
self._scene.update_rtree()
self._scene.unselect_items()
scene_position = Vector2D(self._viewport_area.viewport_to_scene(position))
radius = self.length_viewport_to_scene(radius_px)
self._logger.info('Item selection at {} with radius {:1f} mm'.format(scene_position, radius))
items = self._scene.item_at(scene_position, radius)
if items:
distance, nearest_item = items[0]
print('nearest item at {} #{:6.2f} {} {}'.format(scene_position, len(items), distance, nearest_item.user_data))
# print('nearest item at {} #{:6.2f} {} {}'.format(scene_position, len(items), distance, nearest_item.user_data))
nearest_item.selected = True
# Fixme: z_value ???
for pair in items[1:]:
distance, item = pair
print(' {:6.2f} {}'.format(distance, item.user_data))
# print(' {:6.2f} {}'.format(distance, item.user_data))
self.update()
####################################################################################################
......
......@@ -46,6 +46,7 @@ def __init__():
_color_data.VALENTINA_COLORS ,
):
for name, value in color_set.items():
name = name.replace(' ', '_')
color = Color(value, name=name)
if name in Colors and color != Colors[name]:
pass
......
......@@ -18,6 +18,12 @@
#
####################################################################################################
"""Module to implement a Qt Application.
"""
####################################################################################################
__all__ = [
'QmlApplication',
]
......@@ -25,15 +31,20 @@ __all__ = [
####################################################################################################
import argparse
import datetime
import logging
import sys
import traceback
from pathlib import Path
# Fixme:
from PyQt5 import QtCore
from QtShim.QtCore import (
Property, Signal, QObject,
Qt, QTimer, QUrl,
Qt, QTimer, QUrl
)
from QtShim.QtGui import QGuiApplication
from QtShim.QtGui import QGuiApplication, QIcon
from QtShim.QtQml import qmlRegisterType, QQmlApplicationEngine
# Fixme: PYSIDE-574 qmlRegisterSingletonType and qmlRegisterUncreatableType missing in QtQml
from QtShim.QtQml import qmlRegisterUncreatableType
......@@ -41,6 +52,7 @@ from QtShim.QtQuick import QQuickPaintedItem, QQuickView
# from QtShim.QtQuickControls2 import QQuickStyle
from Patro.Common.Platform import QtPlatform
from Patro.Common.ArgparseAction import PathAction
from Patro.GraphicEngine.Painter.QtPainter import QtScene, QtQuickPaintedSceneItem
from .rcc import PatroRessource
......@@ -53,6 +65,8 @@ _module_logger = logging.getLogger(__name__)
class QmlApplication(QObject):
"""Class to implement a Qt QML Application."""
_logger = _module_logger.getChild('QmlApplication')
##############################################
......@@ -75,32 +89,16 @@ class QmlApplication(QObject):
@scene.setter
def scene(self, scene):
if self._scene is not scene:
print('QmlApplication set scene', scene)
self._logger.info('set scene') # Fixme: don't print ???
self._logger.info('set scene {}'.format(scene))
self._scene = scene
self.sceneChanged.emit()
####################################################################################################
class PathAction(argparse.Action):
##############################################
def __call__(self, parser, namespace, values, option_string=None):
if values is not None:
if isinstance(values, list):
path = [Path(x) for x in values]
else:
path = Path(values)
else:
path = None
setattr(namespace, self.dest, path)
####################################################################################################
class Application(QObject):
"""Class to implement a Qt Application."""
instance = None
_logger = _module_logger.getChild('Application')
......@@ -124,12 +122,17 @@ class Application(QObject):
super().__init__()
QtCore.qInstallMessageHandler(self._message_handler)
self._parse_arguments()
self._appplication = QGuiApplication(sys.argv)
self._engine = QQmlApplicationEngine()
self._qml_application = QmlApplication(self)
logo_path = ':/icons/logo-256.png'
self._appplication.setWindowIcon(QIcon(logo_path))
self._platform = QtPlatform()
# self._logger.info('\n' + str(self._platform))
......@@ -164,6 +167,48 @@ class Application(QObject):
##############################################
def _print_critical_message(self, message):
# print('\nCritical Error on {}'.format(datetime.datetime.now()))
# print('-'*80)
# print(message)
self._logger.critical(message)
##############################################
def _message_handler(self, msg_type, context, msg):
if msg_type == QtCore.QtDebugMsg:
method = self._logger.debug
elif msg_type == QtCore.QtInfoMsg:
method = self._logger.info
elif msg_type == QtCore.QtWarningMsg:
method = self._logger.warning
elif msg_type in (QtCore.QtCriticalMsg, QtCore.QtFatalMsg):
method = self._logger.critical
# method = None
# local_msg = msg.toLocal8Bit()
# localMsg.constData()
context_file = context.file
if context_file is not None:
file_path = Path(context_file).name
else:
file_path = ''
message = '{1} {3} — {0}'.format(msg, file_path, context.line, context.function)
if method is not None:
method(message)
else:
self._print_critical_message(message)
##############################################
def _on_critical_exception(self, exception):
message = str(exception) + '\n' + traceback.format_exc()
self._print_critical_message(message)
sys.exit(1)
##############################################
@classmethod
def setup_gui_application(cls):
......@@ -227,7 +272,6 @@ class Application(QObject):
##############################################
def _set_context_properties(self):
context = self._engine.rootContext()
context.setContextProperty('application', self._qml_application)
......@@ -238,25 +282,29 @@ class Application(QObject):
# self._engine.addImportPath('qrc:///qml')
qml_path = Path(__file__).parent.joinpath('qml', 'main.qml')
qml_url = QUrl.fromLocalFile(str(qml_path))
self._qml_url = QUrl.fromLocalFile(str(qml_path))
# QUrl('qrc:/qml/main.qml')
self._engine.load(qml_url)
self._engine.objectCreated.connect(self._check_qml_is_loaded)
self._engine.load(self._qml_url)
##############################################
def exec_(self):
def _check_qml_is_loaded(self, obj, url):
# See https://bugreports.qt.io/browse/QTBUG-39469
if (obj is None and url == self._qml_url):
sys.exit(-1)
##############################################
def exec_(self):
# self._view.show()
sys.exit(self._appplication.exec_())
##############################################
def _post_init(self):
# Fixme: ui refresh ???
self._logger.info('post init')
if self._args.user_script is not None:
self.execute_user_script(self._args.user_script)
......@@ -264,19 +312,24 @@ class Application(QObject):
def execute_user_script(self, script_path):
"""Execute an user script provided by file *script_path* in a context where is defined a variable
*application* that is a reference to the application instance.
"""Execute an user script provided by file *script_path* in a context where is defined a
variable *application* that is a reference to the application instance.
"""
script_path = Path(script_path).absolute()
self._logger.info('Execute user script: {}'.format(script_path))
self._logger.info('Execute user script:\n {}'.format(script_path))
try:
source = open(script_path).read()
except FileNotFoundError:
self._logger.info('File {} not found'.format(script_path))
sys.exit(1)
bytecode = compile(source, script_path, 'exec')
exec(bytecode, {'application':self})
try:
bytecode = compile(source, script_path, 'exec')
except SyntaxError as exception:
self._on_critical_exception(exception)
try:
exec(bytecode, {'application':self})
except Exception as exception:
self._on_critical_exception(exception)
self._logger.info('User script done')
......@@ -98,8 +98,8 @@ ApplicationWindow {
scene: application.scene
focus: true
property int pan_speed: 10
property int pan_step: 10
property real pan_speed: 1.5 // scale to speedup the mouse paning
property int pan_step: 10 // px
Keys.onLeftPressed: scene_view.pan_x_y(-pan_step, 0)
Keys.onRightPressed: scene_view.pan_x_y(pan_step, 0)
......@@ -108,7 +108,7 @@ ApplicationWindow {
function pan_x_y(dx, dy) {
var dxy = Qt.point(dx, dy)
console.info('pan', dxy)
// console.info('pan', dxy)
scene_view.pan(dxy)
}
......@@ -150,8 +150,10 @@ ApplicationWindow {
onPositionChanged: {
// console.info('onPositionChanged', mouse.button, mouse_start)
if (mouse_start !== null) {
var dx = (mouse.x - mouse_start.x) / scene_view.pan_speed
var dy = (mouse_start.y - mouse.y) / scene_view.pan_speed
var dx = (mouse.x - mouse_start.x)
var dy = (mouse_start.y - mouse.y)
dx *= scene_view.pan_speed
dy *= scene_view.pan_speed
// if (dx^2 + dy^2 > 100)
scene_view.pan_x_y(dx, dy)
mouse_start = Qt.point(mouse.x, mouse.y)
......@@ -162,7 +164,7 @@ ApplicationWindow {
onWheel: {
var direction = wheel.angleDelta.y > 0
console.info('Mouse wheel', wheel.x, wheel.y, direction)
// console.info('Mouse wheel', wheel.x, wheel.y, direction)
var zoom = scene_view.zoom
if (direction)
zoom *= 2
......
../../../../doc/sphinx/source/_static/logo-128.png
\ No newline at end of file
../../../../doc/sphinx/source/_static/logo-256.png
\ No newline at end of file
../../../../doc/sphinx/source/_static/logo-32.png
\ No newline at end of file
../../../../doc/sphinx/source/_static/logo-512.png
\ No newline at end of file
../../../../doc/sphinx/source/_static/logo-64.png
\ No newline at end of file
../../../../doc/sphinx/source/_static/logo-96.png
\ No newline at end of file
../../../../doc/sphinx/source/_static/make-logo-png
\ No newline at end of file
../../../../doc/sphinx/source/_static/svg/
\ No newline at end of file
......@@ -4,6 +4,8 @@
<file>qtquickcontrols2.conf</file>
</qresource>
<qresource prefix="/">
<file>icons/svg/logo.svg</file>
<file>icons/logo-256.png</file>
<file>icons/36x36/settings-overscan-black.png</file>
<file>icons/36x36/zoom-fit-width.png</file>
<file>icons/36x36/zoom-out-black.png</file>
......
......@@ -22,9 +22,13 @@
####################################################################################################
import logging
from Patro.Common.Logging import Logging
Logging.setup_logging()
_module_logger = logging.getLogger(__name__)
_module_logger.info('Started Patro')
####################################################################################################
from Patro.QtApplication.QmlApplication import Application
......