Skip to content
.. include:: /abbreviation.txt
.. _transformation-geometry-ressources-page:
=================
Transformations
=================
Transformation matrices
-----------------------
To transform a vector, we multiply the vector with a transformation matrix
.. math::
\begin{pmatrix} x' \\ y' \end{pmatrix} = \mathbf{T} \begin{pmatrix} x \\ y \end{pmatrix}
Usual transformation matrices in 2D are
.. math::
\begin{align}
\mathbf{Id} &= \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix} \\[1em]
\mathbf{Scale}(s_x, s_y) &= \begin{bmatrix}
s_x & 0 \\
0 & s_y
\end{bmatrix} \\[1em]
\mathbf{Rotation}(\theta) &= \begin{bmatrix}
\cos\theta & \sin\theta \\
-\sin\theta & \cos\theta
\end{bmatrix} \\[1em]
\end{align}
For translation and affine transformation, we must introduce the concept of homogeneous coordinate
which add a virtual third dimension:
.. math::
\mathbf{V} = \begin{bmatrix}
x \\
y \\
1
\end{bmatrix}
Then the translation and affine transformation matrix are expressed as:
.. math::
\begin{align}
\mathbf{Translation}(t_x, t_y) &= \begin{bmatrix}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1
\end{bmatrix} \\[1em]
\mathbf{Generic} &= \begin{bmatrix}
r_{11} & r_{12} & t_x \\
r_{12} & r_{22} & t_y \\
0 & 0 & 1
\end{bmatrix}
\end{align}
To compose transformations, we must multiply the transformations in this order:
.. math::
\mathbf{T} = \mathbf{T_n} \ldots \mathbf{T_2} \mathbf{T_1}
Note the matrix multiplication is not commutative.
.. include:: /abbreviation.txt
.. _ressources-page:
===========
Resources
===========
This section contains resource documentation.
Contents:
.. toctree::
:maxdepth: 2
file-format/index.rst
geometry/index.rst
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
__all__ = ['find_data_path']
####################################################################################################
from pathlib import Path
import os
####################################################################################################
_root_path = Path(__file__).resolve().parents[2]
_data_path = _root_path.joinpath('examples', 'data')
# print(_root_path)
####################################################################################################
def find_data_path(*args):
path = _data_path.joinpath(*args)
if path.exists():
return path
else:
raise NameError("File {} don't exists".format(path))
Subproject commit 5047b04269744db3c6de8cf5cbc08809dfabcf1e Subproject commit a4133aa1d6082563ee6dc112d5aefbf1b58338e6
#################################################################################################### ####################################################################################################
#
from pathlib import Path # Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Fabrice Salvaire
from Patro.Common.Logging import Logging #
Logging.setup_logging() # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
from Patro.FileFormat.Dxf.Importer import DxfImporter # the Free Software Foundation, either version 3 of the License, or
from Patro.GeometryEngine.Conic import Circle2D, Ellipse2D # (at your option) any later version.
from Patro.GeometryEngine.Segment import Segment2D #
from Patro.GeometryEngine.Spline import BSpline2D # This program is distributed in the hope that it will be useful,
from Patro.GeometryEngine.Vector import Vector2D # but WITHOUT ANY WARRANTY; without even the implied warranty of
from Patro.GraphicEngine.GraphicScene.GraphicStyle import GraphicPathStyle, GraphicBezierStyle # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
from Patro.GraphicEngine.Painter.QtPainter import QtScene # GNU General Public License for more details.
from Patro.GraphicStyle import Colors, StrokeStyle #
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
#################################################################################################### ####################################################################################################
...@@ -89,110 +93,3 @@ def spline_from_svg(scene_importer): ...@@ -89,110 +93,3 @@ def spline_from_svg(scene_importer):
for vertex in vertices: for vertex in vertices:
print(vertex) print(vertex)
# scene_importer.scene.bezier_path(vertices, degree=3, path_style=path_style, user_data=None) # scene_importer.scene.bezier_path(vertices, degree=3, path_style=path_style, user_data=None)
####################################################################################################
class SceneImporter:
##############################################
def __init__(self, dxf_path):
self._importer = DxfImporter(dxf_path)
self._scene = QtScene()
self._bounding_box = None
for item in self._importer:
self._add_item(item)
self._scene.bounding_box = self._bounding_box # Fixme:
##############################################
@property
def scene(self):
return self._scene
##############################################
def _update_bounding_box(self, item):
if hasattr(item, 'bounding_box'):
interval = item.bounding_box
if self._bounding_box is None:
self._bounding_box = interval
else:
self._bounding_box |= interval
##############################################
def _add_item(self, item):
# Fixme:
path_style = GraphicPathStyle(
line_width=2.0,
stroke_color=Colors.black,
stroke_style=StrokeStyle.SolidLine,
)
if isinstance(item, Segment2D):
self._scene.segment(item.p0, item.p1,
path_style,
user_data=item,
)
elif isinstance(item, Circle2D):
kwargs = dict(user_data=item)
if item.domain:
kwargs['start_angle'] = item.domain.start
kwargs['stop_angle'] = item.domain.stop
self._scene.circle(item.center, item.radius,
path_style,
**kwargs,
)
elif isinstance(item, BSpline2D):
path_style = GraphicPathStyle(
line_width=3.,
stroke_color=Colors.green,
) # Fixme
self._scene.polyline(item.points,
path_style,
user_data=item,
)
path_style = GraphicBezierStyle(
line_width=5.0,
stroke_color=Colors.black,
show_control=True,
control_color=Colors.red,
) # Fixme
for bezier in item.to_bezier():
self._scene.cubic_bezier(*bezier.points,
path_style,
user_data=item,
)
elif isinstance(item, Ellipse2D):
self._scene.ellipse(item.center,
item.x_radius,
item.y_radius,
item.angle,
path_style,
user_data=item,
)
elif isinstance(item, list):
for segment in item:
self._add_item(segment)
self._update_bounding_box(item)
####################################################################################################
# filename = 'protection-rectangulaire-v2.dxf'
filename = 'test-dxf-r15.dxf'
try:
dxf_path = Path(__file__).parent.joinpath(filename)
except:
dxf_path = Path('examples', 'dxf', filename)
scene_importer = SceneImporter(dxf_path)
# spline_from_svg(scene_importer)
application.qml_application.scene = scene_importer.scene
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import logging
from Patro.Common.Logging import Logging
Logging.setup_logging()
from Patro.FileFormat.Dxf.Importer import DxfImporter
from Patro.GeometryEngine.Spline import BSpline2D
from Patro.GraphicEngine.GraphicScene.GraphicStyle import GraphicPathStyle, GraphicBezierStyle
from Patro.GraphicEngine.Painter.QtPainter import QtScene
from Patro.GraphicStyle import Colors, StrokeStyle
from PatroExample import find_data_path
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class SceneImporter:
_logger = _module_logger.getChild('SceneImporter')
##############################################
def __init__(self, dxf_path):
self._importer = DxfImporter(dxf_path)
self._scene = QtScene()
self._bounding_box = None
for item in self._importer:
self._add_item(item)
self._scene.bounding_box = self._bounding_box # Fixme:
##############################################
@property
def scene(self):
return self._scene
##############################################
def _update_bounding_box(self, item):
if hasattr(item, 'bounding_box'):
interval = item.bounding_box
if self._bounding_box is None:
self._bounding_box = interval
else:
self._bounding_box |= interval
##############################################
def _add_item(self, item):
self._logger.info(item)
line_width = 3.
path_style = GraphicPathStyle(
line_width=line_width,
stroke_color=Colors.black,
stroke_style=StrokeStyle.SolidLine,
)
if isinstance(item, list):
for segment in item:
self._add_item(segment)
elif isinstance(item, BSpline2D):
path_style = GraphicPathStyle(
line_width=line_width,
stroke_color=Colors.blue_blue,
)
self._scene.polyline(item.points,
path_style,
user_data=item,
)
path_style = GraphicBezierStyle(
line_width=5.0,
stroke_color=Colors.black,
show_control=True,
control_color=Colors.red,
)
self._scene.add_spline(item, path_style)
else:
self._scene.add_geometry(item, path_style)
self._update_bounding_box(item)
####################################################################################################
dxf_path = find_data_path('dxf', 'test-dxf-r15.dxf')
scene_importer = SceneImporter(dxf_path)
application.qml_application.scene = scene_importer.scene
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
#################################################################################################### ####################################################################################################
import logging
from pathlib import Path from pathlib import Path
from Patro.Common.Logging import Logging # Disable if executed by patro
Logging.setup_logging() use_qt = True
if not use_qt:
from Patro.Common.Logging import Logging
Logging.setup_logging()
from Patro.FileFormat.Svg import SvgFormat
from Patro.FileFormat.Svg.SvgFile import SvgFile, SvgFileInternal
from Patro.GeometryEngine.Transformation import AffineTransformation2D
from Patro.GraphicEngine.GraphicScene.GraphicStyle import GraphicPathStyle, GraphicBezierStyle
from Patro.GraphicEngine.Painter.QtPainter import QtScene
from Patro.GraphicStyle import Colors, StrokeStyle, CapStyle
from Patro.GraphicStyle.Color.ColorDataBase import Color
from PatroExample import find_data_path
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class SceneImporter(SvgFileInternal):
_logger = _module_logger.getChild('SceneImporter')
##############################################
def __init__(self, svg_path):
self._bounding_box = None
self._item_counter = 0
self._scene = QtScene()
super().__init__(svg_path)
# Fixme:
self._scene.bounding_box = self._bounding_box
self._logger.info('Number of SVG item: {}'.format(self._item_counter))
self._logger.info('Number of scene item: {}'.format(self._scene.number_of_items))
from Patro.FileFormat.Svg.SvgFile import SvgFile ##############################################
@property
def scene(self):
return self._scene
##############################################
def _update_bounding_box(self, item):
interval = item.bounding_box
if self._bounding_box is None:
self._bounding_box = interval
else:
self._bounding_box |= interval
##############################################
def on_svg_root(self, svg_root):
super().on_svg_root(svg_root)
self._screen_transformation = AffineTransformation2D.Screen(self._view_box.y.sup)
##############################################
def on_group(self, group):
# self._logger.info('Group: {}\n{}'.format(group.id, group))
pass
##############################################
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))
self._item_counter += 1
stroke_style = StrokeStyle.SolidLine if state.stroke_dasharray is None else StrokeStyle.DashLine
fill_color = None if state.fill is None else Color(state.fill)
path_style = GraphicPathStyle(
line_width=2,
stroke_color=Colors.black,
stroke_style=stroke_style,
cap_style=CapStyle.RoundCap,
# fill_color=fill_color,
)
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
path = item.path_data
elif isinstance(item, SvgFormat.Rect):
path = item.geometry
path = path.transform(transformation)
self._update_bounding_box(path)
self._scene.add_path(path, path_style)
#################################################################################################### ####################################################################################################
svg_file = SvgFile(Path('patterns', 'veravenus-little-bias-dress.pattern-a0.svg')) # svg_path = find_data_path('svg', 'basic-demo-2.by-hand.svg')
svg_path = find_data_path('svg', 'demo.svg')
# svg_path = find_data_path('svg', 'demo.simple.svg')
# svg_path = find_data_path('patterns-svg', 'veravenus-little-bias-dress.pattern-a0.svg')
# svg_path = find_data_path('patterns-svg', 'veravenus-little-bias-dress.pattern-a0.no-text-zaggy.svg')
# svg_file = SvgFile(svg_path)
scene_importer = SceneImporter(svg_path)
if use_qt:
application.qml_application.scene = scene_importer.scene
#################################################################################################### ####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
from Patro.Common.Logging import Logging ####################################################################################################
Logging.setup_logging()
from Patro.GeometryEngine.Path import ( from Patro.GeometryEngine.Path import Path2D
Path2D,
LinearSegment, QuadraticBezierSegment, CubicBezierSegment
)
from Patro.GeometryEngine.Vector import Vector2D from Patro.GeometryEngine.Vector import Vector2D
from Patro.GraphicEngine.GraphicScene.GraphicStyle 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
...@@ -24,10 +38,16 @@ class SceneBuilder: ...@@ -24,10 +38,16 @@ class SceneBuilder:
self._bounding_box = None self._bounding_box = None
for path in ( for path in (
self._make_path1(), self._make_directional_path((0, 0)),
self._make_path2(), self._make_rounded_rectangle((20, 0), width=10, height=15, radius=5),
self._make_path3(), self._make_closed_path((35, 0), radius=None),
self._make_path4(), self._make_closed_path((55, 0), radius=3),
self._make_absolute_cw_path((0, 40), radius=3),
self._make_absolute_ccw_path((0, 45), radius=3),
self._make_quadratic((25, 40)),
self._make_absolute_quadratic((35, 40)),
self._make_cubic((40, 40)),
self._make_absolute_cubic((50, 40)),
): ):
self._add_path(path) self._add_path(path)
self._scene.bounding_box = self._bounding_box # Fixme: self._scene.bounding_box = self._bounding_box # Fixme:
...@@ -50,9 +70,8 @@ class SceneBuilder: ...@@ -50,9 +70,8 @@ class SceneBuilder:
############################################## ##############################################
def _make_path1(self): def _make_directional_path(self, start_point):
start_point = Vector2D(0, 0)
path = Path2D(start_point) path = Path2D(start_point)
path.horizontal_to(10) path.horizontal_to(10)
path.vertical_to(10) path.vertical_to(10)
...@@ -69,47 +88,113 @@ class SceneBuilder: ...@@ -69,47 +88,113 @@ class SceneBuilder:
############################################## ##############################################
def _make_path2(self): def _make_rounded_rectangle(self, start_point, width, height, radius):
start_point = Vector2D(20, 0)
path = Path2D(start_point) path = Path2D(start_point)
path.horizontal_to(10) path.horizontal_to(width)
path.vertical_to(10) path.vertical_to(height, radius=radius)
path.north_east_to(10) path.horizontal_to(-width, radius=radius)
path.north_west_to(10) path.close(radius=radius, close_radius=radius)
path.south_west_to(10)
path.south_east_to(5)
return path return path
############################################## ##############################################
def _make_path3(self): def _make_closed_path(self, start_point, radius):
start_point = Vector2D(40, 0)
path = Path2D(start_point) path = Path2D(start_point)
path.line_to(Vector2D(10, 0)) path.line_to(Vector2D(10, 0))
path.line_to(Vector2D(0, 10), radius=5) path.line_to(Vector2D(0, 10), radius=radius)
path.line_to(Vector2D(10, 0), radius=5) path.line_to(Vector2D(10, 0), radius=radius)
path.line_to(Vector2D(0, 20), radius=5) path.line_to(Vector2D(0, 20), radius=radius)
path.line_to(Vector2D(-10, 0), radius=5) path.line_to(Vector2D(-10, 0), radius=radius)
path.line_to(Vector2D(0, -10), radius=5) path.line_to(Vector2D(0, -10), radius=radius)
path.close(radius=0) # Fixme: path.close(radius=radius, close_radius=radius)
return path return path
############################################## ##############################################
def _make_path4(self): def _make_absolute_cw_path(self, start_point, radius):
start_point = Vector2D(70, 0) # radius = None
path = Path2D(start_point) path = Path2D(start_point)
# path.line_to(Vector2D(10, 0)) for i, vector in enumerate((
# path.line_to(Vector2D(0, 10), radius=5) (10, -5),
# path.line_to(Vector2D(-10, 0), radius=5) (5, -15),
path.east_to(10) (-5, -15),
path.north_to(10, radius=5) (-10, -5),
path.west_to(10, radius=5) )):
path.line_to(path.p0 + Vector2D(vector), absolute=True, radius=(radius if i else None))
path.close(radius=radius, close_radius=radius)
return path
##############################################
def _make_absolute_ccw_path(self, start_point, radius):
path = Path2D(start_point)
for i, vector in enumerate((
(10, 0),
(15, 10),
(5, 15),
(-5, 10),
)):
path.line_to(path.p0 + Vector2D(vector), absolute=True, radius=(radius if i else None))
path.close(radius=radius, close_radius=radius)
return path
##############################################
def _make_quadratic(self, start_point):
path = Path2D(start_point)
path.quadratic_to(
Vector2D(0, 10),
Vector2D(10, 10),
)
return path
##############################################
def _make_absolute_quadratic(self, start_point):
path = Path2D(start_point)
path.quadratic_to(
path.p0 + Vector2D(0, 10),
path.p0 + Vector2D(10, 10),
absolute=True,
)
return path
##############################################
def _make_cubic(self, start_point):
path = Path2D(start_point)
path.cubic_to(
Vector2D(5, 10),
Vector2D(10, 10),
Vector2D(15, 0),
)
return path
##############################################
def _make_absolute_cubic(self, start_point):
path = Path2D(start_point)
path.cubic_to(
path.p0 + Vector2D(5, 10),
path.p0 + Vector2D(10, 10),
path.p0 + Vector2D(15, 0),
absolute=True,
)
return path return path
...@@ -123,43 +208,14 @@ class SceneBuilder: ...@@ -123,43 +208,14 @@ class SceneBuilder:
stroke_style=StrokeStyle.SolidLine, stroke_style=StrokeStyle.SolidLine,
) )
for item in path: self._scene.add_path(path, path_style)
# print('Path part:', item)
if isinstance(item, LinearSegment): # Fixme: why here ???
# print('linear', item.start_point, item.stop_point, item.points) self._update_bounding_box(path)
if item.radius is not None:
print('-'*10)
print(item.bissector)
print(item.bulge_angle, item.bulge_center, item.start_point, item.stop_point)
print(item.bulge_geometry)
arc = item.bulge_geometry
self._scene.circle(arc.center, arc.radius,
path_style,
start_angle=arc.domain.start,
stop_angle=arc.domain.stop,
user_data=item,
)
self._scene.segment(*item.points,
path_style,
user_data=item,
)
elif isinstance(item, QuadraticBezierSegment):
self._scene.quadratic_bezier(*item.points,
path_style,
user_data=item,
)
elif isinstance(item, CubicBezierSegment):
self._scene.cubic_bezier(*item.points,
path_style,
user_data=item,
)
# Fixme: why here ???
self._update_bounding_box(item)
#################################################################################################### ####################################################################################################
scene = QtScene() scene = QtScene()
scene_builder = SceneBuilder() scene_builder = SceneBuilder()
application.qml_application.scene = scene_builder.scene application.qml_application.scene = scene_builder.scene
#################################################################################################### ####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
from Patro.Common.Logging import Logging ####################################################################################################
Logging.setup_logging()
from Patro.GeometryEngine.Conic import Circle2D from Patro.GeometryEngine.Conic import Circle2D
from Patro.GeometryEngine.Path import ( from Patro.GeometryEngine.Path import Path2D
Path2D,
LinearSegment, QuadraticBezierSegment, CubicBezierSegment
)
from Patro.GeometryEngine.Transformation import Transformation2D
from Patro.GeometryEngine.Vector import Vector2D
from Patro.GraphicEngine.GraphicScene.GraphicStyle 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, CapStyle from Patro.GraphicStyle import Colors, StrokeStyle, CapStyle
...@@ -107,38 +119,9 @@ class SceneBuilder: ...@@ -107,38 +119,9 @@ class SceneBuilder:
cap_style=CapStyle.RoundCap, cap_style=CapStyle.RoundCap,
) )
for item in path: self._scene.add_path(path, path_style)
# print('Path part:', item)
if isinstance(item, LinearSegment):
# print('linear', item.start_point, item.stop_point, item.points)
if item.radius is not None:
print('-'*10)
print(item.bissector)
print(item.bulge_angle, item.bulge_center, item.start_point, item.stop_point)
print(item.bulge_geometry)
arc = item.bulge_geometry
self._scene.circle(arc.center, arc.radius,
path_style,
start_angle=arc.domain.start,
stop_angle=arc.domain.stop,
user_data=item,
)
self._scene.segment(*item.points,
path_style,
user_data=item,
)
elif isinstance(item, QuadraticBezierSegment):
self._scene.quadratic_bezier(*item.points,
path_style,
user_data=item,
)
elif isinstance(item, CubicBezierSegment):
self._scene.cubic_bezier(*item.points,
path_style,
user_data=item,
)
# Fixme: why here ??? # Fixme: why here ???
self._update_bounding_box(item) self._update_bounding_box(path)
############################################## ##############################################
...@@ -150,11 +133,7 @@ class SceneBuilder: ...@@ -150,11 +133,7 @@ class SceneBuilder:
stroke_style=StrokeStyle.SolidLine, stroke_style=StrokeStyle.SolidLine,
) )
kwargs = dict(user_data=circle) self._scene.add_geometry(circle, path_style)
if circle.domain:
kwargs['start_angle'] = circle.domain.start
kwargs['stop_angle'] = circle.domain.stop
self._scene.circle(circle.center, circle.radius, path_style, **kwargs)
self._update_bounding_box(circle) self._update_bounding_box(circle)
#################################################################################################### ####################################################################################################
...@@ -162,7 +141,6 @@ class SceneBuilder: ...@@ -162,7 +141,6 @@ class SceneBuilder:
scene = QtScene() scene = QtScene()
scene_builder = SceneBuilder() scene_builder = SceneBuilder()
application.qml_application.scene = scene_builder.scene application.qml_application.scene = scene_builder.scene
# from Patro.GraphicEngine.Painter.QtPainter import QtPainter # from Patro.GraphicEngine.Painter.QtPainter import QtPainter
......
source /opt/python-virtual-env/py37/bin/activate py37
append_to_python_path_if_not ${PWD} append_to_python_path_if_not ${PWD}
append_to_python_path_if_not ${PWD}/examples
append_to_python_path_if_not ${PWD}/tools append_to_python_path_if_not ${PWD}/tools
if [ -e ${PWD}/non-public-submodule/setenv.sh ]; then if [ -e ${PWD}/non-public-submodule/setenv.sh ]; then
......
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import logging
import unittest
from pathlib import Path
from Patro.Common.Logging import Logging
Logging.setup_logging()
from IntervalArithmetic import Interval2D
from Patro.FileFormat.Svg import SvgFormat
from Patro.FileFormat.Svg.SvgFile import SvgFile, SvgFileInternal
from Patro.GeometryEngine.Transformation import AffineTransformation2D
from Patro.GeometryEngine.Vector import Vector2D
from PatroExample import find_data_path
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
svg_data = """
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 140 140"
height="140mm"
width="140mm"
>
<title id="title1">SVG Basic Demo</title>
<g id="layer1" transform="translate(0,0)" style="stroke:#000000">
<path id="path-x" d="M 20,20 h 100" />
<path id="path-y" d="M 20,20 v 100" />
<path id="path-45" d="M 20,20 l 100,100" />
<rect id="rect-0" x="20" y="20" height="10" width="10" />
<rect id="rect-x1" x="40" y="20" height="10" width="10" />
<rect id="rect-x2" x="80" y="20" height="10" width="10" />
<rect id="rect-y1" x="20" y="40" height="10" width="10" />
<rect id="rect-y2" x="20" y="80" height="10" width="10" />
<rect id="rect-bisect" x="50" y="50" height="10" width="10" />
<rect id="rect-45-2" transform="rotate(45)" x="50" y="50" height="10" width="10" />
<rect id="rect-45" transform="rotate(45,75,75)" x="70" y="70" height="10" width="10" />
<rect id="rect-30" transform="rotate(30,85,85)" x="80" y="80" height="10" width="10" />
<rect id="rect-60" transform="rotate(60,95,95)" x="90" y="90" height="10" width="10" />
</g>
</svg>
"""
####################################################################################################
class SceneImporter(SvgFileInternal):
# Fixme: duplicated code
_logger = _module_logger.getChild('SceneImporter')
##############################################
def __init__(self, svg_path, data=None):
self._scene = {}
self._bounding_box = None
super().__init__(svg_path, data)
##############################################
def __len__(self):
return len(self._scene)
def __getitem__(self, name):
return self._scene[name]
@property
def scene(self):
return self._scene
@property
def bounding_box(self):
return self._bounding_box
##############################################
def _add_to_scene(self, name, geometry):
self._scene[name] = geometry
##############################################
def _update_bounding_box(self, item):
interval = item.bounding_box
if self._bounding_box is None:
self._bounding_box = interval
else:
self._bounding_box |= interval
##############################################
def on_svg_root(self, svg_root):
super().on_svg_root(svg_root)
self._screen_transformation = AffineTransformation2D.Screen(self._view_box.y.sup)
##############################################
def on_group(self, group):
# self._logger.info('Group: {}\n{}'.format(group.id, group))
pass
##############################################
def on_graphic_item(self, item):
state = self._dispatcher.state.clone().merge(item)
self._logger.info('Item: {}\n{}'.format(item.id, item))
# self._logger.info('Item State:\n' + str(state))
transformation = state.transform
# transformation = self._screen_transformation * state.transform
self._logger.info('Sate Transform\n' + str(transformation))
if isinstance(item, SvgFormat.Path):
path = item.path_data
if path is not None: # Fixme:
path = path.transform(transformation)
self._update_bounding_box(path)
self._add_to_scene(item.id, path)
elif isinstance(item, SvgFormat.Rect):
path = item.geometry
self._add_to_scene(item.id, path)
####################################################################################################
def count_svg_tags(svg_data):
tag_counter = {}
for line in svg_data.splitlines():
line = line.strip()
if line.startswith('<'):
position = line.find(' ')
tag = line[1:position]
if tag[0].isalpha():
tag_counter.setdefault(tag, 0)
tag_counter[tag] += 1
return tag_counter
####################################################################################################
class TestLine2D(unittest.TestCase):
##############################################
def test(self):
svg_path = find_data_path('svg', 'basic-demo-2.by-hand.svg')
#data = None
data = svg_data
scene_importer = SceneImporter(svg_path, data=data)
scene = scene_importer.scene
interval = Interval2D((20, 120), (20, 120))
self.assertEqual(scene_importer.bounding_box, interval)
tag_counter = count_svg_tags(data)
number_of_items = sum([tag_counter[x] for x in ('path', 'rect')])
self.assertEqual(len(scene_importer), number_of_items)
# for name, item in scene.items():
# print(name, item)
origin = Vector2D(20, 20)
for name in ('path-x', 'path-y', 'path-45'):
self.assertEqual(scene[name].p0, origin)
for name, p0 in (
('rect-0', (20, 20)),
('rect-x1', (40, 20)),
('rect-x2', (80, 20)),
('rect-y1', (20, 40)),
('rect-y2', (20, 80)),
('rect-bisect', (50, 50)),
#
# ('rect-45-2', (50, 50)),
# ('rect-45', (70, 70)),
# ('rect-30', (80, 80)),
# ('rect-60', (90, 90)),
):
self.assertEqual(scene[name].p0, Vector2D(p0))
####################################################################################################
if __name__ == '__main__':
unittest.main()
...@@ -73,41 +73,6 @@ class TestCubicBezier(unittest.TestCase): ...@@ -73,41 +73,6 @@ class TestCubicBezier(unittest.TestCase):
#################################################################################################### ####################################################################################################
class TestCubicSpline(unittest.TestCase):
##############################################
def test_spline_part(self):
p0 = Vector2D(0, 0)
p1 = Vector2D(3, 5)
p2 = Vector2D(6, 5)
p3 = Vector2D(10, 0)
bezier = CubicBezier2D(p0, p1, p2, p3)
spline = bezier.to_spline()
bezier2 = spline.to_bezier()
self.assertTrue(bezier2.is_close(bezier))
##############################################
def test_spline(self):
points = (
Vector2D(0, 0),
Vector2D(3, 5),
Vector2D(6, 6),
Vector2D(10, 8),
Vector2D(15, 10),
Vector2D(19, 15),
)
spline = CubicSpline2D(points)
for part in spline.iter_on_parts():
print(part)
####################################################################################################
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Conic import *
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
class TestConic(unittest.TestCase):
##############################################
def test(self):
pass
####################################################################################################
if __name__ == '__main__':
unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Path import *
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
class TestPath(unittest.TestCase):
##############################################
def test(self):
pass
####################################################################################################
if __name__ == '__main__':
unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Polygon import *
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
class TestPolygon(unittest.TestCase):
##############################################
def test(self):
x, y = 10, 20
p0 = Vector2D( x, y)
p1 = Vector2D( x, -y)
p2 = Vector2D(-x, -y)
p3 = Vector2D(-x, y)
points = [p0, p1, p2, p3]
polygon = Polygon2D(*points)
self.assertEqual(polygon.number_of_points, len(points))
self.assertListEqual(list(polygon.points), points)
self.assertEqual(polygon.perimeter, 4*(x+y))
self.assertEqual(polygon.area, 4*x*y)
origin = Vector2D(0, 0)
self.assertEqual(polygon.point_barycenter, origin)
self.assertEqual(polygon.barycenter, origin)
####################################################################################################
if __name__ == '__main__':
unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Polyline import *
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
class TestPolyline(unittest.TestCase):
##############################################
def test(self):
pass
####################################################################################################
if __name__ == '__main__':
unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Rectangle import *
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
class TestRectangle(unittest.TestCase):
##############################################
def test(self):
pass
####################################################################################################
if __name__ == '__main__':
unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2019 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Segment import *
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
class TestSegment(unittest.TestCase):
##############################################
def test(self):
pass
####################################################################################################
if __name__ == '__main__':
unittest.main()
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2017 Salvaire Fabrice
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import unittest
from Patro.GeometryEngine.Vector import Vector2D
####################################################################################################
### class TestCubicSpline(unittest.TestCase):
###
### ##############################################
###
### def test_spline_part(self):
###
### p0 = Vector2D(0, 0)
### p1 = Vector2D(3, 5)
### p2 = Vector2D(6, 5)
### p3 = Vector2D(10, 0)
###
### bezier = CubicBezier2D(p0, p1, p2, p3)
### spline = bezier.to_spline()
### bezier2 = spline.to_bezier()
###
### self.assertTrue(bezier2.is_close(bezier))
###
### ##############################################
###
### def test_spline(self):
###
### points = (
### Vector2D(0, 0),
### Vector2D(3, 5),
### Vector2D(6, 6),
### Vector2D(10, 8),
### Vector2D(15, 10),
### Vector2D(19, 15),
### )
### spline = CubicSpline2D(points)
### for part in spline.iter_on_parts():
### print(part)
####################################################################################################
if __name__ == '__main__':
unittest.main()
...@@ -25,6 +25,7 @@ import unittest ...@@ -25,6 +25,7 @@ import unittest
import numpy.testing as np_testing import numpy.testing as np_testing
from Patro.GeometryEngine.Transformation import * from Patro.GeometryEngine.Transformation import *
from Patro.GeometryEngine.Vector import Vector2D
#################################################################################################### ####################################################################################################
...@@ -50,13 +51,21 @@ class TestTransformation(unittest.TestCase): ...@@ -50,13 +51,21 @@ class TestTransformation(unittest.TestCase):
p1_true = Vector2D(20, 30) p1_true = Vector2D(20, 30)
self.assertTrue(p1.almost_equal(p1_true)) self.assertTrue(p1.almost_equal(p1_true))
# Test composition with *= and *
p0 = Vector2D(20, 10) p0 = Vector2D(20, 10)
center = Vector2D(10, 10) center = Vector2D(10, 10)
rotation_at = AffineTransformation2D.RotationAt(center, 90) angle = 90
rotation_at = AffineTransformation2D.RotationAt(center, angle)
p1 = rotation_at * p0 p1 = rotation_at * p0
p1_true = Vector2D(10, 20) p1_true = Vector2D(10, 20)
self.assertTrue(p1.almost_equal(p1_true)) self.assertTrue(p1.almost_equal(p1_true))
rotation_at = (AffineTransformation2D.Translation(center) *
AffineTransformation2D.Rotation(angle) *
AffineTransformation2D.Translation(-center))
p1 = rotation_at * p0
self.assertTrue(p1.almost_equal(p1_true))
# np_testing.assert_almost_equal() # np_testing.assert_almost_equal()
#################################################################################################### ####################################################################################################
......