--- 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} }