Files
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

100 lines
6.4 KiB
Python

import json, math, random, numpy as np
from PIL import Image
from shapely.geometry import Polygon, Point, LineString
random.seed(7)
R = json.load(open("rhino.json")); T = json.load(open("transfo.json")); B = json.load(open("batiments.json"))
INST = json.load(open("instances_monde.json")); ROUTES = json.load(open("routes_px.json"))
s, x0, y1, dx, dy = T["s"], T["x0"], T["y1"], T["dx"], T["dy"]
W, H = 1654, 1170
px = lambda x, y: ((x - x0) * s + dx, (y1 - y) * s + dy) # Rhino m -> pixel (100 dpi)
wd = lambda X, Y: (round((X - W / 2) / s, 2), round((Y - H / 2) / s, 2)) # pixel -> monde (x, z)
rw = lambda x, y: wd(*px(x, y)) # Rhino m -> monde
dedans = lambda X, Y, m=0: -m <= X <= W + m and -m <= Y <= H + m
# --- routes : retouche ouest et carrefour (relevé sur zoom), reste = axe calculé
princ = ROUTES["principale"]
ouest = [(0, 180), (100, 236), (200, 292), (300, 349), (400, 405), (460, 440)]
milieu = [p for p in princ if 490 <= p[0] <= 846 and p[1] < 880]
bas = [p for p in princ if p[1] >= 960]
ROUTES["principale"] = ouest + milieu + [(868, 888), (930, 935)] + bas
routes = {k: [wd(*p) for p in v] for k, v in ROUTES.items()}
# --- maisons
def poly_monde(pts): return [rw(*p) for p in pts]
maisons = [{"t": m["type"], "p": poly_monde(m["p"]), "f": m["faitage"]} for m in B["maisons"]]
for sp in B["speciaux"]:
maisons.append({"t": "grand" if sp["aire"] > 200 else "transformee", "p": poly_monde(sp["p"]), "f": sp["faitage"]})
# --- arbres : palette quantifiée
arbres = B["arbres"]
def hexrgb(h): return tuple(int(h[i:i + 2], 16) for i in (1, 3, 5))
# densification de la forêt (zone est, délimitée sur le dessin)
foret = Polygon([(1335, 0), (1654, 0), (1654, 780), (1565, 752), (1160, 596), (1330, 40)])
fond = np.asarray(Image.open("fond1654.png").convert("RGB")).astype(int)
existants = [px(a["x"], a["y"]) for a in arbres]
grille = {}
for (X, Y) in existants: grille.setdefault((int(X // 20), int(Y // 20)), []).append((X, Y))
sentier_foret = [LineString([px(*p) for p in c["p"]]) for c in R["courbes"] if c["l"] == "C_SENTIER" and not c["f"] and len(c["p"]) > 5]
ajouts = 0
for _ in range(9000):
X, Y = random.uniform(1160, 1654), random.uniform(0, 780)
if not foret.contains(Point(X, Y)): continue
voisins = [q for gx in range(int(X // 20) - 1, int(X // 20) + 2) for gy in range(int(Y // 20) - 1, int(Y // 20) + 2) for q in grille.get((gx, gy), [])]
if any((X - a) ** 2 + (Y - b) ** 2 < (2.9 * s) ** 2 for a, b in voisins): continue
if any(l.distance(Point(X, Y)) < 1.6 * s for l in sentier_foret): continue
z = fond[max(0, int(Y) - 4):int(Y) + 5, max(0, int(X) - 4):int(X) + 5].reshape(-1, 3)
z = z[(z.sum(1) < 690) & (z.max(1) - z.min(1) > 18)]
c = z.mean(0) if len(z) > 4 else np.array([140, 190, 140])
r = random.uniform(1.3, 2.4)
Xm, Ym = (X - dx) / s + x0, y1 - (Y - dy) / s
arbres.append({"x": Xm, "y": Ym, "k": "foret+", "r": r, "c": "#%02x%02x%02x" % tuple(int(v) for v in c)})
grille.setdefault((int(X // 20), int(Y // 20)), []).append((X, Y)); ajouts += 1
# palette : couleurs éclaircies façon lavis, quantifiées
def lavis(c):
c = np.array(c, float); g = c.mean(); c = g + (c - g) * 1.15; c = c * 0.85 + 255 * 0.15
return tuple(int(max(0, min(255, v))) for v in c)
cols = [lavis(hexrgb(a["c"])) for a in arbres]
pal, idx = [], []
for c in cols:
q = tuple(int(v // 24 * 24 + 12) for v in c)
if q not in pal: pal.append(q)
idx.append(pal.index(q))
kind = {"Ishigami's fruit tree's 1": 0, "ishigami foret": 1, "foret+": 1, "A_Existant arbre": 2, "B_Existantbuisson": 3}
arbres_out = []
for a, i in zip(arbres, idx):
X, Z = rw(a["x"], a["y"]); arbres_out.append([X, Z, round(a["r"], 2), i, kind.get(a["k"], 1)])
# --- personnages
persos = [rw(i["x"], i["y"]) for i in INST if i["l"] == "PERSOS" and dedans(*px(i["x"], i["y"]))]
# --- eau, sentiers, étals, bassins plantés, liaisons
def polys(layer, ferme=True, min_pts=3):
out = []
for c in R["courbes"]:
if c["l"] != layer or c["f"] != ferme or len(c["p"]) < min_pts: continue
P = [px(*p) for p in c["p"]]
if not any(dedans(*q) for q in P): continue
out.append([wd(*q) for q in P])
return out
noues = polys("B_EAU noues", True)
liaisons = [l for l in polys("TIRE-T", False, 2) if LineString(l).length < 40]
plantes = polys("planté", True) + polys("planté", False, 2)
sentiers = polys("C_SENTIER", True)
chemins = polys("C_SENTIER", False, 2)
etals = []
for c in R["courbes"]:
if c["l"] == "5_ tire-T pointillés" and c["f"]:
P = Polygon(c["p"])
if 6 < P.area < 60:
q = px(P.centroid.x, P.centroid.y)
if dedans(*q): etals.append({"p": [rw(*p) for p in list(P.minimum_rotated_rectangle.exterior.coords)[:4]], "c": wd(*q)})
# --- bulles de la page 15 (centres relevés sur la page alignée, en pixels 100 dpi)
bulles = [("film", 880, 95, 30), ("marche", 388, 238, 38), ("ballons", 775, 240, 37), ("barbecue", 856, 238, 37), ("enfant", 937, 240, 37),
("confiture", 1153, 203, 37), ("bouteille", 1233, 196, 37), ("pomme", 1287, 292, 35), ("miel", 1494, 300, 38), ("abeille", 1403, 360, 38),
("fleur", 1310, 418, 38), ("arbre", 1376, 503, 37), ("biere", 157, 383, 38), ("couverts", 157, 463, 38), ("outils", 157, 543, 38),
("musique", 573, 513, 37), ("oiseaux", 684, 578, 27), ("promeneurs", 857, 388, 38), ("bols", 1036, 525, 28), ("soin", 963, 782, 28),
("entraide", 1259, 780, 28), ("reemploi", 225, 880, 28), ("garage", 1233, 992, 28), ("apaise1", 293, 352, 17), ("apaise2", 857, 860, 17)]
json.dump([{"id": b[0], "x": b[1], "y": b[2], "r": b[3], "w": wd(b[1], b[2])} for b in bulles], open("bulles_px.json", "w"))
scene = {"s": s, "W": W, "H": H, "Wm": round(W / s, 2), "Hm": round(H / s, 2), "maisons": maisons, "pal": pal, "arbres": arbres_out, "persos": persos,
"noues": noues, "liaisons": liaisons, "plantes": plantes, "sentiers": sentiers, "chemins": chemins, "etals": etals, "routes": routes,
"bulles": [{"id": b[0], "w": wd(b[1], b[2])} for b in bulles]}
json.dump(scene, open("scene.json", "w"), separators=(",", ":"))
print("maisons", len(maisons), "arbres", len(arbres_out), "(forêt ajoutée", ajouts, ") palette", len(pal), "persos", len(persos))
print("noues", len(noues), "liaisons", len(liaisons), "plantés", len(plantes), "sentiers", len(sentiers), "chemins", len(chemins), "étals", len(etals))
import os; print("scene.json %.0f Ko" % (os.path.getsize("scene.json") / 1024))