Safetensors
English
File size: 12,140 Bytes
0b8bda6
 
 
 
 
 
 
 
bf25589
 
 
 
0b8bda6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e909dca
 
0b8bda6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e909dca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b8bda6
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
---
datasets:
- lamm-mit/Bioinspired3D
language:
- en
base_model:
- meta-llama/Llama-3.2-3B-Instruct
---

# Bioinspired3D

Fine-tuned version of meta-llama/Llama-3.2-3B-Instruct using LoRA adapters for Blender code generation for bioinspired 3D models.

## Abstract

Generative AI has made rapid progress in text, image, and video synthesis, yet text-to-3D modeling for
scientific design remains particularly challenging due to limited controllability and high computational
cost. Most existing 3D generative methods rely on meshes, voxels, or point clouds which can be costly
to train and difficult to control. We introduce Bioinspired123D, a lightweight and modular code-
as-geometry pipeline that generates fabricable 3D structures directly through parametric programs
rather than dense visual representations. At the core of Bioinspired123D is Bioinspired3D, a compact
language model finetuned to translate natural language design cues into Blender Python scripts
encoding smooth, biologically inspired geometries. We curate a domain-specific dataset of over
4,000 bioinspired and geometric design scripts spanning helical, cellular, and tubular motifs with
parametric variability. The dataset is expanded and validated through an automated LLM-driven,
Blender-based quality control pipeline. Bioinspired3D is then embedded in a graph-based agentic
framework that integrates multimodal retrieval-augmented generation and a vision–language model
critic to iteratively evaluate, critique, and repair generated scripts. We evaluate performance on a new
benchmark for 3D geometry script generation and show that Bioinspired123D demonstrates a near
fourfold improvement over its unfinetuned base model, while also outperforming substantially larger
state-of-the-art language models despite using far fewer parameters and compute. By prioritizing
code-as-geometry representations, Bioinspired123D enables compute-efficient, controllable, and
interpretable text-to-3D generation, lowering barriers to AI driven scientific discovery in materials
and structural design.

## What’s in this repo (Hugging Face)

This Hugging Face release contains **Bioinspired3D only**: a LoRA adapter that you load on top of the base model to generate **Blender Python scripts from natural-language prompts**.

For the full **Bioinspired123D** agentic framework (retrieval + VLM critic + iterative repair), see the GitHub repo:
https://github.com/lamm-mit/Bioinspired123D . For training and evaluation scripts, see also the project GitHub.  


## Usage

### Install

```bash
pip install -U transformers accelerate peft torch
```

### Load the base model + LoRA adapter
```bash
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import torch

BASE_MODEL = "meta-llama/Llama-3.2-3B-Instruct"
LORA_ADAPTER = "rachelkluu/bioinspired3D"

# Set this to your preferred device, e.g. "cuda:0" or "cpu"
DEVICE_3D = "cuda:0"

bio3d_tok = AutoTokenizer.from_pretrained(BASE_MODEL)

base_model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL,
    torch_dtype=torch.float16,
    device_map={"": DEVICE_3D},
)

bio3d_model = PeftModel.from_pretrained(base_model, LORA_ADAPTER)
bio3d_model.eval()

def format_input(prompt: str) -> str:
    return (
        "<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n"
        "You are a helpful assistant<|eot_id|>"
        "<|start_header_id|>user<|end_header_id|>\n\n"
        f"{prompt}<|eot_id|>"
        "<|start_header_id|>assistant<|end_header_id|>\n\n"
    )
```
### Load utility functions
```bash
def extract_blender_code(model_out: str) -> str:
    matches = list(re.finditer(r"```python\s*(.*?)```", model_out, flags=re.DOTALL))
    if matches:
        return matches[-1].group(1).strip()
    pos = model_out.rfind("import bpy")
    return model_out[pos:].strip() if pos != -1 else model_out.strip()


def clean_blender_code(text: str) -> str:
    if not text:
        return "import bpy"
    code = text.strip()
    code = code.replace("```python", "").replace("```", "")
    code = re.sub(r"[\x00-\x08\x0b-\x1f]", "", code)
    if not code.lstrip().startswith("import bpy"):
        code = "import bpy\n" + code
    return code
```
### Generate Blender code from a natural-language prompt
```bash
prompt = """Write Blender code to make a cellular structure."""

formatted = format_input(prompt)
inputs = bio3d_tok(formatted, return_tensors="pt").to(bio3d_model.device)

with torch.no_grad():
    outputs = bio3d_model.generate(
        **inputs,
        max_new_tokens=2048,
        do_sample=True,
        temperature=0.1,
        top_p=0.9,
    )

raw = bio3d_tok.decode(outputs[0], skip_special_tokens=True)
raw_code = extract_blender_code(raw)
blender_code = clean_blender_code(raw_code)

print(blender_code)
```
### Prompting tips

Input: Natural language design intent (for example: “tubular structure with noisy placement”, “helical material with cylindrical fibers”, “smoothed cellular structure”).

Output: A Blender Python script (intended to be executed in Blender) that constructs the requested geometry.

To encourage explicit reasoning, append a variant of: “Think step by step.” to the end of your prompt. For example: "Write Blender code to make a tubular structure with z-aligned tubules. Think step by step."

## Sample output #1

### Example prompt
```text
Write Blender code to make a cellular structure with smooth curves and layers on top and bottom
```
### Example model output (Blender Python)
```python
import bpy
import addon_utils

# Clear the scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# Parameters for the sandwich structure
strut_thickness = 0.2  # Thickness of the struts
smoothness_level = 3  # Level of smoothness
slab_thickness = 0.1  # Thickness of the top and bottom slabs

def create_cellular_sandwich():
    # Function to create a smooth cellular sandwich structure.
    # Steps:
    # 1. Create a cube.
    # 2. Fracture the cube into pieces.
    # 3. Apply wireframe modifier for cellular effect.
    # 4. Apply subsurface modifier for smoothing.
    # 5. Create top and bottom slabs.

    # Enable the Fracture Cell add-on
    addon_utils.enable('object_fracture_cell')

    # Create a cube
    bpy.ops.mesh.primitive_cube_add()
    cube = bpy.context.active_object

    # Scale the cube
    cube.scale = (1, 1, 0.75)
    bpy.ops.object.transform_apply(scale=True)

    # Fracture the cube
    bpy.ops.object.add_fracture_cell_objects(
        source={'PARTICLE_OWN'},
        source_limit=100,
        source_noise=0.5,
        cell_scale=(1, 1, 1),
        recursion=0,
        recursion_source_limit=8,
        recursion_clamp=250,
        recursion_chance=0.25,
        recursion_chance_select='SIZE_MIN',
        use_smooth_faces=False,
        use_sharp_edges=True,
        use_sharp_edges_apply=True,
        use_data_match=True,
        use_island_split=True,
        margin=0.001,
        material_index=0,
        use_interior_vgroup=False,
        mass_mode='VOLUME',
        mass=1,
        use_recenter=True,
        use_remove_original=True,
        collection_name='',
        use_debug_points=False,
        use_debug_redraw=True,
        use_debug_bool=False
    )

    # Remove the original cube
    if cube and cube.name in bpy.data.objects:
        bpy.data.objects.remove(cube, do_unlink=True)

    # Join fractured pieces
    bpy.ops.object.select_all(action='DESELECT')
    for obj in bpy.context.scene.objects:
        if obj.type == 'MESH':
            obj.select_set(True)
            bpy.context.view_layer.objects.active = obj
    bpy.ops.object.join()

    # Clean up geometry
    active_mesh = bpy.context.view_layer.objects.active
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.remove_doubles(threshold=0.2)
    bpy.ops.mesh.dissolve_limited()
    bpy.ops.object.mode_set(mode='OBJECT')

    # Wireframe Modifier
    wireframe_mod = active_mesh.modifiers.new(name='Wireframe', type='WIREFRAME')
    wireframe_mod.thickness = strut_thickness
    wireframe_mod.offset = -1
    wireframe_mod.use_even_offset = False
    wireframe_mod.use_relative_offset = True
    wireframe_mod.use_replace = True
    bpy.ops.object.convert(target='MESH')

    # Subsurface Modifier
    subsurf_mod = active_mesh.modifiers.new(name='Subdivision', type='SUBSURF')
    subsurf_mod.levels = smoothness_level
    subsurf_mod.render_levels = smoothness_level

    # Create slabs
    slab_height = 1.5
    bottom_slab_z = -slab_height / 2 - slab_thickness / 2
    top_slab_z = slab_height / 2 + slab_thickness / 2

    # Bottom slab
    bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, bottom_slab_z))
    bottom_slab = bpy.context.active_object
    bottom_slab.scale = (1, 1, slab_thickness / 2)
    bpy.ops.object.transform_apply(scale=True)

    # Top slab
    bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, top_slab_z))
    top_slab = bpy.context.active_object
    top_slab.scale = (1, 1, slab_thickness / 2)
    bpy.ops.object.transform_apply(scale=True)

# Create the sandwich structure
create_cellular_sandwich()
```
### Rendered example (image)
![image](https://cdn-uploads.huggingface.co/production/uploads/63cad3071b705cc951e26b3a/Z3Xd-GleEmGgj4YL3Cg9N.png)


## Sample output #2
### Example prompt
```text
Write Blender code to make a helical bioinspired structure with flat rectangular struts in each layer
```
### Example model output (Blender Python)
```python
import bpy
import math

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# Parameters for the helical fiber structure
ply_count = 7  # Number of plies in the structure
angle_increment = 10  # Rotation angle for each ply
fiber_form ='rect'  # Shape of the fibers

# Dimensions of the structure
structure_height = 2  # Total height of the structure
plate_dimension = 2  # Width and length of each plate
thickness_per_ply = structure_height / ply_count  # Thickness of each ply
fiber_gap = 0.01  # Gap between fibers

def create_fiber(form, diameter, x, y, z, thickness):
    if form =='rect':
        bpy.ops.mesh.primitive_cube_add(size=1.0, location=(x, y, z), rotation=(0, math.radians(90), 0))
        fiber = bpy.context.object
        fiber.dimensions = (thickness, diameter, plate_dimension)
        return fiber

def generate_ply(index):
    # Generate a ply of fibers in a helical arrangement.
    z_position = index * thickness_per_ply
    rotation_angle = index * angle_increment

    bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0, 0, z_position))
    empty_object = bpy.context.object

    fiber_diameter = thickness_per_ply
    fiber_distance = fiber_diameter + fiber_gap
    fiber_count = max(1, int(plate_dimension / fiber_distance))

    total_fiber_space = fiber_count * fiber_distance
    start_y_position = -plate_dimension / 2 + fiber_distance / 2 + (plate_dimension - total_fiber_space) / 2

    for i in range(fiber_count):
        fiber_y_center = start_y_position + i * fiber_distance
        fiber_instance = create_fiber(fiber_form, fiber_diameter, 0, fiber_y_center, z_position, thickness_per_ply)
        fiber_instance.parent = empty_object
        fiber_instance.matrix_parent_inverse = empty_object.matrix_world.inverted()

    empty_object.rotation_euler[2] = math.radians(rotation_angle)
    return empty_object

# Create the helical structure
for i in range(ply_count):
    generate_ply(i)
```

### Rendered example (image)
![image](https://cdn-uploads.huggingface.co/production/uploads/63cad3071b705cc951e26b3a/d7c2kNs2uwqQk8zPOlea9.png)


## Notes:
This adapter is meant to be used with the specified base model. Generated scripts should be treated like code: run in a sandboxed environment and validate geometry as needed.

## Citation

If you use Bioinspired3D or the broader Bioinspired123D framework in your work, please cite:

```bibtex
@article{luu2026bioinspired123d,
  title={Bioinspired123D: Generative 3D Modeling System for Bioinspired Structures},
  author={Luu, Rachel K. and Buehler, Markus J.},
  year={2026}
}