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,369 @@
1
+ """
2
+ Generate sample DWG for Attico Brera demo.
3
+
4
+ Creates a realistic 120 m² floor 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
+ - Walls (perimeter + internal)
7
+ - Doors and windows as blocks
8
+ - Dimensions
9
+ - Room labels
10
+ - A1 sheet with cartiglio CNAPPC
11
+
12
+ Output: stato-attuale.dxf
13
+
14
+ Usage:
15
+ python3 generate_attico_brera_dwg.py [output_path]
16
+
17
+ Default output: ./stato-attuale.dxf
18
+ """
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ try:
23
+ import ezdxf
24
+ from ezdxf.enums import TextEntityAlignment
25
+ except ImportError:
26
+ sys.stderr.write(
27
+ "ERROR: ezdxf not installed. Run: pip install ezdxf\n"
28
+ )
29
+ sys.exit(1)
30
+
31
+
32
+ # Layer specifications (UNI ISO standard)
33
+ LAYERS = [
34
+ ("CAD-A-WALL", 7, "Continuous"), # 7 = white
35
+ ("CAD-A-WALL-EXT", 1, "Continuous"), # 1 = red (perimeter)
36
+ ("CAD-A-DOOR", 4, "Continuous"), # 4 = cyan
37
+ ("CAD-A-WIND", 5, "Continuous"), # 5 = blue
38
+ ("CAD-A-DIM", 3, "Continuous"), # 3 = green
39
+ ("CAD-A-TEXT", 2, "Continuous"), # 2 = yellow
40
+ ("CAD-A-SYMB", 6, "Continuous"), # 6 = magenta
41
+ ("CAD-A-FURN", 8, "Continuous"), # 8 = grey
42
+ ("CAD-A-CART", 7, "Continuous"), # cartiglio
43
+ ]
44
+
45
+
46
+ def setup_layers(doc):
47
+ """Create all required layers."""
48
+ for name, color, linetype in LAYERS:
49
+ if name not in doc.layers:
50
+ doc.layers.add(name=name, color=color, linetype=linetype)
51
+
52
+
53
+ def setup_dim_style(doc):
54
+ """Create UNI dimension style with mm precision."""
55
+ if "UNI_DIM" not in doc.dimstyles:
56
+ dim_style = doc.dimstyles.new("UNI_DIM")
57
+ dim_style.dxf.dimtxt = 25 # text height (mm in plot 1:50 = 2.5mm real)
58
+ dim_style.dxf.dimasz = 25 # arrow size
59
+ dim_style.dxf.dimexe = 12 # extension line beyond dim
60
+ dim_style.dxf.dimexo = 12 # extension line offset
61
+ dim_style.dxf.dimdec = 0 # 0 decimals (cm)
62
+ dim_style.dxf.dimlfac = 0.1 # scale factor: mm → cm display
63
+
64
+
65
+ def add_wall(msp, p1, p2, *, layer="CAD-A-WALL", thickness=80):
66
+ """
67
+ Add a wall as 2 parallel lines (thickness mm).
68
+ p1, p2 are wall axis (in mm).
69
+ Wall thickness perpendicular to axis.
70
+ """
71
+ import math
72
+ dx = p2[0] - p1[0]
73
+ dy = p2[1] - p1[1]
74
+ length = math.sqrt(dx * dx + dy * dy)
75
+ if length == 0:
76
+ return
77
+ # perpendicular unit vector
78
+ nx = -dy / length
79
+ ny = dx / length
80
+ half = thickness / 2
81
+
82
+ p1a = (p1[0] + nx * half, p1[1] + ny * half)
83
+ p1b = (p1[0] - nx * half, p1[1] - ny * half)
84
+ p2a = (p2[0] + nx * half, p2[1] + ny * half)
85
+ p2b = (p2[0] - nx * half, p2[1] - ny * half)
86
+
87
+ # 4 lines forming a closed thick wall
88
+ msp.add_line(p1a, p2a, dxfattribs={"layer": layer})
89
+ msp.add_line(p1b, p2b, dxfattribs={"layer": layer})
90
+ msp.add_line(p1a, p1b, dxfattribs={"layer": layer})
91
+ msp.add_line(p2a, p2b, dxfattribs={"layer": layer})
92
+
93
+
94
+ def add_door(msp, center, width=900, swing="right"):
95
+ """Add a simple door symbol."""
96
+ half = width / 2
97
+ msp.add_line(
98
+ (center[0] - half, center[1]),
99
+ (center[0] + half, center[1]),
100
+ dxfattribs={"layer": "CAD-A-DOOR"},
101
+ )
102
+ # arc representing swing
103
+ msp.add_arc(
104
+ center=(center[0] - half, center[1]),
105
+ radius=width,
106
+ start_angle=0,
107
+ end_angle=90,
108
+ dxfattribs={"layer": "CAD-A-DOOR"},
109
+ )
110
+
111
+
112
+ def add_window(msp, p1, p2):
113
+ """Add a window symbol (double line)."""
114
+ import math
115
+ dx = p2[0] - p1[0]
116
+ dy = p2[1] - p1[1]
117
+ length = math.sqrt(dx * dx + dy * dy)
118
+ if length == 0:
119
+ return
120
+ nx = -dy / length
121
+ ny = dx / length
122
+ offset = 30 # mm
123
+
124
+ p1a = (p1[0] + nx * offset, p1[1] + ny * offset)
125
+ p1b = (p1[0] - nx * offset, p1[1] - ny * offset)
126
+ p2a = (p2[0] + nx * offset, p2[1] + ny * offset)
127
+ p2b = (p2[0] - nx * offset, p2[1] - ny * offset)
128
+
129
+ msp.add_line(p1a, p2a, dxfattribs={"layer": "CAD-A-WIND"})
130
+ msp.add_line(p1b, p2b, dxfattribs={"layer": "CAD-A-WIND"})
131
+ msp.add_line(p1a, p1b, dxfattribs={"layer": "CAD-A-WIND"})
132
+ msp.add_line(p2a, p2b, dxfattribs={"layer": "CAD-A-WIND"})
133
+
134
+
135
+ def add_room_label(msp, center, name, area):
136
+ """Add room label with name and area."""
137
+ msp.add_text(
138
+ name,
139
+ dxfattribs={"layer": "CAD-A-TEXT", "height": 200},
140
+ ).set_placement(center, align=TextEntityAlignment.MIDDLE_CENTER)
141
+ msp.add_text(
142
+ f"{area:.1f} m²",
143
+ dxfattribs={"layer": "CAD-A-TEXT", "height": 150},
144
+ ).set_placement(
145
+ (center[0], center[1] - 250),
146
+ align=TextEntityAlignment.MIDDLE_CENTER,
147
+ )
148
+
149
+
150
+ def add_dimension_h(msp, p1, p2, offset_y, text_override=None):
151
+ """Add horizontal linear dimension."""
152
+ dim = msp.add_linear_dim(
153
+ base=(p1[0], p1[1] + offset_y),
154
+ p1=p1,
155
+ p2=p2,
156
+ dimstyle="UNI_DIM",
157
+ text=text_override,
158
+ )
159
+ dim.render()
160
+
161
+
162
+ def add_cartiglio(msp, origin):
163
+ """Add CNAPPC-style cartiglio (title block) at given origin."""
164
+ # Outer frame: 180 × 50 mm in plot scale
165
+ w, h = 18000, 5000 # in mm at scale 1:1
166
+ x0, y0 = origin
167
+
168
+ # Frame
169
+ msp.add_lwpolyline(
170
+ [
171
+ (x0, y0),
172
+ (x0 + w, y0),
173
+ (x0 + w, y0 + h),
174
+ (x0, y0 + h),
175
+ (x0, y0),
176
+ ],
177
+ dxfattribs={"layer": "CAD-A-CART"},
178
+ close=True,
179
+ )
180
+ # Internal divisions
181
+ msp.add_line((x0 + 6000, y0), (x0 + 6000, y0 + h), dxfattribs={"layer": "CAD-A-CART"})
182
+ msp.add_line((x0 + 12000, y0), (x0 + 12000, y0 + h), dxfattribs={"layer": "CAD-A-CART"})
183
+ msp.add_line((x0, y0 + 2500), (x0 + 18000, y0 + 2500), dxfattribs={"layer": "CAD-A-CART"})
184
+
185
+ # Texts
186
+ fields = [
187
+ ("Progetto:", x0 + 200, y0 + 4500, 200),
188
+ ("Attico Brera · Ristrutturazione interna", x0 + 200, y0 + 4200, 250),
189
+ ("Cliente: Marco Rossini & Giulia Bianchi", x0 + 200, y0 + 3700, 200),
190
+ ("Indirizzo: Via Fiori Chiari 17, Milano", x0 + 200, y0 + 3400, 200),
191
+ ("Architetto:", x0 + 6200, y0 + 4500, 200),
192
+ ("Pablo Ruan · Ord. Arch. Milano", x0 + 6200, y0 + 4200, 200),
193
+ ("Tavola:", x0 + 12200, y0 + 4500, 200),
194
+ ("01 · Pianta Stato Attuale", x0 + 12200, y0 + 4200, 250),
195
+ ("Scala 1:50 · Data 25/04/2026 · Rev. 0", x0 + 12200, y0 + 3700, 200),
196
+ ("Formato A1 (UNI ISO 5457)", x0 + 200, y0 + 1500, 200),
197
+ ("Fase: progetto definitivo", x0 + 6200, y0 + 1500, 200),
198
+ ("File: stato-attuale.dxf", x0 + 12200, y0 + 1500, 200),
199
+ ]
200
+ for txt, x, y, h_text in fields:
201
+ msp.add_text(
202
+ txt,
203
+ dxfattribs={"layer": "CAD-A-CART", "height": h_text},
204
+ ).set_placement((x, y), align=TextEntityAlignment.LEFT)
205
+
206
+
207
+ def build_attico_brera(output_path: Path) -> dict:
208
+ """
209
+ Build the Attico Brera 120 m² DWG.
210
+
211
+ Layout (origin at bottom-left, +X right, +Y up, mm):
212
+ - Total: 12000 × 10000 mm = 120 m²
213
+ - Walls 12cm (perimeter) and 8cm (internal partitions)
214
+
215
+ Returns:
216
+ dict with summary (rooms, areas, etc.)
217
+ """
218
+ doc = ezdxf.new(dxfversion="R2018", setup=True)
219
+ setup_layers(doc)
220
+ setup_dim_style(doc)
221
+ msp = doc.modelspace()
222
+
223
+ # ----------------------------------------------------------------------
224
+ # Perimeter walls (12 cm thick) · outer rectangle 12000 × 10000 mm
225
+ # ----------------------------------------------------------------------
226
+ P = 120 # perimeter wall thickness (mm)
227
+ add_wall(msp, (0, 0), (12000, 0), layer="CAD-A-WALL-EXT", thickness=P)
228
+ add_wall(msp, (12000, 0), (12000, 10000), layer="CAD-A-WALL-EXT", thickness=P)
229
+ add_wall(msp, (12000, 10000), (0, 10000), layer="CAD-A-WALL-EXT", thickness=P)
230
+ add_wall(msp, (0, 10000), (0, 0), layer="CAD-A-WALL-EXT", thickness=P)
231
+
232
+ # ----------------------------------------------------------------------
233
+ # Internal partitions (8 cm)
234
+ # Layout (state attuale, before redesign):
235
+ # - Ingresso (left bottom): 0-2500, 0-3000
236
+ # - Cucina (left top): 0-3500, 6000-10000
237
+ # - Soggiorno (center): 2500-7500, 0-6000
238
+ # - Camera 1: 7500-12000, 0-4000
239
+ # - Camera 2: 0-4000, 3000-6000
240
+ # - Camera 3: 7500-12000, 4000-7000
241
+ # - Bagno: 7500-10000, 7000-10000
242
+ # - Ripostiglio: 10000-12000, 7000-10000
243
+ # ----------------------------------------------------------------------
244
+ T = 80 # internal wall thickness
245
+
246
+ # Ingresso / Soggiorno divider
247
+ add_wall(msp, (2500, 0), (2500, 3000), thickness=T)
248
+ # Ingresso / Camera 2 divider
249
+ add_wall(msp, (0, 3000), (4000, 3000), thickness=T)
250
+ # Camera 2 / Cucina divider
251
+ add_wall(msp, (0, 6000), (3500, 6000), thickness=T)
252
+ add_wall(msp, (3500, 6000), (3500, 10000), thickness=T)
253
+ # Camera 2 / Soggiorno
254
+ add_wall(msp, (4000, 3000), (4000, 6000), thickness=T)
255
+ # Soggiorno / Camera 1 divider
256
+ add_wall(msp, (7500, 0), (7500, 4000), thickness=T)
257
+ add_wall(msp, (7500, 4000), (12000, 4000), thickness=T)
258
+ # Camera 3 sopra
259
+ add_wall(msp, (7500, 7000), (12000, 7000), thickness=T)
260
+ add_wall(msp, (7500, 4000), (7500, 7000), thickness=T)
261
+ # Bagno / Ripostiglio
262
+ add_wall(msp, (10000, 7000), (10000, 10000), thickness=T)
263
+ # Soggiorno / cucina (parete tramezzo anni 80)
264
+ add_wall(msp, (3500, 6000), (7500, 6000), thickness=T)
265
+
266
+ # ----------------------------------------------------------------------
267
+ # Doors (centroid + width)
268
+ # ----------------------------------------------------------------------
269
+ add_door(msp, (1250, 3000), width=800) # ingresso → camera 2
270
+ add_door(msp, (2500, 1500), width=900) # ingresso → soggiorno
271
+ add_door(msp, (5500, 6000), width=800) # soggiorno → cucina
272
+ add_door(msp, (7500, 2000), width=800) # soggiorno → camera 1
273
+ add_door(msp, (7500, 5500), width=800) # corridoio → camera 3
274
+ add_door(msp, (8750, 7000), width=800) # corridoio → bagno
275
+ add_door(msp, (10500, 7000), width=700) # corridoio → ripostiglio
276
+
277
+ # ----------------------------------------------------------------------
278
+ # Windows (perimeter)
279
+ # ----------------------------------------------------------------------
280
+ # Cucina (north)
281
+ add_window(msp, (1000, 10000), (2500, 10000))
282
+ # Cucina (north, second)
283
+ add_window(msp, (3000, 10000), (3500, 10000))
284
+ # Bagno (north)
285
+ add_window(msp, (8500, 10000), (9500, 10000))
286
+ # Camera 3 (east)
287
+ add_window(msp, (12000, 5000), (12000, 6500))
288
+ # Camera 1 (east)
289
+ add_window(msp, (12000, 1000), (12000, 3000))
290
+ # Soggiorno (south, terrace door)
291
+ add_window(msp, (3500, 0), (6500, 0))
292
+ # Camera 2 (west)
293
+ add_window(msp, (0, 4000), (0, 5500))
294
+ # Ingresso (south)
295
+ add_window(msp, (500, 0), (1500, 0))
296
+
297
+ # ----------------------------------------------------------------------
298
+ # Room labels with areas
299
+ # ----------------------------------------------------------------------
300
+ rooms = [
301
+ # (name, center, area_m2)
302
+ ("INGRESSO", (1250, 1500), 7.5),
303
+ ("CUCINA", (1750, 8000), 14.0),
304
+ ("SOGGIORNO", (5000, 3000), 30.0),
305
+ ("CAMERA 2", (2000, 4500), 12.0),
306
+ ("CAMERA 1", (9750, 2000), 18.0),
307
+ ("CAMERA 3", (9750, 5500), 13.5),
308
+ ("BAGNO", (8750, 8500), 7.5),
309
+ ("RIPOSTIGLIO", (11000, 8500), 6.0),
310
+ ]
311
+ for name, center, area in rooms:
312
+ add_room_label(msp, center, name, area)
313
+
314
+ # ----------------------------------------------------------------------
315
+ # Main dimensions (perimeter)
316
+ # ----------------------------------------------------------------------
317
+ add_dimension_h(msp, (0, 0), (12000, 0), -800, text_override="1200")
318
+ add_dimension_h(msp, (0, 10000), (12000, 10000), 800, text_override="1200")
319
+
320
+ # vertical dim left
321
+ dim_v = msp.add_linear_dim(
322
+ base=(-800, 0),
323
+ p1=(0, 0),
324
+ p2=(0, 10000),
325
+ angle=90,
326
+ dimstyle="UNI_DIM",
327
+ text="1000",
328
+ )
329
+ dim_v.render()
330
+
331
+ # ----------------------------------------------------------------------
332
+ # Cartiglio (title block)
333
+ # ----------------------------------------------------------------------
334
+ add_cartiglio(msp, (-1000, -8000))
335
+
336
+ # ----------------------------------------------------------------------
337
+ # Save
338
+ # ----------------------------------------------------------------------
339
+ output_path = Path(output_path)
340
+ output_path.parent.mkdir(parents=True, exist_ok=True)
341
+ doc.saveas(str(output_path))
342
+
343
+ # Summary
344
+ total_lordo = 12000 * 10000 / 1_000_000 # m²
345
+ total_utile = sum(area for _, _, area in rooms)
346
+ return {
347
+ "output_path": str(output_path),
348
+ "total_lordo_m2": total_lordo,
349
+ "total_utile_m2": total_utile,
350
+ "muratura_m2": total_lordo - total_utile,
351
+ "rooms_count": len(rooms),
352
+ "rooms": [
353
+ {"name": n, "area_m2": a, "center_mm": c}
354
+ for n, c, a in rooms
355
+ ],
356
+ "layers": [name for name, _, _ in LAYERS],
357
+ "dxf_version": "R2018",
358
+ }
359
+
360
+
361
+ def main():
362
+ output = sys.argv[1] if len(sys.argv) > 1 else "stato-attuale.dxf"
363
+ summary = build_attico_brera(Path(output))
364
+ import json
365
+ print(json.dumps(summary, indent=2, ensure_ascii=False))
366
+
367
+
368
+ if __name__ == "__main__":
369
+ main()