From 9b87874e13eb530b9641b8346d4c63be70ca2481 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 13 Jun 2024 19:49:38 +0200 Subject: [PATCH] Working polylines demo --- main.py | 66 ++++++++++++++++++++++++++++------ networks/geometry/Circle.py | 2 +- networks/geometry/Polyline.py | 32 +++++++++-------- output_image.png | Bin 3803 -> 5780 bytes 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index c636111..a0d761e 100644 --- a/main.py +++ b/main.py @@ -286,9 +286,9 @@ 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 = 250 +w = 200 -n_points = 5 +n_points = 20 min_val, max_val = -w, w random_points = [Point2D(random.randint(min_val, max_val), random.randint( @@ -298,6 +298,8 @@ random_points = [Point2D(random.randint(min_val, max_val), random.randint( # 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) + p = Polyline(random_points) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) @@ -307,14 +309,15 @@ radius = p.get_radii() center = p.get_centers() y = 200 - -ww = 40 +ww = 15 width, height = 2*w, 2*w -image = Image.new('RGB', (width, height), 'white') +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: s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( @@ -325,18 +328,59 @@ for i in range(len(p.output_points)-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='red') + w-s.points_thick[j].y), fill='grey') + + +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) + + 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 i in range(len(center)): if center[i]: circle = Circle(center[i]) - circle.circle_thick(radius[i]-ww/2+1, radius[i]+ww/2+1) + circle.circle_thick(round(radius[i]-ww/2), round(radius[i]+ww/2)) for j in range(len(circle.points_thick)-1): - # 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='black') + 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') + +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)) +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') + +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)) +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') image.save('output_image.png') diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index c6f78a2..caff87c 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -20,7 +20,7 @@ class Circle: def __repr__(self): return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" - def cirlce(self, radius: int) -> List[Point2D]: + def circle(self, radius: int) -> List[Point2D]: self.radius = radius center = self.center.copy() diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index e3e8635..da20544 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -18,8 +18,9 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.points = Point2D.to_vectors(self._remove_collinear_points(points)) - self.length_polyline = len(points) + self.points_array = Point2D.to_vectors( + self._remove_collinear_points(points)) + self.length_polyline = len(self.points_array) if self.length_polyline < 4: raise ValueError("The list must contain at least 4 elements.") @@ -29,11 +30,12 @@ class Polyline: self.unit_vectors = [None] * self.length_polyline # n self.tangente = [0] * self.length_polyline # f - self.alpha_radii = [None] * self.length_polyline # alpha + # alpha, maximum radius factor + self.alpha_radii = [None] * self.length_polyline self.radii = [None] * self.length_polyline # r self.centers = [None] * self.length_polyline # c - self.connections = [None] * self.length_polyline + self.acrs_intersections = [None] * self.length_polyline self._compute_requirements() self._compute_alpha_radii() @@ -58,19 +60,20 @@ class Polyline: bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) - array = self.points[i] + sqrt((self.radii[i] - ** 2) + (self.alpha_radii[i] ** 2)) * bisector + array = self.points_array[i] + sqrt((self.radii[i] + ** 2) + (self.alpha_radii[i] ** 2)) * bisector self.centers[i] = Point2D(array[0], array[1]).round() return self.centers - def get_arcs(self): + def get_arcs_intersections(self): for i in range(1, self.length_polyline-1): - point_1 = self.points[i] - \ + point_1 = self.points_array[i] - \ self.alpha_radii[i] * self.unit_vectors[i-1] - point_2 = self.points[i] + \ + point_2 = self.points_array[i] + \ self.alpha_radii[i] * self.unit_vectors[i] - self.connections[i] = (point_1, point_2) - return self.connections + 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() + return self.acrs_intersections def _alpha_assign(self, start_index: int, end_index: int): """ @@ -109,9 +112,8 @@ class Polyline: alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] # Assign alphas at ends of selected segment - self.alpha_radii[minimum_index] = alpha_low/1.5 - self.alpha_radii[minimum_index+1] = alpha_high/1.5 - + self.alpha_radii[minimum_index] = alpha_low + self.alpha_radii[minimum_index+1] = alpha_high # Recur on lower segments self._alpha_assign(start_index, minimum_index) # Recur on higher segments @@ -130,7 +132,7 @@ class Polyline: def _compute_requirements(self): # Between two points, there is only one segment for j in range(self.length_polyline-1): - self.vectors[j] = self.points[j+1] - self.points[j] + self.vectors[j] = self.points_array[j+1] - self.points_array[j] self.lengths[j] = np.linalg.norm(self.vectors[j]) self.unit_vectors[j] = self.vectors[j]/self.lengths[j] diff --git a/output_image.png b/output_image.png index 4bb92b505c81f423113a8084e7f408483a3ee257..d90c85411584c5a3365ac9283c7faac614bb1808 100644 GIT binary patch literal 5780 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

Dqc!@kngYs@*1MM`i9L zx5-QTfUUOW^Y+b?1M7zp;=!UiX=t<9VBzT(w8gB@PIzlPD*8}FaG2iceT4s9zsgbW zNl3kjP!oV9%$b&_1%E8=L|_AyqF{AR_@f3w-xDdZIPj}*;_fUG7%wTNEvEO$&Q@)L z)T8!KIM_hy0e>~a-WPJ7SJyvB)rtoqciFSq96b8jm&P}4c(l92sQ;wAfBXqd0?Z!K zCS$W1BZubAwXtT5T>4dlttkoUcaOdgf1bG>QU@YBRt~8Se{5&E-+&N7U(Pbd0_423 zWAUOV$;MQzuVT{aQe`|C7LBPq4z!ZM-jRIT<=3{2`LC~lVRw}T@dj#Y$RUBUlGB8S zgo{US+C3E6a8>{|Nv>gS+q!Ks^Q@_P5ibow4&QB*uV4yF-LIofPD1DQPwix3u}Fv3 zF3-OU_p2EyOpcHMz90T@a6oC+Gv34O(5w^onH@b(q#CNp{cYazwcahN;9dY9_}J>4 zpOCsU)@BPHon}Bp7zVtbskUe$-=re=f^w)UbL`vp^Qz|1;)7kM6z(K&5-Olo%2rF& zF+;`utn;I1u1$7M^ZBzKs{udVu%zBqP4_Q9p>F%UB{uM~<67V#cq>`d*se?7Wh8?qi>(%w~^KW_8)8W)CU(`s#T zazMh@MnCM+_eXfNUfpA&@9O;|(1X)(xmEu?KmxaSJi7n>kMN7ATGG3LL&6*B%P7a6 zbqbhEAafRbH2zf~3H-JlkG{7_XWhLoX{g$%QTphXc4ob{NPRo9o*pLw&$hfu-(W&) zkW$;(o3L7GLe=V1(&N8Q1?57nmpJv90JOSQVJ-^YhU(CM&-c?x>