FerrellSyntheticIntelligence commited on
Commit
dde6b93
·
0 Parent(s):

Initial Vitalis DevCore 3D Blueprint Builder: NL parser + Three.js interactive 3D rendering

Browse files
README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Vitalis DevCore — 3D Blueprint Builder
3
+ emoji: 🏗️
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.19.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # Vitalis DevCore — 3D Blueprint Builder
13
+
14
+ Interactive 3D architectural visualizer. Describe a building in natural language and see it rendered as an explorable 3D model in your browser.
15
+
16
+ ## Features
17
+
18
+ - **Natural language input** — "2000 sq ft ranch house with 3 bedrooms and 2 baths"
19
+ - **Real-time 3D rendering** — Three.js in-browser, fully interactive (orbit, zoom, pan)
20
+ - **Structured parsing** — Extracts dimensions, style, stories, rooms, features
21
+ - **Live labels** — Room names rendered as floating sprites
22
+ - **No GPU required** — All rendering client-side
23
+
24
+ ## Example Prompts
25
+
26
+ - `2000 sq ft ranch house with 3 bedrooms and 2 baths`
27
+ - `modern 2-story house 30x40 with 4 bedrooms 3 bathrooms and a garage`
28
+ - `small cabin 800 sq ft with 1 bedroom and a loft`
29
+ - `victorian 3500 sq ft 5 bedroom 3 bath with office and library`
30
+ - `my house is 50 feet wide and 60 feet long with 3 bedrooms`
31
+
32
+ ## Architecture
33
+
34
+ ```
35
+ User Input → NL Parser → Blueprint JSON → Three.js HTML → Browser Render
36
+ ```
37
+
38
+ Built for the Vitalis ecosystem by FerrellSyntheticIntelligence.
app.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from devcore.parser import parse_description
3
+ from devcore.builder import description_to_blueprint
4
+
5
+ EXAMPLE_PROMPTS = [
6
+ "2000 sq ft ranch house with 3 bedrooms and 2 baths",
7
+ "modern 2-story house 30x40 with 4 bedrooms 3 bathrooms and a garage",
8
+ "small cabin 800 sq ft with 1 bedroom and a loft",
9
+ "victorian 3500 sq ft 5 bedroom 3 bath with office and library",
10
+ "colonial with 4 bedrooms and 2.5 baths",
11
+ "my house is 50 feet wide and 60 feet long with 3 bedrooms",
12
+ "30 feet by 50 feet modern house with 4 bedrooms and a deck",
13
+ ]
14
+
15
+
16
+ def build_blueprint(description):
17
+ if not description or not description.strip():
18
+ html = _render_empty("Enter a description of the building you want to visualize.")
19
+ return html, "No description provided.", ""
20
+ try:
21
+ html = description_to_blueprint(description)
22
+ data = parse_description(description)
23
+ s = data["structure"]
24
+ f = data["features"]
25
+ summary = data["summary"]
26
+ details = (
27
+ f"🏠 **{s['style'].title()}** · {f['stories']} story · "
28
+ f"{s['width']}ft × {s['depth']}ft · ~{s['sqft']} sqft\n"
29
+ f"🛏️ {f['bedrooms']} bed · 🚿 {f['bathrooms']} bath · "
30
+ f"{'🚗 Garage' if f['garage'] else 'No garage'}"
31
+ )
32
+ return html, summary, details
33
+ except Exception as e:
34
+ html = _render_empty(f"Error: {e}")
35
+ return html, f"Failed to build: {e}", ""
36
+
37
+
38
+ def _render_empty(message):
39
+ return f"""<!DOCTYPE html><html><head><style>
40
+ * {{ margin:0; padding:0; box-sizing:border-box; }}
41
+ body {{ font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
42
+ background:#0a0a1a; color:#888; display:flex; align-items:center;
43
+ justify-content:center; height:100vh; }}
44
+ .msg {{ text-align:center; padding:40px; }}
45
+ .msg h2 {{ color:#6666aa; margin-bottom:12px; }}
46
+ .msg p {{ color:#666; font-size:14px; line-height:1.6; }}
47
+ </style></head><body>
48
+ <div class="msg"><h2>🏗️ 3D Blueprint Builder</h2>
49
+ <p>{message}</p>
50
+ <p style="margin-top:20px;font-size:12px;color:#444;">Type a description above and click Build</p></div>
51
+ </body></html>"""
52
+
53
+
54
+ with gr.Blocks(title="Vitalis DevCore — 3D Blueprint Builder", theme=gr.themes.Soft(primary_hue="purple", secondary_hue="indigo")) as demo:
55
+ gr.HTML("""
56
+ <div style="text-align:center;padding:20px;background:linear-gradient(135deg,#1a0a3e,#312e81);
57
+ border-radius:12px;margin-bottom:12px;border:1px solid #7c3aed44;">
58
+ <h1 style="color:#a78bfa;margin:0;font-size:1.8em;">Vitalis DevCore — 3D Blueprint Builder</h1>
59
+ <p style="color:#c4b5fd;margin:4px 0 0;font-size:0.9em;">
60
+ Describe a building in natural language — see it rendered as an interactive 3D model
61
+ </p>
62
+ </div>
63
+ """)
64
+
65
+ with gr.Row():
66
+ with gr.Column(scale=3):
67
+ prompt = gr.Textbox(
68
+ label="Describe your building",
69
+ placeholder="e.g., 2000 sq ft ranch house with 3 bedrooms and 2 baths",
70
+ lines=2,
71
+ )
72
+ with gr.Column(scale=1):
73
+ build_btn = gr.Button("🏗️ Build Blueprint", variant="primary", size="lg", scale=1)
74
+
75
+ with gr.Row():
76
+ summary_display = gr.Markdown(label="Summary", value="Enter a description and click Build.")
77
+
78
+ with gr.Row():
79
+ with gr.Column(scale=2):
80
+ model_display = gr.HTML(label="3D Model")
81
+
82
+ with gr.Accordion("Example prompts", open=True):
83
+ example_btns = []
84
+ for ex in EXAMPLE_PROMPTS:
85
+ btn = gr.Button(ex, size="sm")
86
+ btn.click(fn=lambda e=ex: e, outputs=prompt)
87
+ example_btns.append(btn)
88
+
89
+ with gr.Accordion("Blueprint Details", open=False):
90
+ details_display = gr.Markdown(label="Details")
91
+
92
+ def on_build(desc):
93
+ html, summary, details = build_blueprint(desc)
94
+ return html, summary, details
95
+
96
+ build_btn.click(
97
+ fn=on_build,
98
+ inputs=[prompt],
99
+ outputs=[model_display, summary_display, details_display],
100
+ )
101
+ prompt.submit(
102
+ fn=on_build,
103
+ inputs=[prompt],
104
+ outputs=[model_display, summary_display, details_display],
105
+ )
106
+
107
+ gr.Markdown("---")
108
+ gr.Markdown("""
109
+ ### How it works
110
+ 1. **Parse** — Natural language description → structured blueprint data
111
+ 2. **Generate** — Blueprint → Three.js 3D scene with walls, rooms, roof, labels
112
+ 3. **Render** — Interactive model with orbit controls (drag to rotate, scroll to zoom)
113
+
114
+ *Built with Three.js · All rendering happens in your browser — no server-side graphics needed*
115
+ """)
116
+
117
+
118
+ if __name__ == "__main__":
119
+ demo.launch()
devcore/__init__.py ADDED
File without changes
devcore/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (142 Bytes). View file
 
devcore/__pycache__/builder.cpython-312.pyc ADDED
Binary file (3.53 kB). View file
 
devcore/__pycache__/parser.cpython-312.pyc ADDED
Binary file (11.9 kB). View file
 
devcore/builder.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from devcore.parser import parse_description
4
+
5
+ TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "templates", "viewer.html")
6
+
7
+
8
+ def description_to_blueprint(text):
9
+ data = parse_description(text)
10
+ return _build_threejs_html(data)
11
+
12
+
13
+ def _build_threejs_html(data):
14
+ scene = data["structure"]
15
+ rooms = data["rooms"]
16
+ w = scene["width"]
17
+ d = scene["depth"]
18
+ s = scene["stories"]
19
+
20
+ room_meshes = []
21
+ wall_meshes = []
22
+ label_data = []
23
+
24
+ wall_height = 8 if s <= 1 else 9
25
+ floor_height = 0.5
26
+
27
+ for room in rooms:
28
+ rx, rz = room["x"], room["z"]
29
+ rw, rd = room["w"], room["d"]
30
+ floor_n = room.get("floor", 0)
31
+ cy = floor_n * (wall_height + floor_height) + floor_height / 2
32
+ exterior = room.get("exterior", False)
33
+
34
+ color = room["color"]
35
+
36
+ room_meshes.append({
37
+ "x": rx, "z": rz, "w": rw, "d": rd,
38
+ "y": cy, "h": floor_height,
39
+ "color": color,
40
+ "name": room["name"],
41
+ "exterior": exterior,
42
+ })
43
+
44
+ lx = rx + rw / 2
45
+ lz = rz + rd / 2
46
+ label_data.append({
47
+ "x": lx, "z": lz, "y": cy + 0.3,
48
+ "text": room["name"],
49
+ })
50
+
51
+ hw = rw / 2
52
+ hd = rd / 2
53
+
54
+ if not exterior:
55
+ for side in ["north", "south", "east", "west"]:
56
+ wall_meshes.append({
57
+ "side": side,
58
+ "x": rx, "z": rz, "w": rw, "d": rd,
59
+ "h": wall_height,
60
+ "room_name": room["name"],
61
+ })
62
+
63
+ roof_height = wall_height + 4
64
+ roof_mesh = {
65
+ "w": w * 1.15,
66
+ "d": d * 1.15,
67
+ "y": s * (wall_height + floor_height),
68
+ "h": roof_height,
69
+ "color": "#5a4a3a",
70
+ }
71
+
72
+ blueprint = {
73
+ "scene": {
74
+ "width": w,
75
+ "depth": d,
76
+ "stories": s,
77
+ "wall_height": wall_height,
78
+ "floor_height": floor_height,
79
+ },
80
+ "floors": room_meshes,
81
+ "walls": wall_meshes,
82
+ "roof": roof_mesh,
83
+ "labels": label_data,
84
+ "summary": data["summary"],
85
+ "features": data["features"],
86
+ }
87
+
88
+ html = _render_html(blueprint)
89
+ return html
90
+
91
+
92
+ def _render_html(blueprint):
93
+ bp_json = json.dumps(blueprint)
94
+
95
+ with open(TEMPLATE_PATH, "r") as f:
96
+ template = f.read()
97
+
98
+ html = template.replace("/* BLUEPRINT_DATA */", f"const BLUEPRINT = {bp_json};")
99
+ return html
100
+
101
+
102
+ if __name__ == "__main__":
103
+ html = description_to_blueprint("2000 sq ft ranch house with 3 bedrooms and 2 baths")
104
+ print(f"Generated {len(html)} bytes of HTML")
105
+ with open("/tmp/test_blueprint.html", "w") as f:
106
+ f.write(html)
107
+ print("Saved to /tmp/test_blueprint.html")
devcore/parser.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ STYLES = {
4
+ "ranch": "ranch", "rambler": "ranch",
5
+ "colonial": "colonial", "cape": "colonial",
6
+ "modern": "modern", "contemporary": "modern",
7
+ "victorian": "victorian",
8
+ "craftsman": "craftsman",
9
+ "cabin": "cabin", "cottage": "cabin",
10
+ "tudor": "tudor",
11
+ "farmhouse": "farmhouse",
12
+ }
13
+
14
+ ROOM_COLORS = {
15
+ "living": "#e8dcc8", "family": "#e8dcc8",
16
+ "kitchen": "#d4e8d0", "dining": "#e0d0c0",
17
+ "bedroom": "#d0d8e8", "master": "#c8d0e8",
18
+ "bath": "#d8e8e8", "bathroom": "#d8e8e8",
19
+ "office": "#e0d0d8", "den": "#e0d0d8",
20
+ "garage": "#d0d0d0",
21
+ "hall": "#f0ece8", "hallway": "#f0ece8",
22
+ "laundry": "#e8e0d8",
23
+ "closet": "#ece8e0",
24
+ "loft": "#e8e0d0",
25
+ "basement": "#c8c0b8",
26
+ "porch": "#d8d0c8", "deck": "#c8b8a8",
27
+ "patio": "#c8b8a8",
28
+ }
29
+
30
+
31
+ def parse_description(text):
32
+ text = text.lower().strip()
33
+
34
+ sqft = _extract_sqft(text)
35
+ width, depth = _extract_dimensions(text, sqft)
36
+ style = _extract_style(text)
37
+ stories = _extract_stories(text)
38
+ bedrooms = _extract_bedrooms(text)
39
+ bathrooms = _extract_bathrooms(text)
40
+ garage = _has_garage(text)
41
+ extra_rooms = _extract_extra_rooms(text)
42
+
43
+ rooms = _generate_rooms(width, depth, stories, bedrooms, bathrooms, garage, extra_rooms, style)
44
+
45
+ return {
46
+ "structure": {
47
+ "width": width,
48
+ "depth": depth,
49
+ "stories": stories,
50
+ "style": style,
51
+ "sqft": sqft or (width * depth * stories),
52
+ },
53
+ "rooms": rooms,
54
+ "features": {
55
+ "bedrooms": int(bedrooms),
56
+ "bathrooms": float(bathrooms),
57
+ "garage": garage,
58
+ "stories": int(stories),
59
+ },
60
+ "summary": _generate_summary(width, depth, stories, style, bedrooms, bathrooms),
61
+ }
62
+
63
+
64
+ def _extract_sqft(text):
65
+ m = re.search(r'(\d+[,.]?\d*)\s*(sq\s*ft|square\s*feet|sqft)', text)
66
+ if m:
67
+ return int(float(m.group(1).replace(",", "")))
68
+ m = re.search(r'(\d+[,.]?\d*)\s*x\s*(\d+[,.]?\d*)\s*(?:feet|ft|foot)', text)
69
+ if m:
70
+ w = float(m.group(1).replace(",", ""))
71
+ d = float(m.group(2).replace(",", ""))
72
+ return int(w * d)
73
+ return None
74
+
75
+
76
+ def _extract_dimensions(text, sqft):
77
+ m = re.search(r'(\d+)\s*(?:feet|ft|foot)\s*(?:x|by|and)?\s*(\d+)\s*(?:feet|ft|foot)', text)
78
+ if m:
79
+ return int(m.group(1)), int(m.group(2))
80
+ m = re.search(r'(\d+)\s*x\s*(\d+)', text)
81
+ if m:
82
+ return int(m.group(1)), int(m.group(2))
83
+ m = re.search(r'(\d+)\s*(?:feet|ft|foot)\s*(?:wide|width)?', text)
84
+ if m:
85
+ w = int(m.group(1))
86
+ if sqft:
87
+ d = max(1, sqft // w)
88
+ else:
89
+ d = int(w * 0.75)
90
+ return w, d
91
+ m = re.search(r'(\d+)\s*(?:by|wide|width)\s*(\d+)', text)
92
+ if m:
93
+ return int(m.group(1)), int(m.group(2))
94
+ if sqft:
95
+ w = int(sqft ** 0.5)
96
+ d = max(1, sqft // w)
97
+ return w, d
98
+ return 40, 30
99
+
100
+
101
+ def _extract_style(text):
102
+ for style, normalized in STYLES.items():
103
+ if style in text:
104
+ return normalized
105
+ if "2 story" in text or "two story" in text or "two-story" in text:
106
+ return "colonial"
107
+ return "modern"
108
+
109
+
110
+ def _extract_stories(text):
111
+ m = re.search(r'(\d+)\s*[-]?\s*(?:story|floor|level|storey)', text)
112
+ if m:
113
+ return min(int(m.group(1)), 3)
114
+ if "single story" in text or "ranch" in text or "rambler" in text:
115
+ return 1
116
+ return 1
117
+
118
+
119
+ def _extract_bedrooms(text):
120
+ m = re.search(r'(\d+)\s*(?:bedroom|bed\b)', text)
121
+ if m:
122
+ return int(m.group(1))
123
+ m = re.search(r'(\d+)\s*br\b', text)
124
+ if m:
125
+ return int(m.group(1))
126
+ return 3
127
+
128
+
129
+ def _extract_bathrooms(text):
130
+ m = re.search(r'(\d+)\s*(?:bathroom|bath\b)', text)
131
+ if m:
132
+ return int(m.group(1))
133
+ m = re.search(r'(\d+)\s*ba\b', text)
134
+ if m:
135
+ return int(m.group(1))
136
+ m = re.search(r'(\d+\.?\d*)\s*-?\s*bath', text)
137
+ if m:
138
+ return float(m.group(1))
139
+ return 2
140
+
141
+
142
+ def _has_garage(text):
143
+ if re.search(r'\bgarage\b', text):
144
+ return True
145
+ if re.search(r'\bcar\s*(?:garage|port)\b', text):
146
+ return True
147
+ return False
148
+
149
+
150
+ def _extract_extra_rooms(text):
151
+ extras = []
152
+ room_keywords = ["office", "den", "library", "gym", "theater", "workshop",
153
+ "laundry", "mudroom", "sunroom", "conservatory", "studio",
154
+ "loft", "basement", "attic", "porch", "deck", "patio",
155
+ "wine cellar", "pantry", "playroom", "nursery"]
156
+ for kw in room_keywords:
157
+ if kw in text:
158
+ extras.append(kw)
159
+ return extras
160
+
161
+
162
+ def _generate_rooms(width, depth, stories, bedrooms, bathrooms, garage, extras, style):
163
+ rooms = []
164
+ color = lambda name: ROOM_COLORS.get(name, "#e8e0d8")
165
+
166
+ w3 = width / 3.0
167
+ d2 = depth / 2.0
168
+
169
+ rooms.append({"name": "Living Room", "x": 0, "z": 0, "w": w3 * 1.5, "d": d2, "color": color("living"), "floor": 0})
170
+ rooms.append({"name": "Kitchen", "x": w3 * 1.5, "z": 0, "w": w3 * 0.8, "d": d2, "color": color("kitchen"), "floor": 0})
171
+ rooms.append({"name": "Dining", "x": w3 * 1.5 + w3 * 0.8, "z": 0, "w": w3 * 0.7, "d": d2, "color": color("dining"), "floor": 0})
172
+
173
+ bed_colors = ["#d0d8e8", "#c8d0e8", "#d8d0e0", "#d0d0e0", "#d8d8e8"]
174
+ bed_w = min(w3 * 0.7, 14)
175
+ bed_d = min(d2 * 0.6, 14)
176
+
177
+ for i in range(bedrooms):
178
+ bx = (i % 3) * w3
179
+ bz = d2 + (i // 3) * bed_d
180
+ if bx + bed_w > width:
181
+ bx = width - bed_w
182
+ name = "Master Bedroom" if i == 0 else f"Bedroom {i+1}"
183
+ rooms.append({"name": name, "x": bx, "z": bz, "w": bed_w, "d": bed_d, "color": bed_colors[i % len(bed_colors)], "floor": 0})
184
+
185
+ bath_w = 6
186
+ bath_d = 6
187
+ for i in range(int(bathrooms)):
188
+ bx = width - bath_w - (i * bath_w) % (width / 3)
189
+ bz = 0 if i % 2 == 0 else d2 - bath_d
190
+ if bx < 0:
191
+ bx = width - bath_w
192
+ rooms.append({"name": f"{'Master ' if i == 0 and bedrooms > 0 else ''}Bathroom" if i == 0 else f"Bathroom {i+1}",
193
+ "x": bx, "z": bz, "w": bath_w, "d": bath_d, "color": color("bathroom"), "floor": 0})
194
+
195
+ if garage:
196
+ gw = min(width * 0.35, 24)
197
+ gd = min(depth * 0.4, 24)
198
+ rooms.append({"name": "Garage", "x": 0, "z": depth - gd, "w": gw, "d": gd, "color": color("garage"), "floor": 0})
199
+
200
+ hall_w = 4
201
+ rooms.append({"name": "Hallway", "x": 0, "z": d2, "w": hall_w, "d": bed_d if bedrooms > 0 else d2 * 0.5, "color": color("hallway"), "floor": 0})
202
+
203
+ for extra in extras:
204
+ ex = width * 0.6
205
+ ez = depth * 0.3
206
+ ew = min(w3 * 0.6, 12)
207
+ ed = min(d2 * 0.5, 10)
208
+ if extra in ("deck", "patio", "porch"):
209
+ rooms.append({"name": extra.capitalize(), "x": width * 0.3, "z": -8, "w": width * 0.4, "d": 8, "color": color(extra), "floor": 0, "exterior": True})
210
+ else:
211
+ rooms.append({"name": extra.capitalize(), "x": ex, "z": ez, "w": ew, "d": ed, "color": color(extra), "floor": 0})
212
+
213
+ if stories > 1:
214
+ for room in rooms:
215
+ if room["floor"] == 0 and room["name"] not in ("Garage",):
216
+ upper = room.copy()
217
+ upper["name"] = f"Upper {room['name']}"
218
+ upper["floor"] = 1
219
+ upper["color"] = "#e0dcd8"
220
+ rooms.append(upper)
221
+
222
+ return rooms
223
+
224
+
225
+ def _generate_summary(width, depth, stories, style, bedrooms, bathrooms):
226
+ sqft = width * depth * stories
227
+ return f"A {style} {stories}-story home, {width}ft x {depth}ft — approximately {sqft} sqft. {bedrooms} bedrooms, {bathrooms} bathrooms."
228
+
229
+
230
+ if __name__ == "__main__":
231
+ tests = [
232
+ "2000 sq ft ranch house with 3 bedrooms and 2 baths",
233
+ "modern 2-story house 30x40 with 4 bedrooms 3 bathrooms and a 2 car garage",
234
+ "small cabin 800 sq ft with 1 bedroom",
235
+ "victorian 3500 sq ft 5 bedroom 3 bath with office and library",
236
+ "colonial with 4 bedrooms and 2.5 baths",
237
+ ]
238
+ for t in tests:
239
+ r = parse_description(t)
240
+ print(f"\n{'='*60}")
241
+ print(f"Input: {t}")
242
+ print(f"Structure: {r['structure']}")
243
+ print(f"Rooms: {len(r['rooms'])}")
244
+ for room in r['rooms'][:5]:
245
+ print(f" {room['name']}: {room['w']:.0f}x{room['d']:.0f} @ ({room['x']:.0f},{room['z']:.0f}) floor={room.get('floor',0)}")
246
+ print(f"Summary: {r['summary']}")
devcore/templates/viewer.html ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Vitalis DevCore — 3D Blueprint</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0a1a; overflow: hidden; color: #fff; }
10
+ #canvas-container { width: 100vw; height: 100vh; display: block; }
11
+ #info-overlay { position: fixed; top: 16px; left: 16px; background: rgba(10,10,30,0.85); border: 1px solid rgba(100,60,255,0.3); border-radius: 12px; padding: 16px 20px; max-width: 320px; font-size: 13px; line-height: 1.5; backdrop-filter: blur(8px); z-index: 10; }
12
+ #info-overlay h2 { font-size: 16px; margin-bottom: 6px; color: #a78bfa; }
13
+ #info-overlay p { color: #c4b5fd; margin: 2px 0; }
14
+ #info-overlay .badge { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 11px; margin: 2px; background: rgba(100,60,255,0.2); color: #a78bfa; border: 1px solid rgba(100,60,255,0.2); }
15
+ #controls-hint { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(10,10,30,0.7); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 8px 16px; font-size: 12px; color: #888; backdrop-filter: blur(4px); z-index: 10; }
16
+ .label-2d { position: absolute; color: rgba(255,255,255,0.6); font-size: 11px; font-weight: 500; text-shadow: 0 0 8px rgba(0,0,0,0.8); pointer-events: none; transform: translate(-50%, -50%); white-space: nowrap; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div id="info-overlay">
21
+ <h2>🏗️ Blueprint</h2>
22
+ <p id="summary-text">Loading...</p>
23
+ <div id="features" style="margin-top:6px;"></div>
24
+ </div>
25
+ <div id="controls-hint">🖱️ Drag to orbit · Scroll to zoom · Right-drag to pan</div>
26
+ <div id="canvas-container"></div>
27
+
28
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
29
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
30
+
31
+ <script>
32
+ /* BLUEPRINT_DATA */
33
+
34
+ function initScene(data) {
35
+ const container = document.getElementById('canvas-container');
36
+ const w = container.clientWidth;
37
+ const h = container.clientHeight;
38
+
39
+ const scene = new THREE.Scene();
40
+ scene.background = new THREE.Color(0x0a0a1a);
41
+ scene.fog = new THREE.Fog(0x0a0a1a, 100, 200);
42
+
43
+ const camera = new THREE.PerspectiveCamera(40, w / h, 0.1, 500);
44
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
45
+ renderer.setSize(w, h);
46
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
47
+ renderer.shadowMap.enabled = true;
48
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
49
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
50
+ renderer.toneMappingExposure = 1.2;
51
+ container.appendChild(renderer.domElement);
52
+
53
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
54
+ controls.enableDamping = true;
55
+ controls.dampingFactor = 0.08;
56
+ controls.maxPolarAngle = Math.PI / 2.1;
57
+ controls.minDistance = 5;
58
+ controls.maxDistance = 150;
59
+
60
+ // Lights
61
+ const ambient = new THREE.AmbientLight(0x404060, 0.6);
62
+ scene.add(ambient);
63
+
64
+ const sun = new THREE.DirectionalLight(0xffeedd, 1.2);
65
+ sun.position.set(40, 60, 30);
66
+ sun.castShadow = true;
67
+ sun.shadow.mapSize.width = 2048;
68
+ sun.shadow.mapSize.height = 2048;
69
+ sun.shadow.camera.near = 0.5;
70
+ sun.shadow.camera.far = 150;
71
+ scene.add(sun);
72
+
73
+ const fill = new THREE.DirectionalLight(0x8888ff, 0.4);
74
+ fill.position.set(-30, 20, -20);
75
+ scene.add(fill);
76
+
77
+ const hemi = new THREE.HemisphereLight(0x8888ff, 0x444422, 0.6);
78
+ scene.add(hemi);
79
+
80
+ // Ground
81
+ const groundGeo = new THREE.PlaneGeometry(200, 200);
82
+ const groundMat = new THREE.MeshStandardMaterial({
83
+ color: 0x181830,
84
+ roughness: 0.9,
85
+ metalness: 0.0,
86
+ transparent: true,
87
+ opacity: 0.8,
88
+ });
89
+ const ground = new THREE.Mesh(groundGeo, groundMat);
90
+ ground.rotation.x = -Math.PI / 2;
91
+ ground.position.y = -0.25;
92
+ ground.receiveShadow = true;
93
+ scene.add(ground);
94
+
95
+ // Grid
96
+ const grid = new THREE.GridHelper(100, 40, 0x444488, 0x333366);
97
+ grid.position.y = 0.01;
98
+ scene.add(grid);
99
+
100
+ const bp = data.scene;
101
+ const cx = bp.width / 2;
102
+ const cz = bp.depth / 2;
103
+ const sw = bp.width;
104
+ const sd = bp.depth;
105
+
106
+ // Foundation outline
107
+ const outlineMat = new THREE.LineBasicMaterial({ color: 0x6666cc, transparent: true, opacity: 0.4 });
108
+ const outlineGeo = new THREE.BufferGeometry();
109
+ const outlineVerts = new Float32Array([
110
+ 0, 0, 0, sw, 0, 0, sw, 0, sd, 0, 0, sd, 0, 0, 0
111
+ ]);
112
+ outlineGeo.setAttribute('position', new THREE.BufferAttribute(outlineVerts, 3));
113
+ const outline = new THREE.Line(outlineGeo, outlineMat);
114
+ outline.position.y = 0.02;
115
+ scene.add(outline);
116
+
117
+ // Floors
118
+ const floorMat = new THREE.MeshStandardMaterial({ roughness: 0.7, metalness: 0.05 });
119
+
120
+ data.floors.forEach(function(f) {
121
+ const geo = new THREE.BoxGeometry(f.w, f.h, f.d);
122
+ const mat = floorMat.clone();
123
+ mat.color = new THREE.Color(f.color || 0x444466);
124
+ mat.transparent = true;
125
+ mat.opacity = f.exterior ? 0.5 : 0.85;
126
+ const mesh = new THREE.Mesh(geo, mat);
127
+ mesh.position.set(f.x + f.w/2, f.y, f.z + f.d/2);
128
+ mesh.castShadow = true;
129
+ mesh.receiveShadow = true;
130
+ scene.add(mesh);
131
+
132
+ if (!f.exterior) {
133
+ const edge = new THREE.EdgesGeometry(geo);
134
+ const line = new THREE.LineSegments(edge, new THREE.LineBasicMaterial({
135
+ color: 0x8888cc, transparent: true, opacity: 0.3
136
+ }));
137
+ line.position.copy(mesh.position);
138
+ scene.add(line);
139
+ }
140
+ });
141
+
142
+ // Walls
143
+ const wallMat = new THREE.MeshStandardMaterial({
144
+ color: 0x444488, roughness: 0.6, metalness: 0.1,
145
+ transparent: true, opacity: 0.15, wireframe: false,
146
+ });
147
+
148
+ data.walls.forEach(function(w) {
149
+ var wx, wz, ww, wd, wh = w.h;
150
+ var margin = 0.05;
151
+
152
+ if (w.side === 'north') {
153
+ wx = w.x; wz = w.z; ww = w.w; wd = 0.15;
154
+ } else if (w.side === 'south') {
155
+ wx = w.x; wz = w.z + w.d; ww = w.w; wd = 0.15;
156
+ } else if (w.side === 'east') {
157
+ wx = w.x + w.w; wz = w.z; ww = 0.15; wd = w.d;
158
+ } else {
159
+ wx = w.x; wz = w.z; ww = 0.15; wd = w.d;
160
+ }
161
+
162
+ const geo = new THREE.BoxGeometry(ww, wh, wd);
163
+ const mat = wallMat.clone();
164
+ mat.color = new THREE.Color(0x6666aa);
165
+ const mesh = new THREE.Mesh(geo, mat);
166
+ mesh.position.set(wx + ww/2, wh/2, wz + wd/2);
167
+ scene.add(mesh);
168
+ });
169
+
170
+ // Roof
171
+ const roof = data.roof;
172
+ const roofMat = new THREE.MeshStandardMaterial({
173
+ color: new THREE.Color(roof.color || 0x5a4a3a),
174
+ roughness: 0.8, metalness: 0.0,
175
+ });
176
+ const roofGeo = new THREE.ConeGeometry(Math.max(roof.w, roof.d) * 0.7, roof.h, 4);
177
+ const roofMesh = new THREE.Mesh(roofGeo, roofMat);
178
+ roofMesh.position.set(sw/2, roof.y, sd/2);
179
+ roofMesh.rotation.y = Math.PI / 4;
180
+ roofMesh.castShadow = true;
181
+ scene.add(roofMesh);
182
+
183
+ // Labels using sprites
184
+ data.labels.forEach(function(l) {
185
+ const canvas = document.createElement('canvas');
186
+ const ctx = canvas.getContext('2d');
187
+ canvas.width = 512;
188
+ canvas.height = 128;
189
+
190
+ ctx.shadowColor = 'rgba(0,0,0,0.8)';
191
+ ctx.shadowBlur = 12;
192
+
193
+ ctx.fillStyle = 'rgba(10,10,30,0.6)';
194
+ const tw = ctx.measureText(l.text).width || 200;
195
+ const rw = Math.min(tw + 40, 500);
196
+ const rh = 60;
197
+ const rx = (canvas.width - rw) / 2;
198
+ const ry = (canvas.height - rh) / 2;
199
+ ctx.shadowBlur = 0;
200
+ ctx.fillStyle = 'rgba(20,20,50,0.5)';
201
+ ctx.beginPath();
202
+ ctx.roundRect(rx, ry, rw, rh, 8);
203
+ ctx.fill();
204
+ ctx.strokeStyle = 'rgba(100,60,255,0.3)';
205
+ ctx.lineWidth = 1;
206
+ ctx.stroke();
207
+
208
+ ctx.shadowColor = 'rgba(0,0,0,0.6)';
209
+ ctx.shadowBlur = 6;
210
+ ctx.fillStyle = l.text.includes('Upper') ? 'rgba(180,180,220,0.6)' : '#ffffff';
211
+ ctx.font = 'bold 32px -apple-system, BlinkMacSystemFont, sans-serif';
212
+ ctx.textAlign = 'center';
213
+ ctx.textBaseline = 'middle';
214
+ ctx.fillText(l.text, canvas.width / 2, canvas.height / 2);
215
+
216
+ const texture = new THREE.CanvasTexture(canvas);
217
+ texture.needsUpdate = true;
218
+
219
+ const spriteMat = new THREE.SpriteMaterial({
220
+ map: texture,
221
+ transparent: true,
222
+ depthTest: false,
223
+ sizeAttenuation: true,
224
+ });
225
+ const sprite = new THREE.Sprite(spriteMat);
226
+ sprite.position.set(l.x, l.y + 2, l.z);
227
+ sprite.scale.set(8, 2, 1);
228
+ scene.add(sprite);
229
+ });
230
+
231
+ // Camera position
232
+ const dist = Math.max(sw, sd) * 1.3 + 10;
233
+ camera.position.set(dist * 0.6, dist * 0.7, dist * 0.8);
234
+ controls.target.set(sw / 2, 1, sd / 2);
235
+ controls.update();
236
+
237
+ // Resize
238
+ window.addEventListener('resize', function() {
239
+ const w2 = container.clientWidth;
240
+ const h2 = container.clientHeight;
241
+ camera.aspect = w2 / h2;
242
+ camera.updateProjectionMatrix();
243
+ renderer.setSize(w2, h2);
244
+ });
245
+
246
+ function animate() {
247
+ requestAnimationFrame(animate);
248
+ controls.update();
249
+ renderer.render(scene, camera);
250
+ }
251
+ animate();
252
+ }
253
+
254
+ // RoundRect polyfill for labels
255
+ if (!CanvasRenderingContext2D.prototype.roundRect) {
256
+ CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
257
+ if (r > w/2) r = w/2;
258
+ if (r > h/2) r = h/2;
259
+ this.moveTo(x + r, y);
260
+ this.arcTo(x + w, y, x + w, y + h, r);
261
+ this.arcTo(x + w, y + h, x, y + h, r);
262
+ this.arcTo(x, y + h, x, y, r);
263
+ this.arcTo(x, y, x + w, y, r);
264
+ return this;
265
+ };
266
+ }
267
+
268
+ // Update info overlay
269
+ document.getElementById('summary-text').textContent = BLUEPRINT.summary || 'No description available.';
270
+ var feat = BLUEPRINT.features || {};
271
+ var featHtml = '';
272
+ var featMap = {
273
+ bedrooms: '🛏️', bathrooms: '🚿', stories: '🏠', garage: '🚗'
274
+ };
275
+ for (var k in feat) {
276
+ if (featMap[k]) {
277
+ var val = feat[k];
278
+ if (k === 'garage') val = val ? 'Yes' : 'No';
279
+ featHtml += '<span class="badge">' + featMap[k] + ' ' + k.charAt(0).toUpperCase() + k.slice(1) + ': ' + val + '</span>';
280
+ }
281
+ }
282
+ document.getElementById('features').innerHTML = featHtml;
283
+
284
+ initScene(BLUEPRINT);
285
+ </script>
286
+ </body>
287
+ </html>
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=6.19.0
2
+ huggingface-hub