Subscribe to our free newsletter

To make sure you won't miss any valuable content we share with our community.

Introduction To 3D Modeling in Three.js

With the Metaverse and virtual reality applications being created these days, 3D modeling and webGL-based apps are becoming more popular among designers and developers. The concept of creating a virtual world with the closest features to that of the real world seems more and more feasible with the new tools like Blender or Three.js. The whole idea of creating a virtual reality (VR) next to augmented reality (AR) is also becoming the hot passion of the day.

Introduction to Three.js 3D Modeling

In this article, we will introduce Three.js and its wide applications in summary and then we will get started with the very first project you can start in Three.js as a beginner. If you have had experience with animations and 3D modeling in Blender, you will find Three.js like a piece of cake and if you haven’t, don’t worry because we are going to explain all the details of the simplest project you can find in Three.js.

Three.js is a cross-browser JavaScript library and API which uses WebGL to create and display 3D animated computer graphics in a web browser. The range of applications you can create using Three.js is wide. You can use this library for creating online games, animated websites, user-interactive 3D widgets, Metaverse, and so on. Imagine creating a 3D online shopping center where you can virtually see the items you want to purchase or seeing a 3D animated architecture of a building before it is built or wandering inside a virtual world with your 3D animated Avatar. The variety of use cases for Three.js is so wide that we can confidently say “Sky is the limit”.

Getting Started with a Simple Project of Three js 3D Modeling

First off, make sure you have npm installed. You can start your first project by git cloning one of the simple Three.js projects to find a boilerplate for further development. Most of these projects can be found on Github, animate simple objects like a cube, sphere, torus, and so on. The animation is usually rotation. Running these projects and developing them step by step by changing some of the features about rendering, lighting, camera perspective, the color of the objects, textures, animations, and so on will help you find the necessary tools to design more complex objects. mkdir Starting_Three
cd Starting_Three
git clone https://github.com/designcourse/threejs-webpack-starter
Then, enter the following commands one by one to install the dependencies: npm install
npm install webpack-dev-server -g
Now, change the directory to threejs-webpack-starter: cd threejs-webpack-starter Then, enter the followings one by one to get a pre-designed 3D animated torus: npm i
npm run dev
npm run build
And, you will be able to see the animated (rotating) torus like this in your browser:

blender

Now, let’s head over to our the JavaScript code behind the scenes and see the elements of this 3D design and rendering. In src folder, script.js file, you will see:

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'dat.gui'

// Debug
const gui = new dat.GUI()
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
 

Up to here, you can write the code and forget it because you are not going to modify it. But from this part on, you should begin to look closer at the features of the design.

3D Modeling Features in Three js

The first important feature is the geometry of the object or in other words the very first shape that you start your design with. This shape could be a cube, a cylinder, a sphere, and so on. Here, for instance, we have a torus.

The second one is the materials which are the type of the mesh and its color. As you can see the color can be set by a hexadecimal number. In your code, the number is 0xff000f0. However, we have changed the number to 0xff0ff0 which will turn the color to pink.

After defining the objects and the materials, It is time to create the object out of these features. To do so, we can use the new THREE.Mesh (geometry, material) to apply the defined geometry and material to the object and then use the scene.add() to add the object to the scene. There are other features of the scene such as lighting, camera, animation, and so on.

 // Objects
const geometry = new THREE.TorusGeometry( .7, .2, 16, 100 );
// Materials
const material = new THREE.MeshBasicMaterial()
material.color = new THREE.Color(0xff0ff0)
// Mesh
const sphere = new THREE.Mesh(geometry,material)
scene.add(sphere)
// Lights
const pointLight = new THREE.PointLight(0xffffff, 0.1)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight) 


Of course, we are not going to talk about the rest of the code as most of the parts are not the subject of our article and for now, we can set and forget them. We are going to talk about these details in the next articles where the animation and other details are going to be modified.

Blender and Three.js

If you have ever worked with Blender, you are most probably familiar with many concepts of 3D modeling, designing, rendering animations, shadings, and so on. The The good news is that Three.js is pretty much the same as Blender with the same geometry objects and meshing formations. Another good news for Blender developers is that you can import the Blender 3D models into Three.js. So if you have a good experience in 3D modeling in Blender you can export your UV mapped 3D models as glTF and then import these glTF files in Three.js. We will explain the detailed procedures in the next tutorials.

Final Word

In this article, we have introduced Three.js as a tool to create 3D web applications,3D games, Virtual Reality, Augmented Reality, and other 3D-based productions leading to the development of the Metaverse. We have also compared Blender with Three.js, explaining the similarities and the capability of importing the 3D models from Blender to Three.js.

Moreover, we have provided the guidelines for setting up the simplest the project you can run in Three.js and explain the important scripts that you can modify to change the 3D model features. Hopefully, we will explain more details about the lighting, shading, animations, and other useful details of decorating a model in Three.js.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Tell us about your ideas
Fill in the Form
3D Development

How to Design A Wearable in Blender: A Step by Step Guide

Designing and manufacturing custom-made wearables, splints and casts is one of the hottest topics of the day. To design a custom-made wearable object in Blender, considering that they shouldn’t have any sharp edges in addition to having small and large lattice structures on their shell, makes the design a bit complicated. However, using some useful scripts in Python and the guides that we give you throughout this tutorial, you are going to finally learn how to design different kinds of wearables.

Design A Wearable in Blender

design a wearable in Blender

The example that we are going to work on is wearable for the knee with the fillets, lattice structures, and a hole with a certain size for placing a certain kind of sensor. We are going to provide a panel with tools to hasten the process of our design.

design a wearable in Blender

In the first 2 parts of the tutorial, we work on the scripts related to the utility functions. And if you are not interested in the details of these useful functions, you can skip ahead straight to the 3rd part.

Writing the Utility Functions to Design A Wearable in Blender

The below utility functions will help us a lot in simplifying the execution of the design and the readability of our scripts. Many of the functions written in the following are explained throughout the article.

import  bpy
import bmesh
import math
import os
import sys

####################################################################
#####                Utility Functions
####################################################################

class BOOLEAN_TYPE:
        UNION = 'UNION'
        DIFFERENCE = 'DIFFERENCE'
        INTERSECT = 'INTERSECT'


Boolean Function

The above function will apply the boolean operation on the object specified according to the type of boolean categorized in the class BOOLEAN_TYPE:

The difference, Union, and Intersect.

def make_boolean(obj1, obj2, boolean_type):
        if not obj1 or not obj2:
                return

        modifier = obj1.modifiers.new(name='booly', type='BOOLEAN')
        modifier.object = obj2
        modifier.operation = boolean_type

        res = bpy.ops.object.modifier_apply({"object": obj1}, apply_as='DATA', modifier=modifier.name)
        assert "FINISHED" in res, "Error"


Fixing the Non-Manifold Meshes

Using the function below, we can fix the object with open meshes or what we know as non-manifold objects. At first, we remesh the object to a voxel-based mesh object. Then, we check if the object has any non-manifold meshes or not. If it has we will remove the non-manifold meshes. And finally will smooth the object and meshes of it one more time.

def fixMesh(obj_name):
        make_voxel_remesh(get_object_by_name(obj_name), 0.5)
        if is_object_have_non_manifolds(get_object_by_name(obj_name)):
                print(obj_name, "have non manifolds")
                if remove_object_non_manifold_loops(obj_name, loops=2):
                        print("Filled:", fill_non_manifolds(obj_name))
                        obj = get_object_by_name(obj_name)
                        make_smooth_remesh(obj, 9, 0.9, 1, True, True)    


The functions below, which are used above, will check if the object has any non-manifold meshes or not, remove the non-manifold loops and also fill the non-manifold meshes.

def is_object_have_non_manifolds(obj):
        assert obj.type == 'MESH', "Unsupported object type"

        bmo = bmesh.new()
        bmo.from_mesh(obj.data)

        have = False
        for edge in bmo.edges:
                if not edge.is_manifold:
                        have = True
                        break

        if not have:
                for vert in bmo.verts:
                        if not vert.is_manifold:
                                have = True
                                break

        bmo.free()  # free and prevent further access
        return have

def is_object_contain_selected_vertices(obj):
        if obj.mode == "EDIT":
                bm = bmesh.from_edit_mesh(obj.data)
        else:
                bm = bmesh.new()
                bm.from_mesh(obj.data)

        selected = False
        for v in bm.verts:
                if v.select:
                        selected = True
                        break
        bm.free()
        return selected


def remove_object_non_manifold_loops(obj_name, loops=0):
        deselect_objects()
        select_object_by_name(obj_name)
        activate_object_by_name(obj_name)

        removed = False
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="VERT")
        bpy.ops.mesh.select_non_manifold(extend=False)
        if is_object_contain_selected_vertices(get_object_by_name(obj_name)):
                if loops:
                        for i in range(loops):
                                bpy.ops.mesh.select_more()
                        bpy.ops.mesh.delete(type='FACE')
                else:
                        bpy.ops.mesh.delete(type='VERT')
                removed = True
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')
        return removed

def fill_non_manifolds(obj_name):
        deselect_objects()
        select_object_by_name(obj_name)
        # activate_object_by_name(obj_name)

        filled = False
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="VERT")
        bpy.ops.mesh.select_non_manifold(extend=False)
        if is_object_contain_selected_vertices(get_object_by_name(obj_name)):
                bpy.ops.mesh.fill(use_beauty=True)
                bpy.ops.mesh.normals_make_consistent(inside=False)
                bpy.ops.mesh.faces_shade_smooth()
                filled = True
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')
        return filled


Object Selection and Deleting to Design a Wearable in Blender

The functions below are useful when dealing with the list of items and actions like selecting, deselecting, deleting, activating the object or getting it by name.

def delete_object(objName):   
        bpy.ops.object.select_all(action='DESELECT')
        bpy.data.objects[objName].select_set(True) # Blender 2.8x
        bpy.ops.object.delete()

def deselect_objects():
        bpy.ops.object.select_all(action='DESELECT')
    
def select_object_by_name(obj_name):
        get_object_by_name(obj_name).select_set(True) # Blender 2.8x

def activate_object_by_name(obj_name):
        bpy.context.view_layer.objects.active = get_object_by_name(obj_name)    

def get_object_by_name(obj_name):
        assert obj_name in bpy.data.objects, "Error getting object by name:	{}".format(obj_name)
        obj = bpy.data.objects[obj_name]
        return obj


Remesh Function

The first remesh function will convert the meshes from triangular to voxel-based and the second one will remesh the object by smoothing it.

def make_voxel_remesh(obj, voxel_size, adaptivity=0, use_smooth_shade=True):
        modifier = obj.modifiers.new(name='remesh', type='REMESH')
        modifier.mode = 'VOXEL'
        modifier.voxel_size = voxel_size
        modifier.adaptivity = adaptivity
        modifier.use_smooth_shade = use_smooth_shade
        res = bpy.ops.object.modifier_apply({"object": obj}, apply_as='DATA', modifier=modifier.name)
        assert "FINISHED" in res, "Error"

def make_smooth_remesh(obj, octree_depth=9, scale=0.9, threshold=1, use_smooth_shade=True,         
                use_remove_disconnected=True):
        modifier = obj.modifiers.new(name='remesh', type='REMESH')
        modifier.mode = 'SMOOTH'
        modifier.use_smooth_shade = use_smooth_shade
        modifier.octree_depth = octree_depth
        modifier.scale = scale
        modifier.use_remove_disconnected = use_remove_disconnected
        modifier.threshold = threshold

        res = bpy.ops.object.modifier_apply({"object": obj}, apply_as='DATA', modifier=modifier.name)

        assert "FINISHED" in res, "Error"


The above functions work for different kinds of remeshing.

Utility Functions to Design A Wearable in Blender

design a wearable in Blender

In this 2nd part of the tutorial, we are going to continue writing the useful utility functions for designing different kinds of wearables. IMPORTANT NOTE:

Remember that the scripts we are using here are related to Blender version 2.83 and if you are working with any other versions, it is probable that the scripts might differ a little bit, but we will show you ways to find the proper functions if there are any differences at all.

Functions for Creating the Fillets

The following function will create a proper fillet for a shell with a pattern (for example a circle) and an object that has been cut (with the selected vertices at the cutting curve). We can later join this fillet to the main shell. This kind of lattice is of the additive type not subtractive like most fillets.

def make_Fillet2(ob,pattern):
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.duplicate_move()
        bpy.ops.object.editmode_toggle()
                            
        bpy.ops.object.convert(target='CURVE')
        bpy.context.object.data.bevel_object = bpy.data.objects[pattern]
        bpy.ops.object.convert(target='MESH')
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.normals_make_consistent(inside=False)
        bpy.ops.object.editmode_toggle()


Using the following function, we will get the thickness of the shell and creates a circular curve pattern for creating the fillet.

def Fillet_pattern(thickness):
        #Fillet pattern based on thickness  
        bpy.ops.curve.primitive_bezier_circle_add(radius=thickness/2, 				                                                                                                                        
                enter_editmode=False, align='WORLD', location=(0, 0, 0))
        bpy.context.object.data.dimensions = '2D'
        bpy.context.object.data.fill_mode = 'BOTH'
        for obj in bpy.context.selected_objects:
                obj.name = "Fillet"


Function for Creating A Shell Around the Object

With the help of the function below, we can create a shell using the data like the thickness, offset, and the object that we are going to create a shell out of.

def make_solidify(obj, offset, thickness, only_external=False):
        modifier = obj.modifiers.new(name='solidify', type='SOLIDIFY')
        modifier.offset = offset
        modifier.thickness = thickness
        if only_external:
                modifier.use_rim = True  # Fill Rim
                modifier.use_rim_only = True

        res = bpy.ops.object.modifier_apply({"object": obj}, apply_as='DATA', modifier=modifier.name)

        assert "FINISHED" in res, "Error"


Object Translation Functions

Using all the following functions, you can translate an object to a certain point, and get the specified point by the user.

def object_closest_point_mesh(p, obj):

        result, location, normal, face_index = obj.closest_point_on_mesh(p)
        assert result, "Can't find closest point on mesh"
        location = location.to_tuple()
        normal = normal.to_tuple()
        return location + normal  # return tuple of 6 floats

def obj_transform(filename, obj_name, size, location, angle):
    
        ob = bpy.context.scene.objects[obj_name]       # Get the object
        bpy.ops.object.select_all(action='DESELECT') # Deselect all objects
        bpy.context.view_layer.objects.active = ob   # Make the cube the active object
        ob.select_set(True)             

        obj = bpy.data.objects[obj_name]
        obj.location = location

        bpy.ops.transform.rotate(value=angle, orient_axis='Z',
                orient_type='GLOBAL',
                orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
                constraint_axis=(False, False, True))
                                                            
def object_put_part2(part_name, point, obj, scale, obj_name):
        vx,vy,vz,a,b,c = object_closest_point_mesh(point, obj)
        a1 = math.atan2(b, a)
        obj_transform(part_name, obj_name, scale, (point[0], point[1], point[2]), a1)


The above 3 functions will translate the object to the point given in the def object_put_part2 function.

def get_vertex():
        bm = bmesh.new()
        ob = bpy.context.active_object
        bm = bmesh.from_edit_mesh(ob.data)

        points = []
        for v in bm.verts:
            if (v.select == True):
                obMat = ob.matrix_world
                points.append(obMat @ v.co)
        
        for p in points:
            pOb = bpy.data.objects.new("VertexPoint", None)
            bpy.context.collection.objects.link(pOb)
            pOb.location = p
        return p



The above function will get the vertex given by the user in the edit mode and store it in a variable.

More Dependency Functions

The below dependency functions are mainly for selecting objects and optimized boolean union. The optimized boolean union creates no open or buggy meshes on the object whereas the simple boolean union created non-manifold meshes. Fixing the meshes in another way than mentioned, importing the .stl files, and so on.

def Fix(obj_name):
        if is_object_have_non_manifolds(get_object_by_name(obj_name)):
                print(obj_name, "have non manifolds")
                if remove_object_non_manifold_loops(obj_name, loops=2):
                        print("Filled:", fill_non_manifolds(obj_name))
                        obj = get_object_by_name(obj_name)


The above function independently fixes the mesh.

def make_custom_context(*object_names, base_context=None, mode=None):
        if base_context is not None:
                ctx = base_context
        else:
                ctx = {}
        if mode is not None:
                assert mode in ('OBJECT', 'EDIT'), "Wrong mode used"
                ctx['mode'] = mode
        objs = [get_object_by_name(obj_name) for obj_name in object_names]
        ctx['active_object'] = ctx['object'] = objs[0]
        ctx['selected_editable_objects'] = ctx['selected_objects'] = objs
        ctx['editable_objects'] = ctx['selectable_objects'] = ctx['visible_objects'] = objs
        return ctx

def import_stl(filename, obj_name=None, deselect=True, path=None):
        if path is None:
                filepath = os.path.abspath('%s.stl' % filename)
        else:
                filepath = os.path.join(path, '%s.stl' % filename)

        deselect_objects()
        bpy.ops.import_mesh.stl(filepath=filepath)

        bpy.ops.object.mode_set(mode='OBJECT')
        if deselect:
                for obj in bpy.context.selected_objects:
                        set_mesh_items_selection(obj)

        if obj_name:
                set_selected_object_name(obj_name)


The above function imports the object with the given name and the new name that is going to appear in the list of items.

Another Kind of Remeshing


def Remesh_Smooth_Voxel(obj):
        make_smooth_remesh(obj, octree_depth=8)  
        make_voxel_remesh(obj, 0.5)  # it is necessary because it normal works with self intersections
        make_smooth_remesh(obj, octree_depth=8)  # to make out smoother


And another function for creating shell with a little different functionality:

def solidify(obj,offset,thickness): 
        bpy.ops.object.modifier_add(type='SOLIDIFY')
        bpy.context.object.modifiers["Solidify"].offset = offset
        bpy.context.object.modifiers["Solidify"].thickness = thickness
        bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Solidify")

def set_selected_object_name(obj_name):
        for obj in bpy.context.selected_objects:
                obj.name = obj_name

def set_mesh_items_selection(obj, select=False):
        if obj.type != 'MESH':
                return
        set_mesh_data_selection(obj.data.vertices, select)
        set_mesh_data_selection(obj.data.edges, select)
        set_mesh_data_selection(obj.data.polygons, select)

def set_mesh_data_selection(items, select=False):
        for item in items:
                item.select = select     

def makeUnionOpt(*object_names):
        ctx = bpy.context.copy()
        if object_names:
                ctx = make_custom_context(*object_names, base_context=ctx, mode='OBJECT')
        bpy.ops.object.join(ctx)  # mostly the same as export/import combination



design a wearable in Blender

Designing the Main Panel to Automate Designing a Wearable in Blender

In the previous parts, we wrote the necessary utility functions and now we are ready to create a panel in Blender so that we can automate the process of 3D designing the custom-made wearables.

So we continue our script by designing the main panel containing 6 important buttons. 1 class is going to be defined for the main panel and 6 classes for the 6 buttons.

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
        bl_label = "Object Adder"
        bl_idname = "VIEW_PT_MainPanel"
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
        bl_category = 'Design Automation'
    
        def draw(self, context):
                layout = self.layout
                layout.scale_y = 1.2
        
                row = layout.row()
                row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
                row = layout.row()
                row.operator("wm_shell.myop", text= "Import & Create a shell")
                row = layout.row()
                row.operator("wm_trim.myop", text= "Draw the cutting pattern")           
                row = layout.row()
                row.operator("wm_confirm.myop", text= "Confirm the cut")
                row = layout.row()
                row.operator("wm_lattice.myop", text= "Lattice")
                row = layout.row()
                row.operator("wm_Fillet.myop", text= "Fillet")
                row = layout.row()
                row.operator("wm_union.myop", text= "Optimized Final Union")


In the main panel class, we have determined the structure of the user interface of the panel containing the buttons, the classes they are going to use, and their name on the screen.

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Shell_myOp(bpy.types.Operator):
        """Enter the thickness of the Shell"""
        bl_label = "Enter the thickness of the shell"
        bl_idname = "wm_shell.myop"

        def execute(self, context):        
                thickness = 0.5
                import_stl("Leg", obj_name="Scan", deselect=True, path='/home/mohamad/Desktop/Blender')
                #change the path to None or to your directory
       
                obj = get_object_by_name("Scan")
                make_solidify(obj, thickness/2, thickness, only_external=True)      

                # Scan Fillet Object
                import_stl("Leg", obj_name="LegFillet", deselect=True,     
                        path='/home/mohamad/Desktop/Blender')
                import_stl("Leg", obj_name="Object", deselect=True, path='/home/mohamad/Desktop/Blender')

                obj = get_object_by_name("Object")
                solidify(obj,0,thickness)
                obj2 = get_object_by_name("LegFillet")
                make_boolean(obj2, obj, 'UNION')
                obj = get_object_by_name("LegFillet")
                Remesh_Smooth_Voxel(obj)        
                delete_object('Object')
        
                return {'FINISHED'}
        def invoke(self, context, event):
                return context.window_manager.invoke_props_dialog(self)



The above class will import the leg.stl scan file and change its name to Scan then it will create a shell out of the leg scan. It also creates an object like the imported leg and will later use for the fillet.

class WM_Trim_myOp(bpy.types.Operator):
        """Draw The trim pattern"""
        bl_label = "Draw The trim pattern"
        bl_idname = "wm_trim.myop"
      
        def execute(self, context):     
                bpy.ops.curve.primitive_nurbs_curve_add(enter_editmode=False, 									                        
                        align='WORLD', location=(0,0,0))
                bpy.ops.object.editmode_toggle()
                bpy.context.scene.tool_settings.curve_paint_settings.curve_type = 'BEZIER'
                bpy.ops.curve.delete(type='VERT')
        
                return {'FINISHED'}

        def invoke(self, context, event):
                return context.window_manager.invoke_props_dialog(self)


The above class will help the user draw the cutting curve.

class WM_Confirm_myOp(bpy.types.Operator):
        """Click OK to confirm"""
        bl_label = "Click OK to confirm"
        bl_idname = "wm_confirm.myop"
          
        def execute(self, context):
                name = self.name                     
                bpy.context.object.data.dimensions = '2D'
                bpy.context.object.data.fill_mode = 'BOTH'
                bpy.context.object.data.extrude = 1000
                bpy.ops.object.editmode_toggle()
                context = bpy.context
                scene = context.scene
                cube = scene.objects.get("NurbsCurve")
                bpy.ops.object.convert(target='MESH')
        
                bpy.ops.object.editmode_toggle()
                bpy.ops.mesh.select_all(action='SELECT')
                bpy.ops.object.editmode_toggle()
                
                obj1 = get_object_by_name('Scan')
                obj2 = get_object_by_name('NurbsCurve')
                obj3 = get_object_by_name('LegFillet')
                
                make_boolean(obj1, obj2, 'DIFFERENCE')
                make_boolean(obj3, obj2, 'DIFFERENCE')
                delete_object("NurbsCurve")

                return {'FINISHED'}
        def invoke(self, context, event):
                return context.window_manager.invoke_props_dialog(self)


The above class will let the user confirm the drawn cut.

class WM_Lattice_myOp(bpy.types.Operator):
        """In editmode determine the point of lattice"""
        bl_label = "In editmode determine the point of lattice"
        bl_idname = "wm_lattice.myop"
     
        Lattice_Name = bpy.props.StringProperty(name= "Enter the name of lattice", default= '')     
      
        def execute(self, context):
        
                Lattice_Name = self.Lattice_Name
                point = get_vertex()            
                bpy.ops.object.editmode_toggle()         
                obj = get_object_by_name('Scan')
                obj2 = get_object_by_name('%s'%Lattice_Name)  
                obj3 = get_object_by_name('LegFillet')               
                object_put_part2('%s'%Lattice_Name, point, obj, 1, '%s'%Lattice_Name)
                make_boolean(obj,obj2,'DIFFERENCE')                                                                                
                make_boolean(obj3,obj2,'DIFFERENCE')

                delete_object("VertexPoint")
                delete_object('%s'%Lattice_Name)
        
                return {'FINISHED'}
        def invoke(self, context, event):
                return context.window_manager.invoke_props_dialog(self)


The above class will create a lattice based on the point and the object given by the user.

class WM_Fillet_myOp(bpy.types.Operator):
        """Click OK"""
        bl_label = "Click OK"
        bl_idname = "wm_fillet.myop"
      
        def execute(self, context):
        
                Fillet_pattern(0.2)        
                ob = bpy.context.scene.objects["LegFillet"]       # Get the object
                bpy.ops.object.select_all(action='DESELECT') # Deselect all objects
                bpy.context.view_layer.objects.active = ob   # Make active  
                ob.select_set(True)                   
                make_Fillet2('LegFillet','Fillet')
                
                return {'FINISHED'}
        def invoke(self, context, event):
                return context.window_manager.invoke_props_dialog(self)


The above class will automatically create a fillet for all the sharp edges.

class WM_Union_myOp(bpy.types.Operator):
        """Click OK"""
        bl_label = "Click OK"
        bl_idname = "wm_union.myop"
      
        def execute(self, context):
                makeUnionOpt('Scan','LegFillet')
                Fix('Scan')     
                return {'FINISHED'}

        def invoke(self, context, event):
                return context.window_manager.invoke_props_dialog(self)


The above class will boolean union the object and its fillet. And we will finally finish our script by registering and unregistering the classes.

####################################################################
#####                     Register and Unregister
####################################################################
        
def register():
        bpy.utils.register_class(MainPanel)
        bpy.utils.register_class(WM_Shell_myOp)
        bpy.utils.register_class(WM_Trim_myOp)
        bpy.utils.register_class(WM_Confirm_myOp)
        bpy.utils.register_class(WM_Lattice_myOp)                                            
        bpy.utils.register_class(WM_Fillet_myOp)
        bpy.utils.register_class(WM_Union_myOp)     

def unregister():
        bpy.utils.unregister_class(MainPanel)
        bpy.utils.unregister_class(WM_Shell_myOp)
        bpy.utils.unregister_class(WM_Trim_myOp)
        bpy.utils.unregister_class(WM_Confirm_myOp)  
        bpy.utils.unregister_class(WM_Lattice_myOp)
        bpy.utils.unregister_class(WM_Fillet_myOp)
        bpy.utils.unregister_class(WM_Union_myOp)                                                        
    
if __name__ == "__main__":
        register()



Testing the Panel For Designing A Wearable Properly in Blender

After writing all the scripts, it is now time to test the panel we have created for our wearable design automation in Blender. First of all, click Import and create a shell.

design a wearable in Blender

Then, click Draw the curve to be able to draw the cutting curve with the pencil tool on the left-hand side of the Blender UI.

design a wearable in Blender

Then, press Confirm the cut to apply the trimming tool.

design a wearable in Blender

Do the same for the lower limb and remove the ankle.

design a wearable in Blender

Now make sure you have large meshes on your object. If you don’t, you can use the Decimate modifier.

design a wearable in Blender

Now, either design or import your lattice shape. Here we have designed a shape and have named it Cylinder. After that determine a point for placing the lattice. Click Lattice, enter the name of the lattice in the box, and then click OK. Notice that this is the larger lattice and it is mainly used for placing a sensor or a necessary medical item.

As you can see, we have the lattice created on the point we had determined.

Now, simply click on Fillet and OK buttons to get all the fillets created.

It is now time to get the wireframe of the object for smaller lattice structures. We can go to modifiers >> Wireframes and after that smooth modifier.

Then, we can use the Multiresolution modifier to subdivide the meshes.

After subdividing and smoothing the object a number of times we can finally boolean union the fillet and the shell with the last button of the panel.

And here is the final result!

Summing Up

In this tutorial, we have managed to create a panel for designing a wearable, splint or cast for the 3D scan of a limb. At first, we have defined some utility functions to organize and simplify the execution of the steps of the design in Blender Python. Afterward, we have used the functions to create simple steps for the user to be able to quickly design their wearables with the aid of only six buttons or six simple steps. In the end, we have taught the steps of designing a wearable using the designed panel.

Download this Article in PDF format

web development

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
3D Development

Different Kinds of Lattice Structure Using Blender

In this article, we are going to directly use the Blender modifiers to create an aesthetic costume lattice structure in Blender. But, as we have said before, the purpose of creating these kinds of lattice structures is art and aesthetics, because the end result is more stochastic rather than deterministic. We start with adding a simple shape like a cube, cylinder, or cone and use modifiers to change the formation of the mesh, then use the Wireframe modifier to make a lattice form out of our mesh, after that, by using other modifiers like Multiresolution or smooth, we post-process our design.

Uisng Blender Modifiers to Create A Lattice Structure

In this 3rd part of our tutorial, we are going to directly use the Blender modifiers to create an aesthetic costume lattice structure but as we have said before the purpose of creating this kind of lattice structure is art and aesthetics, because the end result is more stochastic rather than deterministic.

blender

lattice structure

We start with adding a simple shape like a cube, cylinder, or cone and use modifiers to change the formation of the mesh, then use the Wireframe modifier to make a lattice form out of our mesh, after that, by using other modifiers like Multiresolution or smooth, we post-process our design. With that said, let’s get started:
    1. creating a cone:
Click on add button, on top of the page and go to mesh >> cone. And you can see that a cone like below is created:

lattice

    2. Modifying the object:
In the modifiers section, choose the multiresolution modifier and by using Multiresulotion >> Subdivide, you can subdivide (increase) the number of meshes on the object like the below photo:

lattice in blender

    3. Scaling:
Now, scale the the modified cone in x-y direction To get a shape like below:

blender

    4. Decreasing the number of meshes:
In the modifiers section, select the Decimate modifier and by using the Decimate >> Unsubdivide, decrease the number of meshes:

blender

    5. Creating a lattice form:
In the modifiers section, find the wireframe and use it to create lattice form of the object like below:

blender

    6. Finding the right thickness:
Change the thickness of the wireframe according to the size of the object. This part is a little bit tricky because if they are too thin, the wireframe will shrink at the time of smoothing and if they are too thick, they may not create a nice structure or holes may shrink instead of the wireframes.

blender

    7. Smoothing the object:
Use the Multiresolution modifier once again to make the object smoother:

lattice structure

    8. Final scaling:
Scale the object according to your design preferences.

lattice structure in blender

Notice that the procedure of designing the lattice structure is not the same for all the objects. It is very optional and based on your taste which method to choose.

In the next part, we are going to create a lattice structure using Blender for a different object with a few differences in the procedure.

Tricky Parts of Designing Lattice Structures in Blender

In this section, we are going to directly use the Blender modifiers to create aesthetic costume lattice structures but as we have said before the purpose of creating this kind of lattice structure is art and aesthetics, because the end result is more stochastic rather than deterministic. The main focus of this section is to teach you the tricky parts of designing these structures.

Mesh structures:

The most important part of designing every lattice structure is the mesh formation and there are many ways to change the way the meshes are formed. Different mesh formations are as follows:
  1. Triangular mesh
  2. Voxel mesh
  3. Quadrimesh
  4. Blocks
  5. Smooth
And so on.

The trickiest part about designing lattice structures is finding the best option for forming the meshes.

blender

Lattice Structure for A Clyinder

The next design we are going to work on is the lattice structure for a cylinder. The recommended steps are as follows:
    1. Creating a cylinder:
From the top of the page, click on Add >> Mesh >> Cylinder and you will be able to see that we have a cylinder created in the origin.

blender

    2. Remesh the object in voxel form:
By using the Remesh modifier from the modifiers section, you can change the formation of the meshes from triangular to voxel-based.

lattice structure

    3. Change the size of the voxels:
By selecting the remesh modifier from the modifiers section, you can remesh the object from triangular to voxel-based and also decrease the number of meshes leading to making it larger.

blender

    4. Subdivide the mesh
Using the Multi-resolution modifier, make the object smoother.

blender

    5. Unsubdivide the mesh:
Using the decimate modifier, unsubdivided the mesh so that we will have larger holes in our lattice structure. The reason why we subdivide and then unsubdivided is that the shape of the meshes changes and becomes more beautiful.

lattice structure in blender

    6. Wireframe modifier:
And then, use the Wireframe modifier to form the lattice shape.

lattice structure in blender

    7. thickness of the wireframe:
Change the thickness of the wireframe to reach the optimal shape.

lattice structure

    8. Another subdivision:
Again, using the Multi-resolution modifier, subdivide the mesh to get a smooth shape.

lattice structure using blender

You might want to have your own costume lattice shape to be applied to an object. In that case, we have a more complex process of placing the lattice objects on the main object and smoothing the end result. That kind of lattice is better to be performed with the aid of python blender API. In the next chapter, we work on the dependencies that will lead us to the costume shape lattice. The benefit of this kind of lattice is that the dimension of the main object will not be affected by the other modifiers that try to remesh it.

Translating Shapes of the Lattice Structure Using Blender

So far we have learned many ways to create lattice structures, but what we haven’t learned about is how we can create these holes with the shape that we want and the places that we choose to be. In this section, we are going to create a tool to translate our desired shape to the specified vertex of an object and then we will Boolean the difference between the translated object from the main object. We use many of the functions that we used in Creating a tool in Blender to instantly translate an object to the point we show with the cursor article.

lattice structure in blender

Python Scripts

So let’s get started:

import  bpy
import bmesh
import math

####################################################################
#####                Utility Functions
####################################################################
class BOOLEAN_TYPE:
    UNION = 'UNION'
    DIFFERENCE = 'DIFFERENCE'
    INTERSECT = 'INTERSECT'
    
def make_boolean(obj1, obj2, boolean_type):
    if not obj1 or not obj2:
        return

    modifier = obj1.modifiers.new(name='booly', type='BOOLEAN')
    modifier.object = obj2
    modifier.operation = boolean_type

    res = bpy.ops.object.modifier_apply({"object": obj1}, apply_as='DATA', mod-ifier=modifier.name)

    assert "FINISHED" in res, "Error"

def solidify(obj,offset,thickness):
 
    bpy.ops.object.modifier_add(type='SOLIDIFY')
    bpy.context.object.modifiers["Solidify"].offset = offset
    bpy.context.object.modifiers["Solidify"].thickness = thickness
    bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Solidify")


The above function will convert a solid object to a shell of its shape.

def object_closest_point_mesh(p, obj):

    result, location, normal, face_index = obj.closest_point_on_mesh(p)
    assert result, "Can't find closest point on mesh"
    location = location.to_tuple()
    normal = normal.to_tuple()
    return location + normal  # return tuple of 6 floats

def obj_transform(filename, obj_name, size, location, angle):
    
    ob = bpy.context.scene.objects[obj_name]       # Get the object
    bpy.ops.object.select_all(action='DESELECT') # Deselect all objects
    bpy.context.view_layer.objects.active = ob   # Make the cube the active object
    ob.select_set(True)             

    obj = bpy.data.objects[obj_name]
    obj.location = location

    bpy.ops.transform.rotate(value=angle, orient_axis='Z',
        orient_type='GLOBAL',
        orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        constraint_axis=(False, False, True))
                                                            
def object_put_part2(part_name, point, obj, scale, obj_name):
    vx,vy,vz,a,b,c = object_closest_point_mesh(point, obj)
    a1 = math.atan2(b, a)
    obj_transform(part_name, obj_name, scale, (point[0], point[1], point[2]), a1)

def get_vertex():
    bm = bmesh.new()
    ob = bpy.context.active_object
    bm = bmesh.from_edit_mesh(ob.data)

    points = []
    for v in bm.verts:
        if (v.select == True):
            obMat = ob.matrix_world
            points.append(obMat @ v.co)
        
    for p in points:
        pOb = bpy.data.objects.new("VertexPoint", None)
        bpy.context.collection.objects.link(pOb)
        pOb.location = p
    return p

def delete_object(objName):
    
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[objName].select_set(True) # Blender 2.8x
    bpy.ops.object.delete()

def get_object_by_name(obj_name):

    assert obj_name in bpy.data.objects, "Error getting object by name:{}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    return obj

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "Create single Lattice")           

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Go to edit mode and determine the point then Click the button"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
    
    scale = bpy.props.FloatProperty(name= "Enter the scale of lattice", default= 1)
    Lattice_Name = bpy.props.StringProperty(name= "Enter the scale of lattice", default= '')
    
    def execute(self, context):
        
        Scale = self.scale
        Lattice_Name = self.Lattice_Name      
        point = get_vertex()            
        bpy.ops.object.editmode_toggle()  
        obj = get_object_by_name('Icosphere')          
        solidify(obj, 0, 0.02)
        obj2 = get_object_by_name('%s'%Lattice_Name)        
        object_put_part2('%s'%Lattice_Name, point, obj, Scale, '%s'%Lattice_Name)
        make_boolean(obj,obj2,'DIFFERENCE')
        delete_object("VertexPoint")
        return {'FINISHED'}
    
    def invoke(self, context, event):
        
        return context.window_manager.invoke_props_dialog(self)  


What We Have Done in the Scripts Above

In the above def execute function, at first get the vertex of the object, then we will create the shell of the main object. After that, we get the name of the lattice shape that has been imported or created by the user. In the next step, we will translate the lattice to the specified point by the user and finally we apply the boolean difference function to the objects. Do not forget the below scripts related to registering and unregistering the classes.

####################################################################
#####                     Register and Unregister
####################################################################
        
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                               
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)
                                                         
if __name__ == "__main__":
    register()


How to Use the Translating Tool

Now, it is time to test the panel we have designed and created. To do so, select a vertex on the object in edit mode:

lattice structure using blender

Then, simply click on the button on the panel (Create single lattice).

lattice structure in blender

And here we go! You can now see the lattice structure with the exact shape that you want on the object.

Wrapping Up

In this tutorial, we have managed to design an aesthetic internal lattice structure manually without the need for any coding python scripts. The design process is easy, however, you need to repeat and practice it in order to easily deal with the tricky parts.

Then, we got familiar with the tricks of creating aesthetic internal lattice structures and how to mesh formations such as triangular, voxel-based, and other ones that can influence the beauty of the design of these objects.

Moreover, we have managed to design a panel using python scripts (Blender Python API) to create any number of lattices with any shape and any size on our object. The only thing that the user interface wants from the user is the vertex on which the lattice is going to be placed. We can use this panel to create our custom shape lattice structure with accurate dimensions.

Download this Article in PDF format

Stuck in Creating Lattice Structure?

It’s completely normal if you feel lack of knowledge when facing complicated articles. However, don’t worry! We’de thought about it before. So, you can read our first tutorial about lattice structure using Blender too.

Arashtad Serivces
We work on 3D Models, 3D Apps, and everything about 3D in the world of programming.
Why not telling your ideas?
We surely can help
3D Development

Creating Different Kinds of Internal Lattice Structure: A Complete Tutorial

In this article, we are going to design a scientific and accurate kind of lattice structure for a cube or other geometrical 3D shapes using Blender. The difference between a scientific and an aesthetic lattice structure is that in a scientific lattice structure we need accurate dimensions as opposed to aesthetic lattice structures where beauty is the priority. We use different methods for modeling the 2 kinds of lattice structures.

An introduction to Internal Lattice Structure

Internal lattice structures are used for many different purposes. And there are many ways to design them in Blender or Meshmixer software, one of which is to use wireframes which gives you a low-quality lattice structure even if you utilize the modifier. It would mostly be useful for artistic purposes meaning that you cannot precisely determine the size of the holes and channels and the whole object itself.

We usually design our lattice structures this way when we want some aesthetics in our design but it lacks accuracy. The tool we use for this type of modeling is a wireframe modifier next to some other tools that can be used according to the preferences of the design.

As you can see, the above photo is the wireframe of a cube that is shrunk and has been made so modern and artistic that way but the problem is that it cannot be used for the cases that require size accuracy.

The design that we are going to work on is cube-like below. A kind of lattice structure that we can set its height, width, and length and we also determine the size of the holes and channels precisely.

We know that designing each one of these, takes many hours manually. However, if we design it through the code and design a panel with a button that receives all of the sizes and creates such an object, it takes only a few seconds to design any of these objects by any size, not to mention that, if we want a sphere shape to have such kinds of lattice structures we can simply intersect that object with this cube using the boolean modifier.

The only problem that we face when we design such kind of an object, is that in Blender boolean difference is not as robust as it should be and as a result, when we want to boolean difference a ton of these square-shaped cylinders, we face an object with so many open meshes and the result will be horrible. The fix here is to use a utility function we have written called makeUnionOpt which is an optimized function that uses joining as a way to boolean union several objects. Notice that instead of boolean difference, we can use union in other words instead of cutting through a large cube, we make our large lattice structure using a grid of small square-shaped cylinders that are placed in a sequence.

In this part of the tutorial, we focus on the utility functions that will help us design our main lattice structure.

import  bpy

####################################################################
#####                Utility Functions
####################################################################

def rotation_X(object,D_yz):
        context = bpy.context
        scene = context.scene    
        cube = scene.objects.get(object)    
        bpy.ops.transform.rotate(value= D_yz, orient_axis='X',
                                    orient_type='GLOBAL',
                                    orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
                                    constraint_axis=(True, False,False ))        

def rotation_Y(object,D_xz):
        context = bpy.context
        scene = context.scene    
        cube = scene.objects.get(object)    
        bpy.ops.transform.rotate(value= D_xz, orient_axis='Y',
                                    orient_type='GLOBAL',
                                    orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
                                    constraint_axis=(False, True,False ))
                                    
def rotation_Z(object,D_xy):
        context = bpy.context
        scene = context.scene    
        cube = scene.objects.get(object)    
        bpy.ops.transform.rotate(value= D_xy, orient_axis='Z',
                                    orient_type='GLOBAL',
                                    orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
                                    constraint_axis=(False, False,True ))
                                    


The above functions will determine the rotation of any object that is given in all different directions.

def make_cube(name,Features):
     
    lx = Features[0]
    ly = Features[1]  
    lz = Features[2]
        
    dx = Features[3]
    dy = Features[4]  
    dz = Features[5]
        
    rx = Features[6]
    ry = Features[7]
    rz = Features[8]
        
    bpy.ops.mesh.primitive_cube_add(location=(0,0,0))
    bpy.ops.transform.resize(value=(dx, dy, dz))
    for obj in bpy.context.selected_objects:
        obj.name = name            
    rotation_X(name,rx)
    rotation_Y(name,ry)               
    rotation_Z(name,rz)
                           
    context = bpy.context
    scene = context.scene    
    cube = scene.objects.get(name)         
    cube.location = (lx,ly,lz)


The above function will make a cube with the given name, location on all 3 axes, direction, and their dimension in length, width, and height (The size).

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name: {}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    
    return obj


the above function will select an object from the list, using its name.

def make_custom_context(*object_names, base_context=None, mode=None):
    if base_context is not None:
        ctx = base_context
    else:
        ctx = {}
    if mode is not None:
        assert mode in ('OBJECT', 'EDIT'), "Wrong mode used"
        ctx['mode'] = mode
    objs = [get_object_by_name(obj_name) for obj_name in object_names]
    ctx['active_object'] = ctx['object'] = objs[0]
    ctx['selected_editable_objects'] = ctx['selected_objects'] = objs
    ctx['editable_objects'] = ctx['selectable_objects'] = ctx['visible_objects'] = objs

    return ctx

def makeUnionOpt(*object_names):
    ctx = bpy.context.copy()
    if object_names:
        ctx = make_custom_context(*object_names, base_context=ctx, mode='OBJECT')
    bpy.ops.object.join(ctx)  # mostly the same as export/import combination


Using the 2 functions above, we will be able to boolean union a lot of objects altogether at once without bringing up any open or destroyed meshes. In the next part, we will make lattice structures using the above utility functions.

Creating the Main Panel of Lattice Structure

In this second part of our tutorial, we want to create a panel in Blender to be able to easily design any shape of the lattice structure that we want with any size.

In the main panel, here we set the required button and the parameters that we want from the user.

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "Create Cube with internal lattice structures")           

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Click to apply our customized function"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
    
    ls = bpy.props.FloatProperty(name= "Enter the size of lattice", default= 0.25)
    cw = bpy.props.FloatProperty(name= "Enter the WIDTH of the object", default= 2.5)
    cl = bpy.props.FloatProperty(name= "Enter the LENGTH of the object", default= 5.0)
    ch = bpy.props.FloatProperty(name= "Enter the HEIGHT of the object", default= 1.0)

    def execute(self, context):
        
        LATTICE_SIZE = self.ls
        CUBE_WIDTH = self.cw
        CUBE_LENGTH = self.cl
        CUBE_HEIGHT = self.ch        
        return {'FINISHED'}
    
    def invoke(self, context, event):       
        return context.window_manager.invoke_props_dialog(self)

After receiving the required parameters from the user, we will determine the number of square cylinders or rods in all 3 axis:

nRodx = (CUBE_LENGTH / LATTICE_SIZE)+2
nRody = (CUBE_WIDTH / LATTICE_SIZE)+2
nRodz = (CUBE_HEIGHT / LATTICE_SIZE)+2


We also apply a modification in the width, length, and height of the cube, as we want to place all of the holes inside the cube.

CUBE_WIDTH = CUBE_WIDTH + LATTICE_SIZE*3
CUBE_LENGTH = CUBE_LENGTH + LATTICE_SIZE*3
CUBE_HEIGHT = CUBE_HEIGHT + LATTICE_SIZE*3


And we also determine the height of each square rod:

rodHeightx = CUBE_LENGTH - LATTICE_SIZE*2
rodHeighty = CUBE_WIDTH - LATTICE_SIZE*2
rodHeightz = CUBE_HEIGHT - LATTICE_SIZE*2         


And the starting point of where we want to start placing our rods:

startx = CUBE_LENGTH/2 - LATTICE_SIZE*1.5
starty = CUBE_WIDTH/2 - LATTICE_SIZE*1.5
startz = CUBE_HEIGHT/2 - LATTICE_SIZE*1.5


Now, we use for loops to create and places all the cubes on XY plane:

for i in range(int(nRodx/2)):
     for j in range(int(nRody/2)):
          make_cube("Cube",(startx-2*i*LATTICE_SIZE, starty-2*j*LATTICE_SIZE, 0, LATTICE_SIZE/2,                
                    LATTICE_SIZE/2, rodHeightz/2, 0, 0, 0))
          if((i+j) != 0):
               makeUnionOpt('Cube','Cube.001')


Placing the vertical cubic cylinders: If we run the code up to here, we will get the following result. We will be able to see a large set of cubic cylinders that appear all at once, when we click the button (Create Cube with internal lattice structures) :

Placing the Horizontal Cubic Cylinders

We are not done yet. To get the complete lattice structure we should place the horizontal cubic cylinders. To do so, we should write the following for loops in the def execute() function after the for loops written for the vertical rods. we should place the rods in XZ plane (horizontal cubic cylinders) using the following script:

for i in range(int(nRodx/2)):
     for j in range(int(nRodz/2)):
          make_cube("C",(startx-2*i*LATTICE_SIZE, 0, startz-2*j*LATTICE_SIZE,LATTICE_SIZE/2,rodHeighty/2,      
                    LATTICE_SIZE/2, 0, 0, 0))
          if((i+j) != 0):
               makeUnionOpt('C', 'C.001')


The result up to here will be like this:

The above result looks much closer to the expected result. We only need another set of rods normal to YZ plane.

Completing the Project

And finally, we should place the rods on YZ plane to get the complete model of internal lattice structure. The following code containing for loops should be writ-ten after the 2 previous set of for loops to serve our purpose:

for i in range(int(nRody/2)):
     for j in range(int(nRodz/2)):
          make_cube("Cu", (0, starty-2*i*LATTICE_SIZE, startz-2*j*LATTICE_SIZE, rodHeightx/2,      
                         LATTICE_SIZE/2, LATTICE_SIZE/2, 0, 0, 0))
           if((i+j) != 0):
                makeUnionOpt('Cu','Cu.001')
          

We also boolean union all three set of rods using the utility boolean union function we wrote in the last section:

makeUnionOpt('Cu','C','Cube')										

The result will be like this:

And do not forget to close the project to be able to use the panel for creating all the different shapes of lattice all around the object:


####################################################################
#####                     Register and Unregister
####################################################################  
         
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                                   
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)
                                                           
if __name__ == "__main__":
    register()


Now, using the above interface, you will be able to design and modify a robust internal lattice structure in a few seconds. You can also boolean intersect it with the shape that you want and make that shape have an internal lattice structure.

Conclusion

In this tutorial, we have introduced all types of lattice structures including the ones in beauty and aesthetics are considered more important than accuracy of size and also the ones that accuracy of the dimensions is placed on top of our priorities. We have also managed to get started with the design of an internal lattice structure with accurate dimensions of height, width, and length for the object and the channels.

Finally, we have managed to complete the design tool for creating an internal lattice structure with accurate dimensions. Using the said tool which is provided in a panel, you can design a cube with a certain height, width, length, and internal lattice structures. Moreover, you can use the boolean intersect modifier to apply the said internal lattice structure on any object with any shape.

Download this Article in PDF format

web development

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
3D Development

How to Easily Trim Objects in Blender with A Clean Cut

To Trim or cut objects in Blender can sometimes seem impossible as the tools in Blender will only allow you to cut only through the meshes. As a result, you will sometimes end up in a situation where you cannot find a proper way to cut your object. In this tutorial, we will show you a way to create a tool using which you can make your own custom cut on the shape from above. However, you can develop this tool in a way that it can cut from all angles.

Making A Tool to Trim Objects in Blender

trim objects in Blender

We want to create a tool that you can draw your custom cut shape and after you have made sure the cut shape is alright, confirm it.

trim objects in Blender

So let’s get started. Using the below scripts, we will be able to easily trim with a clear cut through the meshes with the pencil tool. We first begin with the utility functions and then execute them within a sequence. In the panel that is created after the running of the codes, we will see 2 buttons:
  1. For drawing the pattern.
  2. For applying the cut with the pattern drawn by the user.

import  bpy
import bmesh
import math

####################################################################
#####                Utility Functions
####################################################################
class BOOLEAN_TYPE:
    UNION = 'UNION'
    DIFFERENCE = 'DIFFERENCE'
    INTERSECT = 'INTERSECT'
       
def make_boolean(obj1, obj2, boolean_type):
    if not obj1 or not obj2:
        return

    modifier = obj1.modifiers.new(name='booly', type='BOOLEAN')
    modifier.object = obj2
    modifier.operation = boolean_type
    res = bpy.ops.object.modifier_apply({"object": obj1}, apply_as='DATA', modifier=modifier.name)

    assert "FINISHED" in res, "Error"


The above function will apply the boolean operation on the object specified according to the type of boolean categorized in the class BOOLEAN_TYPE.

def delete_object(objName):
    
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[objName].select_set(True) # Blender 2.8x
    bpy.ops.object.delete()


The above function will delete any given object. To delete an object, we need to first deselect all the other objects and then select the object that we specified its name in the function and finally delete the selected object.

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name:	{}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    return obj


The above function will get the object by its name. Meaning that it will select it according to the name given.

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_trim.myop", text= "Trim from above")           
        row = layout.row()
        row.operator("wm_confirm.myop", text= "Confirm the cut")

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Trim_myOp(bpy.types.Operator):
    """Draw The trim pattern"""
    bl_label = "Draw The trim pattern"
    bl_idname = "wm_trim.myop"
      
    def execute(self, context):
             
        bpy.ops.curve.primitive_nurbs_curve_add(enter_editmode=False, align='WORLD', location=(0,0,0))
        bpy.ops.object.editmode_toggle()
        bpy.context.scene.tool_settings.curve_paint_settings.curve_type = 'BEZIER'
        bpy.ops.curve.delete(type='VERT')
        
        return {'FINISHED'}
    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)


In the above class, we have provided everything for the user to easily draw a pattern. This preparation contains creating a Nurbs Curve, switching to edit mode, specifying the type of the curve as Bezier, and deleting a curve. All of these tasks provide means for the user to draw another curve for trimming.

class WM_Confirm_myOp(bpy.types.Operator):
    """Click OK to confirm"""
    bl_label = "Click OK to confirm"
    bl_idname = "wm_confirm.myop"
    name = bpy.props.StringProperty(name= "Enter the name of the object to be trimmed", default= "")
          
    def execute(self, context):

        name = self.name                     
        bpy.context.object.data.dimensions = '2D'
        bpy.context.object.data.fill_mode = 'BOTH'
        bpy.context.object.data.extrude = 1000
        bpy.ops.object.editmode_toggle()
        context = bpy.context
        scene = context.scene
        cut = scene.objects.get("NurbsCurve")
        bpy.ops.object.convert(target='MESH')
        
        obj1 = get_object_by_name('%s'%name)
        obj2 = get_object_by_name('NurbsCurve')
        
        make_boolean(obj1, obj2, 'DIFFERENCE')
        delete_object("NurbsCurve")
        #fixMesh('%s'%name)

        return {'FINISHED'}
    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)




The above class will fill the curve and extrude it for the height of 1000 mm (You can increase the number if you have a larger object than that height) and switches back to object mode then it will subtract the created object from the main object.

Don’t forget to add the ending of your script:

####################################################################
#####                     Register and Unregister
####################################################################

         
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Trim_myOp)
    bpy.utils.register_class(WM_Confirm_myOp)                                            
    

def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Trim_myOp)
    bpy.utils.unregister_class(WM_Confirm_myOp)   
                                                       
    
if __name__ == "__main__":
    register()



Now, let’s run the code and test our tool. After you have successfully run the code and the panel has appeared, you can click the Trim from the above button and choose the pencil tool from the left-hand side of the page where we have a toolbar and then draw the pattern from the top view. You can set the view from the top by clicking on the z-axis (blue circle). After you have drawn the pattern, it is time to apply the pattern for trimming. To do so, click on the Confirm the cut button. Let’s follow the instruction with the photos:

trim objects in Blender

Using the pencil tool from the left-hand side menu draw the shape that you want to trim from above (Notice that your view must be perpendicular to the XY plane).

blender

blender

Now, click Confirm the cut, enter the name of the shape that is going to be trimmed, and then click OK.

blender

And as you can see, we have successfully trimmed our object exactly the way we had drawn.

Final Thought

In this tutorial, we have managed to show you a way to create a tool using which you can make your own custom cut on the shape from above. However, you can develop this tool in a way that it can cut from all angles.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Fill in the Form
3D Development

Retrieve the Coordinates of a Selected Point in Blender

The purpose of this article is to find an easy way to obtain the coordinates and the normal of a point in Blender specified by the user. Using the obtained point data, we can automate the translation of the objects that are going to be transferred to that certain point and rotated according to the normal directions of that point. The use cases of this kind of object translation are for 3D modeling of complex objects as well as placing the internal lattice structures.

Obtaining the Coordinates of Any Point in Blender

Finding the coordinates of a selected point is a key function for a lot of other important tasks, such as translating another part to that point and so on. Here, we write some functions in addition to an interface for you to get the coordinates of a selected point on an object and print the data of the location and the direction of the mesh normal.

blender

Notice that the user should go to edit mode while selecting the object and click on the edge that they want to get the coordinates from. As you know edges have no normals so we have to find the closest mesh to the edge that has been selected by the user.

Using the below python scripts in the scripts section of Blender, we will define some utility functions as well as the main execute class that is going to apply the utility functions with a certain sequence.

import  bpy
import bmesh

####################################################################
#####                Utility Functions
####################################################################

def object_closest_point_mesh(p, obj):

    result, location, normal, face_index = obj.closest_point_on_mesh(p)
    assert result, "Can't find closest point on mesh"

    location = location.to_tuple()
    normal = normal.to_tuple()


The above function will find the closest point on the mesh for us. The function itself uses another function that is built in Blender and finds the closest vertex on a mesh based on the given point and returns the location and the normal data of the said vertex. Finally, it returns the 2 sets of data.

def get_vertex():

        bm = bmesh.new()
        ob = bpy.context.active_object
        bm = bmesh.from_edit_mesh(ob.data)

        points = []
        for v in bm.verts:
            if (v.select == True):
                obMat = ob.matrix_world
                points.append(obMat @ v.co)       
        for p in points:
            pOb = bpy.data.objects.new("VertexPoint", None)
            bpy.context.collection.objects.link(pOb)
            pOb.location = p
        return p


The above function will get the vertex that has been selected by the user in the edit mode and returns the coordinates of the said vertex.


def delete_object(objName):    
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[objName].select_set(True) # Blender 2.8x
    bpy.ops.object.delete()


The above function will delete any given object. To delete an object, we need to first deselect all the other objects and then select the object that we specified its name in the function and finally delete the selected object.

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name: {}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    return obj


The above function will get the object by its name. Meaning that it will select according to the name given.

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "retrieve the point data")           


####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Go to edit mode and determine the point then Click the button"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
        
    def execute(self, context):
        
        p = get_vertex()            
        bpy.ops.object.editmode_toggle()  
        obj = get_object_by_name('Sphere')   
        print (object_closest_point_mesh(p,obj))
        delete_object("VertexPoint")
        return {'FINISHED'}
    
    def invoke(self, context, event):
        
        return context.window_manager.invoke_props_dialog(self)  



The above execute function is so simple. It first gets the selected vertex in the edit mode from the user, then it toggles the edit mode to object mode. After that, it prints the coordinates and the normals of the selected point. And at last, it deletes the VertexPoint object from the list of objects.

####################################################################
#####                     Register and Unregister
####################################################################
        
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                               
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)                                                      
    
if __name__ == "__main__":
    register()


Don’t forget to close the project by registering and unregistering the classes defined. If you run the scripts, you will be able to see the panel below. Click the button in the panel (retrieve the point data button) and Click OK and notice that before you click OK, you need to have determined the point you wish to select, in the edit mode of Blender.

coordinates of a point in blender

The following is the result of retrieved data of the point: (-0.46193963289260864, 0.3086579740047455, 0.8314695954322815, -0.41729676723480225, 0.22304992377758026, 0.8809722661972046) Info: Deleted 1 object(s) The first 3 numbers are the XYZ coordinates of the point and the second 3 numbers are related to the direction of the mesh normal.

The Job Is Done

In this tutorial, we have managed to propose a way to quickly obtain the coordinates and normal directions of any point on an object. This data is very useful especially when you want to operate some complex 3D modeling or translate objects to a certain point on another object.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Fill in the Form
3D Development

Fixing Non-Manifold Meshes in Blender

One of the things that will make you fed up with Boolean modifiers in Blender is the non-manifold output. Unfortunately, there is no robust way to cover this issue. However, we can create a tool that can fix most (if not all) of the problematic meshes. This tool helps us fix non-manifold meshes in Blender.

Creating A Tool to fix Non-Manifold Meshes in Blender

In the photo below, you can see an example of a non-manifold result that appeared as a result of a Boolean difference of another cube from this one. If you apply another Boolean operation on this object, you will see that the object gets fully destroyed.

non-manifold meshes

Python Scripts

In the scripts below we will get benefit from the functions that will eventually create a standard mesh that is the result of remeshing from triangular meshes to voxel-based meshes and also removing the non-manifold meshes.

import  bpy
import bmesh
import math

####################################################################
#####                Utility Functions
####################################################################
   
def fixMesh(obj_name):
    make_voxel_remesh(get_object_by_name(obj_name), 0.5)
    if is_object_have_non_manifolds(get_object_by_name(obj_name)):
        print(obj_name, "have non manifolds")
        if remove_object_non_manifold_loops(obj_name, loops=2):
            print("Filled:", fill_non_manifolds(obj_name))
            obj = get_object_by_name(obj_name)
            make_smooth_remesh(obj, 9, 0.9, 1, True, True)    


The above function is the main function for fixing the meshes. At first, it remeshes the object from triangular to voxel-based. Then, it will check whether has non-manifold parts and if the answer is yes, it will remove those parts. In the end, it will smooth and remesh the object.

def is_object_have_non_manifolds(obj):
    assert obj.type == 'MESH', "Unsupported object type"
    bmo = bmesh.new()
    bmo.from_mesh(obj.data)
    have = False
    for edge in bmo.edges:
        if not edge.is_manifold:
            have = True
            break
    if not have:
        for vert in bmo.verts:
            if not vert.is_manifold:
                have = True
                break
    bmo.free()  # free and prevent further access
    return have


The above function checks whether the object has non-manifold parts or not.

def deselect_objects():
    bpy.ops.object.select_all(action='DESELECT')

def select_object_by_name(obj_name):
    get_object_by_name(obj_name).select_set(True) # Blender 2.8x

def activate_object_by_name(obj_name):
    bpy.context.view_layer.objects.active = get_object_by_name(obj_name)    

def is_object_contain_selected_vertices(obj):
    if obj.mode == "EDIT":
        bm = bmesh.from_edit_mesh(obj.data)
    else:
        bm = bmesh.new()
        bm.from_mesh(obj.data)
    selected = False
    for v in bm.verts:
        if v.select:
            selected = True
            break
    bm.free()
    return selected

def remove_object_non_manifold_loops(obj_name, loops=0):
    deselect_objects()
    select_object_by_name(obj_name)
    activate_object_by_name(obj_name)
    removed = False
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_mode(type="VERT")
    bpy.ops.mesh.select_non_manifold(extend=False)
    if is_object_contain_selected_vertices(get_object_by_name(obj_name)):
        if loops:
            for i in range(loops):
                bpy.ops.mesh.select_more()
            bpy.ops.mesh.delete(type='FACE')
        else:
            bpy.ops.mesh.delete(type='VERT')
        removed = True
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode='OBJECT')
    return removed

def fill_non_manifolds(obj_name):
    deselect_objects()
    select_object_by_name(obj_name)
    filled = False
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_mode(type="VERT")
    bpy.ops.mesh.select_non_manifold(extend=False)
    if is_object_contain_selected_vertices(get_object_by_name(obj_name)):
        bpy.ops.mesh.fill(use_beauty=True)
        bpy.ops.mesh.normals_make_consistent(inside=False)
        bpy.ops.mesh.faces_shade_smooth()
        filled = True
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode='OBJECT')
    return filled


The above function will fill the open meshes.

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name:	{}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    return obj

def make_voxel_remesh(obj, voxel_size, adaptivity=0, use_smooth_shade=True):
    modifier = obj.modifiers.new(name='remesh', type='REMESH')
    modifier.mode = 'VOXEL'
    modifier.voxel_size = voxel_size
    modifier.adaptivity = adaptivity
    modifier.use_smooth_shade = use_smooth_shade
    res = bpy.ops.object.modifier_apply({"object": obj}, apply_as='DATA', modifier=modifier.name)
    assert "FINISHED" in res, "Error"


The above function will remesh the object to a voxel-based object.

def make_smooth_remesh(obj, octree_depth=9, scale=0.9, threshold=1, use_smooth_shade=True,                         
        use_remove_disconnected=True):
    modifier = obj.modifiers.new(name='remesh', type='REMESH')
    modifier.mode = 'SMOOTH'
    modifier.use_smooth_shade = use_smooth_shade
    modifier.octree_depth = octree_depth
    modifier.scale = scale
    modifier.use_remove_disconnected = use_remove_disconnected
    modifier.threshold = threshold
    res = bpy.ops.object.modifier_apply({"object": obj}, apply_as='DATA', modifier=modifier.name)
    assert "FINISHED" in res, "Error"

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "Fix Mesh")           

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Go to edit mode and determine the point then Click the button"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
     
    def execute(self, context):
        
        fixMesh('Cube')
        return {'FINISHED'}
    
    def invoke(self, context, event):
        
        return context.window_manager.invoke_props_dialog(self)  


####################################################################
#####                     Register and Unregister
####################################################################

         
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                            
    
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)
   
                                                       
    
if __name__ == "__main__":
    register()



Now if we run the code and click the Fix Mesh button in the panel, we will see that mesh of the cube has been fixed.

meshes in Blender

Fixing Non-Manifold Meshes: Done!

In this tutorial, we have managed to write the scripts of the utility functions that would help us remove or fix the non-manifold meshes in Blender. We have applied these utility functions in our main panel class and as a result, we have a fixed output.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
3D Development

How to Apply Boolean Union of Many Objects in Blender

This tutorial covers most (if not all) of the problems concerning the boolean union of multiple objects in Blender. If you have worked with a boolean modifier in Blender more than just a try, you must have faced the circumstances in which the result of union would have been a non-manifold mesh or you have wanted to boolean union a great number of objects and you have ended up with a simple modifier that only does the boolean union only for a couple of objects that you wouldn’t be sure if the result of the union would have non-manifold mesh or not. This article guides you with different ways to solve this issue.

Boolean Union of Multiple Objects in Blender

Suppose you have a lot of cubes that you want them all to merge into one object, if the number of cubes is n, you will have to apply the boolean union modifier n-1 times, which takes a lot of time. You may use the python Blender API and do this job in a loop. This solution is OK until you face with the non-manifold result caused by the boolean modifier. That is when the issue begins.

To solve the above problem manually (without any scripting), you can get benefit from a useful hack in Blender and which is exporting the objects altogether. To do this, all you have to do is to select all the objects and export them (do not forget to check the Selection Only box). If you import the exported file, you will see that all of the objects have merged into one object.

There is also another way to do this in a more standard and sophisticated way and that is through scripting. The benefit of this method is that we can use it next to the rest of our code for more complex functionalities rather than just a boolean union.

Now imagine that we want to create a button that boolean union any number of cubes at once without any need to export or import anything. The following code will serve this purpose for us:

IMPORTANT NOTE:

Remember that the scripts we are using here are related to Blender version 2.83 and if you are working with any other versions, it is probable that the scripts might differ a little bit, but we will show you ways to find the proper functions if there are any differences at all.

Python Scripts

The below scripts will create a panel inside which you can boolean union a large number of objects that have been imported in Blender or designed by the user. Notice that the main purpose of the below code is to make you familiar with the procedure and for other boolean unions of another object with different names and numbers you should modify the code a bit. However, the utility functions remain the same.


import  bpy

####################################################################
#####                Utility Functions
####################################################################

def make_custom_context(*object_names, base_context=None, mode=None):
    if base_context is not None:
        ctx = base_context
    else:
        ctx = {}

    if mode is not None:
        assert mode in ('OBJECT', 'EDIT'), "Wrong mode used"
        ctx['mode'] = mode

    objs = [get_object_by_name(obj_name) for obj_name in object_names]
    ctx['active_object'] = ctx['object'] = objs[0]
    ctx['selected_editable_objects'] = ctx['selected_objects'] = objs
    ctx['editable_objects'] = ctx['selectable_objects'] = ctx['visible_objects'] = objs
    return ctx

def makeUnionOpt(*object_names):
    ctx = bpy.context.copy()
    if object_names:
        ctx = make_custom_context(*object_names, base_context=ctx, mode='OBJECT')
    bpy.ops.object.join(ctx)  # mostly the same as export/import combination

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name: {}".format(obj_name)
    obj = bpy.data.objects[obj_name]    
    return obj

def deselect_objects():
    bpy.ops.object.select_all(action='DESELECT')

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "Boolean Union of multiple cubes")           

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Click to apply our customized function"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
    
    n = bpy.props.IntProperty(name= "Enter the size of the Cube", default = 20)
  
    def execute(self, context):   
        n = self.n                    
        cubes = ["Cube" for i in range(n)]
        #cubes[0] = "Cube"      
        if(n >= 10 and n <= 99):
            for i in range (9):
                cubes[i+1] = "Cube.00%d"%(i+1)
          
            for i in range (n-10):
                cubes[i+10] = "Cube.0%d"%(i+10)
                
        elif(n < 10 and n >= 2):
            for i in range (9):
                cubes[i+1] = "Cube.00%d"%(i+1)
        
        cubes = tuple(cubes)      
        makeUnionOpt(cubes)

        return {'FINISHED'}
    
    def invoke(self, context, event):    
        return context.window_manager.invoke_props_dialog(self)  

####################################################################
#####                     Register and Unregister
####################################################################   
        
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                                  
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)
                                                              
if __name__ == "__main__":
    register()
 

And as you can see, we have got one object at the end with the help of just a button.

Last Word

In this tutorial, we have managed to boolean union a large number of objects using the boolean union other than the one in the modifiers section. This type of union will join the objects and the good thing about this type of union is that the output will not have the non-manifold meshes especially when we have overlap between the objects. We have also provided a manual method so that you can avoid the scripts.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

A Quick Way to Automatically Remove Loose Parts in Blender

There are times that you face some unwanted extra objects all over your mesh such as when you get a raw 3d scan object that has been converted from point cloud to mesh. And, you want to quickly get rid of them all. In this tutorial, we are going to see how we can remove these loose parts in Blender and how we can create a button to quickly do that for us.

How to Remove Loose Parts in Blender

There are times that you face some unwanted extra objects all over your mesh such as when you get a raw 3d scan object that has been converted from point cloud to mesh and you want to quickly get rid of them all. In this tutorial, we are going to see how we can get away with these small useless specks of dust using Blender python and how we can create a button to quickly do that for us.

remove loose parts in Blender

IMPORTANT NOTE:

Remember that the scripts we are using here are related to Blender version 2.83 and if you are working with any other versions, it is probable that the scripts might differ a little bit, but we will show you ways to find the proper functions if there are any differences at all.

remove loose parts in Blender

Python Scripts

First of all, we write our utility functions and then apply them in the main panel.

import  bpy


####################################################################
#####                Utility Functions
####################################################################


def clean_from_loose_parts(obj):
    deselect_objects()

    # separate the mesh
    if obj.type == 'MESH':
        curr_obj_name = obj.name

        obj.select_set(True)
        bpy.ops.mesh.separate(type='LOOSE')

        # separated_objects = tuple(obj.name for obj in bpy.context.selected_objects))
        separated_objects_count = len(bpy.context.selected_objects)
        print("Mesh object {} separated in to {} object(s)".format(curr_obj_name, 									        
            separated_objects_count))

        if separated_objects_count > 1:
            # remove mesh objects which have less than max vertices
            obj_max_vertices = get_max_vertices_object_from_selection()
            obj_max_vertices.select_set(False)
            bpy.ops.object.delete()
            # restore input name of the object
            obj_max_vertices.name = curr_obj_name

        else:
            obj.select_set(False)



The above function will do the main job of removing small loose parts for us.

def get_max_vertices_object_from_selection():
    max_vert_count = 0
    obj_max_vertices = None
    for obj in bpy.context.selected_objects:
        vert_count = len(obj.data.vertices)
        if max_vert_count < vert_count:
            max_vert_count = vert_count
            obj_max_vertices = obj

    return obj_max_vertices            


The above function will count the vertices of the selected objects and return the one that has the maximum amount of vertices.

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name:{}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    
    return obj


The above function will get the object by name and store it in a variable.

def deselect_objects():
    bpy.ops.object.select_all(action='DESELECT')


The above function will deselect the active object. Let’s write the code of our main panel.

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "Clean object from loose parts")           

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Click to apply our customized function"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
    

    def execute(self, context):
        
        obj = get_object_by_name('Sphere')
        clean_from_loose_parts(obj)

        return {'FINISHED'}
    
    def invoke(self, context, event):
        
        return context.window_manager.invoke_props_dialog(self)  

####################################################################
#####                     Register and Unregister
####################################################################

         
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                               
    
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)
   
                                                       
    
if __name__ == "__main__":
    register()
    


In the above main panel script, we use the utility function clean_from_loose_parts(obj). The rest is related to the GUI of the panel.

Testing the Scripts

Now, it is finally time to run our code using CTRL + P or the run button at the top of the scripting window and we will see that a panel appears with a button using which we can remove extra loose parts.

Testing

And as you can see, the small loose parts are removed. Consequently, we can have a clean object from noises. You can apply this tool to any raw 3D scan and make the edition of it much faster.

Getting Rid of Loose Parts in Blender: Wrapping Up

In this tutorial, we have managed to create a panel that can remove the noises and loose parts from all the objects that have them in Blender. Most of the time we face the issue of having loose parts when we receive a raw 3D scan from a scanner device. Having such a tool is very useful and saves a lot of time for you.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Fill in the Form
3D Development

How to Instantly Translate An Object in Blender

To Translate an object to the place we visually determine by eyeballing in Blender is a time-taking process especially when we are working with a great number of objects. In this tutorial, we are going to see how we can instantly move an object to the desired location we specify with the cursor. To do so, we first need to retrieve the point, and then using the acquired coordinates of the point, we can change the location of the object.

Translate An Object to A Special Point in Blender

Translating an object to the place we visually determine by eyeballing is a time-taking process especially when we are working with a great number of objects. In this tutorial, we are going to see how we can instantly move an object to the desired location we specify with the cursor. To do so we first need to retrieve the point and then using the acquired coordinates of the point, we can change the location of the object.

translate an object in Blender

With that said, let’s get started:

import  bpy
import bmesh
import math
####################################################################
#####                Utility Functions
####################################################################

def object_closest_point_mesh(p, obj):
    result, location, normal, face_index = obj.closest_point_on_mesh(p)
    assert result, "Can't find closest point on mesh"
    location = location.to_tuple()
    normal = normal.to_tuple()
    return location + normal  # return tuple of 6 floats

def obj_transform(filename, obj_name, size, location, angle):    
    ob = bpy.context.scene.objects[obj_name]       # Get the object
    bpy.ops.object.select_all(action='DESELECT') # Deselect all objects
    bpy.context.view_layer.objects.active = ob   # Make the cube the active object
    ob.select_set(True)             
    obj = bpy.data.objects[obj_name]
    obj.location = location
    bpy.ops.transform.rotate(value=angle, orient_axis='Z',
        orient_type='GLOBAL',
        orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        constraint_axis=(False, False, True))
                                                               
def object_put_part(part_name, point, obj, scale, obj_name):
    vx,vy,vz,a,b,c = object_closest_point_mesh(point, obj)
    a1 = math.atan2(b, a)
    obj_transform(part_name, obj_name, scale, (point[0], point[1], point[2]), a1)


The above functions will get the specified point and translate our object part_name on the main object obj.

def get_vertex():
    bm = bmesh.new()
    ob = bpy.context.active_object
    bm = bmesh.from_edit_mesh(ob.data)
    points = []
    for v in bm.verts:
        if (v.select == True):
            obMat = ob.matrix_world
            points.append(obMat @ v.co)
    for p in points:
        pOb = bpy.data.objects.new("VertexPoint", None)
        bpy.context.collection.objects.link(pOb)
        pOb.location = p
    return p


The above function will get the vertex we specified and return its data. The data contains the coordinates of the point that has been selected. Here, we do not need the orientation of the normal object. Otherwise, we would have had to use another script to be able to retrieve the data of the normal vector as well.

def delete_object(objName):    
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[objName].select_set(True) # Blender 2.8x
    bpy.ops.object.delete()

def get_object_by_name(obj_name):
    assert obj_name in bpy.data.objects, "Error getting object by name:{}".format(obj_name)
    obj = bpy.data.objects[obj_name]
    return obj

####################################################################
########             Main Panel
####################################################################

class MainPanel(bpy.types.Panel):
    bl_label = "Object Adder"
    bl_idname = "VIEW_PT_MainPanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Design Automation'
    
    def draw(self, context):
        layout = self.layout
        layout.scale_y = 1.2
        
        row = layout.row()
        row.label(text= "Design Automation", icon= 'OBJECT_ORIGIN')
        row = layout.row()
        row.operator("wm_function.myop", text= "Translate the Object")           

####################################################################
####                  Main UI ّFunctions                  
####################################################################

class WM_Function_myOp(bpy.types.Operator):
    """Go to edit mode and determine the point then Click the button"""
    bl_label = "Our customized function"
    bl_idname = "wm_function.myop"
    
    scale = bpy.props.FloatProperty(name= "Enter the scale of lattice", default= 1)
    
    def execute(self, context):
        
        Scale = self.scale        
        point = get_vertex()            
        bpy.ops.object.editmode_toggle()  
        obj = get_object_by_name('Cylinder')   
        object_put_part('Sphere', point, obj, Scale, 'Sphere')
        delete_object("VertexPoint")
        return {'FINISHED'}
    
    def invoke(self, context, event):       
        return context.window_manager.invoke_props_dialog(self)  


In the above def execute function, we use the get vertex function to get the data of the point that the user specifies. Then, use object_put_part to translate the object that we want to translate.

####################################################################
#####                     Register and Unregister
####################################################################

         
def register():
    bpy.utils.register_class(MainPanel)
    bpy.utils.register_class(WM_Function_myOp)
                                                
def unregister():
    bpy.utils.unregister_class(MainPanel)
    bpy.utils.unregister_class(WM_Function_myOp)
                                                         
if __name__ == "__main__":
    register()


Now, if we test our UI by determining a point on the main object:

translate an object in Blender

And clicking on the Translate the object button and then clicking OK, we will see that our object has been translated to our specified point.

Translate An Object in Blender: Final Word

In this tutorial, we have managed to create a tool in Blender that will help us translate (change the location of the object) using the point specified by the user on the object. This tool is very useful for designing complex 3D models especially if we want to save some time for the creativity of the design rather than operating time-taking tasks.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
3D Development