From 8c615b5df7d6ed4794943e5cbbd9f290b28020da Mon Sep 17 00:00:00 2001 From: Fabrice Salvaire Date: Mon, 21 Jan 2019 17:57:41 +0100 Subject: [PATCH] Geometry Engine: implemented path --- Patro/GeometryEngine/Path.py | 394 +++++++++++++++++++++++++++++++++ examples/geometry/test-path.py | 102 +++++++++ 2 files changed, 496 insertions(+) create mode 100644 Patro/GeometryEngine/Path.py create mode 100644 examples/geometry/test-path.py diff --git a/Patro/GeometryEngine/Path.py b/Patro/GeometryEngine/Path.py new file mode 100644 index 0000000..1b71311 --- /dev/null +++ b/Patro/GeometryEngine/Path.py @@ -0,0 +1,394 @@ +#################################################################################################### +# +# 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 . +# +#################################################################################################### + +#################################################################################################### + +__all__ = [ + 'LinearSegment', + 'QuadraticBezierSegment', + 'CubicBezierSegment', + 'Path2D', + ] + +#################################################################################################### + +from .Primitive import Primitive1P, Primitive2DMixin +from .Bezier import QuadraticBezier2D, CubicBezier2D +from .Segment import Segment2D +from .Vector import Vector2D + +#################################################################################################### + +class PathPart: + + ############################################## + + def __init__(self, path, position): + + self._path = path + self._position = position + + ############################################## + + def __repr__(self): + return self.__class__.__name__ + + ############################################## + + @property + def path(self): + return self._path + + @property + def position(self): + return self._position + + @position.setter + def position(self, value): + self._position = int(value) + + ############################################## + + @property + def prev_part(self): + return self._path[self._position -1] + + @property + def next_part(self): + return self._path[self._position +1] + + ############################################## + + @property + def start_point(self): + prev_part = self.prev_part + if prev_part is not None: + return prev_part.stop_point + else: + return self._path.p0 + + ############################################## + + @property + def stop_point(self): + raise NotImplementedError + + @property + def geometry(self): + raise NotImplementedError + + ############################################## + + @property + def bounding_box(self): + return self.geometry.bounding_box + +#################################################################################################### + +class LinearSegment(PathPart): + + @property + def points(self): + return self.start_point, self.stop_point + +#################################################################################################### + +class PathSegment(LinearSegment): + + ############################################## + + def __init__(self, path, position, point): + PathPart.__init__(path, position) + self.point = point + + ############################################## + + @property + def point(self): + return self._point + + @point.setter + def point(self, value): + self._point = Vector2D(value) # self._path.__vector_cls__ + + ############################################## + + @property + def stop_point(self): + return self._point + self.start_point + + ############################################## + + @property + def geometry(self): + # Fixme: cache ??? + return Segment2D(*self.points) + +#################################################################################################### + +class DirectionalSegment(LinearSegment): + + __angle__ = None + + ############################################## + + def __init__(self, path, position, length): + PathPart.__init__(self, path, position) + self.length = length + + ############################################## + + @property + def length(self): + return self._length + + @length.setter + def length(self, value): + self._length = float(value) + + ############################################## + + @property + def stop_point(self): + # Fixme: cache ??? + return self.start_point + Vector2D.from_polar(self._length, self.__angle__) + + ############################################## + + @property + def geometry(self): + # Fixme: cache ??? + return Segment2D(self.start_point, self.stop_point) + +#################################################################################################### + +class HorizontalSegment(DirectionalSegment): + __angle__ = 0 + +class VerticalSegment(DirectionalSegment): + __angle__ = 90 + +class NorthSegment(DirectionalSegment): + __angle__ = 90 + +class SouthSegment(DirectionalSegment): + __angle__ = -90 + +class EastSegment(DirectionalSegment): + __angle__ = 0 + +class WestSegment(DirectionalSegment): + __angle__ = 180 + +class NorthEastSegment(DirectionalSegment): + __angle__ = 45 + +class NorthWestSegment(DirectionalSegment): + __angle__ = 180 - 45 + +class SouthEastSegment(DirectionalSegment): + __angle__ = -45 + +class SouthWestSegment(DirectionalSegment): + __angle__ = -180 + 45 + +#################################################################################################### + +class TwoPointsMixin: + + @property + def point1(self): + return self._point1 + + @point1.setter + def point1(self, value): + self._point1 = Vector2D(value) # self._path.__vector_cls__ + + @property + def point2(self): + return self._point2 + + @point2.setter + def point2(self, value): + self._point2 = Vector2D(value) + +#################################################################################################### + +class QuadraticBezierSegment(PathPart, TwoPointsMixin): + + # Fixme: abs / inc + + ############################################## + + def __init__(self, path, position, point1, point2): + PathPart.__init__(self, path, position) + self.point1 = point1 + self.point2 = point2 + + ############################################## + + @property + def stop_point(self): + return self._point2 + + @property + def points(self): + return (self.start_point, self._point1, self._point2) + + ############################################## + + @property + def geometry(self): + # Fixme: cache ??? + return QuadraticBezier2D(self.start_point, self._point1, self._point2) + +#################################################################################################### + +class CubicBezierSegment(PathPart, TwoPointsMixin): + + ############################################## + + def __init__(self, path, position, point1, point2, point3): + PathPart.__init__(self, path, position) + self.point1 = point1 + self.point2 = point2 + self.point3 = point3 + + ############################################## + + @property + def point3(self): + return self._point3 + + @point3.setter + def point3(self, value): + self._point3 = Vector2D(value) # self._path.__vector_cls__ + + ############################################## + + @property + def stop_point(self): + return self._point3 + + @property + def points(self): + return (self.start_point, self._point1, self._point2, self._point3) + + ############################################## + + @property + def geometry(self): + # Fixme: cache ??? + return CubicBezier2D(self.start_point, self._point1, self._point2, self._point3) + +#################################################################################################### + +class Path2D(Primitive2DMixin, Primitive1P): + + """Class to implements 2D Path.""" + + ############################################## + + def __init__(self, start_point): + + Primitive1P.__init__(self, start_point) + + self._parts = [] + + ############################################## + + def __len__(self): + return len(self._parts) + + def __iter__(self): + return iter(self._parts) + + def __getitem__(self, position): + # try: + # return self._parts[slice_] + # except IndexError: + # return None + position = int(position) + if 0 <= position < len(self._parts): + return self._parts[position] + else: + return None + + ############################################## + + def _add_part(self, part_cls, *args): + obj = part_cls(self, len(self._parts), *args) + self._parts.append(obj) + return obj + + ############################################## + + def move_to(self, point): + self.p0 = point + + ############################################## + + def horizontal_to(self, distance): + return self._add_part(HorizontalSegment, distance) + + def vertical_to(self, distance): + return self._add_part(VerticalSegment, distance) + + def north_to(self, distance): + return self._add_part(NorthSegment, distance) + + def south_to(self, distance): + return self._add_part(SouthSegment, distance) + + def west_to(self, distance): + return self._add_part(WestSegment, distance) + + def east_to(self, distance): + return self._add_part(EastSegment, distance) + + def north_east_to(self, distance): + return self._add_part(NorthEastSegment, distance) + + def south_east_to(self, distance): + return self._add_part(SouthEastSegment, distance) + + def north_west_to(self, distance): + return self._add_part(NorthWestSegment, distance) + + def south_west_to(self, distance): + return self._add_part(SouthWestSegment, distance) + + ############################################## + + def line_to(self, point): + return self._add_part(PathSegment, point) + + def close(self): + return self._add_part(PathSegment, self._p0) + + ############################################## + + def quadratic_to(self, point1, point2): + return self._add_part(QuadraticBezierSegment, point1, point2) + + ############################################## + + def cubic_to(self, point1, point2, point3): + return self._add_part(CubicBezierSegment, point1, point2, point3) diff --git a/examples/geometry/test-path.py b/examples/geometry/test-path.py new file mode 100644 index 0000000..263ba6f --- /dev/null +++ b/examples/geometry/test-path.py @@ -0,0 +1,102 @@ +#################################################################################################### + +from Patro.Common.Logging import Logging +Logging.setup_logging() + +from Patro.GeometryEngine.Path import ( + Path2D, + LinearSegment, QuadraticBezierSegment, CubicBezierSegment +) +from Patro.GeometryEngine.Vector import Vector2D +from Patro.GraphicEngine.GraphicScene.GraphicItem import GraphicPathStyle, GraphicBezierStyle +from Patro.GraphicEngine.Painter.QtPainter import QtScene +from Patro.GraphicStyle import Colors, StrokeStyle + +#################################################################################################### + +class SceneBuilder: + + ############################################## + + def __init__(self): + + self._scene = QtScene() + + self._bounding_box = None + path = self._make_path() + self._add_items(path) + self._scene.bounding_box = self._bounding_box # Fixme: + + ############################################## + + @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 _make_path(self): + + start_point = Vector2D(0, 0) + path = Path2D(start_point) + path.horizontal_to(10) + path.vertical_to(10) + path.north_east_to(10) + path.north_west_to(10) + path.south_west_to(10) + path.south_east_to(5) + path.south_to(5) + path.west_to(10) + path.north_to(5) + path.east_to(5) + + return path + + ############################################## + + def _add_items(self, path): + + path_style = GraphicPathStyle( + line_width=3.0, + stroke_color=Colors.black, + stroke_style=StrokeStyle.SolidLine, + ) + + for item in path: + print('Path part:', item) + if isinstance(item, LinearSegment): + print('linear', item.start_point, item.stop_point, item.points) + 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_builder = SceneBuilder() + +application.qml_application.scene = scene_builder.scene -- GitLab