dev-bjoern Claude commited on
Commit
d3b96a3
Β·
0 Parent(s):

feat: BPY MCP - Blender + OpenVINO SmolVLM/SmolLM3

Browse files

- bpy 5.0.2 Blender Python API
- OpenVINO INT4 for CPU inference
- SmolVLM for image understanding
- SmolLM3 for text generation
- Gradio 6.0.2 MCP Server

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (3) hide show
  1. README.md +49 -0
  2. app.py +276 -0
  3. requirements.txt +8 -0
README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: BPY MCP
3
+ emoji: 🎨
4
+ colorFrom: yellow
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 6.0.2
8
+ python_version: "3.11"
9
+ app_file: app.py
10
+ pinned: false
11
+ license: mit
12
+ tags:
13
+ - mcp-server-track
14
+ - blender
15
+ - bpy
16
+ - openvino
17
+ - cpu
18
+ - 3d
19
+ short_description: "Blender Python API with OpenVINO SmolVLM/SmolLM3 - CPU only"
20
+ ---
21
+
22
+ # 🎨 BPY MCP Server
23
+
24
+ **Blender Python API + OpenVINO AI** - CPU-only 3D generation
25
+
26
+ ## Features
27
+
28
+ - **bpy 5.0.2** - Blender Python API
29
+ - **OpenVINO INT4** - Fast CPU inference
30
+ - **SmolVLM** - Vision-Language (image understanding)
31
+ - **SmolLM3** - Text generation (scene descriptions)
32
+ - **MCP Server** - Tool integration
33
+
34
+ ## Models
35
+
36
+ - `dev-bjoern/smolvlm-int4-ov` - Image-to-Text
37
+ - `dev-bjoern/smollm3-int4-ov` - Text Generation
38
+
39
+ ## MCP Integration
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "bpy-mcp": {
45
+ "url": "https://dev-bjoern-bpy-mcp.hf.space/gradio_api/mcp/sse"
46
+ }
47
+ }
48
+ }
49
+ ```
app.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BPY MCP Server - Blender Python API with OpenVINO SmolVLM/SmolLM3
3
+ CPU-only 3D generation
4
+ """
5
+ import os
6
+ import tempfile
7
+ import uuid
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import gradio as gr
12
+ import numpy as np
13
+ from huggingface_hub import snapshot_download
14
+
15
+ # OpenVINO imports
16
+ import openvino_genai as ov_genai
17
+
18
+ # Blender Python API
19
+ import bpy
20
+
21
+ # Global models
22
+ SMOLVLM_PIPE = None
23
+ SMOLLM3_PIPE = None
24
+
25
+
26
+ def load_smolvlm():
27
+ """Load SmolVLM OpenVINO model for image understanding"""
28
+ global SMOLVLM_PIPE
29
+
30
+ if SMOLVLM_PIPE is not None:
31
+ return SMOLVLM_PIPE
32
+
33
+ print("Loading SmolVLM INT4 OpenVINO...")
34
+ model_path = snapshot_download("dev-bjoern/smolvlm-int4-ov")
35
+ SMOLVLM_PIPE = ov_genai.VLMPipeline(model_path, device="CPU")
36
+ print("SmolVLM loaded")
37
+ return SMOLVLM_PIPE
38
+
39
+
40
+ def load_smollm3():
41
+ """Load SmolLM3 OpenVINO model for text generation"""
42
+ global SMOLLM3_PIPE
43
+
44
+ if SMOLLM3_PIPE is not None:
45
+ return SMOLLM3_PIPE
46
+
47
+ print("Loading SmolLM3 INT4 OpenVINO...")
48
+ model_path = snapshot_download("dev-bjoern/smollm3-int4-ov")
49
+ SMOLLM3_PIPE = ov_genai.LLMPipeline(model_path, device="CPU")
50
+ print("SmolLM3 loaded")
51
+ return SMOLLM3_PIPE
52
+
53
+
54
+ def analyze_image(image: np.ndarray, prompt: str = "Describe this image for 3D scene creation") -> str:
55
+ """Analyze image with SmolVLM"""
56
+ if image is None:
57
+ return "No image provided"
58
+
59
+ try:
60
+ pipe = load_smolvlm()
61
+
62
+ # Convert to PIL
63
+ if isinstance(image, np.ndarray):
64
+ pil_image = Image.fromarray(image)
65
+ else:
66
+ pil_image = image
67
+
68
+ # Generate description
69
+ result = pipe.generate(
70
+ prompt,
71
+ image=pil_image,
72
+ max_new_tokens=256
73
+ )
74
+
75
+ return result
76
+ except Exception as e:
77
+ return f"Error: {e}"
78
+
79
+
80
+ def generate_scene_description(scene_type: str, style: str = "realistic") -> str:
81
+ """Generate scene description with SmolLM3"""
82
+ try:
83
+ pipe = load_smollm3()
84
+
85
+ prompt = f"Create a detailed description for a {style} {scene_type} 3D scene. Include objects, materials, lighting, and camera position."
86
+
87
+ result = pipe.generate(
88
+ prompt,
89
+ max_new_tokens=512
90
+ )
91
+
92
+ return result
93
+ except Exception as e:
94
+ return f"Error: {e}"
95
+
96
+
97
+ def create_primitive(primitive_type: str, name: str = "Object", location: tuple = (0, 0, 0)) -> str:
98
+ """Create a Blender primitive object"""
99
+ try:
100
+ # Clear existing objects
101
+ bpy.ops.object.select_all(action='SELECT')
102
+ bpy.ops.object.delete()
103
+
104
+ # Create primitive
105
+ if primitive_type == "cube":
106
+ bpy.ops.mesh.primitive_cube_add(location=location)
107
+ elif primitive_type == "sphere":
108
+ bpy.ops.mesh.primitive_uv_sphere_add(location=location)
109
+ elif primitive_type == "cylinder":
110
+ bpy.ops.mesh.primitive_cylinder_add(location=location)
111
+ elif primitive_type == "cone":
112
+ bpy.ops.mesh.primitive_cone_add(location=location)
113
+ elif primitive_type == "torus":
114
+ bpy.ops.mesh.primitive_torus_add(location=location)
115
+ elif primitive_type == "plane":
116
+ bpy.ops.mesh.primitive_plane_add(location=location)
117
+ elif primitive_type == "monkey":
118
+ bpy.ops.mesh.primitive_monkey_add(location=location)
119
+ else:
120
+ return f"Unknown primitive: {primitive_type}"
121
+
122
+ # Rename object
123
+ obj = bpy.context.active_object
124
+ obj.name = name
125
+
126
+ # Export to GLB
127
+ output_dir = tempfile.mkdtemp()
128
+ glb_path = f"{output_dir}/{name}_{uuid.uuid4().hex[:8]}.glb"
129
+
130
+ bpy.ops.export_scene.gltf(
131
+ filepath=glb_path,
132
+ export_format='GLB'
133
+ )
134
+
135
+ return glb_path
136
+ except Exception as e:
137
+ return f"Error: {e}"
138
+
139
+
140
+ def create_scene_from_description(description: str) -> str:
141
+ """Create a simple scene based on AI description"""
142
+ try:
143
+ # Clear scene
144
+ bpy.ops.object.select_all(action='SELECT')
145
+ bpy.ops.object.delete()
146
+
147
+ # Parse description for keywords and create objects
148
+ desc_lower = description.lower()
149
+
150
+ # Add ground plane
151
+ bpy.ops.mesh.primitive_plane_add(size=10, location=(0, 0, 0))
152
+ ground = bpy.context.active_object
153
+ ground.name = "Ground"
154
+
155
+ # Add objects based on keywords
156
+ if "cube" in desc_lower or "box" in desc_lower:
157
+ bpy.ops.mesh.primitive_cube_add(location=(0, 0, 1))
158
+ bpy.context.active_object.name = "Cube"
159
+
160
+ if "sphere" in desc_lower or "ball" in desc_lower:
161
+ bpy.ops.mesh.primitive_uv_sphere_add(location=(2, 0, 1))
162
+ bpy.context.active_object.name = "Sphere"
163
+
164
+ if "cylinder" in desc_lower or "pillar" in desc_lower:
165
+ bpy.ops.mesh.primitive_cylinder_add(location=(-2, 0, 1))
166
+ bpy.context.active_object.name = "Cylinder"
167
+
168
+ # Add camera
169
+ bpy.ops.object.camera_add(location=(7, -7, 5))
170
+ camera = bpy.context.active_object
171
+ camera.rotation_euler = (1.1, 0, 0.8)
172
+ bpy.context.scene.camera = camera
173
+
174
+ # Add light
175
+ bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
176
+
177
+ # Export
178
+ output_dir = tempfile.mkdtemp()
179
+ glb_path = f"{output_dir}/scene_{uuid.uuid4().hex[:8]}.glb"
180
+
181
+ bpy.ops.export_scene.gltf(
182
+ filepath=glb_path,
183
+ export_format='GLB'
184
+ )
185
+
186
+ return glb_path
187
+ except Exception as e:
188
+ return f"Error: {e}"
189
+
190
+
191
+ # Gradio Interface
192
+ with gr.Blocks(title="BPY MCP") as demo:
193
+ gr.Markdown("""
194
+ # 🎨 BPY MCP Server
195
+ **Blender Python API + OpenVINO AI** - CPU-only
196
+
197
+ Using SmolVLM for image understanding and SmolLM3 for text generation
198
+ """)
199
+
200
+ with gr.Tab("Image Analysis (SmolVLM)"):
201
+ with gr.Row():
202
+ with gr.Column():
203
+ img_input = gr.Image(label="Input Image", type="numpy")
204
+ img_prompt = gr.Textbox(
205
+ label="Prompt",
206
+ value="Describe this image for 3D scene creation"
207
+ )
208
+ img_btn = gr.Button("πŸ” Analyze", variant="primary")
209
+ with gr.Column():
210
+ img_output = gr.Textbox(label="Description", lines=10)
211
+
212
+ img_btn.click(analyze_image, [img_input, img_prompt], img_output)
213
+
214
+ with gr.Tab("Scene Generator (SmolLM3)"):
215
+ with gr.Row():
216
+ with gr.Column():
217
+ scene_type = gr.Dropdown(
218
+ ["forest", "desert", "city", "interior", "space", "underwater"],
219
+ label="Scene Type",
220
+ value="forest"
221
+ )
222
+ scene_style = gr.Dropdown(
223
+ ["realistic", "stylized", "low-poly", "fantasy"],
224
+ label="Style",
225
+ value="realistic"
226
+ )
227
+ scene_btn = gr.Button("πŸ“ Generate Description", variant="primary")
228
+ with gr.Column():
229
+ scene_desc = gr.Textbox(label="Scene Description", lines=10)
230
+
231
+ scene_btn.click(generate_scene_description, [scene_type, scene_style], scene_desc)
232
+
233
+ with gr.Row():
234
+ create_btn = gr.Button("🎬 Create 3D Scene from Description", variant="secondary")
235
+ scene_model = gr.Model3D(label="3D Scene")
236
+
237
+ create_btn.click(create_scene_from_description, scene_desc, scene_model)
238
+
239
+ with gr.Tab("Primitives"):
240
+ with gr.Row():
241
+ with gr.Column():
242
+ prim_type = gr.Dropdown(
243
+ ["cube", "sphere", "cylinder", "cone", "torus", "plane", "monkey"],
244
+ label="Primitive Type",
245
+ value="cube"
246
+ )
247
+ prim_name = gr.Textbox(label="Name", value="MyObject")
248
+ prim_btn = gr.Button("πŸ”² Create Primitive", variant="primary")
249
+ with gr.Column():
250
+ prim_model = gr.Model3D(label="3D Preview")
251
+ prim_file = gr.File(label="Download GLB")
252
+
253
+ prim_btn.click(create_primitive, [prim_type, prim_name], prim_model)
254
+ prim_model.change(lambda x: x, inputs=[prim_model], outputs=[prim_file])
255
+
256
+ gr.Markdown("""
257
+ ---
258
+ ### MCP Server
259
+ ```json
260
+ {
261
+ "mcpServers": {
262
+ "bpy-mcp": {
263
+ "url": "https://dev-bjoern-bpy-mcp.hf.space/gradio_api/mcp/sse"
264
+ }
265
+ }
266
+ }
267
+ ```
268
+
269
+ ### Models
270
+ - **SmolVLM**: `dev-bjoern/smolvlm-int4-ov` (Image-to-Text)
271
+ - **SmolLM3**: `dev-bjoern/smollm3-int4-ov` (Text Generation)
272
+ """)
273
+
274
+
275
+ if __name__ == "__main__":
276
+ demo.launch(server_name="0.0.0.0", server_port=7860, mcp_server=True)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ bpy==5.0.2
2
+ gradio>=5.0.0
3
+ openvino>=2025.0.0
4
+ openvino-genai>=2025.0.0
5
+ optimum[openvino]>=1.20.0
6
+ transformers>=4.45.0
7
+ huggingface_hub>=0.26.0
8
+ numpy>=1.26.0