LOT Q0 : socle du démonstrateur Quelque part en 2035
Scaffold Vite + TypeScript + Three.js (scène vide : sol + boîte, marqueur de build), store et encodage de l'état dans l'URL (5 tests), lint de frontières entre modules, pipeline d'extraction Rhino (graine du 26/09) et première géométrie v0, deploy.sh statique (releases + lien symbolique + smoke), AGENTS.md, RUNBOOK, licences MIT + CC BY-SA 4.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# quartier-seed — graine du repo `quartier` (Quartier 3D TMIP)
|
||||
|
||||
Produit le 2026-09-26 par la session de cadrage (Fable 5.1), hors Dropbox, pas encore un repo git.
|
||||
LOT-Q0 crée le repo Gitea `jules/quartier` et adopte ce dossier (comme `reinnover` a adopté son scaffold).
|
||||
|
||||
- `pipeline/extract_quartier.py <plan.3dm> <sortie.json>` : lit le plan Rhino 2D du concours 2020 avec `rhino3dm`
|
||||
(calques PARCELLES, B_EAU noues, C_SENTIER, 7_TOPO, planté ; maisons = quadrilatères 40-400 m² compacts
|
||||
sur les calques 3_ TRAITS VUS / 4_ TRAITS VUS FIINS). Unités cm → m.
|
||||
- `pipeline/plot_quartier.py` : contrôle visuel → `extraction-quartier.png`.
|
||||
- `data/quartier.geo.v0.json` : première extraction (79 parcelles, 11 noues, 9 sentiers, 39 courbes topo,
|
||||
5 zones plantées, 27 maisons candidates sur ~40 visibles). La voirie n'est pas sur un calque : à dériver ou tracer (LOT-Q1).
|
||||
- Source : `2 CASQUETTES/Architecte/TMIP/Archives TMIP/04_ArchiJeunes/02_rhino,autocad,illus/PL-quartier transformé/plan de quartier-concours VP TRANSFO.3dm`
|
||||
(le vault n'est pas lu par le build : copier le `.3dm` dans `sources/` du repo, hors Dropbox, au LOT-Q1).
|
||||
|
||||
Venv : `python3 -m venv v && ./v/bin/pip install -r pipeline/requirements.txt`.
|
||||
Pilote : vault `1 PROJETS/TMIP/REinNOVER/Quartier 3D/PILOTE - Quartier 3D TMIP.md`.
|
||||
@@ -0,0 +1,34 @@
|
||||
import sys, json, math, rhino3dm
|
||||
from shapely.geometry import Polygon
|
||||
p = sys.argv[1]; m = rhino3dm.File3dm.Read(p)
|
||||
L = {i: l.Name for i, l in enumerate(m.Layers)}
|
||||
def pts(c):
|
||||
n = 64 if not isinstance(c,(rhino3dm.PolylineCurve,)) else c.PointCount
|
||||
if isinstance(c, rhino3dm.PolylineCurve):
|
||||
return [(c.Point(i).X/100, c.Point(i).Y/100) for i in range(c.PointCount)]
|
||||
pl = c.ToPolyline(0.0,0.0,0.1,5.0,0.0,0.0,0.0,0.0,True) if hasattr(c,'ToPolyline') else None
|
||||
if pl is None:
|
||||
d=c.Domain; return [(c.PointAt(d.T0+(d.T1-d.T0)*i/n).X/100, c.PointAt(d.T0+(d.T1-d.T0)*i/n).Y/100) for i in range(n+1)]
|
||||
return [(pl[i].X/100, pl[i].Y/100) for i in range(pl.Count)]
|
||||
out = {"parcelles":[], "noues":[], "sentiers":[], "topo":[], "plante":[], "maisons_candidates":[], "autres_fermes":[]}
|
||||
for o in m.Objects:
|
||||
g=o.Geometry; ln=L.get(o.Attributes.LayerIndex,"")
|
||||
if not isinstance(g, rhino3dm.Curve): continue
|
||||
try: P=pts(g)
|
||||
except Exception: continue
|
||||
if len(P)<2: continue
|
||||
if ln=="7_TOPO": out["topo"].append(P); continue
|
||||
if not g.IsClosed: continue
|
||||
try: poly=Polygon(P); a=poly.area
|
||||
except Exception: continue
|
||||
if ln=="PARCELLES": out["parcelles"].append(P)
|
||||
elif ln=="B_EAU noues": out["noues"].append(P)
|
||||
elif ln=="C_SENTIER": out["sentiers"].append(P)
|
||||
elif ln=="planté": out["plante"].append(P)
|
||||
elif ln in ("0","caché","3_ TRAITS VUS","4_ TRAITS VUS FIINS","traits cachés","Défaut","ombres portées") and 40<=a<=400:
|
||||
# maison candidate : quadrilatère-ish compact
|
||||
mrr=poly.minimum_rotated_rectangle;
|
||||
if mrr.area>0 and a/mrr.area>0.7: out["maisons_candidates"].append({"layer":ln,"area":round(a,1),"pts":P})
|
||||
json.dump(out, open(sys.argv[2],"w"))
|
||||
print({k:len(v) for k,v in out.items()})
|
||||
import collections; print(collections.Counter(c["layer"] for c in out["maisons_candidates"]))
|
||||
@@ -0,0 +1,19 @@
|
||||
import sys, rhino3dm, collections
|
||||
for p in sys.argv[1:]:
|
||||
m = rhino3dm.File3dm.Read(p)
|
||||
if m is None: print("FAIL", p); continue
|
||||
types = collections.Counter(); dims = collections.Counter()
|
||||
bb=None
|
||||
for o in m.Objects:
|
||||
g=o.Geometry; t=type(g).__name__; types[t]+=1
|
||||
try:
|
||||
b=g.GetBoundingBox()
|
||||
if b.IsValid:
|
||||
dims['3D' if (b.Max.Z-b.Min.Z)>0.01 else 'flat']+=1
|
||||
bb = b if bb is None else rhino3dm.BoundingBox(min(bb.Min.X,b.Min.X),min(bb.Min.Y,b.Min.Y),min(bb.Min.Z,b.Min.Z),max(bb.Max.X,b.Max.X),max(bb.Max.Y,b.Max.Y),max(bb.Max.Z,b.Max.Z))
|
||||
except Exception: pass
|
||||
print("==", p.split('/')[-1], "| unités:", m.Settings.ModelUnitSystem, "| objets:", len(m.Objects), "| calques:", len(m.Layers))
|
||||
print(" types:", dict(types.most_common(8)))
|
||||
print(" 3D vs plat:", dict(dims))
|
||||
if bb: print(" emprise: %.0f x %.0f x %.0f" % (bb.Max.X-bb.Min.X, bb.Max.Y-bb.Min.Y, bb.Max.Z-bb.Min.Z))
|
||||
print(" calques:", [l.Name for l in m.Layers][:25])
|
||||
@@ -0,0 +1,12 @@
|
||||
import sys, rhino3dm, collections
|
||||
m = rhino3dm.File3dm.Read(sys.argv[1])
|
||||
layers = {i: l.Name for i, l in enumerate(m.Layers)}
|
||||
cnt = collections.Counter(); closed = collections.Counter(); blocks=collections.Counter()
|
||||
for o in m.Objects:
|
||||
li = o.Attributes.LayerIndex; g = o.Geometry
|
||||
cnt[li]+=1
|
||||
if isinstance(g, rhino3dm.Curve) and g.IsClosed: closed[li]+=1
|
||||
if isinstance(g, rhino3dm.InstanceReference): blocks[li]+=1
|
||||
for li,n in cnt.most_common():
|
||||
print(f"{n:6d} obj | {closed[li]:5d} fermés | {blocks[li]:5d} blocs | {layers.get(li)}")
|
||||
print("blocs définis:", [d.Name for d in m.InstanceDefinitions][:30])
|
||||
@@ -0,0 +1,11 @@
|
||||
import json, matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt
|
||||
d=json.load(open("quartier.json"))
|
||||
fig,ax=plt.subplots(figsize=(14,12)); ax.set_aspect('equal'); ax.set_facecolor("#fbfaf5")
|
||||
for P in d["topo"]: xs,ys=zip(*P); ax.plot(xs,ys,color="#d8d2c0",lw=0.5)
|
||||
for P in d["parcelles"]: xs,ys=zip(*P); ax.plot(xs,ys,color="#8fbf6a",lw=0.8)
|
||||
for P in d["sentiers"]: xs,ys=zip(*P); ax.fill(xs,ys,color="#e6c77a",alpha=0.6)
|
||||
for P in d["noues"]: xs,ys=zip(*P); ax.fill(xs,ys,color="#6fb3d9",alpha=0.7)
|
||||
for P in d["plante"]: xs,ys=zip(*P); ax.fill(xs,ys,color="#b7d9a0",alpha=0.5)
|
||||
for c in d["maisons_candidates"]: xs,ys=zip(*c["pts"]); ax.fill(xs,ys,color="#444",alpha=0.85)
|
||||
ax.set_title("Extraction rhino3dm — plan quartier TRANSFO 2020 (m) : %d parcelles, %d noues, %d sentiers, %d maisons candidates"%(len(d["parcelles"]),len(d["noues"]),len(d["sentiers"]),len(d["maisons_candidates"])),fontsize=10)
|
||||
plt.savefig("extraction-quartier.png",dpi=110,bbox_inches="tight"); print("ok")
|
||||
@@ -0,0 +1,3 @@
|
||||
rhino3dm
|
||||
shapely
|
||||
matplotlib
|
||||
Reference in New Issue
Block a user