Today, I created a simple clothing addon with a two-mesh coat. The addon adds everything needed for the simulation including material types for the clothes.

Is a blog about python programming language. You can see my work with python programming language, tutorials and news.

bl_info = {
"name": "Append Materials from Folder",
"author": "Grok",
"version": (1, 2),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > Append Materials",
"description": "Select a folder and append all materials from .blend files recursively",
"category": "Import-Export",
}
import bpy
import os
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import StringProperty, PointerProperty
class AppendMaterialsProperties(PropertyGroup):
folder_path: StringProperty(
name="Folder Path",
description="Path to the folder containing .blend files",
default="",
maxlen=1024,
subtype='DIR_PATH'
)
class APPEND_OT_materials_from_folder(Operator):
bl_idname = "append.materials_from_folder"
bl_label = "Append Materials from Folder"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Append all materials from .blend files in the selected folder and subfolders"
def execute(self, context):
props = context.scene.append_materials_props
folder_path = props.folder_path
# Normalize path to avoid issues with slashes
folder_path = os.path.normpath(bpy.path.abspath(folder_path))
if not folder_path or not os.path.isdir(folder_path):
self.report({'ERROR'}, f"Invalid or no folder selected: {folder_path}")
return {'CANCELLED'}
self.report({'INFO'}, f"Scanning folder: {folder_path}")
blend_files_found = 0
materials_appended = 0
errors = []
# Walk recursively through the folder
for root, dirs, files in os.walk(folder_path):
self.report({'INFO'}, f"Checking folder: {root}")
for file in files:
if file.lower().endswith('.blend'):
blend_files_found += 1
blend_path = os.path.join(root, file)
self.report({'INFO'}, f"Found .blend file: {blend_path}")
try:
# Open the .blend file to inspect materials
with bpy.data.libraries.load(blend_path, link=False) as (data_from, data_to):
if data_from.materials:
data_to.materials = data_from.materials
materials_appended += len(data_from.materials)
self.report({'INFO'}, f"Appended {len(data_from.materials)} materials from: {blend_path}")
else:
self.report({'WARNING'}, f"No materials found in: {blend_path}")
except Exception as e:
errors.append(f"Failed to process {blend_path}: {str(e)}")
self.report({'WARNING'}, f"Error in {blend_path}: {str(e)}")
# Final report
if blend_files_found == 0:
self.report({'WARNING'}, f"No .blend files found in {folder_path} or its subfolders!")
else:
self.report({'INFO'}, f"Found {blend_files_found} .blend files, appended {materials_appended} materials.")
if errors:
self.report({'WARNING'}, f"Encountered {len(errors)} errors: {'; '.join(errors)}")
return {'FINISHED'}
class VIEW3D_PT_append_materials(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Append Materials"
bl_label = "Append Materials from Folder"
def draw(self, context):
layout = self.layout
props = context.scene.append_materials_props
layout.prop(props, "folder_path")
layout.operator("append.materials_from_folder", text="Append Materials")
def register():
bpy.utils.register_class(AppendMaterialsProperties)
bpy.utils.register_class(APPEND_OT_materials_from_folder)
bpy.utils.register_class(VIEW3D_PT_append_materials)
bpy.types.Scene.append_materials_props = PointerProperty(type=AppendMaterialsProperties)
def unregister():
bpy.utils.unregister_class(VIEW3D_PT_append_materials)
bpy.utils.unregister_class(APPEND_OT_materials_from_folder)
bpy.utils.unregister_class(AppendMaterialsProperties)
del bpy.types.Scene.append_materials_props
if __name__ == "__main__":
register()
bl_info = {
"name": "3D File Renderer by catafest",
"blender": (4, 3, 2),
"category": "Object",
"author": "Catalin George Festila\n"
"nicknames: catafest and mythcat\n"
"country: Romania\n"
"mail: catafest [at] yahoo.com",
"version": (1, 0),
"blender": (2, 80, 0),
"location": "View3D > UI > 3D File Renderer",
"description": "Addon for rendering 3D files",
"warning": "",
"doc_url": "https://github.com/catafest",
"tracker_url": "https://github.com/catafest/issues",
"support": "COMMUNITY",
}
import bpy
import os
class FileRendererProperties(bpy.types.PropertyGroup):
input_directory: bpy.props.StringProperty(
name="Input Directory",
description="Directory containing 3D files",
default="",
maxlen=1024,
subtype='DIR_PATH'
)
output_directory: bpy.props.StringProperty(
name="Output Directory",
description="Directory to save rendered images",
default="",
maxlen=1024,
subtype='DIR_PATH'
)
class RENDER_OT_files(bpy.types.Operator):
bl_idname = "render.files"
bl_label = "Start render 3D files for all files"
def execute(self, context):
input_directory = context.scene.file_renderer_props.input_directory
output_directory = context.scene.file_renderer_props.output_directory
if not input_directory or not output_directory:
self.report({'ERROR'}, "Input and Output directories must be set.")
return {'CANCELLED'}
if not os.path.exists(output_directory):
os.makedirs(output_directory)
def render_file(file_path, output_path):
try:
bpy.ops.wm.read_factory_settings(use_empty=True)
ext = os.path.splitext(file_path)[1].lower()
if ext == ".glb":
bpy.ops.import_scene.gltf(filepath=file_path)
elif ext == ".obj":
bpy.ops.import_scene.obj(filepath=file_path)
elif ext == ".fbx":
bpy.ops.import_scene.fbx(filepath=file_path)
else:
raise ValueError("Unsupported file format")
bpy.ops.object.camera_add(location=(0, -3, 1.5), rotation=(1.1, 0, 0))
camera = bpy.context.scene.objects['Camera']
bpy.context.scene.camera = camera
bpy.ops.object.light_add(type='POINT', location=(0, -3, 3))
light = bpy.context.view_layer.objects.active
light.data.energy = 1000
bpy.context.scene.render.resolution_x = 512
bpy.context.scene.render.resolution_y = 512
bpy.context.scene.render.filepath = output_path
bpy.ops.render.render(write_still=True)
except Exception as e:
# Generate a red image with "BAD FILE" text using Blender
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.mesh.primitive_plane_add(size=2)
plane = bpy.context.active_object
mat = bpy.data.materials.new(name="BadFileMaterial")
mat.diffuse_color = (1, 0, 0, 1) # Red
plane.data.materials.append(mat)
# Add "BAD FILE" text
bpy.ops.object.text_add(location=(0, 0, 0.1))
text_obj = bpy.context.active_object
text_obj.data.body = "BAD FILE"
text_obj.data.size = 0.5
text_obj.data.align_x = 'CENTER'
text_obj.data.align_y = 'CENTER'
text_obj.rotation_euler = (1.5708, 0, 0)
# Set camera and light
bpy.ops.object.camera_add(location=(0, -3, 1.5), rotation=(1.1, 0, 0))
camera = bpy.context.scene.objects['Camera']
bpy.context.scene.camera = camera
bpy.ops.object.light_add(type='POINT', location=(0, -3, 3))
light = bpy.context.view_layer.objects.active
light.data.energy = 1000
bpy.context.scene.render.resolution_x = 512
bpy.context.scene.render.resolution_y = 512
bpy.context.scene.render.filepath = output_path
bpy.ops.render.render(write_still=True)
for filename in os.listdir(input_directory):
if filename.lower().endswith((".glb", ".obj", ".fbx")):
file_path = os.path.join(input_directory, filename)
output_path = os.path.join(output_directory, os.path.splitext(filename)[0] + ".png")
render_file(file_path, output_path)
self.report({'INFO'}, "Rendering of files is complete.")
return {'FINISHED'}
class ABOUT_OT_dialog(bpy.types.Operator):
bl_idname = "wm.about_dialog"
bl_label = "About this addon"
def execute(self, context):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.label(text="3D File Renderer by catafest")
layout.label(text="Author: Catalin George Festila")
layout.label(text="Nicknames: catafest and mythcat")
layout.label(text="Country: Romania")
layout.label(text="Email: catafest [at] yahoo.com")
layout.operator("wm.url_open", text="LinkedIn").url = "https://www.linkedin.com/in/c%C4%83t%C4%83lin-george-fe%C8%99til%C4%83-05780a67"
layout.operator("wm.url_open", text="Author Site").url = "https://sites.google.com/view/festila-george-catalin"
layout.operator("wm.url_open", text="catafest GitHub").url = "https://github.com/catafest"
layout.operator("wm.url_open", text="catafest-work GitHub").url = "https://github.com/catafest-work"
class FileRendererPanel(bpy.types.Panel):
bl_label = "3D File Renderer by catafest"
bl_idname = "OBJECT_PT_file_renderer"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'File Renderer'
def draw(self, context):
layout = self.layout
scene = context.scene
file_renderer_props = scene.file_renderer_props
layout.prop(file_renderer_props, "input_directory")
layout.prop(file_renderer_props, "output_directory")
# Styling the render button
render_button = layout.operator("render.files", text="Start render 3D files for all files")
layout.separator()
layout.operator("wm.about_dialog", text="About this addon")
def register():
bpy.utils.register_class(FileRendererProperties)
bpy.utils.register_class(RENDER_OT_files)
bpy.utils.register_class(ABOUT_OT_dialog)
bpy.utils.register_class(FileRendererPanel)
bpy.types.Scene.file_renderer_props = bpy.props.PointerProperty(type=FileRendererProperties)
def unregister():
bpy.utils.unregister_class(FileRendererProperties)
bpy.utils.unregister_class(RENDER_OT_files)
bpy.utils.unregister_class(ABOUT_OT_dialog)
bpy.utils.unregister_class(FileRendererPanel)
del bpy.types.Scene.file_renderer_props
if __name__ == "__main__":
register()

import bpy, bmesh
obj = bpy.context.active_object
me = obj.data
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_mode(type="VERT")
bm = bmesh.from_edit_mesh(obj.data)
selected = [False,False,True,True,True,True,True,True]
verts = [vert for vert in bpy.context.active_object.data.vertices if vert.select]
all = [vert for vert in bpy.context.active_object.data.vertices]
print("selected:",len(verts))
print("all:",len(all))
bpy.ops.object.mode_set(mode = 'OBJECT')
me.vertices.foreach_set(
"select",
selected
)
bpy.ops.object.mode_set(mode = 'EDIT')
# give Python access to Blender's functionality
import bpy
# extend Python's math functionality
import math
# extend Python functionality to generate random numbers
import random
def partially_clean_the_scene():
# select all object in the scene
bpy.ops.object.select_all(action="SELECT")
# delete all selected objects in the scene
bpy.ops.object.delete()
# make sure we remove data that was connected to the objects we just deleted
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
def create_noise_mask(material):
"""Add a set of nodes to create a noise mask using:
* Texture Coordinate node
* Mapping node
* Noise Texture node
* Color Ramp node
"""
node_location_x_step = 300
node_location_x = -node_location_x_step
# create a Color Ramp node
# https://docs.blender.org/api/current/bpy.types.ShaderNodeValToRGB.html
color_ramp_node = material.node_tree.nodes.new(type="ShaderNodeValToRGB")
color_ramp_node.color_ramp.elements[0].position = 0.45
color_ramp_node.color_ramp.elements[1].position = 0.5
color_ramp_node.location.x = node_location_x
node_location_x -= node_location_x_step
# create a Noise Texture node
# https://docs.blender.org/api/current/bpy.types.ShaderNodeTexNoise.html#bpy.types.ShaderNodeTexNoise
noise_texture_node = material.node_tree.nodes.new(type="ShaderNodeTexNoise")
noise_texture_node.inputs["Scale"].default_value = random.uniform(1.0, 20.0)
noise_texture_node.location.x = node_location_x
node_location_x -= node_location_x_step
# create a Mapping node
# https://docs.blender.org/api/current/bpy.types.ShaderNodeMapping.html#bpy.types.ShaderNodeMapping
mapping_node = material.node_tree.nodes.new(type="ShaderNodeMapping")
mapping_node.inputs["Rotation"].default_value.x = math.radians(random.uniform(0.0, 360.0))
mapping_node.inputs["Rotation"].default_value.y = math.radians(random.uniform(0.0, 360.0))
mapping_node.inputs["Rotation"].default_value.z = math.radians(random.uniform(0.0, 360.0))
mapping_node.location.x = node_location_x
node_location_x -= node_location_x_step
# create a Texture Coordinate node
texture_coordinate_node = material.node_tree.nodes.new(type="ShaderNodeTexCoord")
texture_coordinate_node.location.x = node_location_x
# connect the nodes
# https://docs.blender.org/api/current/bpy.types.NodeTree.html#bpy.types.NodeTree
# https://docs.blender.org/api/current/bpy.types.NodeLinks.html#bpy.types.NodeLinks
material.node_tree.links.new(noise_texture_node.outputs["Color"], color_ramp_node.inputs["Fac"])
material.node_tree.links.new(mapping_node.outputs["Vector"], noise_texture_node.inputs["Vector"])
material.node_tree.links.new(texture_coordinate_node.outputs["Generated"], mapping_node.inputs["Vector"])
return color_ramp_node
def create_material(name):
# create new material
material = bpy.data.materials.new(name=name)
# enable creating a material via nodes
material.use_nodes = True
# get a reference to the Principled BSDF shader node
principled_bsdf_node = material.node_tree.nodes["Principled BSDF"]
# set the base color of the material
principled_bsdf_node.inputs["Base Color"].default_value = (0.8, 0.120827, 0.0074976, 1)
# set the metallic value of the material
principled_bsdf_node.inputs["Metallic"].default_value = 1.0
color_ramp_node = create_noise_mask(material)
material.node_tree.links.new(color_ramp_node.outputs["Color"], principled_bsdf_node.inputs["Roughness"])
return material
def add_mesh():
# create an ico sphere
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=5)
# shade smooth
bpy.ops.object.shade_smooth()
# get reference to mesh object
mesh_obj = bpy.context.active_object
return mesh_obj
def main():
partially_clean_the_scene()
name = "my_generated_material"
material = create_material(name)
mesh_obj = add_mesh()
# apply the material to the mesh object
mesh_obj.data.materials.append(material)
main()
import bpy
import math
# Set the object that the camera will orbit around
#target_object = bpy.data.objects["MyObject"]
# Create a new empty object
empty = bpy.data.objects.new("Empty", None)
# Set the empty object's location to the origin point
empty.location = (0, 0, 0)
# Set the starting position for the camera
camera = bpy.data.objects["Camera"]
# Set the number of degrees to rotate the camera around the object
degrees = 360
# Set the distance that the camera should be from the object
distance = 7.6
# Set the speed at which the camera should orbit
speed = 10
# Set the direction in which the camera should orbit (1 for clockwise, -1 for counter-clockwise)
direction = 1
# Set the camera to track the object
bpy.ops.object.select_all(action="DESELECT")
camera.select_set(True)
# Set the distance to origin point
camera.location = (-distance, 0, 0)
bpy.context.view_layer.objects.active = camera
# Remove all constraints from the object "Cube"
bpy.data.objects['Cube'].select_get()
bpy.context.view_layer.objects.active = bpy.data.objects['Cube']
bpy.ops.object.constraints_clear()
# Add a track to constraint to the object and set it
bpy.ops.object.constraint_add(type="TRACK_TO")
bpy.ops.object.track_set(type="TRACKTO")
# Set the target object as the tracking target
bpy.data.objects['Cube'].select_get()
bpy.context.view_layer.objects.active = bpy.data.objects['Cube']
# Select the file image format
bpy.context.scene.render.image_settings.file_format = 'PNG'
# Animate the camera orbiting around the object
for frame in range(0, 36):
# Set the current frame
bpy.context.scene.frame_set(frame)
# Calculate the new position for the camera based on its distance from the object
x = distance * math.sin(math.radians(frame*speed*direction))
y = distance * math.cos(math.radians(frame*speed*direction))
camera.location = (x,y,0)
# Set the output path for the rendered image
bpy.context.scene.render.filepath = "C:\\tmp\\myimage_" + str(frame).zfill(3) + ".png"
# Render the frame and save it to the output file
bpy.ops.render.render(write_still=True)pip install ursina
Collecting ursina
...
Installing collected packages: panda3d, screeninfo, panda3d-simplepbr, panda3d-gltf, ursina
Successfully installed panda3d-1.10.14 panda3d-gltf-1.2.0 panda3d-simplepbr-0.12.0 screeninfo-0.8.1 ursina-7.0.0from ursina import *
app = Ursina(size=(1280,720))
physics_entities = []
class PhysicsEntity(Entity):
def __init__(self, model='cube', collider='box', **kwargs):
super().__init__(model=model, collider=collider, **kwargs)
physics_entities.append(self)
def update(self):
if self.intersects():
self.stop()
return
self.velocity = lerp(self.velocity, Vec3(0), time.dt)
self.velocity += Vec3(0,-1,0) * time.dt * 5
self.position += (self.velocity + Vec3(0,-4,0)) * time.dt
def stop(self):
self.velocity = Vec3(0,0,0)
if self in physics_entities:
physics_entities.remove(self)
def on_destroy(self):
self.stop()
def throw(self, direction, force):
pass
from ursina.shaders import lit_with_shadows_shader
Entity.default_shader = lit_with_shadows_shader
DirectionalLight().look_at(Vec3(1,-1,-1))
ground = Entity(model='plane', scale=32, texture='white_cube', texture_scale=Vec2(32), collider='box')
from ursina.prefabs.first_person_controller import FirstPersonController
player = FirstPersonController()
def input(key):
if key == 'left mouse down':
e = PhysicsEntity(model='cube', color=color.azure, velocity=Vec3(0), position=player.position+Vec3(0,1.5,0)+player.forward, collider='sphere')
e.velocity = (camera.forward + Vec3(0,.5,0)) * 10
# physics_entities.append(e)
Sky()
app.run()

import bpy
import random
# fix an active object before changing mode
if bpy.context.view_layer.objects.active is None:
# If there is no active object, set the first object in the scene as active
if len(bpy.context.scene.objects) > 0:
bpy.context.view_layer.objects.active = bpy.context.scene.objects[0]
else:
print("There are no objects in the scene to set as active.")
# set the mode
bpy.ops.object.mode_set(mode='OBJECT')
# set camera from the scene
cam_ob = bpy.context.scene.camera
# if is a camera in the scene and set it as active object
if cam_ob is None:
print("There is no camera in the scene.")
elif cam_ob.type == 'CAMERA':
# Set the camera as active object
bpy.context.view_layer.objects.active = cam_ob
print("The camera has been set as active object.")
else:
print(f"The object {cam_ob.type} is set as a camera, but it is not of type 'CAMERA'.")
# set data for the ink brush
gpencil = bpy.data.grease_pencils.new("Bright Pencil")
# make material for the ink brush
if "Bright Material" in bpy.data.materials.keys():
gp_mat = bpy.data.materials["Bright Material"]
else:
gp_mat = bpy.data.materials.new("Bright Material")
if not gp_mat.is_grease_pencil:
bpy.data.materials.create_gpencil_data(gp_mat)
bpy.context.object.active_material.grease_pencil.color = (0, 0, 1, 1)
# set the object for the ink brush
if "Bright Pencil" not in bpy.data.objects:
gp_data = bpy.data.objects.new("Bright Pencil", gpencil)
bpy.context.scene.collection.objects.link(gp_data)
# if it does not already exist in the scene
else:
gp_data = bpy.data.objects["Bright Pencil"]
# assign material for drawing
if gp_mat.name not in gp_data.data.materials:
gp_data.data.materials.append(gp_mat)
# define a function to create random lines
def create_random_strokes(num_strokes, max_width):
# Set the active object and mode
bpy.context.view_layer.objects.active = gp_data
bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
# get or create layer and set it as active
if gpencil.layers and gpencil.layers.active:
layer = gpencil.layers.active
else:
layer = gpencil.layers.new('my_test_layer', set_active=True)
# set layer as active
gpencil.layers.active = layer
# get or create frame and set it as active using change_frame() method
if layer.frames and layer.active_frame:
frame = layer.active_frame
else:
frame = layer.frames.new(1)
for _ in range(num_strokes):
stroke = frame.strokes.new()
stroke.line_width = int(random.uniform(1.0, max_width))
stroke.points.add(count=3)
for point in stroke.points:
point.co = (random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0), 0.0)
# this function with desired parameters
create_random_strokes(num_strokes=10, max_width=16.0)
# return to original mode
bpy.ops.object.mode_set(mode='OBJECT')
import bpy
import mathutils
from mathutils import Vector
from math import *
class ArmatureMenu(bpy.types.Menu):
bl_label = "Mesh 2 Armature Menu"
bl_idname = "OBJECT_MT_Mesh_From_Armature"
def draw(self, context):
layout = self.layout
layout.operator("wm.mesh_from_armature", text="Pyramid").mesh_type = 'Pyramid' # from here
layout.operator("wm.mesh_from_armature", text="Tapered").mesh_type = 'Tapered' # from here
layout.operator("wm.mesh_from_armature", text="Box").mesh_type = 'Box' # from here
def CreateMesh(self, meshType):
obj = bpy.context.active_object
if obj == None:
self.report({"ERROR"}, "No selection" )
elif obj.type != 'ARMATURE':
self.report({"ERROR"}, "Armature expected" )
else:
processArmature( bpy.context, obj, meshType = meshType )
#Create the base object from the armature
def meshFromArmature( arm ):
name = arm.name + "_mesh"
meshData = bpy.data.meshes.new( name + "Data" )
meshObj = bpy.data.objects.new( name, meshData )
meshObj.matrix_world = arm.matrix_world.copy()
return meshObj
#Create the bone geometry (vertices and faces)
def boneGeometry( l1, l2, x, z, baseSize, l1Size, l2Size, base, meshType ):
if meshType == 'Tapered':
print(meshType)
x1 = x * baseSize * l1Size
z1 = z * baseSize * l1Size
x2 = x * baseSize * l2Size
z2 = z * baseSize * l2Size
elif meshType == 'Box':
print(meshType)
lSize = (l1Size + l2Size) / 2
x1 = x * baseSize * lSize
z1 = z * baseSize * lSize
x2 = x * baseSize * lSize
z2 = z * baseSize * lSize
else: # default to Pyramid
print(meshType)
x1 = x * baseSize * l1Size
z1 = z * baseSize * l1Size
x2 = Vector( (0, 0, 0) )
z2 = Vector( (0, 0, 0) )
verts = [
l1 - x1 + z1,
l1 + x1 + z1,
l1 - x1 - z1,
l1 + x1 - z1,
l2 - x2 + z2,
l2 + x2 + z2,
l2 - x2 - z2,
l2 + x2 - z2
]
faces = [
(base+3, base+1, base+0, base+2),
(base+6, base+4, base+5, base+7),
(base+4, base+0, base+1, base+5),
(base+7, base+3, base+2, base+6),
(base+5, base+1, base+3, base+7),
(base+6, base+2, base+0, base+4)
]
return verts, faces
#Process the armature, goes through its bones and creates the mesh
def processArmature(context, arm, genVertexGroups = True, meshType = 'Pyramid'):
print("processing armature {0} {1}".format(arm.name, meshType) )
#Creates the mesh object
meshObj = meshFromArmature( arm )
context.collection.objects.link( meshObj )
verts = []
edges = []
faces = []
vertexGroups = {}
bpy.ops.object.mode_set(mode='EDIT')
try:
#Goes through each bone
for editBone in [b for b in arm.data.edit_bones if b.use_deform]:
boneName = editBone.name
# print( boneName )
poseBone = arm.pose.bones[boneName]
#Gets edit bone informations
editBoneHead = editBone.head
editBoneTail = editBone.tail
editBoneVector = editBoneTail - editBoneHead
editBoneSize = editBoneVector.dot( editBoneVector )
editBoneRoll = editBone.roll
editBoneX = editBone.x_axis
editBoneZ = editBone.z_axis
editBoneHeadRadius = editBone.head_radius
editBoneTailRadius = editBone.tail_radius
#Creates the mesh data for the bone
baseIndex = len(verts)
baseSize = sqrt( editBoneSize )
newVerts, newFaces = boneGeometry( editBoneHead, editBoneTail, editBoneX, editBoneZ, baseSize, editBoneHeadRadius, editBoneTailRadius, baseIndex, meshType )
verts.extend( newVerts )
faces.extend( newFaces )
#Creates the weights for the vertex groups
vertexGroups[boneName] = [(x, 1.0) for x in range(baseIndex, len(verts))]
#Assigns the geometry to the mesh
meshObj.data.from_pydata(verts, edges, faces)
except:
bpy.ops.object.mode_set(mode='OBJECT')
else:
bpy.ops.object.mode_set(mode='OBJECT')
#Assigns the vertex groups
if genVertexGroups:
for name, vertexGroup in vertexGroups.items():
groupObject = meshObj.vertex_groups.new(name=name)
for (index, weight) in vertexGroup:
groupObject.add([index], weight, 'REPLACE')
#Creates the armature modifier
modifier = meshObj.modifiers.new('ArmatureMod', 'ARMATURE')
modifier.object = arm
modifier.use_bone_envelopes = False
modifier.use_vertex_groups = True
meshObj.data.update()
return meshObj
class MeshFromArmatureOperator(bpy.types.Operator):
bl_idname = "wm.mesh_from_armature"
bl_label = "MeshFromArmatureOperator"
mesh_type : bpy.props.StringProperty(name="mesh_type")
def execute(self, context):
print('The mesh type is', self.mesh_type)
CreateMesh(self, self.mesh_type)
return {'FINISHED'}
def register():
bpy.utils.register_class( ArmatureMenu )
bpy.utils.register_class( MeshFromArmatureOperator )
def unregister():
bpy.utils.unregister_class( ArmatureMenu )
bpy.utils.unregister_class( MeshFromArmatureOperator )
if __name__ == "__main__":
register()
# The menu can also be called from scripts
bpy.ops.wm.call_menu(name='OBJECT_MT_Mesh_From_Armature')

bl_info = {
"name": "Tools by catafest",
"blender": (3, 0, 0),
"category": "3D View",
}
import bpy
from bpy.types import Operator, Panel
from bpy.props import StringProperty
try:
import importlib
importlib.import_module("Pillow")
PIL_installed = True
except ImportError:
PIL_installed = False
def install_pillow():
import subprocess
try:
subprocess.run([bpy.app.binary_path, '--python-exit-code', '1', '-m', 'ensurepip'])
subprocess.check_call([bpy.app.binary_path, '-m', 'pip', 'install', 'Pillow'])
except subprocess.CalledProcessError as e:
print("Eroare la instalarea Pillow:", e)
# Operator pentru a afișa fereastra modală cu informații despre instalarea Pillow
class CATAFEST_IMAGES_OT_show_pillow_message(Operator):
bl_idname = "catafest.show_pillow_message"
bl_label = "Show Pillow Message"
def execute(self, context):
global PIL_installed
message = "Pillow este instalat." if PIL_installed else "Pillow nu este instalat."
# Dacă Pillow nu este instalat, încercați să-l instalați
if not PIL_installed:
install_pillow()
try:
import importlib
importlib.import_module("Pillow")
PIL_installed = True
message = "Pillow a fost instalat cu succes!" if PIL_installed else "Eroare la instalarea Pillow."
except ImportError:
PIL_installed = False
# Afișați fereastra modală în centrul ecranului
bpy.ops.catafest.show_modal_message('INVOKE_DEFAULT', title="Starea Pillow", message=message)
return {'FINISHED'}
def invoke(self, context, event):
return self.execute(context)
# Operator pentru a afișa fereastra modală personalizată
class CATAFEST_IMAGES_OT_show_modal_message(Operator):
bl_idname = "catafest.show_modal_message"
bl_label = "Show Modal Message"
title: bpy.props.StringProperty(default="Message")
message: bpy.props.StringProperty(default="")
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
layout.label(text=self.message)
# Panel pentru bara laterală din 3D View
class VIEW3D_PT_tools_image(Panel):
bl_label = "Images"
bl_idname = "VIEW3D_PT_tools_image"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tools by catafest'
def draw(self, context):
layout = self.layout
layout.operator(CATAFEST_IMAGES_OT_show_modal_message.bl_idname)
layout.operator(CATAFEST_IMAGES_OT_show_pillow_message.bl_idname)
def register():
bpy.utils.register_class(CATAFEST_IMAGES_OT_show_modal_message)
bpy.utils.register_class(CATAFEST_IMAGES_OT_show_pillow_message)
bpy.utils.register_class(VIEW3D_PT_tools_image)
def unregister():
bpy.utils.unregister_class(CATAFEST_IMAGES_OT_show_modal_message)
bpy.utils.unregister_class(CATAFEST_IMAGES_OT_show_pillow_message)
bpy.utils.unregister_class(VIEW3D_PT_tools_image)
if __name__ == "__main__":
register()
import bpy
#get active object - default
obj = bpy.context.active_object
# set the default start for working with Geometry Nodes modifier
# you need to have a Geometry Nodes modifier
node_group = obj.modifiers['GeometryNodes'].node_group
nodes = node_group.nodes
#get the node named 'Group Output'
geom_out = nodes.get('Group Output')
#create a string node
string_node = nodes.new('FunctionNodeInputString')
# set the name to 'String'
string_out = string_node.outputs['String']
# set the value to "This is a string"
string_node.string = "This is a string"
# link to the Group Output
node_group.links.new(string_out, geom_out.inputs[-1])

# import python packages
import bpy
from mathutils import Vector
# create a simpple BezierCurve and rename it with 'BezierCurveGeormetryNode'
bpy.ops.curve.primitive_bezier_curve_add()
bpy.ops.object.modifier_add(type='NODES')
curve = bpy.context.active_object
curve.name = 'BezierCurveGeormetryNode'
# define a function for GroupInit and GroupOutput
def new_GeometryNodes_group():
''' Create a new empty node group that can be used
in a GeometryNodes modifier.
'''
node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
inNode = node_group.nodes.new('NodeGroupInput')
inNode.outputs.new('NodeSocketGeometry', 'Geometry')
outNode = node_group.nodes.new('NodeGroupOutput')
outNode.inputs.new('NodeSocketGeometry', 'Geometry')
node_group.links.new(inNode.outputs['Geometry'], outNode.inputs['Geometry'])
# the -3.5 is value for how far will be set the GroupInit and GroupOutput in the area of GeormetryNodes
inNode.location = Vector((-3.5*inNode.width, 0))
outNode.location = Vector((3.5*outNode.width, 0))
return node_group
# the default curve modifier has no node group set, you need to set :
if curve.modifiers[-1].node_group:
node_group = curve.modifiers[-1].node_group
else:
node_group = new_GeometryNodes_group()
curve.modifiers[-1].node_group = node_group
# set default grup node as nodes
nodes = node_group.nodes
# get both nodes for each one
group_in = nodes.get('Group Input')
group_out = nodes.get('Group Output')
# add the GeometryNodeObjectInfo to the GeometryNode area
new_node_obj = nodes.new('GeometryNodeObjectInfo')
new_node_obj.inputs[0].default_value = bpy.data.objects["BezierCurveGeormetryNode"]

import bpy
def menu_func(self, context):
'''Open explorer in windows systems'''
self.layout.operator(
"wm.url_open", text="Open explorer", icon='FILE_FOLDER').url = "C:/"
def register():
bpy.types.TOPBAR_MT_help.append(menu_func)
def unregister():
bpy.types.TOPBAR_MT_help.remove(menu_func)
if __name__ == "__main__":
register()
import bpy
def menu_func(self, context):
self.layout.operator(
OpenOperator.bl_idname, text="Open explorer", icon='FILE_FOLDER')
class OpenOperator(bpy.types.Operator):
"""Open explorer in windows systems"""
bl_idname = "wm.open_explorer"
bl_label = "Open explorer"
def execute(self, context):
bpy.ops.wm.url_open(url="C:/")
return {'FINISHED'}
def register():
bpy.utils.register_class(OpenOperator)
bpy.types.TOPBAR_MT_help.append(menu_func)
def unregister():
bpy.utils.unregister_class(OpenOperator)
bpy.types.TOPBAR_MT_help.remove(menu_func)
if __name__ == "__main__":
register()
StringProperty(
BoolProperty(
IntProperty(
IntVectorProperty(
FloatProperty(
FloatVectorProperty(
BoolVectorProperty(

import bpy
# Assign a collection
class SceneSettingItem(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(name="Cube")
mesh = bpy.props.PointerProperty(type=bpy.types.Mesh)
PROPS = [
('myString', bpy.props.StringProperty(name='myString', default='this is my string!')),
('myBoolean', bpy.props.BoolProperty(name='myBoolean', default=False)),
('myInt', bpy.props.IntProperty(name='myInt', default=1)),
('myIntVectorXYZ', bpy.props.IntVectorProperty(subtype='XYZ')),
('myFloat', bpy.props.FloatProperty(name='myFloat', default=1)),
('myFloatVectorXYZ', bpy.props.FloatVectorProperty(subtype='XYZ')),
('myBooleanVector', bpy.props.BoolVectorProperty(size=3)),
('myBooleanVectorXYZ', bpy.props.BoolVectorProperty(size=3,subtype='XYZ')),
('myBooleanVectorColor', bpy.props.FloatVectorProperty(name="Edit Mode Color", subtype='COLOR', default=(0.76, 0.0, 0.0), size=3, min=0, max=1)),
('myCollectionProperty', bpy.props.CollectionProperty(type=SceneSettingItem)),
]
class HelloWorldPanelVariables(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Hello World Panel Variables"
bl_idname = "OBJECT_PT_hello"
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.label(text="Hello world!", icon='WORLD_DATA')
# row = layout.row()
# row.label(text="Active object is: " + obj.name)
# row = layout.row()
# row.prop(obj, "name")
# row = layout.row()
# row.operator("mesh.primitive_cube_add")
def draw(self, context):
col = self.layout.column()
for (prop_name, _) in PROPS:
row = col.row()
row.prop(context.scene, prop_name)
def register():
bpy.utils.register_class(SceneSettingItem)
bpy.utils.register_class(HelloWorldPanelVariables)
for (prop_name, prop_value) in PROPS:
setattr(bpy.types.Scene, prop_name, prop_value)
def unregister():
bpy.utils.unregister_class(SceneSettingItem)
bpy.utils.unregister_class(HelloWorldPanelVariables)
for (prop_name, _) in PROPS:
delattr(bpy.types.Scene, prop_name)
if __name__ == "__main__":
register()

import bpy
class HelloWorldPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Hello World Panel"
bl_idname = "OBJECT_PT_hello"
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.label(text="Hello world!", icon='WORLD_DATA')
row = layout.row()
row.label(text="Active object is: " + obj.name)
row = layout.row()
row.prop(obj, "name")
row = layout.row()
row.operator("mesh.primitive_cube_add")
def register():
bpy.utils.register_class(HelloWorldPanel)
def unregister():
bpy.utils.unregister_class(HelloWorldPanel)
if __name__ == "__main__":
register()


bl_info = {
"name": "New branch",
"author": "Your Name Here",
"version": (1, 0),
"blender": (3, 3, 0),
"location": "View3D > Add > Mesh > New Object",
"description": "Adds a new branch Mesh Object",
"warning": "",
"doc_url": "",
"category": "Add Mesh",
}
import bpy
from bpy.types import Operator
from bpy.props import FloatVectorProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
scale_x = self.scale.x
scale_y = self.scale.y
verts = [
Vector((-1 * scale_x, 1 * scale_y, 0)),
Vector((1 * scale_x, 1 * scale_y, 0)),
Vector((1 * scale_x, -1 * scale_y, 0)),
Vector((-1 * scale_x, -1 * scale_y, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="New Object Mesh")
mesh.from_pydata(verts, edges, faces)
# useful for development when the mesh may be invalid.
# mesh.validate(verbose=True)
object_data_add(context, mesh, operator=self)
class OBJECT_OT_add_object(Operator, AddObjectHelper):
"""Create a new branch Mesh Object"""
bl_idname = "mesh.add_object"
bl_label = "Add Mesh Object"
bl_options = {'REGISTER', 'UNDO'}
scale: FloatVectorProperty(
name="scale",
default=(1.0, 1.0, 1.0),
subtype='TRANSLATION',
description="scaling",
)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
# Registration
def add_object_button(self, context):
self.layout.operator(
OBJECT_OT_add_object.bl_idname,
text="catafest - add branch",
icon='PLUGIN')
# This allows you to right click on a button and link to documentation
def add_object_manual_map():
url_manual_prefix = "https://docs.blender.org/manual/en/latest/"
url_manual_mapping = (
("bpy.ops.mesh.add_object", "scene_layout/object/types.html"),
)
return url_manual_prefix, url_manual_mapping
def register():
bpy.utils.register_class(OBJECT_OT_add_object)
bpy.utils.register_manual_map(add_object_manual_map)
bpy.types.VIEW3D_MT_mesh_add.append(add_object_button)
def unregister():
bpy.utils.unregister_class(OBJECT_OT_add_object)
bpy.utils.unregister_manual_map(add_object_manual_map)
bpy.types.VIEW3D_MT_mesh_add.remove(add_object_button)
if __name__ == "__main__":
register()

import bpy
import random
# import bmesh
import bmesh
MinNubmer = -10
MaxNumber = 10
# Clean up the area , uncoment the next two row to keep
# branch after running the script
#bpy.ops.object.select_all(action="SELECT")
#bpy.ops.object.delete()
# Number of branches
branch = 4
# Create the verts array
verts = [(0,0,0)]
# Create the edges array
edges = [(0,0)]
# Create the faces array
faces = []
# define random number for X and Y axis
def RN():
return random.randint(MinNubmer, MaxNumber) / 20
# define random number for positive Z axis
def RNZ():
return random.randint(10, 50) / 10
# create a list of branch thicknesses
rand_list = []
name_branch = "TreeMesh"
# define createBranch
def createBranch(branch, name_branch):
# Create the mesh for branch
mesh = bpy.data.meshes.new(name_branch)
for i in range(1,branch):
rand_list.append(RNZ()/30)
# sort all reverse by thicknesses
rand_list.sort(reverse=True)
# generate vertices list for drawing the branch
for i in range(1,branch):
verts.append((rand_list[i-1] +0.1,rand_list[i-1]+0.1,RNZ()))
edges.append((i-1,i))
# sort the list of vertices by last number witch is Z axis
verts.sort(key=lambda x: x[2])
# create branch update and validate, see documentation
mesh.from_pydata(verts, edges, faces)
mesh.update()
mesh.validate()
# Create object to hold the mesh branch with the new name for object
obj = bpy.data.objects.new(name_branch+'_Obj', mesh)
return obj
# create a new branch
def createNewBranch(obj_branch, n):
bpy.ops.object.mode_set(mode="EDIT", toggle=False)
me = obj_branch.data
bm = bmesh.from_edit_mesh(me)
bm.select_mode = {'VERT'}
for i,v in enumerate(bm.verts):
# select only by the index of list
if i == n:
v.select = ( v.co.x > 0.0 )
v2 = v
else:
v.select = False
# flush and update view
v1 = bm.verts.new( (RN()+(v.co.x) + 1.0 , RN()+(v.co.y) + 1.0 , (v.co.z) - (v.co.z)/3) )
#v1 = bm.verts.new(1, 1, 3)
bm.edges.new((v1, v2))
rand_list.append(0.01)
rand_list.sort(reverse=True)
# update
bm.select_flush_mode()
me.update()
#mesh.validate()
#bmesh.update_edit_mesh(obj_branch.data)
# use the createBranch
obj_branch = createBranch(branch, name_branch)
## now set up shape key in Blender
#mesh=obj_branch.data
#sk_basis = obj_branch.shape_key_add(name='Basis',from_mix=False)
#sk_basis.interpolation = 'KEY_LINEAR'
## must set relative to false here
#obj_branch.data.shape_keys.use_relative = False
## create new shape key
#sk = obj_branch.shape_key_add(name='Deform',from_mix=False)
#sk.interpolation = 'KEY_LINEAR'
#sk.slider_min = 0
#sk.slider_max = 2
# ... and add it to the scene
scene = bpy.context.scene
scene.collection.objects.link(obj_branch)
# this will fix the error ... mode_set_poll()
bpy.context.view_layer.objects.active = obj_branch
createNewBranch(obj_branch, 1)
# print tool for developing area
def print_python_console(data):
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == 'CONSOLE':
override = {'window': window, 'screen': screen, 'area': area}
bpy.ops.console.scrollback_append(override, text=str(data), type="OUTPUT")
# used to see the size of radius skin for each vertices
print_python_console(rand_list)
# fix error : skin modifier is locked when using edit mode.
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
# add the skin modifier - NOT FIXED FOR THE LAST BRANC ADDED
obj_branch.modifiers.new(name="SK", type="SKIN")
bpy.context.view_layer.objects.active = obj_branch
# get the skin vertices layers
skin_vertices = obj_branch.data.skin_vertices
# get the layer
skin_layer = skin_vertices[0]
for i in range(1,branch+1):
# assigns radius for each vertice to sized the branch
skin_layer.data[i-1].radius = (rand_list[i-1], rand_list[i-1])
#Indices 0 and 1 are the vertex indices
skin_layer.data[i].radius = (rand_list[i-1],rand_list[i-1])
# this will apply the modifier named 'SK'
bpy.ops.object.modifier_apply( modifier = 'SK' )
#
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
bpy.ops.object.skin_root_mark()
bpy.ops.object.mode_set(mode="OBJECT", toggle=True)
# set modes for user
mesh = bpy.data.meshes.new(name_branch+'_Obj')
mesh.update()
mesh.validate()
import math
def get_dimension(normal):
x_abs = math.fabs(normal[0])
y_abs = math.fabs(normal[1])
z_abs = math.fabs(normal[2])
if z_abs >= x_abs and z_abs >= y_abs:
return 2
elif x_abs >= y_abs:
return 0
else:
return 1
texture_scale = 1.0
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
bpy.ops.mesh.select_all(action='SELECT')
me = obj_branch.data
bm = bmesh.from_edit_mesh(me)
bpy.ops.uv.sphere_project()
uv_layer = bm.loops.layers.uv.verify()
# adjust uv coordinates
for face in bm.faces:
for l in face.loops:
luv = l[uv_layer]
# select UV vertex if these are in certain range
if 0 <= luv.uv.x <= 1 and 0 <= luv.uv.y <= 1:
luv.select = True
luv = l[uv_layer]
luv.uv = l.vert.co.yz * texture_scale
luv.uv = l.vert.co.xz * texture_scale
luv.uv = l.vert.co.xy * texture_scale
bmesh.update_edit_mesh(me)
me.update()
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(angle_limit=1.15192, island_margin=0, area_weight=0, correct_aspect=True, scale_to_bounds=False)
# use this for cylinder project for UV map
#bpy.ops.uv.cylinder_project(direction='ALIGN_TO_OBJECT',
#align='POLAR_ZX',
#radius=1.0,
#correct_aspect=True,
#clip_to_bounds=False,
#scale_to_bounds=True)
bmesh.update_edit_mesh(me)
bpy.ops.object.mode_set(mode="OBJECT", toggle=True)
mesh.validate()
import math
def get_dimension(normal):
x_abs = math.fabs(normal[0])
y_abs = math.fabs(normal[1])
z_abs = math.fabs(normal[2])
if z_abs >= x_abs and z_abs >= y_abs:
return 2
elif x_abs >= y_abs:
return 0
else:
return 1
texture_scale = 1.0
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
me = obj_branch.data
bm = bmesh.from_edit_mesh(me)
uv_layer = bm.loops.layers.uv.verify()
for f in bm.faces:
largest_index = get_dimension(f.normal)
for l in f.loops:
luv = l[uv_layer]
luv.uv = l.vert.co.yz * texture_scale
luv.uv = l.vert.co.xz * texture_scale
luv.uv = l.vert.co.xy * texture_scale
me.update()

## now set up shape key in Blender
#mesh=obj_branch.data
#sk_basis = obj_branch.shape_key_add(name='Basis',from_mix=False)
#sk_basis.interpolation = 'KEY_LINEAR'
## must set relative to false here
#obj_branch.data.shape_keys.use_relative = False
## create new shape key
#sk = obj_branch.shape_key_add(name='Deform',from_mix=False)
#sk.interpolation = 'KEY_LINEAR'
#sk.slider_min = 0
#sk.slider_max = 2
# this will apply the modifier named 'SK'
bpy.ops.object.modifier_apply( modifier = 'SK' )
C:\blender-3.3.0-alpha+master.add1da52ad78-windows.amd64-release\3.3\python\bin
python.exe -m ensurepip
python.exe -m pip install --upgrade pip
python.exe -m pip install opencv-python
python.exe -m pip install opencv-contrib-python
import cv2
cv2.version
import numpy as np
import cv2
# Creating a black image with 3 channels
# RGB and unsigned int datatype
img = np.zeros((400, 400, 3), dtype = "uint8")
# Creating line
cv2.line(img, (21, 167), (100, 99), (0, 0, 255), 8)
cv2.imshow('dark', img)
# Allows us to see image
# until closed forcefully
cv2.waitKey(0)
cv2.destroyAllWindows()
