Created
November 29, 2020 17:43
-
-
Save rene-d/42cbdf24707f1412f8a1d317264ad8e2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # rene-d 2020/07/23 | |
| from sys import argv | |
| import operator | |
| from PIL import Image, ImageDraw, ImageFont | |
| import numpy as np | |
| # import csv | |
| import argparse | |
| parse = argparse.ArgumentParser( | |
| description="Calcule les angles et longueurs du contour de <épaisseur> mm pour une longueur totale de <échelle> mm" | |
| ) | |
| parse.add_argument( | |
| "-m", "--model", action="store_true", help="affiche le modèle en fond" | |
| ) | |
| parse.add_argument( | |
| "scale", | |
| metavar="échelle", | |
| type=float, | |
| nargs="?", | |
| default=915, | |
| help="hauteur du modèle en mm", | |
| ) | |
| parse.add_argument( | |
| "thickness", | |
| metavar="épaisseur", | |
| type=float, | |
| nargs="?", | |
| default=20, | |
| help="épaisseur profilé en mm", | |
| ) | |
| args = parse.parse_args() | |
| scale = args.scale | |
| thickness = args.thickness | |
| # relevé des points dans l'image corse1.png | |
| corse = [ | |
| (1198, 324), # Bonifacio | |
| (966, 239), | |
| (791, 250), | |
| (684, 186), # Aleria | |
| (380, 222), | |
| (319, 265), | |
| (214, 255), | |
| (79, 291), | |
| (88, 339), | |
| (207, 345), | |
| (230, 329), # Nonza | |
| (310, 344), # Saint-Florent | |
| (272, 385), | |
| (272, 435), | |
| (328, 471), | |
| (372, 578), # Algajola | |
| (435, 640), # ajouté | |
| (550, 702), | |
| (606, 621), | |
| (634, 699), | |
| (705, 672), # Cargese | |
| (754, 588), | |
| (820, 664), | |
| (862, 648), # Sanguinaires | |
| (842, 557), # Ajaccio | |
| (958, 603), | |
| (999, 491), # Propriano | |
| (1053, 552), | |
| (1127, 469), | |
| (1147, 378), | |
| (1180, 380), | |
| ] | |
| min_x, min_y = (37, 113) | |
| max_x, max_y = (1247, 774) | |
| def rotate(xy, radians): | |
| x, y = xy | |
| c, s = np.cos(radians), np.sin(radians) | |
| j = np.matrix([[c, s], [-s, c]]) | |
| m = np.dot(j, [x, y]) | |
| r_xy = float(m.T[0]), float(m.T[1]) | |
| return np.array(r_xy) | |
| if args.model: | |
| image = Image.open("corse1.png") | |
| else: | |
| image = Image.new("RGB", size=(776, 424), color=(255, 255, 255)) | |
| precision = 4 | |
| image = image.resize((776 * precision, 424 * precision), Image.BICUBIC) | |
| font_number = ImageFont.truetype("HelveticaNeue.ttc", 8 * precision) | |
| font_angle = ImageFont.truetype("HelveticaNeue.ttc", 10 * precision) | |
| font_fixed = ImageFont.truetype("Menlo", 8 * precision) | |
| # recalcule les coordonnées des points dans l'image | |
| image_width, image_height = image.size | |
| corse2 = [] | |
| for xy in corse: | |
| x, y = xy | |
| x = round((x - min_x) / (max_x - min_x) * image_width) | |
| y = round((y - min_y) / (max_y - min_y) * image_height) | |
| corse2.append((x, y)) | |
| corse = corse2 | |
| # dimensions max de la Corse en pixels | |
| dim_x = max(map(operator.itemgetter(0), corse)) - min( | |
| map(operator.itemgetter(0), corse) | |
| ) | |
| dim_y = max(map(operator.itemgetter(1), corse)) - min( | |
| map(operator.itemgetter(1), corse) | |
| ) | |
| # ajuste le coefficient d'échelle | |
| scale /= dim_x | |
| # contexte PIL pour dessiner dans l'image | |
| draw = ImageDraw.Draw(image) | |
| _, text_height = draw.textsize("0", font=font_fixed) | |
| info = f"n° long. angle coupe" | |
| draw.text( | |
| (0, image_height - 1 - (len(corse) + 1) * text_height), | |
| info, | |
| font=font_fixed, | |
| fill=(0, 0, 0), | |
| ) | |
| # dessine le contour | |
| corse.append(corse[0]) | |
| draw.line(corse, fill=(128, 128, 255), width=8) | |
| corse.pop() | |
| # pour écrire les informations sur chaque segment/sommet | |
| # csv_writer = csv.writer(open("corse-sommets.csv", "w")) | |
| angles = [] | |
| total_length = 0 | |
| for i, p in enumerate(corse): | |
| p1 = p | |
| p2 = corse[(i + 1) % len(corse)] | |
| p3 = corse[(i + 2) % len(corse)] | |
| xy_middle = (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2 | |
| v1 = np.array(p1) - np.array(p2) | |
| v2 = np.array(p3) - np.array(p2) | |
| u1 = v1 / np.linalg.norm(v1) | |
| u2 = v2 / np.linalg.norm(v2) | |
| angle = np.math.atan2(np.linalg.det([v1, v2]), np.dot(v1, v2)) | |
| angles.append(angle) | |
| angle = np.degrees(angle) | |
| length = np.linalg.norm(v1) * scale | |
| total_length += length | |
| if angle >= 0: | |
| cut_angle = angle / 2 # angle saillant | |
| else: | |
| cut_angle = 180 + angle / 2 # angle rentrant | |
| print(f"{(i + 1):2d} l={length:6.1f} 𝛼={angle:6.1f}° cut={cut_angle:6.1f}°") | |
| info = f"{(i + 1):2d} {length:6.1f} {angle:4.0f}° {cut_angle:4.0f}°" | |
| draw.text( | |
| (0, image_height - 1 - (len(corse) - i) * text_height), | |
| info, | |
| font=font_fixed, | |
| fill=(0, 0, 0), | |
| ) | |
| row = [ | |
| i + 1, | |
| round(p[0] * scale, 1), | |
| round(p[1] * scale, 1), | |
| round(length, 1), | |
| round(angle, 1), | |
| round(cut_angle, 1), | |
| ] | |
| # csv_writer.writerow(row) | |
| draw.text(xy_middle, f"{i + 1}", align="center", fill=(0, 0, 0), font=font_number) | |
| draw.text(p2, f"{angle:.0f}°", align="center", fill=(0, 0, 0), font=font_angle) | |
| draw.point(p1, fill=(0, 0, 0)) | |
| interior = [] # bord intérieur | |
| for i, p in enumerate(corse): | |
| # points origine et extrémité | |
| p1 = p | |
| p2 = corse[(i + 1) % len(corse)] | |
| # angles origine/extrémité | |
| a1 = angles[(i - 1) % len(corse)] | |
| a2 = angles[i] | |
| v = np.array(p1) - np.array(p2) | |
| u = v / np.linalg.norm(v) * thickness / scale | |
| # traits de construction (pour vérifier les calculs!) | |
| r = rotate(u, -np.pi / 2) | |
| draw.line([p1, *(r + p1)], fill=(0, 0, 0)) | |
| draw.line([p2, *(r + p2)], fill=(0, 0, 0)) | |
| draw.line([*(r + p1), *(r + p2)], fill=(0, 0, 0)) | |
| if a2 >= 0: | |
| color = (255, 0, 0) # coupe angle aigu | |
| else: | |
| color = (0, 255, 0) # coupe angle obtus | |
| # trace le trait de coupe | |
| xy = rotate(u / np.sin(a2 / 2), -a2 / 2) + p2 | |
| draw.line([p2, *xy], fill=color) | |
| interior.append((*xy,)) | |
| interior.append(interior[0]) | |
| draw.line(interior, fill=(0, 0, 0), width=2) | |
| interior.pop() | |
| # la longueur du profilé est la longueur moyenne: | |
| # - chaque bord est un trapèze, la surface du trapèze est (l1+l2)*h/2 | |
| # - la longueur du profilé est la surface du contour divisée par l'épaisseur | |
| # - la surface du contour est la somme des surfaces des trapèzes, soit (∑(l1+l2))*h/2 | |
| # - et donc la longueur du profilé est ∑(l1+l2)/2 | |
| mid_length = 0 | |
| for i in range(len(corse)): | |
| p1 = corse[i] | |
| p2 = corse[(i + 1) % len(corse)] | |
| v1 = np.array(p1) - np.array(p2) | |
| p1 = interior[i] | |
| p2 = interior[(i + 1) % len(corse)] | |
| v2 = np.array(p1) - np.array(p2) | |
| mid_length += np.linalg.norm(v1) + np.linalg.norm(v2) | |
| mid_length = mid_length / 2 * scale | |
| print(f"outline length: {total_length:.0f} mm") | |
| print(f"profile length: {mid_length:.0f} mm") | |
| # print(f"minimum length: {mid_length + thickness * 2 + len(corse) * 1.6:.0f} mm") | |
| # dimensions Corse et longueur du contour | |
| info = f"dim: {dim_x*scale:.1f} x {dim_y*scale:.1f}\ncontour: {total_length:.0f}" | |
| tw, th = draw.textsize(info, font=font_fixed) | |
| draw.text( | |
| ((image_width - tw) / 2, (image_height - th) / 2), | |
| info, | |
| fill=(0, 0, 0), | |
| font=font_fixed, | |
| align="center", | |
| ) | |
| image.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment