Table of Contents

Blender

cushion

source : https://www.youtube.com/watch?v=MLUnfE4FcOY

dae cleaner

import bpy
 
def decimate():
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            bpy.context.scene.objects.active = obj
            bpy.ops.object.modifier_add(type="DECIMATE")
            mod_name = bpy.context.object.modifiers[-1].name
            bpy.context.object.modifiers[mod_name].decimate_type = 'DISSOLVE'
            bpy.ops.object.modifier_apply(apply_as = 'DATA', modifier=mod_name)
 
def recenter_origin():
    bpy.context.scene.cursor_location = (0.0,0.0,0.0)
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            obj.select = True
    #bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
    bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
 
def set_layers():
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            bpy.context.scene.objects.active = obj
            if obj.parent.name == "group_0":
                if obj.name[0:5] == "group":
                    bpy.context.object.layers[1] = True
                elif obj.name[0:8] == "instance":
                    bpy.context.object.layers[2] = True
            if obj.parent.name[0:8] == "instance":
                bpy.context.object.layers[3] = True
            if (obj.parent.name[0:5] == "group") and (obj.parent.name != "group_0"):
                bpy.context.object.layers[4] = True
            bpy.context.object.layers[0] = False
 
def unparent_all():
    bpy.ops.object.select_all(action='DESELECT')
    for obj in bpy.data.objects:
        bpy.context.scene.objects.active = obj
        if bpy.context.active_object.parent != None:
            obj.select = True
    bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
 
def select_all(status=True):
    #bpy.ops.object.select_all(action='SELECT')
    #bpy.ops.object.select_all(action='DESELECT')
    for obj in bpy.data.objects:
        obj.select = status
 
def hide_all(status=True):
    for obj in bpy.data.objects:
        bpy.context.scene.objects.active = obj
        bpy.context.object.hide = status
 
def remove_duplicates():
    unparent_all()
    bpy.ops.object.select_all(action='DESELECT')
    datas = {}
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            bpy.context.scene.objects.active = obj
            actual = bpy.context.scene.objects.active 
            if actual.data.name not in datas:
                datas[actual.data.name] = [obj.name]
            else:
                datas[actual.data.name].append(obj.name)
    #print(datas)
    for values in datas:
        length = len(datas[values])
        if length > 1:
            print(values, length, sep=':')
            to_delete = sorted(datas[values])[1:length]
            for value in to_delete:
                bpy.data.objects[value].select = True
                bpy.ops.object.delete()
 
        #if length == 6:
        #    print(datas[values])
        #print(values, datas[values][0].split('_')[1], sep=" : ")
        #print(to_delete)
 
print("== Decimation Start ==")
print(bpy.data.scenes['Scene'].statistics())
 
remove_duplicates()
decimate()
recenter_origin()
 
print("== Decimation End ==")
print(bpy.data.scenes['Scene'].statistics())

shortcuts

Camera

Shortcut Function Notes
ctrl+alt+0align camera to view

Interface

http://www.blendertips.com/hotkeys.html

Shortcut Function Notes
ctrl + up/dow arrowsshow / hide full screen panel
keypad 1/3/7change view

Beziers

Shortcut Function Notes
vset handle type
alt+cclose

Edit mode

Shortcut Function Notes
aselect all / deselect all
bbox select
bbbrush select
ggrab (translate)
rrotate
sscale
x/y/zusing axis
shift + x/y/zuse 2 axis except the one selected (ex : shift + z = use x+y)
xx/yy/zzuse local axis
ctrl + lselect linked elements
pseparate items
jjoin item
hhide
alt + hshow all
fcreate a face

Utils

Shortcut Function Notes
ctrl + ttracking mode to align camera to an object
alt + gclear coordinates
shift + c3D Cursor to origin

Blender gmaps

sources

installation

renderdoc
C:\Windows\System32\cmd.exe /c "SET RENDERDOC_HOOK_EGL=0 && START "" ^"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe^" --disable-gpu-sandbox --gpu-startup-dialog"
renderdoc blender addon

meshlab

procedure

capture
optimizing mesh
optimize uv map

import bpy

C = bpy.context

for i in range(0,len(C.object.material_slots)):
    C.object.active_material_index = 1
    bpy.ops.object.material_slot_remove()

bpy.ops.object.mode_set(mode = 'EDIT') 
bpy.ops.mesh.select_all(action = 'SELECT')
bpy.ops.object.material_slot_assign()
bpy.ops.object.mode_set(mode = 'OBJECT')

bake the new texture

blender utils

blender 2.8 get names

import bpy
 
def getNames():
    selection_names = bpy.context.selected_objects
    for i in selection_names:
        print(i.name)
getNames()

blender 2.8 custom join

import bpy
   
def customJoin():
    # convert to single user
    bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, animation=False)
    # get selection
    selected_objects = bpy.context.selected_objects
   
    # do the job
    for o in selected_objects:
        if o.type != 'EMPTY':
            # set active
            bpy.context.view_layer.objects.active = o
     
            # convert to mesh
            if o.type in  ['CURVE', 'FONT']:
                bpy.ops.object.convert(target='MESH')
     
            # apply modifiers
            for mod in o.modifiers:
                bpy.ops.object.modifier_apply(modifier = mod.name)
     
            # get the object's name
            object_name = o.name
     
            # get group
            if object_name in o.vertex_groups:
                group = o.vertex_groups[object_name]
            else:
                group = o.vertex_groups.new(name = object_name)
     
            # get vertices
            verts = []
            for vert in o.data.vertices:
                verts.append(vert.index)
     
            # assign vertices to group
            group.add(verts, 1.0, 'REPLACE')
 
    # join to a single object
    bpy.ops.object.join()
     
customJoin()

2.79 groups

import bpy

def giveGroup(groupname=""):
    selected = bpy.context.selected_objects
    for item in selected:
        bpy.context.scene.objects.active = item
        bpy.ops.object.group_link(group='veranda')

#giveGroup("veranda")

def giveMaterial(materialname=""):
    selected = bpy.context.selected_objects
    for item in selected:
        bpy.context.scene.objects.active = item
        bpy.context.object.active_material.name = materialname

giveMaterial("vitre")

addon template

import bpy

bl_info = {
    "name": "my test addon",
    "author": "jojo",
    "version": (1,0),
    "blender": (2,83,0),
    "category": "Object",
    "location": "Operator Search",
    "description": "More monkeys!!",
    "warning" : "",
    "doc_url": "",
    "tracker_url": ""
}

class MESH_OT_monkey_grid(bpy.types.Operator):
    """Let's spread some joy"""
    bl_idname = "mesh.monkey_grid"
    bl_label = "Monkey Grid"
    bl_options = {'REGISTER', 'UNDO'}

    count_x: bpy.props.IntProperty(
        name="X",
        description="Number of monkeys in the x direction",
        default=3,
        min=1,soft_max=10
    )
    count_y: bpy.props.IntProperty(
        name="Y",
        description="Number of monkeys in the y direction",
        default=2,
        min=1,soft_max=10
    )
    size: bpy.props.FloatProperty(
        name="Size",
        description="Size of each monkey",
        min=0,max=1
    )

    @classmethod
    def poll(cls, context):
        if context.area.type == 'VIEW_3D':
            return True
        return False

    def execute(self, context):
        for idx in range(self.count_x * self.count_y):
            x = idx % self.count_x
            y = idx // self.count_x
            bpy.ops.mesh.primitive_monkey_add(
                size=self.size,
                location=(x, y, 1))
        return {'FINISHED'}

class VIEW3D_PT_monkey_grid(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Monkeys"
    bl_label = "Grid"

    def draw(self, context):
        self.layout.operator('mesh.monkey_grid',
            text='Default grid',
            icon='MONKEY')
        props = self.layout.operator('mesh.monkey_grid',
            text='Big grid',
            icon='MONKEY')
        props.count_x = 10
        props.count_y = 10
        props.size = 0.8

def register():
    bpy.utils.register_class(MESH_OT_monkey_grid)
    bpy.utils.register_class(VIEW3D_PT_monkey_grid)

def unregister():
    bpy.utils.unregister_class(MESH_OT_monkey_grid)
    bpy.utils.unregister_class(VIEW3D_PT_monkey_grid)

Simple button

source : https://blenderartists.org/t/calling-a-function-from-a-button/616752/7

import bpy



class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        self.report({'INFO'}, "Button clicked!")
        return {'FINISHED'}


def draw_func(self, context):
    layout = self.layout
    layout.operator("object.simple_operator")
    

def register():
    bpy.utils.register_class(SimpleOperator)
    bpy.types.VIEW3D_HT_header.prepend(draw_func)


def unregister():
    bpy.utils.unregister_class(SimpleOperator)
    bpy.types.VIEW3D_HT_header.remove(draw_func)


if __name__ == "__main__":
    register()

bl_info = {
    "name": "Exploded Bake",
    "category": "Render",
}


import bpy


class ExplodedBake(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Exploded Bake"
    bl_idname = "OBJECT_PT_exploded_bake" # follow Blender convention for id names
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"
    
    def draw(self, context):
        layout = self.layout
        obj = context.object
        
        row = layout.row()
        row.prop(obj, "name")
        
        row = layout.row()
        row.operator("button.explode")


class buttonExplode(bpy.types.Operator):
    bl_idname = "button.explode" # translates to C-name BUTTON_OT_explode
    bl_label = "Button text"

    def execute(self, context):
        #self.report({'INFO'}, "Hello world!")
        print("hello")
        return {'FINISHED'}
    
    
# (un-)register entire module, so you don't need to add every class here...
def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)


if __name__ == "__main__":
    register()

import bpy

class SEBMAS_PROP_move_origins(bpy.types.PropertyGroup):

    def update_function(self, context):
        if self.move_origin_toggle:
            bpy.context.scene.tool_settings.use_transform_data_origin = True
            bpy.context.scene.tool_settings.snap_elements = {'VERTEX'}
        else:
            bpy.context.scene.tool_settings.use_transform_data_origin = False
            bpy.context.scene.tool_settings.snap_elements = {'INCREMENT'}
        return

    bpy.types.WindowManager.move_origin_toggle = bpy.props.BoolProperty(
        default = False,
        update = update_function)

class SEBMAS_PT_tools_move_origin(bpy.types.Panel):
    bl_category = "Tool"
    bl_context = ".objectmode"  # dot on purpose (access from topbar)
    bl_label = "SebMas"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

    def draw(self, context):
        self.layout.prop(context.window_manager,
            'move_origin_toggle',
            text="Move origin",
            toggle=True,
            icon='MONKEY')

classes = (
    SEBMAS_PT_tools_move_origin,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

if __name__ == "__main__":
    register()

bounding box

Select an object and then :

import bpy;
ob = bpy.data.scenes.active.objects.active;
ob.getData(mesh=True).verts.extend(ob.getBoundBox(0))
the object needs to dont have any “transformation”

inventory

import bpy
import csv

def displayLine(title, qty, price):
    print(title+ " : "+ str(qty)+ "("+ str(price* qty)+ ")")

def cherche():
    # get selection
    selected_objects = bpy.context.selected_objects

    inventory = {
        "eclisse-de-liaison-40-52" : {
            "price":4.75,
            "qty":0
        },
        "corniere-murale-2000mm": {
            "price":33.82,
            "qty":0
        },
        # solives
        "solive-omega 65-1000mm": {
            "price":17,
            "qty":0
        },
        "solive-omega 65-2000mm": {
            "price":34,
            "qty":0
        },
        "solive-omega 65-2500mm": {
            "price":42.44,
            "qty":0
        },
        "solive-omega 65-3000mm": {
            "price":51.03,
            "qty":0
        },
        # lambourdes
        "lambourde-omega 35-100mm": {
            "price":1.018,
            "qty":0
        },
        "lambourde-omega 35-150mm": {
            "price":1.52,
            "qty":0
        },
        "lambourde-omega 35-2000mm": {
            "price":20.37,
            "qty":0
        },
        "lambourde-omega 35-3000mm": {
            "price":30.52,
            "qty":0
        },
        "longueur-en-metres-lambourde-a-decouper": {
            "price":10.18,
            "qty":0
        },
        # profil de descente
        "profil-de-descente-55-20-100mm": {
            "price":0.57,
            "qty":0
        },
        "longueur-en-metres-profil-de-descente": {
            "price":5.78,
            "qty":0
        },
        # plots
        "plot": {
            "price":2.4,
            "qty":0
        },
        # lames (7 par m² donc a 90€ le m² ça fait 12.85 la lame)
        "lame-terrasse-1000mm": {
            "price":12.85,
            "qty":0
        },
        # scotch
        "bande-adhesive-epdm-100mm": {
            "price":0.12,
            "qty":0
        },
        "bande-adhesive-epdm-150mm": {
            "price":0.18,
            "qty":0
        },
        "bande-adhesive-epdm-1000mm": {
            "price":1.21,
            "qty":0
        },
        "bande-adhesive-epdm-2000mm": {
            "price":2.42,
            "qty":0
        },
        "longueur-en-metres-bande-adhesive": {
            "price": 1.21,
            "qty":0
        }
    }

    # do the job
    for o in selected_objects:
        if o.type != 'EMPTY':
            # get the object's name
            object_name = o.name

            for item, details in inventory.items():
                if object_name.__contains__(item):
                    details["qty"] += 1
                    # longueur bande adhesive
                    if object_name.__contains__("bande-adhesive-epdm-100mm"):
                        inventory["longueur-en-metres-bande-adhesive"]["qty"] += 0.1
                    if object_name.__contains__("bande-adhesive-epdm-150mm"):
                        inventory["longueur-en-metres-bande-adhesive"]["qty"] += 0.15
                    if object_name.__contains__("bande-adhesive-epdm-1000mm"):
                        inventory["longueur-en-metres-bande-adhesive"]["qty"] += 1
                    if object_name.__contains__("bande-adhesive-epdm-2000mm"):
                        inventory["longueur-en-metres-bande-adhesive"]["qty"] += 2
                    # longueur lambourde a decouper
                    if object_name.__contains__("lambourde-omega 35-100mm"):
                        inventory["longueur-en-metres-lambourde-a-decouper"]["qty"] += 0.1
                    if object_name.__contains__("lambourde-omega 35-150mm"):
                        inventory["longueur-en-metres-lambourde-a-decouper"]["qty"] += 0.15
                    # longueur profil de descente
                    if object_name.__contains__("profil-de-descente-55-20-100mm"):
                        inventory["longueur-en-metres-profil-de-descente"]["qty"] += 0.1
    return inventory

def montre(inventory):
    total_price = 0
    for item, value in inventory.items():
        if not item.__contains__("bande-adhesive-epdm")\
        and not item.__contains__("lambourde-omega 35-100mm")\
        and not item.__contains__("lambourde-omega 35-150mm")\
        and not item.__contains__("profil-de-descente-55-20-100mm"):
            if value["qty"] > 0:
                print(item)
                print("\t\tqty:\t"+ str(round(value["qty"], 2)))
                print("\t\tp/u:\t"+ str(value["price"])+ " ht")
                price = value["qty"]*value["price"]
                total_price += price
                print("\ttotal price:\t"+ str(round(price, 2))+ " ht\n")
    print("----------------------------------------")
    print("total price: "+ str(round((total_price* 1.206), 2))+ " ttc")

def writeCSV(inventory):
    total_price = 0
    rows = [['designation', 'qty', 'pu', 'total']]
    for item, value in inventory.items():
        if not item.__contains__("bande-adhesive-epdm")\
        and not item.__contains__("lambourde-omega 35-100mm")\
        and not item.__contains__("lambourde-omega 35-150mm")\
        and not item.__contains__("profil-de-descente-55-20-100mm"):
            if value["qty"] > 0:
                price = value["qty"]*value["price"]
                rows.append([item, round(value["qty"], 2), value["price"], round(price, 2)])

    with open(bpy.path.abspath("//")+'\\prix_terrasse.csv', 'w', newline='') as file: 
        writer = csv.writer(file, delimiter='|')
        writer.writerows(rows)
    
print("========================================")
bpy.ops.object.select_all(action='DESELECT')
# get collection
col = bpy.data.collections["terrassteel-droit"]

# get all children collections recursively
for subcol in col.children_recursive:
    for obj in col.all_objects:
        obj.select_set(True)


inventory = cherche()
montre(inventory)
writeCSV(inventory)
print("========================================")

script as module

import bpy
my_module = bpy.data.texts["Text"].as_module()

my_module.toto()

delete collection

# source : https://blender.stackexchange.com/a/173894

import bpy
#from bpy import context


name = "Collection 1"
remove_collection_objects = True

#coll = context.collection # 
coll = bpy.data.collections.get(name)

if coll:
    if remove_collection_objects:
        obs = [o for o in coll.objects if o.users == 1]
        while obs:
            bpy.data.objects.remove(obs.pop())

    bpy.data.collections.remove(coll)

duplicate collection linked

import bpy

def duplicate_collection_by_name(collection_name):
    original_collection = bpy.data.collections.get(collection_name)
    
    if original_collection is None:
        print(f"Collection '{collection_name}' does not exist.")
        return
    
    new_collection = original_collection.copy()
    new_collection.name = f"{collection_name}_duplicate"
    
    bpy.context.scene.collection.children.link(new_collection)

duplicate collection

import bpy

def duplicate_collection_by_name(collection_name):
    original_collection = bpy.data.collections.get(collection_name)
    
    if original_collection is None:
        print(f"Collection '{collection_name}' does not exist.")
        return
    
    new_collection = bpy.data.collections.new(f"{collection_name}_duplicate")
    bpy.context.scene.collection.children.link(new_collection)
    
    for obj in original_collection.objects:
        new_object = obj.copy()
        new_object.data = obj.data.copy()
        new_collection.objects.link(new_object)

delete empty collection

import bpy

def delete_empty_collections():
    collections = bpy.data.collections
    
    for collection in collections:
        if len(collection.objects) == 0:
            bpy.data.collections.remove(collection, do_unlink=True)

delete collection and its content

import bpy

def delete_collection_by_name(collection_name):
    collection = bpy.data.collections.get(collection_name)
    
    if collection is not None:
        bpy.data.collections.remove(collection, do_unlink=True)
    else:
        print(f"Collection '{collection_name}' does not exist.")