lovarch-cli 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. lovarch_cli/__init__.py +16 -0
  2. lovarch_cli/__main__.py +10 -0
  3. lovarch_cli/ai/__init__.py +21 -0
  4. lovarch_cli/ai/gateway.py +240 -0
  5. lovarch_cli/api.py +111 -0
  6. lovarch_cli/auth/__init__.py +32 -0
  7. lovarch_cli/auth/keyring_store.py +214 -0
  8. lovarch_cli/auth/local_server.py +165 -0
  9. lovarch_cli/auth/pkce.py +57 -0
  10. lovarch_cli/auth/session.py +189 -0
  11. lovarch_cli/cli.py +262 -0
  12. lovarch_cli/clients/__init__.py +33 -0
  13. lovarch_cli/clients/factory.py +54 -0
  14. lovarch_cli/clients/local_client.py +432 -0
  15. lovarch_cli/clients/lovarch_storage.py +174 -0
  16. lovarch_cli/clients/lovarch_supabase.py +295 -0
  17. lovarch_cli/clients/persistence.py +166 -0
  18. lovarch_cli/clients/storage.py +66 -0
  19. lovarch_cli/commands/__init__.py +10 -0
  20. lovarch_cli/commands/account.py +172 -0
  21. lovarch_cli/commands/audit.py +394 -0
  22. lovarch_cli/commands/config_cmd.py +80 -0
  23. lovarch_cli/commands/consolidate.py +217 -0
  24. lovarch_cli/commands/context_cmd.py +73 -0
  25. lovarch_cli/commands/dev.py +287 -0
  26. lovarch_cli/commands/do_cmd.py +120 -0
  27. lovarch_cli/commands/init.py +218 -0
  28. lovarch_cli/commands/jobs_cmd.py +95 -0
  29. lovarch_cli/commands/login.py +202 -0
  30. lovarch_cli/commands/mcp_cmd.py +26 -0
  31. lovarch_cli/commands/run.py +375 -0
  32. lovarch_cli/commands/signup.py +185 -0
  33. lovarch_cli/commands/status.py +243 -0
  34. lovarch_cli/commands/upgrade.py +108 -0
  35. lovarch_cli/commands/verifica_cmd.py +174 -0
  36. lovarch_cli/config.py +101 -0
  37. lovarch_cli/config_store.py +111 -0
  38. lovarch_cli/credits/__init__.py +35 -0
  39. lovarch_cli/credits/base.py +84 -0
  40. lovarch_cli/credits/factory.py +36 -0
  41. lovarch_cli/credits/local.py +34 -0
  42. lovarch_cli/credits/lovarch.py +56 -0
  43. lovarch_cli/i18n/__init__.py +27 -0
  44. lovarch_cli/i18n/loader.py +121 -0
  45. lovarch_cli/i18n/translations/en.json +168 -0
  46. lovarch_cli/i18n/translations/es.json +168 -0
  47. lovarch_cli/i18n/translations/it.json +168 -0
  48. lovarch_cli/i18n/translations/pt.json +168 -0
  49. lovarch_cli/mcp/__init__.py +9 -0
  50. lovarch_cli/mcp/server.py +199 -0
  51. lovarch_cli/mcp/tools.py +372 -0
  52. lovarch_cli/sample_downloader.py +255 -0
  53. lovarch_cli/squad/README.md +206 -0
  54. lovarch_cli/squad/agents/auditor-input.md +353 -0
  55. lovarch_cli/squad/agents/bim-engineer.md +404 -0
  56. lovarch_cli/squad/agents/briefing-architect.md +249 -0
  57. lovarch_cli/squad/agents/cad-engineer.md +278 -0
  58. lovarch_cli/squad/agents/capitolato-writer.md +256 -0
  59. lovarch_cli/squad/agents/computo-engineer.md +258 -0
  60. lovarch_cli/squad/agents/concept-designer.md +399 -0
  61. lovarch_cli/squad/agents/contratto-architect.md +243 -0
  62. lovarch_cli/squad/agents/deliverable-builder.md +253 -0
  63. lovarch_cli/squad/agents/energy-prelim.md +388 -0
  64. lovarch_cli/squad/agents/pratiche-it.md +251 -0
  65. lovarch_cli/squad/agents/progetto-chief.md +768 -0
  66. lovarch_cli/squad/agents/quality-dati.md +409 -0
  67. lovarch_cli/squad/agents/quality-misure.md +418 -0
  68. lovarch_cli/squad/agents/quality-normativa.md +417 -0
  69. lovarch_cli/squad/agents/quality-output.md +436 -0
  70. lovarch_cli/squad/agents/regolatorio-it.md +278 -0
  71. lovarch_cli/squad/checklists/handoff-quality-gate.md +232 -0
  72. lovarch_cli/squad/checklists/quality-dati-checklist.md +134 -0
  73. lovarch_cli/squad/checklists/quality-misure-checklist.md +139 -0
  74. lovarch_cli/squad/checklists/quality-normativa-checklist.md +121 -0
  75. lovarch_cli/squad/checklists/quality-output-checklist.md +116 -0
  76. lovarch_cli/squad/config.yaml +408 -0
  77. lovarch_cli/squad/data/CHANGELOG.md +272 -0
  78. lovarch_cli/squad/data/agents-prd.md +428 -0
  79. lovarch_cli/squad/data/architettura-progetto-rules.md +328 -0
  80. lovarch_cli/squad/data/handoff-card-template.md +231 -0
  81. lovarch_cli/squad/data/mocks/catasto-visura.json +72 -0
  82. lovarch_cli/squad/data/mocks/firma-envelope.json +43 -0
  83. lovarch_cli/squad/data/prezzario-lombardia-sample.json +312 -0
  84. lovarch_cli/squad/scripts/api_clients.py +206 -0
  85. lovarch_cli/squad/scripts/architect_profile.py +276 -0
  86. lovarch_cli/squad/scripts/deliverable_generators.py +844 -0
  87. lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +369 -0
  88. lovarch_cli/squad/scripts/generate_chianti_dxf.py +368 -0
  89. lovarch_cli/squad/scripts/generate_chianti_images.py +223 -0
  90. lovarch_cli/squad/scripts/generate_real_sample_images.py +189 -0
  91. lovarch_cli/squad/scripts/generate_sample_assets.py +382 -0
  92. lovarch_cli/squad/scripts/lovarch_client.py +1046 -0
  93. lovarch_cli/squad/scripts/pipeline_runner.py +2095 -0
  94. lovarch_cli/squad/scripts/render_dxf_to_png.py +57 -0
  95. lovarch_cli/squad/scripts/run_palestra_demo.sh +277 -0
  96. lovarch_cli/squad/scripts/simulate_squad_execution.py +515 -0
  97. lovarch_cli/squad/scripts/validate-squad.py +383 -0
  98. lovarch_cli/squad/tasks/audit-input.md +146 -0
  99. lovarch_cli/squad/tasks/compute-metric.md +105 -0
  100. lovarch_cli/squad/tasks/consolidate-dossier.md +187 -0
  101. lovarch_cli/squad/tasks/generate-cad-plan.md +120 -0
  102. lovarch_cli/squad/tasks/generate-ifc-model.md +108 -0
  103. lovarch_cli/squad/tasks/write-capitolato.md +100 -0
  104. lovarch_cli/squad/templates/asseverazione-tecnica.md +126 -0
  105. lovarch_cli/squad/templates/capitolato-uni-11337.md +235 -0
  106. lovarch_cli/squad/templates/cila-comune-milano.md +177 -0
  107. lovarch_cli/squad/templates/contratto-cnappc.md +220 -0
  108. lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +218 -0
  109. lovarch_cli/squad_loader.py +114 -0
  110. lovarch_cli/verify/__init__.py +15 -0
  111. lovarch_cli/verify/contratto.py +110 -0
  112. lovarch_cli/verify/dossier.py +97 -0
  113. lovarch_cli/verify/misure.py +83 -0
  114. lovarch_cli/verify/normativa.py +178 -0
  115. lovarch_cli/version.py +13 -0
  116. lovarch_cli/workflows/__init__.py +9 -0
  117. lovarch_cli/workflows/platform.py +212 -0
  118. lovarch_cli-0.2.1.dist-info/METADATA +232 -0
  119. lovarch_cli-0.2.1.dist-info/RECORD +122 -0
  120. lovarch_cli-0.2.1.dist-info/WHEEL +4 -0
  121. lovarch_cli-0.2.1.dist-info/entry_points.txt +3 -0
  122. lovarch_cli-0.2.1.dist-info/licenses/LICENSE +38 -0
@@ -0,0 +1,368 @@
1
+ """
2
+ Generate sample DXF for Casa Colonica Belvedere · Chianti demo.
3
+
4
+ Creates a realistic farmhouse plan in DXF format with:
5
+ - Layer ISO standard (CAD-A-WALL, CAD-A-DIM, CAD-A-DOOR, CAD-A-WIND, CAD-A-TEXT)
6
+ - Two buildings: corpo principale (220 m² ground floor) + annesso ex-stalla (60 m²)
7
+ - Stone perimeter walls 50cm thick, internal partitions 15cm
8
+ - Cartiglio CNAPPC for Pablo Ruan · Ordine Firenze
9
+ - Specific to Italian rural restoration project
10
+
11
+ Output: data/sample-input-villa-chianti/stato-attuale.dxf
12
+
13
+ Usage:
14
+ python3 generate_chianti_dxf.py [output_path]
15
+ """
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ try:
20
+ import ezdxf
21
+ from ezdxf.enums import TextEntityAlignment
22
+ except ImportError:
23
+ sys.stderr.write("ERROR: ezdxf not installed. Run: pip install ezdxf\n")
24
+ sys.exit(1)
25
+
26
+
27
+ LAYERS = [
28
+ ("CAD-A-WALL", 7, "Continuous"),
29
+ ("CAD-A-WALL-EXT", 1, "Continuous"),
30
+ ("CAD-A-DOOR", 4, "Continuous"),
31
+ ("CAD-A-WIND", 5, "Continuous"),
32
+ ("CAD-A-DIM", 3, "Continuous"),
33
+ ("CAD-A-TEXT", 2, "Continuous"),
34
+ ("CAD-A-SYMB", 6, "Continuous"),
35
+ ("CAD-A-FURN", 8, "Continuous"),
36
+ ("CAD-A-CART", 7, "Continuous"),
37
+ ]
38
+
39
+
40
+ def setup_layers(doc):
41
+ for name, color, linetype in LAYERS:
42
+ if name not in doc.layers:
43
+ doc.layers.add(name=name, color=color, linetype=linetype)
44
+
45
+
46
+ def setup_dim_style(doc):
47
+ if "UNI_DIM" not in doc.dimstyles:
48
+ ds = doc.dimstyles.new("UNI_DIM")
49
+ ds.dxf.dimtxt = 25
50
+ ds.dxf.dimasz = 25
51
+ ds.dxf.dimexe = 12
52
+ ds.dxf.dimexo = 12
53
+ ds.dxf.dimdec = 0
54
+ ds.dxf.dimlfac = 0.1
55
+
56
+
57
+ def add_wall(msp, p1, p2, *, layer="CAD-A-WALL", thickness=150):
58
+ import math
59
+ dx = p2[0] - p1[0]
60
+ dy = p2[1] - p1[1]
61
+ length = math.sqrt(dx * dx + dy * dy)
62
+ if length == 0:
63
+ return
64
+ nx = -dy / length
65
+ ny = dx / length
66
+ half = thickness / 2
67
+ p1a = (p1[0] + nx * half, p1[1] + ny * half)
68
+ p1b = (p1[0] - nx * half, p1[1] - ny * half)
69
+ p2a = (p2[0] + nx * half, p2[1] + ny * half)
70
+ p2b = (p2[0] - nx * half, p2[1] - ny * half)
71
+ msp.add_line(p1a, p2a, dxfattribs={"layer": layer})
72
+ msp.add_line(p1b, p2b, dxfattribs={"layer": layer})
73
+ msp.add_line(p1a, p1b, dxfattribs={"layer": layer})
74
+ msp.add_line(p2a, p2b, dxfattribs={"layer": layer})
75
+
76
+
77
+ def add_door(msp, center, width=900):
78
+ half = width / 2
79
+ msp.add_line((center[0] - half, center[1]),
80
+ (center[0] + half, center[1]),
81
+ dxfattribs={"layer": "CAD-A-DOOR"})
82
+ msp.add_arc(center=(center[0] - half, center[1]), radius=width,
83
+ start_angle=0, end_angle=90,
84
+ dxfattribs={"layer": "CAD-A-DOOR"})
85
+
86
+
87
+ def add_window(msp, p1, p2):
88
+ import math
89
+ dx = p2[0] - p1[0]
90
+ dy = p2[1] - p1[1]
91
+ length = math.sqrt(dx * dx + dy * dy)
92
+ if length == 0:
93
+ return
94
+ nx = -dy / length
95
+ ny = dx / length
96
+ offset = 30
97
+ p1a = (p1[0] + nx * offset, p1[1] + ny * offset)
98
+ p1b = (p1[0] - nx * offset, p1[1] - ny * offset)
99
+ p2a = (p2[0] + nx * offset, p2[1] + ny * offset)
100
+ p2b = (p2[0] - nx * offset, p2[1] - ny * offset)
101
+ msp.add_line(p1a, p2a, dxfattribs={"layer": "CAD-A-WIND"})
102
+ msp.add_line(p1b, p2b, dxfattribs={"layer": "CAD-A-WIND"})
103
+ msp.add_line(p1a, p1b, dxfattribs={"layer": "CAD-A-WIND"})
104
+ msp.add_line(p2a, p2b, dxfattribs={"layer": "CAD-A-WIND"})
105
+
106
+
107
+ def add_room_label(msp, center, name, area):
108
+ msp.add_text(name, dxfattribs={"layer": "CAD-A-TEXT", "height": 200}) \
109
+ .set_placement(center, align=TextEntityAlignment.MIDDLE_CENTER)
110
+ msp.add_text(f"{area:.1f} m²", dxfattribs={"layer": "CAD-A-TEXT", "height": 150}) \
111
+ .set_placement((center[0], center[1] - 250),
112
+ align=TextEntityAlignment.MIDDLE_CENTER)
113
+
114
+
115
+ def add_camino(msp, center, w=1800, d=1200):
116
+ """Draw a fireplace symbol · stone monumental on a wall."""
117
+ x, y = center
118
+ pts = [(x - w/2, y), (x + w/2, y), (x + w/2, y + d),
119
+ (x + w/2 - 200, y + d), (x + w/2 - 200, y + 200),
120
+ (x - w/2 + 200, y + 200), (x - w/2 + 200, y + d),
121
+ (x - w/2, y + d), (x - w/2, y)]
122
+ msp.add_lwpolyline(pts, dxfattribs={"layer": "CAD-A-SYMB"}, close=False)
123
+ msp.add_text("CAMINO", dxfattribs={"layer": "CAD-A-SYMB", "height": 120}) \
124
+ .set_placement((x, y + d/2), align=TextEntityAlignment.MIDDLE_CENTER)
125
+
126
+
127
+ def add_cartiglio(msp, origin):
128
+ w, h = 18000, 5000
129
+ x0, y0 = origin
130
+ msp.add_lwpolyline(
131
+ [(x0, y0), (x0 + w, y0), (x0 + w, y0 + h), (x0, y0 + h), (x0, y0)],
132
+ dxfattribs={"layer": "CAD-A-CART"}, close=True,
133
+ )
134
+ msp.add_line((x0 + 6000, y0), (x0 + 6000, y0 + h), dxfattribs={"layer": "CAD-A-CART"})
135
+ msp.add_line((x0 + 12000, y0), (x0 + 12000, y0 + h), dxfattribs={"layer": "CAD-A-CART"})
136
+ msp.add_line((x0, y0 + 2500), (x0 + 18000, y0 + 2500), dxfattribs={"layer": "CAD-A-CART"})
137
+
138
+ fields = [
139
+ ("Progetto:", x0 + 200, y0 + 4500, 200),
140
+ ("Casa Colonica Belvedere · Recupero rurale", x0 + 200, y0 + 4200, 250),
141
+ ("Cliente: Lorenzo Ferri & Alessandra Conti", x0 + 200, y0 + 3700, 200),
142
+ ("Indirizzo: Loc. Belvedere 12, Greve in Chianti (FI)", x0 + 200, y0 + 3400, 200),
143
+ ("Architetto:", x0 + 6200, y0 + 4500, 200),
144
+ ("Pablo Ruan · Ord. Arch. Firenze", x0 + 6200, y0 + 4200, 200),
145
+ ("Tavola:", x0 + 12200, y0 + 4500, 200),
146
+ ("01 · Pianta Stato Attuale · Piano Terra", x0 + 12200, y0 + 4200, 250),
147
+ ("Scala 1:50 · Data 25/04/2026 · Rev. 0", x0 + 12200, y0 + 3700, 200),
148
+ ("Formato A1 (UNI ISO 5457)", x0 + 200, y0 + 1500, 200),
149
+ ("Fase: progetto preliminare", x0 + 6200, y0 + 1500, 200),
150
+ ("File: stato-attuale.dxf", x0 + 12200, y0 + 1500, 200),
151
+ ]
152
+ for txt, x, y, h_text in fields:
153
+ msp.add_text(txt, dxfattribs={"layer": "CAD-A-CART", "height": h_text}) \
154
+ .set_placement((x, y), align=TextEntityAlignment.LEFT)
155
+
156
+
157
+ def build_chianti_dxf(output_path: Path) -> dict:
158
+ """
159
+ Build Casa Colonica Chianti DXF · piano terra.
160
+
161
+ Layout (mm, origin bottom-left):
162
+ - Corpo principale: 16000 × 14000 mm = 224 m² lordi (ground floor)
163
+ - Annesso ex-stalla: 10000 × 6000 mm = 60 m² (offset right by 21000)
164
+ - Perimeter walls 500mm (stone), internal 150mm
165
+ """
166
+ doc = ezdxf.new(dxfversion="R2018", setup=True)
167
+ setup_layers(doc)
168
+ setup_dim_style(doc)
169
+ msp = doc.modelspace()
170
+
171
+ # ----------------------------------------------------------------
172
+ # CORPO PRINCIPALE · perimeter walls 50cm thick
173
+ # ----------------------------------------------------------------
174
+ P = 500
175
+ add_wall(msp, (0, 0), (16000, 0), layer="CAD-A-WALL-EXT", thickness=P)
176
+ add_wall(msp, (16000, 0), (16000, 14000), layer="CAD-A-WALL-EXT", thickness=P)
177
+ add_wall(msp, (16000, 14000), (0, 14000), layer="CAD-A-WALL-EXT", thickness=P)
178
+ add_wall(msp, (0, 14000), (0, 0), layer="CAD-A-WALL-EXT", thickness=P)
179
+
180
+ # ----------------------------------------------------------------
181
+ # Internal partitions (15cm)
182
+ # ----------------------------------------------------------------
183
+ T = 150
184
+
185
+ # Horizontal mid-divider (at y=7500): south = bagno+disimp+cantina, north = cucina+sala+sogg
186
+ add_wall(msp, (0, 7500), (16000, 7500), thickness=T)
187
+
188
+ # Vertical dividers south side
189
+ add_wall(msp, (5500, 0), (5500, 7500), thickness=T)
190
+ add_wall(msp, (10500, 0), (10500, 7500), thickness=T)
191
+
192
+ # Vertical dividers north side
193
+ add_wall(msp, (5500, 7500), (5500, 14000), thickness=T)
194
+ add_wall(msp, (10500, 7500), (10500, 14000), thickness=T)
195
+
196
+ # Bagno separation inside south-west room
197
+ add_wall(msp, (0, 3500), (3000, 3500), thickness=T)
198
+ add_wall(msp, (3000, 0), (3000, 3500), thickness=T)
199
+
200
+ # ----------------------------------------------------------------
201
+ # Doors (centroid + width)
202
+ # ----------------------------------------------------------------
203
+ add_door(msp, (2750, 0), width=1100) # ingresso principale (sud)
204
+ add_door(msp, (5500, 5000), width=900) # ingresso → disimpegno
205
+ add_door(msp, (8000, 7500), width=900) # disimpegno → sala pranzo
206
+ add_door(msp, (3500, 7500), width=900) # disimpegno → cucina (NS)
207
+ add_door(msp, (13500, 7500), width=900) # cantina → soggiorno
208
+ add_door(msp, (10500, 11000), width=900) # sala pranzo → soggiorno
209
+ add_door(msp, (5500, 10500), width=900) # cucina → sala pranzo
210
+ add_door(msp, (1500, 3500), width=800) # disimp → bagno
211
+ add_door(msp, (3000, 1500), width=800) # ingresso → bagno
212
+
213
+ # ----------------------------------------------------------------
214
+ # Windows (perimeter)
215
+ # ----------------------------------------------------------------
216
+ # Cucina (nord)
217
+ add_window(msp, (1000, 14000), (3000, 14000))
218
+ # Sala pranzo (nord)
219
+ add_window(msp, (6500, 14000), (8000, 14000))
220
+ add_window(msp, (8500, 14000), (10000, 14000))
221
+ # Soggiorno (nord)
222
+ add_window(msp, (12000, 14000), (14000, 14000))
223
+ # Soggiorno (est)
224
+ add_window(msp, (16000, 9500), (16000, 12000))
225
+ # Cantina (est)
226
+ add_window(msp, (16000, 2500), (16000, 5000))
227
+ # Cantina (sud)
228
+ add_window(msp, (12000, 0), (14000, 0))
229
+ # Cucina (ovest)
230
+ add_window(msp, (0, 11000), (0, 13000))
231
+ # Bagno (ovest)
232
+ add_window(msp, (0, 2000), (0, 3000))
233
+
234
+ # ----------------------------------------------------------------
235
+ # Camino monumentale · centrale parete nord cucina
236
+ # ----------------------------------------------------------------
237
+ add_camino(msp, center=(2750, 13800), w=1800, d=1200)
238
+
239
+ # Camino piccolo · soggiorno (parete est)
240
+ add_camino(msp, center=(15800, 11500), w=1200, d=900)
241
+
242
+ # ----------------------------------------------------------------
243
+ # Room labels · CORPO PRINCIPALE
244
+ # ----------------------------------------------------------------
245
+ rooms_main = [
246
+ ("INGRESSO + BAGNO", (4250, 1800), 22.0),
247
+ ("DISIMPEGNO", (8000, 3700), 28.0),
248
+ ("CANTINA VINIFIC.", (13250, 3700), 32.0),
249
+ ("CUCINA", (2750, 10500), 28.0),
250
+ ("SALA DA PRANZO", (8000, 10500), 32.0),
251
+ ("SOGGIORNO", (13250, 10500), 30.0),
252
+ ]
253
+ for name, center, area in rooms_main:
254
+ add_room_label(msp, center, name, area)
255
+
256
+ # ----------------------------------------------------------------
257
+ # ANNESSO EX-STALLA · offset 5000mm a destra
258
+ # ----------------------------------------------------------------
259
+ OX, OY = 21000, 4000
260
+ PA = 500 # stone perimeter
261
+ add_wall(msp, (OX, OY), (OX + 10000, OY), layer="CAD-A-WALL-EXT", thickness=PA)
262
+ add_wall(msp, (OX + 10000, OY), (OX + 10000, OY + 6000), layer="CAD-A-WALL-EXT", thickness=PA)
263
+ add_wall(msp, (OX + 10000, OY + 6000), (OX, OY + 6000), layer="CAD-A-WALL-EXT", thickness=PA)
264
+ add_wall(msp, (OX, OY + 6000), (OX, OY), layer="CAD-A-WALL-EXT", thickness=PA)
265
+
266
+ # 1 simple partition · ex-stalla aperta
267
+ add_wall(msp, (OX + 6500, OY), (OX + 6500, OY + 6000), thickness=T)
268
+
269
+ # 1 main door (sud)
270
+ add_door(msp, (OX + 3000, OY), width=1500)
271
+ # piccolo wc con porta
272
+ add_door(msp, (OX + 6500, OY + 3000), width=800)
273
+
274
+ # finestre annesso
275
+ add_window(msp, (OX + 1000, OY + 6000), (OX + 2500, OY + 6000)) # nord
276
+ add_window(msp, (OX + 5000, OY + 6000), (OX + 6000, OY + 6000)) # nord
277
+ add_window(msp, (OX, OY + 2500), (OX, OY + 4000)) # ovest
278
+ add_window(msp, (OX + 10000, OY + 4500), (OX + 10000, OY + 5500)) # est
279
+
280
+ rooms_annex = [
281
+ ("ANNESSO · EX-STALLA", (OX + 3250, OY + 3000), 39.0),
282
+ ("WC SERVIZIO", (OX + 8250, OY + 3000), 21.0),
283
+ ]
284
+ for name, center, area in rooms_annex:
285
+ add_room_label(msp, center, name, area)
286
+
287
+ # Mangiatoie originali in pietra (ovest del annesso)
288
+ msp.add_lwpolyline(
289
+ [(OX + 200, OY + 1000), (OX + 5500, OY + 1000),
290
+ (OX + 5500, OY + 1700), (OX + 200, OY + 1700),
291
+ (OX + 200, OY + 1000)],
292
+ dxfattribs={"layer": "CAD-A-FURN"}, close=True,
293
+ )
294
+ msp.add_text("MANGIATOIE ORIGINALI · pietra serena · da preservare",
295
+ dxfattribs={"layer": "CAD-A-TEXT", "height": 100}) \
296
+ .set_placement((OX + 2850, OY + 1350),
297
+ align=TextEntityAlignment.MIDDLE_CENTER)
298
+
299
+ # ----------------------------------------------------------------
300
+ # Dimensions principali (perimeter corpo principale)
301
+ # ----------------------------------------------------------------
302
+ msp.add_linear_dim(base=(0, -800), p1=(0, 0), p2=(16000, 0),
303
+ dimstyle="UNI_DIM", text="1600").render()
304
+ msp.add_linear_dim(base=(0, 14800), p1=(0, 14000), p2=(16000, 14000),
305
+ dimstyle="UNI_DIM", text="1600").render()
306
+ msp.add_linear_dim(base=(-800, 0), p1=(0, 0), p2=(0, 14000),
307
+ angle=90, dimstyle="UNI_DIM", text="1400").render()
308
+
309
+ # Annesso dim
310
+ msp.add_linear_dim(base=(OX, OY - 800), p1=(OX, OY), p2=(OX + 10000, OY),
311
+ dimstyle="UNI_DIM", text="1000").render()
312
+ msp.add_linear_dim(base=(OX - 800, OY), p1=(OX, OY), p2=(OX, OY + 6000),
313
+ angle=90, dimstyle="UNI_DIM", text="600").render()
314
+
315
+ # ----------------------------------------------------------------
316
+ # Cartiglio
317
+ # ----------------------------------------------------------------
318
+ add_cartiglio(msp, (-1000, -8000))
319
+
320
+ # ----------------------------------------------------------------
321
+ # Title text above plan
322
+ # ----------------------------------------------------------------
323
+ msp.add_text("CASA COLONICA BELVEDERE · PIANTA STATO ATTUALE PIANO TERRA",
324
+ dxfattribs={"layer": "CAD-A-TEXT", "height": 350}) \
325
+ .set_placement((8000, 15500), align=TextEntityAlignment.MIDDLE_CENTER)
326
+ msp.add_text("ANNESSO · EX-STALLA",
327
+ dxfattribs={"layer": "CAD-A-TEXT", "height": 250}) \
328
+ .set_placement((26000, 11000), align=TextEntityAlignment.MIDDLE_CENTER)
329
+
330
+ # ----------------------------------------------------------------
331
+ # Save
332
+ # ----------------------------------------------------------------
333
+ output_path = Path(output_path)
334
+ output_path.parent.mkdir(parents=True, exist_ok=True)
335
+ doc.saveas(str(output_path))
336
+
337
+ total_lordo_main = 16000 * 14000 / 1_000_000
338
+ total_lordo_annex = 10000 * 6000 / 1_000_000
339
+ rooms_all = rooms_main + rooms_annex
340
+ total_utile = sum(area for _, _, area in rooms_all)
341
+
342
+ return {
343
+ "output_path": str(output_path),
344
+ "corpo_principale_lordo_m2": total_lordo_main,
345
+ "annesso_lordo_m2": total_lordo_annex,
346
+ "total_lordo_m2": total_lordo_main + total_lordo_annex,
347
+ "total_utile_m2": total_utile,
348
+ "muratura_m2": (total_lordo_main + total_lordo_annex) - total_utile,
349
+ "rooms_count": len(rooms_all),
350
+ "rooms": [{"name": n, "area_m2": a, "center_mm": c} for n, c, a in rooms_all],
351
+ "layers": [name for name, _, _ in LAYERS],
352
+ "dxf_version": "R2018",
353
+ }
354
+
355
+
356
+ def main():
357
+ if len(sys.argv) > 1:
358
+ output = sys.argv[1]
359
+ else:
360
+ output = str(Path(__file__).resolve().parents[1]
361
+ / "data" / "sample-input-villa-chianti" / "stato-attuale.dxf")
362
+ summary = build_chianti_dxf(Path(output))
363
+ import json
364
+ print(json.dumps(summary, indent=2, ensure_ascii=False))
365
+
366
+
367
+ if __name__ == "__main__":
368
+ main()
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ """Generate photorealistic sample images for Casa Colonica Chianti demo.
3
+
4
+ Same pattern as generate_real_sample_images.py but with rural Tuscan prompts:
5
+ - 6 stato attuale (1850 colonica, neglected/dated, recovery candidate)
6
+ - 6 reference style photos (tuscan country-modern · contemporary rural · Adario/Reschio/Miani)
7
+
8
+ Uses OpenAI gpt-image-2 via direct API.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import base64
14
+ from pathlib import Path
15
+ from openai import OpenAI
16
+
17
+ ROOT = Path(__file__).resolve().parents[1]
18
+ FOTO_DIR = ROOT / "data" / "sample-input-villa-chianti" / "foto"
19
+ PIN_DIR = ROOT / "data" / "sample-input-villa-chianti" / "pinterest-references"
20
+
21
+ client = OpenAI()
22
+
23
+ STATO_ATTUALE_PROMPTS = {
24
+ "01-facciata.jpg": (
25
+ "Photorealistic exterior photo of an 1850 Tuscan rural farmhouse (casa colonica) in the "
26
+ "Chianti Classico countryside near Greve in Chianti. Two-story stone masonry building with "
27
+ "sasso del Chianti rough-cut local stone walls, weathered terracotta tile roof with marsigliesi "
28
+ "tiles, original wooden shutters in faded green-grey, ground floor with stone arched portico "
29
+ "supported by pietra serena columns and chestnut wooden beam. A small rusted metal gate, "
30
+ "overgrown grass, an old fig tree on the right. Cypress trees and rolling hills with vineyards "
31
+ "in the background. Late afternoon golden hour Tuscan light, slight haze. Documentary "
32
+ "architectural photography, 35mm full-frame, f/8, eye-level, no people, no cars. "
33
+ "Building shows neglect: dirt streaks, missing tiles in places, weathered walls."
34
+ ),
35
+ "02-portico-ingresso.jpg": (
36
+ "Photorealistic interior photo of a Tuscan farmhouse entrance under a stone arched portico, "
37
+ "looking inward toward the front door. Original cotto antico terracotta tile floor faded "
38
+ "by 170 years of use, two stone columns in pietra serena flanking the view, chestnut wooden "
39
+ "beam overhead with small spider webs and dust. Pale lime-washed plaster walls with patches "
40
+ "of damp staining and original ochre pigment showing through. Old wooden door painted faded "
41
+ "green-grey with iron hardware, terracotta planters with dead plants. A simple iron lantern "
42
+ "on the wall, dated electrical conduit running along the wall (clearly 1980s), faded straw "
43
+ "broom leaning. Documentary photography, soft afternoon light filtering through the arch, "
44
+ "24mm wide angle, f/5.6. Lived-in but neglected, awaiting restoration."
45
+ ),
46
+ "03-cucina-rustica.jpg": (
47
+ "Photorealistic interior of a 1850 Tuscan farmhouse kitchen, original but dated. Massive "
48
+ "stone fireplace (camino monumentale) on the back wall measuring about 180 by 120 cm with "
49
+ "blackened stone hearth, original cotto antico terracotta floor tiles uneven and worn, "
50
+ "exposed chestnut wooden ceiling beams stained dark with age and smoke, lime-washed walls "
51
+ "in faded ochre with cracks and damp patches. Terrible 1980s additions: a cheap white "
52
+ "melamine kitchen unit with brown laminate doors against the right wall, a yellowed "
53
+ "Smeg-style fridge from the 90s, a generic stainless steel sink on a brick base, an old "
54
+ "rusty wood stove (stufa a legna) connected to the chimney, dated rectangular fluorescent "
55
+ "ceiling light tube. Hanging copper pots covered in dust. Single small window with green "
56
+ "shutters partially open. Real-estate photography, neutral indoor light, 24mm wide, dated "
57
+ "and cluttered, documentary."
58
+ ),
59
+ "04-sala-pranzo.jpg": (
60
+ "Photorealistic interior of an 1850 Tuscan farmhouse dining room, large rectangular space. "
61
+ "Original wide-plank cotto antico terracotta floor uneven and faded with patches of "
62
+ "darker tiles where furniture sat, exposed chestnut wood ceiling beams (5 main visible "
63
+ "running parallel) with smaller joists, original lime-washed walls with peeling paint "
64
+ "showing previous ochre layers. A simple long pine farmhouse table for 8 with mismatched "
65
+ "wooden chairs, dated 1980s pendant light with pleated fabric shade, faded blue-and-white "
66
+ "ceramic plates on a tall walnut sideboard from grandmother era. A small old fireplace on "
67
+ "the side wall, terracotta tile fireplace surround. Two tall double-shutter windows on the "
68
+ "left wall with original wooden internal shutters, looking out toward the vineyard. "
69
+ "Real-estate photography, soft afternoon light filtering through, 35mm, f/4. Lived-in, "
70
+ "outdated, mediocre 1985 partial intervention visible."
71
+ ),
72
+ "05-camera-piano-primo.jpg": (
73
+ "Photorealistic Italian Tuscan farmhouse bedroom on the upper floor (piano primo), about "
74
+ "14 sqm with a sloped ceiling exposing original chestnut wood beams and the underside of "
75
+ "tiled roof (correntini visible). Faded cream lime-washed walls with damp stain patches "
76
+ "near the corner, original cotto antico floor with some tiles cracked and replaced with "
77
+ "darker non-matching pieces. A simple iron-frame double bed from grandmother's era with "
78
+ "white linens, an old wooden wardrobe with mirror, plain wooden chair, a bedside table "
79
+ "with a 1980s lamp. Single small window with green shutters open showing distant Tuscan "
80
+ "hills. No insulation visible, no heating present. Real estate photography, soft natural "
81
+ "light, 24mm wide, simple and modest, dated, documentary."
82
+ ),
83
+ "06-stalla-annesso.jpg": (
84
+ "Photorealistic interior of a Tuscan farmhouse annex that was originally a stable (ex-"
85
+ "stalla) from 1930s, currently disused. Stone walls (sasso del Chianti) partially covered "
86
+ "in old whitewash that's flaking off, exposed wooden ceiling beams much rougher than the "
87
+ "main house, dirt-stained terracotta floor with spots of straw and dust, original stone "
88
+ "feeding troughs (mangiatoie) along the right wall about 1.2m high, an iron tie ring still "
89
+ "embedded in the wall. The space is open and rectangular about 10 by 6 meters with one "
90
+ "main door at the front and a small high window letting in dusty afternoon light. Cobwebs, "
91
+ "an old pitchfork leaning, scattered tools and broken terracotta pots, traces of past "
92
+ "agricultural use. Documentary photography, 24mm wide angle, mixed natural and dim "
93
+ "ambient light, abandoned but restorable, raw and authentic Tuscan rural heritage."
94
+ ),
95
+ }
96
+
97
+ PINTEREST_REFS_PROMPTS = {
98
+ "pin-01-adario-tuscan.jpg": (
99
+ "Photorealistic interior of a contemporary Tuscan farmhouse renovation in Massimo Adario "
100
+ "style, rural country-modern aesthetic. Original cotto antico terracotta floor restored "
101
+ "and gently polished, lime-washed walls in pale ocra siena warm tone, exposed chestnut "
102
+ "ceiling beams treated naturally with linseed oil, large open living area with low-profile "
103
+ "linen cream sofa, sculptural ceramic side table in unglazed terracotta, vintage Persian "
104
+ "kilim runner in earthy reds and ochres, cast iron wood-burning stove (modern but classic "
105
+ "shape) in the corner. Floor-to-ceiling tall window with simple linen curtains showing "
106
+ "olive trees outside. Material honesty, refined rural sophistication, no kitsch. Editorial "
107
+ "interior photography, 35mm f/4, warm afternoon light."
108
+ ),
109
+ "pin-02-pierattelli-recupero.jpg": (
110
+ "Photorealistic Tuscan farmhouse kitchen interior, contemporary recovery in Pierattelli "
111
+ "Architetture style. Massive original stone fireplace preserved as central architectural "
112
+ "feature, restored cotto antico floor in warm orange-brown tones, exposed dark chestnut "
113
+ "ceiling beams with original patina, walls in raw lime plaster with subtle terra di Siena "
114
+ "pigment. Large central island with thick honed pietra serena countertop, light oak shaker-"
115
+ "style cabinets with brushed brass hardware, professional-grade range with brass details, "
116
+ "linear pendant lights with pale fabric shades. A handmade wooden farmhouse table for "
117
+ "10 in the foreground with linen runner, ceramic bowls, fresh herbs. Tall French window "
118
+ "with iron muntins, view onto vineyard. Editorial photography 35mm f/4, soft daylight."
119
+ ),
120
+ "pin-03-miani-valdorcia.jpg": (
121
+ "Photorealistic Tuscan dining room in Ilaria Miani style for a Val d'Orcia rural "
122
+ "renovation. Walls in vibrant restored terra rossa earthy red lime plaster (slightly "
123
+ "uneven, hand-applied), original cotto antico floor with red-orange warm tones, chestnut "
124
+ "ceiling with closely-spaced wood beams. A long oak farmhouse dining table with mismatched "
125
+ "antique wooden chairs (some painted faded blue, some natural wood), simple linen napkins "
126
+ "and earthenware ceramic plates, brass candlesticks. A 17th-century Tuscan painting on the "
127
+ "wall, simple iron pendant light hanging low. Two tall windows with sheer linen curtains "
128
+ "showing rolling Tuscan hills and cypress trees. Lived-in elegance, country aristocracy. "
129
+ "Editorial photography 35mm, warm golden hour light, f/4."
130
+ ),
131
+ "pin-04-reschio-foresteria.jpg": (
132
+ "Photorealistic guest suite at a Castello di Reschio-style luxury renovation, Umbrian-"
133
+ "Tuscan rural luxury. Beautiful canopy bed in dark stained oak with white linen draping, "
134
+ "original cotto antico floor restored, walls in pale dusty rose lime plaster, exposed "
135
+ "wooden ceiling beams with darker patina. Vintage Persian rug in muted terracotta and "
136
+ "indigo tones, a comfortable plush armchair in cream linen, brass nightstand with marble "
137
+ "top, fluted brass wall sconces casting warm light, antique gilt-framed mirror. Tall French "
138
+ "windows with internal wooden shutters open to a stone-arched balcony with view over "
139
+ "vineyards. Refined yet rustic, monastic luxury. Editorial interior photography, soft "
140
+ "late afternoon light filtering, 35mm f/4."
141
+ ),
142
+ "pin-05-cornue-cucina-pro.jpg": (
143
+ "Photorealistic professional residential kitchen with La Cornue / Officine Gullo style "
144
+ "range cooker as centerpiece, restaurant-quality appointed for a serious home cook. "
145
+ "A massive 1.5m wide red enamel and brass La Cornue range with multiple ovens, integrated "
146
+ "gas and induction surfaces. Surround in light oak cabinets with brushed brass handles, "
147
+ "thick honed Carrara marble countertops, large central island with extra prep space. "
148
+ "Pietra serena floor, pale plaster walls. Hanging copper pots from a ceiling rack, fresh "
149
+ "herbs in small terracotta pots, professional knives on magnetic strip, 2 stainless steel "
150
+ "sinks, integrated wine fridge in vertical cabinet. Tall window providing natural light. "
151
+ "Style: chef's residential kitchen elegant and functional. Editorial 35mm f/5.6."
152
+ ),
153
+ "pin-06-bolle-lab-rurale.jpg": (
154
+ "Photorealistic rural Tuscan farmhouse master bathroom in Bolle Lab style contemporary "
155
+ "intervention. Floor in pietra serena tiles with bocciardata textured finish, walls in "
156
+ "raw lime plaster with subtle terra di Siena pigment, ceiling with restored chestnut "
157
+ "wooden beams. A freestanding cast-iron clawfoot bathtub in the center with brushed brass "
158
+ "fixtures, a walk-in shower in the corner with frameless glass and pietra serena lining, "
159
+ "twin sinks with handcrafted ceramic basins on a thick wooden vanity, brushed brass tap "
160
+ "fixtures. A tall vertical window in stone surround providing view onto landscape, "
161
+ "linen curtain. Vintage Persian runner, white linen towels rolled, a small bench in "
162
+ "weathered wood. Spa-like serene atmosphere, masculine-feminine balance. Editorial "
163
+ "photography, soft natural daylight, 35mm f/4."
164
+ ),
165
+ }
166
+
167
+
168
+ OPENAI_IMAGE_MODEL = os.environ.get("OPENAI_IMAGE_MODEL", "gpt-image-2")
169
+
170
+
171
+ def gen_image(prompt: str, out_path: Path, size: str = "1024x1024") -> bool:
172
+ """Generate one image via OpenAI gpt-image-2."""
173
+ print(f" → generating {out_path.name} via {OPENAI_IMAGE_MODEL} ({len(prompt)} chars prompt)...", flush=True)
174
+ try:
175
+ resp = client.images.generate(
176
+ model=OPENAI_IMAGE_MODEL,
177
+ prompt=prompt,
178
+ size=size,
179
+ quality="high",
180
+ n=1,
181
+ )
182
+ b64 = resp.data[0].b64_json
183
+ img_bytes = base64.b64decode(b64)
184
+ out_path.write_bytes(img_bytes)
185
+ kb = len(img_bytes) // 1024
186
+ print(f" ✓ saved {out_path.name} · {kb} KB", flush=True)
187
+ return True
188
+ except Exception as e:
189
+ print(f" ✗ FAILED {out_path.name}: {e}", flush=True)
190
+ return False
191
+
192
+
193
+ def main():
194
+ target = sys.argv[1] if len(sys.argv) > 1 else "all"
195
+
196
+ success = 0
197
+ total = 0
198
+
199
+ if target in ("foto", "all"):
200
+ print("\n=== Stato attuale (1850 colonica · neglected) ===")
201
+ FOTO_DIR.mkdir(parents=True, exist_ok=True)
202
+ for name, prompt in STATO_ATTUALE_PROMPTS.items():
203
+ total += 1
204
+ if gen_image(prompt, FOTO_DIR / name):
205
+ success += 1
206
+
207
+ if target in ("pinterest", "all"):
208
+ print("\n=== Pinterest references (tuscan country-modern) ===")
209
+ PIN_DIR.mkdir(parents=True, exist_ok=True)
210
+ for name, prompt in PINTEREST_REFS_PROMPTS.items():
211
+ total += 1
212
+ if gen_image(prompt, PIN_DIR / name):
213
+ success += 1
214
+
215
+ print(f"\n{'='*60}")
216
+ print(f"Done · {success}/{total} images generated")
217
+ print(f" foto/ → {FOTO_DIR}")
218
+ print(f" pinterest/ → {PIN_DIR}")
219
+ return 0 if success == total else 1
220
+
221
+
222
+ if __name__ == "__main__":
223
+ sys.exit(main())