Skip to content
...@@ -24,8 +24,11 @@ ...@@ -24,8 +24,11 @@
#################################################################################################### ####################################################################################################
__all__ = [
__class__ = ['GraphicScene'] 'GraphicScene',
# sphinx
'GraphicSceneScope',
]
#################################################################################################### ####################################################################################################
...@@ -33,6 +36,18 @@ import logging ...@@ -33,6 +36,18 @@ import logging
import rtree import rtree
from Patro.GeometryEngine import (
Bezier,
Conic,
Line,
Path,
Polygon,
Polyline,
Rectangle,
Segment,
Spline,
Triangle,
)
from Patro.GeometryEngine.Transformation import AffineTransformation2D from Patro.GeometryEngine.Transformation import AffineTransformation2D
from Patro.GeometryEngine.Vector import Vector2D from Patro.GeometryEngine.Vector import Vector2D
from . import GraphicItem from . import GraphicItem
...@@ -256,12 +271,104 @@ class GraphicSceneScope: ...@@ -256,12 +271,104 @@ class GraphicSceneScope:
############################################## ##############################################
def add_geometry(self, item, path_style):
"""Add a geometry primitive"""
ctor = None
points = None
args = []
args_tail = [path_style]
kwargs = dict(user_data=item)
# Bezier
if isinstance(item, Bezier.QuadraticBezier2D):
# ctor = self._scene.quadratic_bezier
# raise NotImplementedError
ctor = self._scene.cubic_bezier
points = list(item.to_cubic().points)
elif isinstance(item, Bezier.CubicBezierItem):
ctor = self._scene.cubic_bezier
# Conic
elif isinstance(item, Conic.Circle2D):
ctor = self._scene.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]
# Line
elif isinstance(item, Line.Line2D):
# Fixme: extent ???
raise NotImplementedError
# Path
elif isinstance(item, Path.Path2D):
raise NotImplementedError
# Polygon
elif isinstance(item, Path.Polygon2D):
# Fixme: to path
raise NotImplementedError
# Polyline
elif isinstance(item, Polyline.Polyline2D):
ctor = self._scene.polyline
# fixme: to path
# Rectangle
elif isinstance(item, Rectangle.Rectangle2D):
ctor = self._scene.rectangle
# Fixme: to path
# Segment
if isinstance(item, Segment.Segment2D):
ctor = self._scene.segment
# Spline
elif isinstance(item, Spline.BSpline2D):
return self._add_spline(item, path_style)
# Triangle
if isinstance(item, Triangle.Triangle2D):
# Fixme: to path
raise NotImplementedError
# Not implemented
else:
raise NotImplementedError
if ctor is not None:
if points is None:
points = list(item.points)
return ctor(*points, *args, *args_tail, **kwargs)
##############################################
def add_spline(self, item, path_style):
return [
self._scene.cubic_bezier(*bezier.points, path_style, user_data=item)
for bezier in item.to_bezier()
]
##############################################
def bezier_path(self, points, degree, *args, **kwargs): def bezier_path(self, points, degree, *args, **kwargs):
"""Add a Bézier curve with the given control points and degree"""
if degree == 1: if degree == 1:
method = self.segment method = self.segment
elif degree == 2: elif degree == 2:
# Fixme:
method = self.quadratic_bezier method = self.quadratic_bezier
raise NotImplementedError
elif degree == 3: elif degree == 3:
method = self.cubic_bezier method = self.cubic_bezier
else: else:
......
...@@ -28,7 +28,7 @@ from IntervalArithmetic import Interval2D ...@@ -28,7 +28,7 @@ from IntervalArithmetic import Interval2D
from QtShim.QtCore import ( from QtShim.QtCore import (
Property, Signal, Slot, QObject, Property, Signal, Slot, QObject,
QRectF, QSizeF, QPointF, Qt, QRectF, QSize, QSizeF, QPointF, Qt,
) )
from QtShim.QtGui import QColor, QFont, QFontMetrics, QImage, QPainter, QPainterPath, QBrush, QPen from QtShim.QtGui import QColor, QFont, QFontMetrics, QImage, QPainter, QPainterPath, QBrush, QPen
# from QtShim.QtQml import qmlRegisterType # from QtShim.QtQml import qmlRegisterType
...@@ -37,7 +37,7 @@ from QtShim.QtQuick import QQuickPaintedItem ...@@ -37,7 +37,7 @@ from QtShim.QtQuick import QQuickPaintedItem
from .Painter import Painter from .Painter import Painter
from Patro.GeometryEngine.Vector import Vector2D from Patro.GeometryEngine.Vector import Vector2D
from Patro.GraphicEngine.GraphicScene.Scene import GraphicScene from Patro.GraphicEngine.GraphicScene.Scene import GraphicScene
from Patro.GraphicStyle import StrokeStyle from Patro.GraphicStyle import StrokeStyle, CapStyle, JoinStyle
#################################################################################################### ####################################################################################################
...@@ -70,6 +70,19 @@ class QtPainter(Painter): ...@@ -70,6 +70,19 @@ class QtPainter(Painter):
StrokeStyle.DashDotDotLine: Qt.DashDotDotLine, StrokeStyle.DashDotDotLine: Qt.DashDotDotLine,
} }
__CAP_STYLE__ = {
CapStyle.FlatCap: Qt.FlatCap,
CapStyle.SquareCap: Qt.SquareCap,
CapStyle.RoundCap: Qt.RoundCap,
}
__JOIN_STYLE__ = {
JoinStyle.MiterJoin: Qt.MiterJoin,
JoinStyle.BevelJoin: Qt.BevelJoin,
JoinStyle.RoundJoin: Qt.RoundJoin,
JoinStyle.SvgMiterJoin: Qt.SvgMiterJoin,
}
_logger = _module_logger.getChild('QtPainter') _logger = _module_logger.getChild('QtPainter')
############################################## ##############################################
...@@ -106,6 +119,38 @@ class QtPainter(Painter): ...@@ -106,6 +119,38 @@ class QtPainter(Painter):
############################################## ##############################################
def to_svg(self, path, scale=10, dpi=100, title='', description=''):
"""Render the scene to SVG"""
from QtShim.QtSvg import QSvgGenerator
generator = QSvgGenerator()
generator.setFileName(str(path))
generator.setTitle(str(title))
generator.setDescription(str(description))
generator.setResolution(dpi)
# Fixme: scale
# Scale applied to (x,y) and radius but not line with
self._scale = scale
bounding_box = self._scene.bounding_box
size = QSize(*[x*self._scale for x in bounding_box.size])
view_box = QRectF(*[x*self._scale for x in bounding_box.rect])
generator.setSize(size)
generator.setViewBox(view_box)
painter = QPainter()
painter.begin(generator)
self.paint(painter)
painter.end()
self._scale = None
##############################################
def paint(self, painter): def paint(self, painter):
self._logger.info('paint') self._logger.info('paint')
...@@ -118,15 +163,16 @@ class QtPainter(Painter): ...@@ -118,15 +163,16 @@ class QtPainter(Painter):
############################################## ##############################################
def length_scene_to_viewport(self, length): def length_scene_to_viewport(self, length):
raise NotImplementedError return length * self._scale
@property @property
def scene_area(self): def scene_area(self):
raise NotImplementedError return None
def scene_to_viewport(self, position): def scene_to_viewport(self, position):
return QPointF(position.x * self._scale, position.y * self._scale)
# Note: painter.scale apply to text as well # Note: painter.scale apply to text as well
raise NotImplementedError
# point = QPointF(position.x, position.y) # point = QPointF(position.x, position.y)
# point += self._translation # point += self._translation
...@@ -156,6 +202,7 @@ class QtPainter(Painter): ...@@ -156,6 +202,7 @@ class QtPainter(Painter):
color = path_syle.stroke_color color = path_syle.stroke_color
if color is not None: if color is not None:
color = QColor(str(color)) color = QColor(str(color))
color.setAlphaF(path_syle.stroke_alpha)
else: else:
color = None color = None
line_style = self.__STROKE_STYLE__[path_syle.stroke_style] line_style = self.__STROKE_STYLE__[path_syle.stroke_style]
...@@ -165,34 +212,41 @@ class QtPainter(Painter): ...@@ -165,34 +212,41 @@ class QtPainter(Painter):
if item.selected: if item.selected:
line_width *= 4 line_width *= 4
fill_color = path_syle.fill_color
if fill_color is not None:
color = QColor(str(fill_color))
color.setAlphaF(path_syle.fill_alpha)
self._painter.setBrush(color)
# return None
else:
self._painter.setBrush(Qt.NoBrush)
# print(item, color, line_style) # print(item, color, line_style)
if color is None or line_style is StrokeStyle.NoPen: if color is None or line_style is StrokeStyle.NoPen:
# invisible item # invisible item
pen = QPen(Qt.NoPen) pen = QPen(Qt.NoPen)
# print('Warning Pen:', item, item.user_data, color, line_style) # print('Warning Pen:', item, item.user_data, color, line_style)
return None
else: else:
pen = QPen( pen = QPen(
QBrush(color), QBrush(color),
line_width, line_width,
line_style, line_style,
self.__CAP_STYLE__[path_syle.cap_style],
self.__JOIN_STYLE__[path_syle.join_style],
) )
self._painter.setPen(pen) self._painter.setPen(pen)
return pen return pen
fill_color = path_syle.fill_color
if fill_color is not None:
color = QColor(str(fill_color))
self._painter.setBrush(color)
else:
self._painter.setBrush(Qt.NoBrush)
return None
############################################## ##############################################
def _paint_grid(self): def _paint_grid(self):
area = self.scene_area area = self.scene_area
# Fixme:
if area is None:
return
xinf, xsup = area.x.inf, area.x.sup xinf, xsup = area.x.inf, area.x.sup
yinf, ysup = area.y.inf, area.y.sup yinf, ysup = area.y.inf, area.y.sup
...@@ -247,16 +301,20 @@ class QtPainter(Painter): ...@@ -247,16 +301,20 @@ class QtPainter(Painter):
pen = self._set_pen(item) pen = self._set_pen(item)
rectangle = QRectF( if item.is_closed:
center + QPointF(-radius, radius), self._painter.drawEllipse(center, radius, radius)
center + QPointF(radius, -radius), else:
) # drawArc cannot be filled !
start_angle, stop_angle = [int(angle*16) for angle in (item.start_angle, item.stop_angle)] rectangle = QRectF(
span_angle = stop_angle - start_angle center + QPointF(-radius, radius),
if span_angle < 0: center + QPointF(radius, -radius),
span_angle = 5760 + span_angle )
self._painter.drawArc(rectangle, start_angle, span_angle) start_angle, stop_angle = [int(angle*16) for angle in (item.start_angle, item.stop_angle)]
## self._painter.drawArc(center.x, center.y, radius, radius, 0, 360) span_angle = stop_angle - start_angle
if span_angle < 0:
span_angle = 5760 + span_angle
self._painter.drawArc(rectangle, start_angle, span_angle)
# self._painter.drawArc(center.x, center.y, radius, radius, start_angle, stop_angle)
############################################## ##############################################
...@@ -480,6 +538,7 @@ class ViewportArea: ...@@ -480,6 +538,7 @@ class ViewportArea:
def fit_scene(self): def fit_scene(self):
# Fixme: AttributeError: 'NoneType' object has no attribute 'center'
if self: if self:
center = np.array(self.scene_area.center, dtype=np.float) center = np.array(self.scene_area.center, dtype=np.float)
scale, axis = self._compute_scale_to_fit_scene() scale, axis = self._compute_scale_to_fit_scene()
......
...@@ -28,6 +28,10 @@ __all__ = ['Color', 'ColorDataBase'] ...@@ -28,6 +28,10 @@ __all__ = ['Color', 'ColorDataBase']
#################################################################################################### ####################################################################################################
import colorsys
####################################################################################################
class Color: class Color:
"""Class to define a colour """Class to define a colour
...@@ -59,19 +63,32 @@ class Color: ...@@ -59,19 +63,32 @@ class Color:
if number_of_args == 1: if number_of_args == 1:
color = args[0] color = args[0]
if isinstance(str, Color): if isinstance(str, Color):
self._red = color.red self._red = color.red_float
self._green = color.green self._green = color.green_float
self._blue = color.blue self._blue = color.blue_float
else: else:
rgb = str(color) rgb = str(color)
if not rgb.startswith('#'): if not rgb.startswith('#'):
raise ValueError('Invalid color {}'.format(rgb)) raise ValueError('Invalid color {}'.format(rgb))
rgb = rgb[1:] rgb = rgb[1:]
self._red = int(rgb[:2], 16) red, green, blue = rgb[:2], rgb[2:4], rgb[-2:]
self._green = int(rgb[2:4], 16) self._red, self._green, self._blue = [self._to_float(int(x, 16))
self._blue = int(rgb[-2:], 16) for x in (red, green, blue)]
elif number_of_args == 3: elif number_of_args == 3:
self._red, self._green, self._blue = [int(arg) for arg in args] self._red, self._green, self._blue = [self._check_value(arg) for arg in args]
else:
self._red, self._green, self._blue = 0
if 'hue' in kwargs:
if 'light' in kwargs:
hue, light, saturation = [kwargs[x] for x in ('hue', 'light', 'saturation')]
red, green, blue = colorsys.hls_to_rgb(hue, light, saturation)
elif 'value' in kwargs:
hue, value, saturation = [kwargs[x] for x in ('hue', 'value', 'saturation')]
red, green, blue = colorsys.hsv_to_rgb(hue, saturation, value)
else:
raise ValueError('Missing color parameter')
self._red, self._green, self._blue = [self._check_value(x) for x in (red, green, blue)]
# self._name = kwargs.get('name', None) # self._name = kwargs.get('name', None)
if 'name' in kwargs: if 'name' in kwargs:
...@@ -87,7 +104,7 @@ class Color: ...@@ -87,7 +104,7 @@ class Color:
############################################## ##############################################
def __str__(self): def __str__(self):
return self.__STR_FORMAT__.format(self._red, self._green, self._blue) return self.__STR_FORMAT__.format(self.red, self.green, self.blue)
############################################## ##############################################
...@@ -96,43 +113,81 @@ class Color: ...@@ -96,43 +113,81 @@ class Color:
############################################## ##############################################
@staticmethod
def _to_int(x):
return int(x*255)
@staticmethod
def _to_float(x):
return x/255
##############################################
def _check_value(self, value): def _check_value(self, value):
if isinstance(value, int): if isinstance(value, int):
if 0 <= value <= 255: if 0 <= value <= 255:
return value return self._to_float(value)
if isinstance(value, float): if isinstance(value, float):
if 0 <= value <= 1: if 0 <= value <= 1:
return int(value * 255) # return int(value * 255)
return value # keep float
raise ValueError('Invalid colour {}'.format(value)) raise ValueError('Invalid colour {}'.format(value))
############################################## ##############################################
@property @property
def red(self): def red_float(self):
return self._red return self._red
@property
def red(self):
return self._to_int(self._red)
@red.setter @red.setter
def red(self, value): def red(self, value):
self._red = self._check_value(value) self._red = self._check_value(value)
@property @property
def green(self): def green_float(self):
return self._green return self._green
@property
def green(self):
return self._to_int(self._green)
@green.setter @green.setter
def green(self, value): def green(self, value):
self._green = self._check_value(value) self._green = self._check_value(value)
@property @property
def blue(self): def blue_float(self):
return self._blue return self._blue
@property
def blue(self):
return self._to_int(self._blue)
@blue.setter @blue.setter
def blue(self, value): def blue(self, value):
self._blue = self._check_value(value) self._blue = self._check_value(value)
############################################## ##############################################
# note hue and saturation is ambiguous
@property
def hls(self):
return colorsys.rgb_to_hls(self._red, self._green, self._blue)
@property
def hsv(self):
return colorsys.rgb_to_hsv(self._red, self._green, self._blue)
##############################################
@property @property
def name(self): def name(self):
return self._name return self._name
......
...@@ -26,7 +26,7 @@ This module import :class:`Color.Colors`. ...@@ -26,7 +26,7 @@ This module import :class:`Color.Colors`.
#################################################################################################### ####################################################################################################
__all__ = ['Colors', 'StrokeStyle'] __all__ = ['Colors', 'StrokeStyle', 'CapStyle', 'JoinStyle']
#################################################################################################### ####################################################################################################
...@@ -41,9 +41,38 @@ class StrokeStyle(Enum): ...@@ -41,9 +41,38 @@ class StrokeStyle(Enum):
"""Enum class to define stroke styles""" """Enum class to define stroke styles"""
NoPen = auto() NoPen = auto() # Inivisble ?
SolidLine = auto() SolidLine = auto()
DashLine = auto() DashLine = auto()
DotLine = auto() DotLine = auto()
DashDotLine = auto() DashDotLine = auto()
DashDotDotLine = auto() DashDotDotLine = auto()
# Custom
####################################################################################################
class CapStyle(Enum):
"""Enum class to define cap styles"""
#: a square line end that does not cover the end point of the line
FlatCap = auto()
#: a square line end that covers the end point and extends beyond it by half the line width
SquareCap = auto()
#: a rounded line end.
RoundCap = auto()
####################################################################################################
class JoinStyle(Enum):
"""Enum class to define join styles"""
#! The outer edges of the lines are extended to meet at an angle, and this area is filled.
MiterJoin = auto()
#: The triangular notch between the two lines is filled.
BevelJoin = auto()
#: A circular arc between the two lines is filled.
RoundJoin = auto()
#: A miter join corresponding to the definition of a miter join in the SVG 1.2 Tiny specification.
SvgMiterJoin = auto()
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
import logging import logging
from Patro.GeometryEngine.Vector import Vector2D from Patro.GeometryEngine.Vector import Vector2D
from Patro.GraphicEngine.GraphicScene.GraphicItem import GraphicPathStyle, Font from Patro.GraphicEngine.GraphicScene.GraphicStyle import GraphicPathStyle, Font
from Patro.GraphicEngine.GraphicScene.Scene import GraphicScene from Patro.GraphicEngine.GraphicScene.Scene import GraphicScene
from . import SketchOperation from . import SketchOperation
from .Calculator import Calculator from .Calculator import Calculator
......
...@@ -40,6 +40,7 @@ from QtShim.QtQml import qmlRegisterUncreatableType ...@@ -40,6 +40,7 @@ from QtShim.QtQml import qmlRegisterUncreatableType
from QtShim.QtQuick import QQuickPaintedItem, QQuickView from QtShim.QtQuick import QQuickPaintedItem, QQuickView
# from QtShim.QtQuickControls2 import QQuickStyle # from QtShim.QtQuickControls2 import QQuickStyle
from Patro.Common.Platform import QtPlatform
from Patro.GraphicEngine.Painter.QtPainter import QtScene, QtQuickPaintedSceneItem from Patro.GraphicEngine.Painter.QtPainter import QtScene, QtQuickPaintedSceneItem
from .rcc import PatroRessource from .rcc import PatroRessource
...@@ -129,6 +130,9 @@ class Application(QObject): ...@@ -129,6 +130,9 @@ class Application(QObject):
self._engine = QQmlApplicationEngine() self._engine = QQmlApplicationEngine()
self._qml_application = QmlApplication(self) self._qml_application = QmlApplication(self)
self._platform = QtPlatform()
# self._logger.info('\n' + str(self._platform))
self._scene = None self._scene = None
# self._load_translation() # self._load_translation()
...@@ -154,6 +158,10 @@ class Application(QObject): ...@@ -154,6 +158,10 @@ class Application(QObject):
def qml_application(self): def qml_application(self):
return self._qml_application return self._qml_application
@property
def platform(self):
return self._platform
############################################## ##############################################
@classmethod @classmethod
......
...@@ -35,3 +35,6 @@ ...@@ -35,3 +35,6 @@
.. |Valentina| replace:: Valentina .. |Valentina| replace:: Valentina
.. _Valentina: https://bitbucket.org/dismine/valentina .. _Valentina: https://bitbucket.org/dismine/valentina
.. |Tikz| replace:: Tikz
.. _Tikz: https://ctan.org/pkg/pgf?lang=en
...@@ -139,16 +139,38 @@ pygments_style = 'sphinx' ...@@ -139,16 +139,38 @@ pygments_style = 'sphinx'
# Options for Autodoc # Options for Autodoc
# #
# http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
# Show both class-level docstring and __init__ docstring in class documentation # Show both class-level docstring and __init__ docstring in class documentation
autoclass_content = 'both' autoclass_content = 'both'
autodoc_default_flags = [
'members', autodoc_member_order = 'alphabetical' # groupwise bysource
'undoc-members',
# 'private-members', # autodoc_default_flags = [
# 'special-members', # 'members',
# 'inherited-members', # 'undoc-members',
# 'show-inheritance', #
] # # 'members',
# # 'undoc-members',
# # 'private-members',
# # 'special-members',
# # 'inherited-members',
# # 'show-inheritance',
# # 'ignore-module-all',
# # 'exclude-members',
# ]
autodoc_default_options = {
'members': None,
# 'member-order': 'alphabetical' ,
'undoc-members': None,
# 'private-members': ,
# 'special-members': ,
# 'inherited-members': ,
# 'show-inheritance': ,
'ignore-module-all': None,
# 'exclude-members': ,
}
#################################################################################################### ####################################################################################################
# #
......
...@@ -63,14 +63,27 @@ A painter is responsible to render the scene on the screen or a graphic file for ...@@ -63,14 +63,27 @@ A painter is responsible to render the scene on the screen or a graphic file for
engine is able to render on the following: engine is able to render on the following:
* show drawing on screen with : |Matplotlib|_, |Qt|_ * show drawing on screen with : |Matplotlib|_, |Qt|_
* export drawing to : PDF, SVG, DXF, LaTeX Tikz * export drawing to : SVG, PDF, DXF, LaTeX |Tikz|_
* export tiled pattern on A4 sheets : PDF, LaTeX Tikz * export tiled pattern on A4 sheets : PDF, LaTeX |Tikz|_
.. duplicated note
.. note:: PDF and SVG format are convertible to each other without data loss
(font handling require more attention).
.. note:: The |Inkscape|_ free software is able to import from / export to a lot of file formats
like SVG, PDF, DXF and to render the drawing to an image format. This job can be done in
batch from command line.
Also the graphic engine is able to render a DXF made of these graphic items: line, circle, arc, Also the graphic engine is able to render a DXF made of these graphic items: line, circle, arc,
ellipse, lwpolyline and spline. ellipse, lwpolyline and spline.
For expert, the LaTeX output can be used to modify the drawing using the power of the |Tikz|_ (PGF)
graphic package.
Implementation details: Implementation details:
* SVG can be rendered using the SVG and Qt painter
* PDF export is implemented with the help of the |Reportlab|_ package * PDF export is implemented with the help of the |Reportlab|_ package
* DXF import/export is implemented with the help of the |ezdxf|_ package of `Manfred Moitzi * DXF import/export is implemented with the help of the |ezdxf|_ package of `Manfred Moitzi
<https://github.com/mozman>`_ <https://github.com/mozman>`_
......
...@@ -6,4 +6,8 @@ ...@@ -6,4 +6,8 @@
* `Valentina developed by Roman Telezhynskyi <https://bitbucket.org/dismine/valentina>`_ * `Valentina developed by Roman Telezhynskyi <https://bitbucket.org/dismine/valentina>`_
* https://inkstitch.org — An open source machine embroidery design platform based on Inkscape.
* https://github.com/EmbroidePy/pyembroidery — pyembroidery library for reading and writing a variety of embroidery formats.
* https://github.com/Embroidermodder/Embroidermodder — Free machine embroidery software supporting a variety of formats.
.. Seamly2D .. Seamly2D
...@@ -10,7 +10,7 @@ from Patro.GeometryEngine.Conic import Circle2D, Ellipse2D ...@@ -10,7 +10,7 @@ from Patro.GeometryEngine.Conic import Circle2D, Ellipse2D
from Patro.GeometryEngine.Segment import Segment2D from Patro.GeometryEngine.Segment import Segment2D
from Patro.GeometryEngine.Spline import BSpline2D from Patro.GeometryEngine.Spline import BSpline2D
from Patro.GeometryEngine.Vector import Vector2D from Patro.GeometryEngine.Vector import Vector2D
from Patro.GraphicEngine.GraphicScene.GraphicItem import GraphicPathStyle, GraphicBezierStyle from Patro.GraphicEngine.GraphicScene.GraphicStyle import GraphicPathStyle, GraphicBezierStyle
from Patro.GraphicEngine.Painter.QtPainter import QtScene from Patro.GraphicEngine.Painter.QtPainter import QtScene
from Patro.GraphicStyle import Colors, StrokeStyle from Patro.GraphicStyle import Colors, StrokeStyle
......