Files
quartier/pipeline/axo/03b_batiments_arbres.py
Jules NényandClaude Opus 5.5 731531dbe2 Test axonométrique du 27/09 : pipeline de fabrication et prototype
pipeline/axo : extraction du Rhino, recalage sur la planche (1:640), maisons,
arbres (positions réelles des blocs, couleurs de la planche), scène en mètres,
sols depuis le PDF Illustrator vectoriel (calques pilotés), bulles de la page 15,
planche de l'eau nettoyée. prototypes/axo-test : la page de test et son assembleur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:46:14 +02:00

78 lines
4.4 KiB
Python

import json, numpy as np, collections, math
from PIL import Image, ImageDraw
from shapely.geometry import Polygon, MultiPoint, LineString, Point
R = json.load(open("rhino.json")); T = json.load(open("transfo.json"))
s, x0, y1, dx, dy = T["s"], T["x0"], T["y1"], T["dx"], T["dy"]
f = lambda x, y: ((x - x0) * s + dx, (y1 - y) * s + dy)
W, H = 1654, 1170
inimg = lambda x, y: 0 <= f(x, y)[0] <= W and 0 <= f(x, y)[1] <= H
# 1. maisons existantes : rectangles de 3_ TRAITS VUS
cand = json.load(open("cand_maisons.json"))
exist = [Polygon(c["p"]) for c in cand if c["l"] == "3_ TRAITS VUS"]
# faîtage : ligne intérieure la plus longue de 3_ TRAITS VUS contenue dans la maison
lignes = [LineString(c["p"]) for c in R["courbes"] if c["l"] in ("3_ TRAITS VUS", "4_ TRAITS VUS FIINS") and not c["f"] and len(c["p"]) >= 2]
maisons = []
for P in exist:
inner = [l for l in lignes if P.buffer(0.3).contains(l) and l.length > 3]
mrr = list(P.minimum_rotated_rectangle.exterior.coords)[:4]
e1 = np.subtract(mrr[1], mrr[0]); e2 = np.subtract(mrr[2], mrr[1])
if inner:
l = max(inner, key=lambda l: l.length); a, b = l.coords[0], l.coords[-1]
ang = math.atan2(b[1] - a[1], b[0] - a[0])
else:
e = e1 if np.linalg.norm(e1) >= np.linalg.norm(e2) else e2; ang = math.atan2(e[1], e[0])
maisons.append({"type": "existante", "p": [list(map(lambda v: round(v, 2), q)) for q in mrr], "faitage": round(ang, 4)})
# 2. maisons transformées et grand bâtiment : grappes du calque A_ISOL _ COUPE
iso = [c for c in R["courbes"] if c["l"] == "A_ISOL _ COUPE"]
pts = [(p, i) for i, c in enumerate(iso) for p in c["p"]]
centres = [np.mean(np.array(c["p"]), axis=0) for c in iso]
# regroupement simple par distance (8 m)
groupes = []
for i, cc in enumerate(centres):
for g in groupes:
if min(np.linalg.norm(cc - centres[j]) for j in g) < 8: g.append(i); break
else: groupes.append([i])
# fusion itérative des groupes proches
chg = True
while chg:
chg = False
for a in range(len(groupes)):
for b in range(a + 1, len(groupes)):
if min(np.linalg.norm(centres[i] - centres[j]) for i in groupes[a] for j in groupes[b]) < 8:
groupes[a] += groupes[b]; del groupes[b]; chg = True; break
if chg: break
transfo = []
for g in groupes:
P = MultiPoint([tuple(p) for i in g for p in iso[i]["p"]]).minimum_rotated_rectangle
if P.area < 60: continue
cx, cy = P.centroid.x, P.centroid.y
if not inimg(cx, cy): continue
mrr = list(P.exterior.coords)[:4]
e1 = np.subtract(mrr[1], mrr[0]); e2 = np.subtract(mrr[2], mrr[1])
e = e1 if np.linalg.norm(e1) >= np.linalg.norm(e2) else e2
transfo.append({"type": "grand" if P.area > 300 else "transformee", "p": [[round(v, 2) for v in q] for q in mrr], "faitage": round(math.atan2(e[1], e[0]), 4), "aire": round(P.area, 1)})
print("existantes", len(maisons), "| transformées/grand", [(t["type"], t["aire"]) for t in transfo])
# 3. arbres : instances de blocs, couleur échantillonnée sur le dessin TOTAL
tot = np.asarray(Image.open("total100-1.png").convert("RGB")).astype(int)
arbres = []
rayons = {"Ishigami's fruit tree's 1": 1.6, "ishigami foret": 2.6, "B_Existantbuisson": 1.2, "A_Existant arbre": 2.4, "0": 2.0, "Ishigami's tree's 2": 2.4}
INST = json.load(open("instances_monde.json"))
for it in INST:
if it["l"] not in rayons or not inimg(it["x"], it["y"]): continue
rayons[it["l"]] = rayons[it["l"]]
px, py = f(it["x"], it["y"]); r = max(2, int(max(it["r"], 0.8) * s * 0.7))
x_, y_ = int(px), int(py)
zone = tot[max(0, y_ - r):y_ + r, max(0, x_ - r):x_ + r].reshape(-1, 3)
col = zone[(zone.sum(1) < 690) & (zone.max(1) - zone.min(1) > 18)]
c = col.mean(0) if len(col) > 3 else np.array([150, 185, 120])
arbres.append({"x": it["x"], "y": it["y"], "k": it["l"], "r": round(max(it["r"], 0.8), 2), "c": "#%02x%02x%02x" % tuple(int(v) for v in c)})
print("arbres", len(arbres), collections.Counter(a["k"] for a in arbres))
json.dump({"maisons": maisons, "speciaux": transfo, "arbres": arbres}, open("batiments.json", "w"))
# contrôle sur TOTAL
ov = Image.open("total100-1.png").convert("RGB"); d = ImageDraw.Draw(ov)
for m_ in maisons: d.polygon([f(*q) for q in m_["p"]], outline=(0, 60, 230), width=3)
for t in transfo: d.polygon([f(*q) for q in t["p"]], outline=(230, 0, 0), width=4)
for a in arbres[::3]:
px, py = f(a["x"], a["y"]); rr = a["r"] * s * 0.5; d.ellipse([px - rr, py - rr, px + rr, py + rr], outline=(0, 0, 0))
ov.save("controle-batiments.png")