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>
100 lines
6.4 KiB
Python
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))
|