From 4a611a4aa2685ca64b1c55bbe89b9188d73bd083 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 15 Jun 2024 01:41:26 +0200 Subject: [PATCH] Finalize Polyline parsing but still arcs precision issue --- main.py | 148 ++++++++++++++++++++------------- networks/geometry/Circle.py | 21 +++-- networks/geometry/Point2D.py | 49 ++++++++--- networks/geometry/Point3D.py | 14 +++- networks/geometry/Polyline.py | 122 +++++++++++++++++++++------ networks/geometry/Segment2D.py | 19 +++-- output_image.png | Bin 5780 -> 1754 bytes 7 files changed, 260 insertions(+), 113 deletions(-) diff --git a/main.py b/main.py index a0d761e..28309d7 100644 --- a/main.py +++ b/main.py @@ -1,31 +1,33 @@ -from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE, ROTATION -from PIL import Image, ImageDraw -import matplotlib.pyplot as plt -from networks.geometry.Point3D import Point3D -from networks.geometry.Segment3D import Segment3D -from networks.geometry.Segment2D import Segment2D -from networks.geometry.Circle import Circle -from networks.geometry.Polyline import Polyline -from networks.geometry.Point2D import Point2D -import networks.roads.lines.Line as Line -import networks.roads.lanes.Lane as Lane -from gdpc import Editor, Block, geometry -import networks.geometry.curve_tools as curve_tools -import networks.geometry.Strip as Strip -import networks.geometry.segment_tools as segment_tools -import numpy as np import json -from buildings.Building import Building import random +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from gdpc import Block, Editor, geometry +from PIL import Image, ImageDraw + +import networks.geometry.curve_tools as curve_tools +import networks.geometry.segment_tools as segment_tools +import networks.geometry.Strip as Strip +import networks.roads.lanes.Lane as Lane +import networks.roads.lines.Line as Line +from buildings.Building import Building +from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE, ROTATION +from networks.geometry.Circle import Circle +from networks.geometry.Point2D import Point2D +from networks.geometry.Point3D import Point3D +from networks.geometry.point_tools import ( + curved_corner_by_curvature, + curved_corner_by_distance, +) +from networks.geometry.Polyline import Polyline +from networks.geometry.Segment2D import Segment2D +from networks.geometry.Segment3D import Segment3D from networks.roads import Road as Road from networks.roads.intersections import Intersection as Intersection -from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance - - -import matplotlib matplotlib.use('Agg') @@ -286,18 +288,26 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -w = 200 +w = 100 -n_points = 20 +n_points = 8 min_val, max_val = -w, w random_points = [Point2D(random.randint(min_val, max_val), random.randint( min_val, max_val)) for _ in range(n_points)] +print(random_points) +print("\n\n") + # random_points = (Point2D(-75, -75), Point2D(0, -75), Point2D(75, 75), # Point2D(75, -50), Point2D(-50, 50), Point2D(0, 0)) +# random_points = random_points[0].optimized_path(random_points) + +# random_points = [Point2D(-40, -56), Point2D(-94, 92), Point2D(19, -47), Point2D( +# 100, 59), Point2D(-85, -73), Point2D(-33, -9), Point2D(57, -25), Point2D(51, -34)] + random_points = random_points[0].optimized_path(random_points) p = Polyline(random_points) @@ -316,10 +326,8 @@ image = Image.new('RGB', (width, height), 'black') draw = ImageDraw.Draw(image) -print(p.output_points) - for i in range(len(p.output_points)-1): - if p.output_points[i] != None: + if p.output_points[i] != 0: s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( p.output_points[i+1].x, p.output_points[i+1].y)) s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) @@ -331,40 +339,40 @@ for i in range(len(p.output_points)-1): w-s.points_thick[j].y), fill='grey') -for i in range(2, len(p.get_arcs_intersections())-2): +# for i in range(2, len(p.get_arcs_intersections())-2): - s = Segment2D(Point2D(p.acrs_intersections[i][0].x, p.acrs_intersections[i][0].y), Point2D( - p.acrs_intersections[i-1][-1].x, p.acrs_intersections[i-1][-1].y)) - s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) +# s = Segment2D(Point2D(p.acrs_intersections[i][0].x, p.acrs_intersections[i][0].y), Point2D( +# p.acrs_intersections[i-1][-1].x, p.acrs_intersections[i-1][-1].y)) +# s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) - for j in range(len(s.points_thick)-1): - # editor.placeBlock( - # s.coordinates[j].coordinate, Block("cyan_concrete")) - draw.point((s.points_thick[j].x+w, - w-s.points_thick[j].y), fill='white') - draw.point((p.acrs_intersections[i][0].x+w, - w-p.acrs_intersections[i][0].y), fill='blue') - draw.point((p.acrs_intersections[i][-1].x+w, - w-p.acrs_intersections[i][-1].y), fill='red') +# for j in range(len(s.points_thick)-1): +# # editor.placeBlock( +# # s.coordinates[j].coordinate, Block("cyan_concrete")) +# draw.point((s.points_thick[j].x+w, +# w-s.points_thick[j].y), fill='green') +# draw.point((p.acrs_intersections[i][0].x+w, +# w-p.acrs_intersections[i][0].y), fill='green') +# draw.point((p.acrs_intersections[i][-1].x+w, +# w-p.acrs_intersections[i][-1].y), fill='green') -for i in range(len(center)): - if center[i]: - circle = Circle(center[i]) - circle.circle_thick(round(radius[i]-ww/2), round(radius[i]+ww/2)) - for j in range(len(circle.points_thick)-1): - if circle.points_thick[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): - # editor.placeBlock( - # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.points_thick[j].x+w, - w-circle.points_thick[j].y), fill='white') - circle.circle(radius[i]) - for j in range(len(circle.points)-1): - if circle.points[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): - # editor.placeBlock( - # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.points[j].x+w, - w-circle.points[j].y), fill='purple') +# for i in range(len(center)): +# if center[i]: +# circle = Circle(center[i]) +# circle.circle_thick(round(radius[i]-ww/2), round(radius[i]+ww/2)) +# for j in range(len(circle.points_thick)-1): +# if circle.points_thick[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): +# # editor.placeBlock( +# # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) +# draw.point((circle.points_thick[j].x+w, +# w-circle.points_thick[j].y), fill='green') +# circle.circle(radius[i]) +# for j in range(len(circle.points)-1): +# if circle.points[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): +# # editor.placeBlock( +# # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) +# draw.point( +# (circle.points[j].x+w, w-circle.points[j].y), fill='green') s1 = Segment2D(Point2D(p.acrs_intersections[1][0].x, p.acrs_intersections[1][0].y), Point2D( p.output_points[0].x, p.output_points[0].y)) @@ -372,7 +380,7 @@ s1.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) for j in range(len(s1.points_thick)-1): draw.point((s1.points_thick[j].x+w, - w-s1.points_thick[j].y), fill='white') + w-s1.points_thick[j].y), fill='grey') s1 = Segment2D(Point2D(p.acrs_intersections[-2][2].x, p.acrs_intersections[-2][2].y), Point2D( p.output_points[-1].x, p.output_points[-1].y)) @@ -380,10 +388,34 @@ s1.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) for j in range(len(s1.points_thick)-1): draw.point((s1.points_thick[j].x+w, - w-s1.points_thick[j].y), fill='white') + w-s1.points_thick[j].y), fill='grey') + +for i in range(0, len(p.arcs)): + for j in range(len(p.arcs[i])): + draw.point((p.arcs[i][j].x+w, w-p.arcs[i][j].y), fill='white') + + +for i in range(1, len(p.segments)-1): + for j in range(len(p.segments[i].segment())): + draw.point((p.segments[i].points[j].x+w, + w-p.segments[i].points[j].y), fill='white') + +for i in range(1, len(p.centers)-1): + draw.point((p.centers[i].x+w, w-p.centers[i].y), fill='red') + draw.point((p.acrs_intersections[i][0].x+w, + w-p.acrs_intersections[i][0].y), fill='blue') + draw.point((p.acrs_intersections[i][1].x+w, + w-p.acrs_intersections[i][1].y), fill='purple') + draw.point((p.acrs_intersections[i][2].x+w, + w-p.acrs_intersections[i][2].y), fill='blue') + image.save('output_image.png') +# s = Segment2D(Point2D(-88, -12), Point2D(9, 75)) +# s.segment_thick(3, LINE_THICKNESS_MODE.MIDDLE) +# print(s.points) + # s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) # print(s) diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index caff87c..cda5585 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -1,21 +1,24 @@ -from networks.geometry.Point2D import Point2D -from math import cos, sin, pi +from math import cos, pi, sin from typing import List +import numpy as np + +from networks.geometry.Point2D import Point2D + class Circle: def __init__(self, center: Point2D): self.center = center self.radius = None - self.points = [] + self.points: List[Point2D] = [] self.inner = None self.outer = None - self.points_thick = [] + self.points_thick: List[Point2D] = [] self.spaced_radius = None - self.spaced_points = [] + self.spaced_points: List[Point2D] = [] def __repr__(self): return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" @@ -43,6 +46,7 @@ class Circle: continue else: break + return self.points def circle_thick(self, inner: int, outer: int) -> List[Point2D]: """Compute discrete value of a 2d-circle with thickness. @@ -114,16 +118,17 @@ class Circle: center = self.center self.spaced_points = [ - Point2D(cos(2 * pi / number * i) * radius, - sin(2 * pi / number * i) * radius) + Point2D(round(cos(2 * pi / number * i) * radius), + round(sin(2 * pi / number * i) * radius)) for i in range(0, number + 1) ] for i in range(len(self.spaced_points)): - self.spaced_points[i] = Point2D( + current_point = Point2D( self.spaced_points[i].x + center.x, self.spaced_points[i].y + center.y ).round() + self.spaced_points[i] = current_point return self.spaced_points def _x_line(self, x1, x2, y): diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index b2bedff..ef8a804 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -1,6 +1,8 @@ -import numpy as np -from typing import List from math import atan2, sqrt +from typing import List, Union + +import numpy as np + from Enums import ROTATION @@ -21,10 +23,26 @@ class Point2D: return self.x == other.x and self.y == other.y return False + def __add__(self, other): + if isinstance(other, np.ndarray) and other.shape == (2,): + return Point2D(self.x + other[0], self.y + other[1]) + elif isinstance(other, Point2D): + return Point2D(self.x + other.x, self.y + other.y) + else: + raise TypeError(f"Unsupported type for addition: {type(other)}") + + def __sub__(self, other): + if isinstance(other, np.ndarray) and other.shape == (2,): + return Point2D(self.x - other[0], self.y - other[1]) + elif isinstance(other, Point2D): + return Point2D(self.x - other.x, self.y - other.y) + else: + raise TypeError(f"Unsupported type for subtraction: {type(other)}") + def is_in_triangle(self, xy0: "Point2D", xy1: "Point2D", xy2: "Point2D"): """Returns True is the point is in a triangle defined by 3 others points. - From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. + From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle Args: xy0 (Type[Point2D]): Point of the triangle. @@ -190,12 +208,21 @@ class Point2D: return abs(x1 * y2 - x2 * y1) < 1e-12 @staticmethod - def to_vectors(points: List["Point3D"]) -> List[np.array]: - vectors = [] - for point in points: - vectors.append(np.array(point.coordinates)) - - if (len(vectors) == 1): - return vectors[0] - else: + def to_arrays(points: Union[List["Point2D"], "Point2D"]) -> Union[List[np.array], "Point2D"]: + if isinstance(points, list): + vectors = [] + for point in points: + vectors.append(np.array(point.coordinates)) return vectors + else: + return np.array(points.coordinates) + + @staticmethod + def from_arrays(vectors: Union[List[np.array], "Point2D"]) -> Union[List["Point2D"], "Point2D"]: + if isinstance(vectors, list): + points = [] + for vector in vectors: + points.append(Point2D(vector[0], vector[1])) + return points + else: + return Point2D(vectors[0], vectors[1]) diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 7873612..75fb36c 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -1,5 +1,6 @@ +from math import sqrt from typing import List -from math import atan2, sqrt + import numpy as np @@ -80,3 +81,14 @@ class Point3D: return vectors[0] else: return vectors + + @staticmethod + def from_arrays(vectors: List[np.array]) -> List["Point3D"]: + points = [] + for vector in vectors: + points.append(Point3D(vector[0], vector[1], vector[2])) + + if (len(points) == 1): + return points[0] + else: + return points diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index da20544..5f320ec 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -1,11 +1,17 @@ -from networks.geometry.Point2D import Point2D +from math import inf, sqrt +from typing import List, Tuple, Union -from math import sqrt, inf import numpy as np +from networks.geometry.Circle import Circle +from networks.geometry.Point2D import Point2D +from networks.geometry.Segment2D import Segment2D + +# from Enums import LINE_THICKNESS_MODE, LINE_OVERLAP + class Polyline: - def __init__(self, points: list["Point2D"]): + def __init__(self, points: List[Point2D]): """A polyline with smooth corners, only composed of segments and circle arc. Mathematics and algorithms behind this can be found here: https://cdr.lib.unc.edu/concern/dissertations/pz50gw814?locale=en, E2 Construction of arc roads from polylines, page 210. @@ -18,7 +24,8 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.points_array = Point2D.to_vectors( + self.output_points = points + self.points_array = Point2D.to_arrays( self._remove_collinear_points(points)) self.length_polyline = len(self.points_array) @@ -26,36 +33,46 @@ class Polyline: raise ValueError("The list must contain at least 4 elements.") self.vectors = [None] * self.length_polyline # v - self.lengths = [None] * (self.length_polyline - 1) # l + self.lengths = [0] * (self.length_polyline - 1) # l self.unit_vectors = [None] * self.length_polyline # n self.tangente = [0] * self.length_polyline # f # alpha, maximum radius factor - self.alpha_radii = [None] * self.length_polyline + self.alpha_radii = [0] * self.length_polyline - self.radii = [None] * self.length_polyline # r - self.centers = [None] * self.length_polyline # c + # Useful outputs. In order to not break indexation, each list has the same length, even if for n points, there is n-2 radius. + # Lists will start and end with None. + self.radii = [0] * self.length_polyline # r, list of points + self.centers = [None] * self.length_polyline # c, list of points + # list of tuple of points (first intersection, corresponding corner, last intersection) self.acrs_intersections = [None] * self.length_polyline + self.arcs = [[]] * self.length_polyline # list of points + # self.not_arcs = [[]] * self.length_polyline + # For n points, there is n-1 segments. Last element should stays None. + self.segments = [None] * \ + self.length_polyline # list of segments + + # Run procedure self._compute_requirements() self._compute_alpha_radii() self._alpha_assign(0, self.length_polyline-1) - - self.output_points = points + self.get_radii() + self.get_centers() + self.get_arcs_intersections() + self.get_arcs() + self.get_segments() def __repr__(self): return str(self.alpha_radii) - def get_radii(self): + def get_radii(self) -> List[Union[int]]: for i in range(1, self.length_polyline-1): self.radii[i] = round(self.alpha_radii[i] * self.tangente[i]) return self.radii - def get_centers(self): - if self.radii == [None] * self.length_polyline: - raise ValueError("No radii found. Run get_radii before.") - + def get_centers(self) -> List[Union[Point2D, None]]: for i in range(1, self.length_polyline-1): bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) @@ -65,16 +82,68 @@ class Polyline: self.centers[i] = Point2D(array[0], array[1]).round() return self.centers - def get_arcs_intersections(self): + def get_arcs_intersections(self) -> List[Tuple[Point2D]]: + """Get arcs intersections points. + + First and last elements elements of the list should be None. For n points, there are n-1 segments, and n-2 angle. + + Returns: + list[tuple(Point2D)]: List of tuples composed - in order - of the first arc points, the corner points, the last arc points. The corresponding arc circle is inside this triangle. + """ for i in range(1, self.length_polyline-1): - point_1 = self.points_array[i] - \ - self.alpha_radii[i] * self.unit_vectors[i-1] - point_2 = self.points_array[i] + \ - self.alpha_radii[i] * self.unit_vectors[i] - self.acrs_intersections[i] = Point2D( - point_1[0], point_1[1]).round(), Point2D(self.points_array[i][0], self.points_array[i][1]), Point2D(point_2[0], point_2[1]).round() + point_1 = Point2D.from_arrays(self.points_array[i] - + self.alpha_radii[i] * self.unit_vectors[i-1]) + point_2 = Point2D.from_arrays(self.points_array[i] + + self.alpha_radii[i] * self.unit_vectors[i]) + self.acrs_intersections[i] = point_1.round(), Point2D.from_arrays( + self.points_array[i]), point_2.round() return self.acrs_intersections + def get_arcs(self) -> List[Point2D]: + for i in range(1, self.length_polyline-1): + circle = Circle(self.centers[i]) + circle.circle(self.radii[i]) + for j in range(len(circle.points)): + if circle.points[j].is_in_triangle(self.acrs_intersections[i][0], self.acrs_intersections[i][1], self.acrs_intersections[i][2]): + self.arcs[i].append(circle.points[j]) + # for j in range(len(circle.points)): + # if (circle.points[j] in Segment2D(self.acrs_intersections[i][0], self.acrs_intersections[i][1]).segment(LINE_OVERLAP.MINOR)): + # self.not_arcs[i].append(circle.points[j]) + # print("hzeh") + # if (circle.points[j] in Segment2D(self.acrs_intersections[i][1], self.acrs_intersections[i][2]).segment(LINE_OVERLAP.MINOR)): + # self.not_arcs[i].append(circle.points[j]) + # print("hzeh") + # if (circle.points[j] in Segment2D(self.acrs_intersections[i][2], self.acrs_intersections[i][0]).segment(LINE_OVERLAP.MINOR)): + # self.not_arcs[i].append(circle.points[j]) + # print("hzeh") + return self.arcs + + def get_segments(self) -> List[Segment2D]: + """Get the segments between the circle arcs and at the start and end. + + Last list element should be None, and last usable index is -2 or self.length_polyline - 2. For n points, there are n-1 segments. + + Returns: + list[Segment2D]: List of segments in order. + """ + # Get first segment. + # segments index is 0, corresponding to the first points_array to the first point ([0]) of the first arc (acrs_intersections[1]). + # First arc index is 1 because index 0 is None due to fix list lenght. Is it a good choice? + self.segments[1] = Segment2D(Point2D.from_arrays( + self.points_array[0]), self.acrs_intersections[1][0]) + + # Get segments between arcs + for i in range(2, self.length_polyline - 2): + self.segments[i] = Segment2D(Point2D(self.acrs_intersections[i][0].x, self.acrs_intersections[i][0].y), Point2D( + self.acrs_intersections[i-1][-1].x, self.acrs_intersections[i-1][-1].y)) + + # Get last segment. Index is -2 because last index -1 should be None due to the same list lenght. + # For n points, there are n-1 segments. + self.segments[-2] = Segment2D(Point2D.from_arrays( + self.points_array[-1]), self.acrs_intersections[-2][2]) + + return self.segments + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. @@ -101,8 +170,8 @@ class Polyline: minimum_radius, minimum_index = current_radius, i alpha_low, alpha_high = alpha_a, alpha_b - alpha_a = min(self.lengths[end_index-2], - self.lengths[end_index-1]-self.alpha_radii[end_index]) + alpha_a = min( + self.lengths[end_index-2], self.lengths[end_index-1]-self.alpha_radii[end_index]) current_radius = max(self.tangente[end_index-1]*alpha_a, self.tangente[end_index] * self.alpha_radii[end_index]) # Radius at final segment @@ -123,9 +192,8 @@ class Polyline: """ Returns the radius that balances the radii on either end segement i. """ - - alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / - (self.tangente[i] + self.tangente[i+1])) + alpha_a = min(self.lengths[i-1], (self.lengths[i] * + self.tangente[i+1])/(self.tangente[i] + self.tangente[i+1])) alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) return alpha_a, alpha_b, min(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 46cc70c..2acf5e8 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -1,21 +1,23 @@ -from typing import List +from typing import List, Union + +import numpy as np + from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE from networks.geometry.Point2D import Point2D -from math import sqrt class Segment2D: def __init__(self, start: Point2D, end: Point2D): self.start = start self.end = end - self.points = [] - self.points_thick = [] + self.points: List[Point2D] = [] + self.points_thick: List[Point2D] = [] self.thickness = None def __repr__(self): - return str(self.points) + return str(f"Segment2D(start: {self.start}, end: {self.end}, points: {self.points})") - def segment(self, start: Point2D = None, end: Point2D = None, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, _is_computing_thickness: bool = False) -> List[Point2D]: + def segment(self, start: Point2D = None, end: Point2D = None, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, _is_computing_thickness: bool = False) -> Union[List[Point2D], None]: """Modified Bresenham draw (line) with optional overlap. From: https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp @@ -29,7 +31,7 @@ class Segment2D: >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ - if start == None or end == None: + if start is None or end is None: start = self.start.copy() end = self.end.copy() else: @@ -90,6 +92,7 @@ class Segment2D: if not _is_computing_thickness: return self.points + return None def segment_thick(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE) -> List[Point2D]: """Bresenham with thickness. @@ -208,7 +211,7 @@ class Segment2D: self.segment( start, end, overlap=overlap, _is_computing_thickness=True) - return self.points + return self.points_thick def perpendicular(self, distance: int) -> List[Point2D]: """Compute perpendicular points from both side of the segment placed at start level. diff --git a/output_image.png b/output_image.png index d90c85411584c5a3365ac9283c7faac614bb1808..b091c4164b965d6678afc3f5decdfdb2757ebf5d 100644 GIT binary patch literal 1754 zcmV<01||84P)-#?> zghE?5^=td5v_zVOb+yqD0-<-}@5IVHAJtCh_l9;Gy{WC-{@}_>j1GN;^LJB{e(Q8r z$II|Qmpf;V(_dVXI6gOzTD`iS$W-tN5&ItlmD_&mrZpF zpfkNvVM)#Fo*?=~T|*&+&u%3U{}c>MCR=eGch5^G8XI%CXpSnm*)vsaEvN~#~D(wnQ$R{cqFdDBPD zPls-kst5f-cXhm$s{fWNZ!uzGJ?`1M#P|4Tum0IFWl5eENR3zCl_u%wTb*mEo2^&gV)T;- za+dfe>}h9f%kc`7BDZ;uMTIN-?9gvdttQ3Y;rrlXLtg4^GoA?kO+R9{JRc}YN z&BZ%H2qK>lmdvVp>uHnz*f54xzU1T3{{gIe@xJN*xO8ij_2l%A!pC>$?3T#3hKT7~ z*@>lGi=UR}AdE7icCs$s2Yt^&82!K+JoA^U_)IRpJZ*FFzHxIL^En&;Hcu&MXkn-E z#1g`C$|kJGIF@AzA^K6X7?CI)i+6^WOJ&2#`9Nf7 z4XypeT3xk8UW2l{yWxsH4sL&NqnC-by8kATwgysTVoBt@dHmam6xP2=VTpBHu`h_q z`uZr`Xx1S3aO_#Vi6xpD6Mp=j0sY_5r?B317S=&OzjhT-s)i-0=bq6uoZ4YY`L>cb z0Yk%tC0>3b>m(Y4C3(N8A@-SD(;ILLHqbvNlPc{E#aWs5bs}eUy&{hs7VXc%YyhD)BN0S$AC36+l zN}HE%bkSBChheQWLC{t?Zo^tVHbGm3jI=QcumDlsq+yBaZyBboqj|$pqOCMjhh_UiB5i$}IxL~BVrCCZSllXuutZpZ zD1fly(3TQm{bgGNZ4uUgT*wl}+)nF#9=i(5Ge%K&X1 z5!M|Rw~`Uo11{v#5Y{u=NEwzSNL z=X$D>iDfD0V#`xiLRjX~+HzKv5LOAXZTYK8xW+0+nTyM+go#y-rWmhP31KzlxGLeJ zu(Z>9d?&SHVwH7lXojkUuqya2X_Bf?4@m(40H9Z&);jRktkK-~}Sim+W26}e`Ggrn(u^H+@5%xi_iPL|7ec%zN{Fd^CSuY$7eST}w z|9Ip@_#4I6o?}eI|9{z*E~!5gmKr%S@?!Zz9E^97p6s3HV|jkkA(W{p*nTZ96k`K$ zDe+oE<jMTk!7QqrU|&=_+Gq9O4y~#-vNzNBXMX`NcHZmdXzL4ft51Km&{Y|V zSrhi+OEYiohA8xAx_smg222!vA~d!-ET!ioo2GG@vQR%>tj7xdE&=o;#@?H2q^NxEAm6EH7ig5 ztOPy>ah4fq&`#H0p3P*V237!&0_zWXGZP5(q5cb{PXx_2^AHPR<#{U6hEH9R9RQR$ z!3Da+M^f;&Y?sBvm|=Q-AZT<59?>VDzODq>)EvcNaCWRbJ~%`PGXZ@|B93-u_k&vq z2PIcj%r2ubqThjhPViZfPZv+W0PuXk2Wj8lk3s3x%^WL@NQ{faNJq%f5(dlWbc*ZJ zZHE19ducfaEuKpzExqm0Y@sC_ol35d^eI=?iT5AARm@oD#&Y|>7L#?HWSL3O{-y2u zWw8<_E5nd0BYvL0F}QdqVc@y<{==YWp?$%$>F+IMV3!lzI%+Do`yoT?yov{ul_yHe zZ_d9#JwMu|I|gN+mp9F-J@@HSIs{CnD7Fn{4n+;G%ct;zj1w%_$P|%TqV;xJ3#vUe z4rL$V{owY@U=f}$07Psw(;3e;zF<5LPnOC$e;TN$(p;Jxn8A6-o`Bn^K!yK|?(b7(s@-raAEC0&)dhVU@Q zlqZoo$n7WyQ0XdH#?;L0%S5V};3DAQ$7lu#NH_|tX`Lv|M0!3P63F^~n#D>+(r+TT zM!k_V0GBqPune3~I<2t&G->8_(M0J?x4$j{1>4$;spcR|AU*9eNQ}2lHIRJq zFN@y20>BUIJYdWgK+<2b+T#6Q>cQ7JVm3rfJnlHj=dRulQ(xKfnmS*yMfr*aW>K&yiiSO&|*&*2sSsiT`a!e=zBmPJvt0)@>|p26&}huqzTpC z!PikXU(&-6SQ-Z>gCU1odUG>)9l%C&;$XAouU_)Fc<9_{3ORMOUig4q1+>ZjC8i~y zrUXeP&Ye~Vm4%GimNLYsVA&oYD?LQM%{`O<~B%oE|#RA zy3t573tE-UJ^%Y>d8hUfL14cK)FlOPwNMxosk+UHb$(&zmID*Ppx%+nJrej4IN19q zLJNZAsyO7&W|O=m)H{Q+MEie+9z|*POqQJ|-JL=DLoukIdSxz(yD5f*#BA7aC8z6X z=i{XGjxK#L*)kc~9VMNBS}-d6t+K*IoXmyY$PaCofg>&O*Fm2pkQ(TGArkb=N|OUR z-#izom%yYOT+s+iLKP2`!`Qc7#x^Fr+?4;2!K`M&?wDJ@Qb>gzi8{GzER{u|Y0)KW z5|K?uHew&1mV}g+@^W~#SCThi8oR6ma2e*p z&fBm3J++Pe#KheduX;;Ji9_PJ9hrnvpCfUq`Ik=#0Z)oRsnG*7jWsGxf&iijRQLTF z^Fgl&w9&wekkt5xMI&)(Tf1H#`4FsTtnL_Sg+8o;&n*G z3yGpz5(}UQPm`b?<}>}(@;4ds7M!qqRPN)VwGXe57h{onoQ-%WdXS3*H4nl=cR6AF zRPO%s5>=cqpBG8amI~41tAQi@j)JAtMWDQ&_b_7r*6SN^-2~^rp65;9#K^JY(*{0U z;t^DCOj3PL9do->zt8_UMuL`%+OBVtprDx_%xZ+XGkNO4hTvH$_lu$%gE4Ko&Pq-D zi7pljuWNr^yjK%)YHn<9^XJi3x87f`vIY}E3QTQJe?AO0>`f7HcmP@)3F3sQt~c#C zn_L?W#19UJjB))xp(SMk$Fg~8FiMGz`eZ=;fxE^9e*x)O+-YIyr z{CZx0W>bdTAxD7e%KN=(lYw6s8!YL#rQ{@?mU9k@PAetArsh8GUq)bOvJ^%dr84WR zrOBoP_F>nz&dZwfB5FZLMh`1FVQ8du;JD%T4Nup1XO1)&$5Hb4%cNoBm9z9uQ<1_| zu({05rCr)VIY=}hNAr*Aa7hZ6f~j4Zo!!R%r8e9G?OtdD{gCdmw-qAW8L-=uw0T-l z@@#3q%9(@FY~RO2w6$tm&CcNPMuT5o7C|+(qi;U`wEJ=L$NM~K_+@SnkWb6!nY~qN zdLu&Z2*uc^g=riyj2dPIw!IOxDelHUWs_cVwB`)s7M|@-CG?|Q=)o;}!5hM1d^$0ZXwjln%Twi{Blfc|5i|wOT zh^TSf44uemeMj{6Z9wn)@jICm-5iLeGp07LRagyf(aw!)yZC5R2`5HV18>5Qr2R#{ z74<4TcL=}4`(#Y=up5E*Q4q$Sxgt53`97k6DKAZ4jMF28IT_j0O7aS;20j(sUNq;B zHr1xpD5GaHsck;3Fk36OuYLUh8d3?X>HP8~g=83i7x#7h1Z>KMAKQg&IhxsdX*y{h zZ*e9dCRPXQxQOBk;f+;WxaMSQmG6+pVcK;Xa3jTstNz*C{neNWtQfOf-Vy`c_{Vov zf=sNJyIJJBMSw=qGe*gs5APHZ!VMQAew0Ik&>tRjKa)x$2(6;Axg$?_}Ul&twhKz8&~_WWHj1waj65x6H_k zlAL+*SgHs=Hq+Rp{-TLkRV|#)UyRP$DY*&xMVEF`&dj@bY~$^FLbT?!{YR|}^#|Ig zHm6>3W5$$q*)zyO8*fimiQaQiWfc@XOlg85VFs zjw+nG2;v~1k~;>(PBzMMWXpsKgGt<|5-9tw#`bLlp=eN$gP?f&mKnt&S*eu7qdzbQwm`s4v>EOr}x%%brIT@$X^Ym$=tVzF_$29O4ij zDo9>@$G=-hXfhoMTMM1@c^T>7%7OPOs|&3tp#0RjWi9>Z3Z^zm)*EmdD`SX+A-k!zlguS*5dZYDxTzc^f`5 zC3k#D^LUc|R$RMg@yORqGAhiCck`J!6Ckn1*s$H{a=p>_uzgVsa35^u(ZB+dh)1dr|Qt7bu)ZKuS?CzEF%YsZ3;{%XbZGHCLO{Qx` zkHZ&fnSPsU5o}tQUXEQl?^CcizkP=D{!hl1htj9TrP?~L0gDE?U+ddW8cp_pVJur) z`vUQGp*UE5TfIZ*Q3Q0=C{xj%L4EefyUxFKi@{JId-bsF@v*e`*E)_b%X??-@ZS^1 zTu7ft*Dted2|=Hr%OmmEocL;y`mM6j&M5eN58_jm#_}ESAW=(@Y{+FyMvE5j#k>b! zlG`kCv-=v!Dt*AJ(?^(x^?AC`H{iGx+iLkm>zcKZsoOC!Roc5jZMj9qSDlKKcN{-I zmQ>bEsTyyxnyb(3@j^W_47Y}1rnTRsa9B#O>WW;|hOhpK?u{xd4sCa!pe-2_X3w>g zAR|1pI!6;HLbd+hZXkHjBh@k}HQ@NSgg(CEJ2CF8ov#}Zaa=s(CvSVRdT@_E`{ULi z18*M)N1cQFM_(T8d!io!;~WA+Dx?g)F&iw=?|Us0ldw7FPvpwi<%DyJ?-^)pOIk%e`J@PSeneTMZuHa#YItEXPjnKX>2})*)7jl!^Is z;RCApWx&P1OOIy0bn7z zsf+jPwMJVY*!l*+*9^)3R32^Zo4v@5>Q_ul4 zP-FhA#!6&19ahH91gbTtwq;Wb0@r4GpGv{cTQIiBeB}O%jzKqwim+c#21mz}AvnQr zPY_3;)y~@?Cth{DiH0PQCDtGxf!&jRTQpaj&vwWR6fT~EWS$CcJ|)HnNJ&|yy?=p1 zym}(1eTe-Awrgn;3QZUgJDaBN`Ds4yW9pQL8)WX3(8& zNA)3Z+H*I~iRY=6uDV^)Bu*Jk70q%F#bqSETl&~-avUnR#^7~*SGJV?WNmAA8G^c%3e8V|L0qwlaZB!DcFzuu$@ji|2ARqauW=UMEq1-?!=Ty#=`o za&yTsWIOB99kVojqF75WKjB`k0qaoY0wWaYNyJIRn zzQ#$3v(hTuN2_~BE?nS^ZtoGb2Kun#_@Ogvaz