tomiconic commited on
Commit
0d2d373
·
verified ·
1 Parent(s): cbcef9d

Upload 5 files

Browse files
Files changed (5) hide show
  1. LICENSE +21 -0
  2. README.md +98 -8
  3. app.py +152 -0
  4. packages.txt +1 -0
  5. requirements.txt +10 -0
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,14 +1,104 @@
1
  ---
2
- title: 3DModelGen
3
- emoji: 😻
4
- colorFrom: blue
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.10.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
- short_description: This space is designed to easily generate models using the p
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Particle Blueprint 3D
3
+ emoji: 🧩
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: gradio
 
 
7
  app_file: app.py
8
  pinned: false
9
+ license: mit
10
  ---
11
 
12
+ # Particle Blueprint 3D
13
+
14
+ A Hugging Face Space for a scaffold-first 3D workflow:
15
+
16
+ **prompt → structured particle blueprint → reconstructed mesh → GLB**
17
+
18
+ This version is tuned for **mobile iPhone use first** and adds an optional **local Hugging Face model** for better prompt-to-shape planning.
19
+
20
+ ## What changed
21
+
22
+ - Mobile-first single-column UX with larger tap targets.
23
+ - Sticky action area on narrow screens.
24
+ - Prompt presets for fast one-thumb use.
25
+ - Tabs instead of side-by-side viewers, which works better on phones.
26
+ - Optional local model planner before blueprint generation.
27
+
28
+ ## Why the local model is used only for planning
29
+
30
+ The local model does **not** try to directly generate a final GLB.
31
+ It is used to convert a messy natural-language prompt into a cleaner structured spec:
32
+
33
+ - object type
34
+ - scale
35
+ - hull style
36
+ - engine count
37
+ - wing span
38
+ - cargo ratio
39
+ - cockpit ratio
40
+ - fin height
41
+ - landing gear
42
+ - asymmetry
43
+
44
+ That structured spec then drives the scaffold and meshing pipeline.
45
+ This is much more realistic than pretending a small local model can do full end-to-end 3D generation well.
46
+
47
+ ## Recommended local models
48
+
49
+ ### Best default
50
+
51
+ - `Qwen/Qwen2.5-1.5B-Instruct`
52
+
53
+ Use this as the main local planner. It is the stronger option for extracting useful structure from prompts.
54
+
55
+ ### Lighter fallback
56
+
57
+ - `HuggingFaceTB/SmolLM2-1.7B-Instruct`
58
+
59
+ Use this if you want a smaller-feeling fallback or if Qwen has issues in your runtime.
60
+
61
+ ## Current pipeline
62
+
63
+ 1. Parse the prompt with either:
64
+ - fast heuristic parser
65
+ - local Hugging Face model planner
66
+ 2. Generate a structured particle blueprint.
67
+ 3. Convert the blueprint into a voxel field.
68
+ 4. Reconstruct a triangle mesh.
69
+ 5. Export both `.ply` and `.glb`.
70
+
71
+ ## ZeroGPU strategy
72
+
73
+ Use the local model only when needed.
74
+ The LLM generation function is isolated so it can be decorated for ZeroGPU use without forcing the whole app onto GPU every time.
75
+
76
+ ## Local development
77
+
78
+ ```bash
79
+ pip install -r requirements.txt
80
+ python app.py
81
+ ```
82
+
83
+ ## Deploy to Hugging Face
84
+
85
+ Create a **Gradio** Space and push these files.
86
+
87
+ ```bash
88
+ git init
89
+ git add .
90
+ git commit -m "Mobile UX + local HF planner"
91
+ git branch -M main
92
+ git remote add origin https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
93
+ git push -u origin main
94
+ ```
95
+
96
+ ## Notes
97
+
98
+ This is still the right honest framing:
99
+
100
+ - AI plans the shape
101
+ - the app builds the scaffold
102
+ - the scaffold becomes the mesh
103
+
104
+ That middle layer is the product.
app.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import gradio as gr
4
+
5
+ from src.generator import run_pipeline
6
+ from src.llm_parser import DEFAULT_LOCAL_MODEL, MODEL_PRESETS
7
+
8
+
9
+ TITLE = "Particle Blueprint 3D"
10
+ TAGLINE = "Mobile-first prompt → particle scaffold → mesh → GLB"
11
+ DESCRIPTION = (
12
+ "Built for iPhone use first: a compact prompt flow, one-tap presets, low-friction controls, "
13
+ "and an optional local Hugging Face model that plans the blueprint before meshing."
14
+ )
15
+
16
+ PROMPT_PRESETS = {
17
+ "Cargo hauler": "small cargo hauler with a boxy hull, cockpit, cargo bay, rear engines and landing gear",
18
+ "Compact fighter": "compact fighter with wings, sleek nose, twin engines and a small cockpit",
19
+ "Industrial dropship": "industrial dropship with cargo hold, fin tail, four engines and wing stubs",
20
+ }
21
+
22
+ CSS = """
23
+ footer {display:none !important}
24
+ .gradio-container {max-width: 1120px !important; margin: 0 auto; padding: 0 12px 28px 12px !important}
25
+ #app-shell {gap: 14px}
26
+ .hero-card, .panel-card {border: 1px solid rgba(255,255,255,0.08); border-radius: 22px; padding: 14px 16px; background: rgba(255,255,255,0.03)}
27
+ .hero-title {font-size: 1.5rem; font-weight: 800; margin: 0 0 6px 0}
28
+ .hero-sub {opacity: 0.88; margin: 0}
29
+ .cta-row {gap: 10px}
30
+ .cta-row button {min-height: 50px !important; border-radius: 16px !important; font-size: 1rem !important}
31
+ .preset-row {display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 8px}
32
+ .preset-row button {min-height: 46px !important; border-radius: 16px !important}
33
+ .model3d-wrap {border-radius: 20px; overflow: hidden}
34
+ .mobile-note {font-size: 0.92rem; opacity: 0.82}
35
+ @media (max-width: 820px) {
36
+ .gradio-container {padding: 0 10px 22px 10px !important}
37
+ .hero-card, .panel-card {padding: 12px 12px}
38
+ .hero-title {font-size: 1.28rem}
39
+ .preset-row {grid-template-columns: 1fr}
40
+ .cta-row {position: sticky; bottom: 10px; z-index: 10; background: rgba(15,18,24,0.86); backdrop-filter: blur(12px); padding: 8px; border: 1px solid rgba(255,255,255,0.08); border-radius: 18px}
41
+ }
42
+ """
43
+
44
+
45
+ def generate_asset(prompt: str, parser_mode: str, model_choice: str, detail: int, voxel_pitch: float):
46
+ prompt = (prompt or "").strip()
47
+ if not prompt:
48
+ raise gr.Error("Enter a prompt first.")
49
+
50
+ model_id = MODEL_PRESETS.get(model_choice, model_choice or DEFAULT_LOCAL_MODEL)
51
+ artifacts = run_pipeline(
52
+ prompt=prompt,
53
+ detail=detail,
54
+ voxel_pitch=voxel_pitch,
55
+ parser_mode=parser_mode,
56
+ model_id=model_id,
57
+ )
58
+ status = (
59
+ f"Built {artifacts.summary['point_count']} scaffold points and "
60
+ f"{artifacts.summary['face_count']} mesh faces using {artifacts.summary['parser_backend']}."
61
+ )
62
+ return artifacts.ply_path, artifacts.glb_path, artifacts.summary, artifacts.ply_path, artifacts.glb_path, status
63
+
64
+
65
+ with gr.Blocks(theme=gr.themes.Soft(), css=CSS, title=TITLE, fill_width=True) as demo:
66
+ with gr.Column(elem_id="app-shell"):
67
+ gr.HTML(
68
+ f"""
69
+ <div class='hero-card'>
70
+ <div class='hero-title'>{TITLE}</div>
71
+ <p class='hero-sub'><strong>{TAGLINE}</strong><br>{DESCRIPTION}</p>
72
+ <p class='mobile-note'>Best on mobile for ships, drones, cargo gear and other hard-surface forms.</p>
73
+ </div>
74
+ """
75
+ )
76
+
77
+ with gr.Column(elem_classes=["panel-card"]):
78
+ prompt = gr.Textbox(
79
+ label="Describe the object",
80
+ lines=4,
81
+ max_lines=6,
82
+ placeholder="Example: small cargo hauler with a boxy hull, rear ramp, cargo bay, 4 engines and landing gear",
83
+ autofocus=True,
84
+ )
85
+
86
+ with gr.Row(elem_classes=["preset-row"]):
87
+ preset_a = gr.Button("Cargo hauler")
88
+ preset_b = gr.Button("Compact fighter")
89
+ preset_c = gr.Button("Industrial dropship")
90
+
91
+ with gr.Accordion("Model and quality", open=False):
92
+ parser_mode = gr.Radio(
93
+ choices=[("Fast heuristic", "heuristic"), ("Local HF model", "local")],
94
+ value="heuristic",
95
+ label="Prompt planner",
96
+ info="Use the local model when you want better shape planning from messy prompts.",
97
+ )
98
+ model_choice = gr.Dropdown(
99
+ choices=list(MODEL_PRESETS.keys()),
100
+ value="Qwen 2.5 1.5B",
101
+ label="Local model",
102
+ info="Qwen is the stronger planner. SmolLM2 is the lighter fallback.",
103
+ )
104
+ detail = gr.Slider(12, 32, value=18, step=2, label="Detail")
105
+ voxel_pitch = gr.Slider(0.06, 0.12, value=0.09, step=0.005, label="Mesh density")
106
+
107
+ with gr.Row(elem_classes=["cta-row"]):
108
+ run_btn = gr.Button("Generate blueprint", variant="primary")
109
+ clear_btn = gr.Button("Clear")
110
+
111
+ status = gr.Markdown("Ready.")
112
+
113
+ with gr.Tabs():
114
+ with gr.TabItem("Blueprint"):
115
+ blueprint_view = gr.Model3D(
116
+ label="Particle blueprint (.ply)",
117
+ display_mode="point_cloud",
118
+ clear_color=(0.02, 0.02, 0.03, 1.0),
119
+ elem_classes=["model3d-wrap"],
120
+ height=520,
121
+ )
122
+ with gr.TabItem("Mesh"):
123
+ mesh_view = gr.Model3D(
124
+ label="Mesh preview (.glb)",
125
+ display_mode="solid",
126
+ clear_color=(0.02, 0.02, 0.03, 1.0),
127
+ elem_classes=["model3d-wrap"],
128
+ height=520,
129
+ )
130
+ with gr.TabItem("Summary and files"):
131
+ summary = gr.JSON(label="Build summary")
132
+ blueprint_file = gr.File(label="Download blueprint (.ply)")
133
+ mesh_file = gr.File(label="Download mesh (.glb)")
134
+
135
+ preset_a.click(lambda: PROMPT_PRESETS["Cargo hauler"], outputs=prompt)
136
+ preset_b.click(lambda: PROMPT_PRESETS["Compact fighter"], outputs=prompt)
137
+ preset_c.click(lambda: PROMPT_PRESETS["Industrial dropship"], outputs=prompt)
138
+
139
+ run_btn.click(
140
+ fn=generate_asset,
141
+ inputs=[prompt, parser_mode, model_choice, detail, voxel_pitch],
142
+ outputs=[blueprint_view, mesh_view, summary, blueprint_file, mesh_file, status],
143
+ )
144
+
145
+ clear_btn.click(
146
+ lambda: ("", None, None, None, None, None, "Ready."),
147
+ outputs=[prompt, blueprint_view, mesh_view, summary, blueprint_file, mesh_file, status],
148
+ )
149
+
150
+
151
+ if __name__ == "__main__":
152
+ demo.queue(default_concurrency_limit=1).launch()
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ libgl1
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=5.24.0
2
+ numpy>=1.26.4
3
+ scipy>=1.13.1
4
+ scikit-image>=0.24.0
5
+ trimesh>=4.4.9
6
+ huggingface_hub>=0.31.0
7
+ transformers>=4.49.0
8
+ accelerate>=1.4.0
9
+ torch>=2.6.0
10
+ safetensors>=0.5.3