Obj sequences to alembic

Hi
I want convert obj sequence exist in a folder to alembic file using Python.
Is that possible?

It has to be python ? OR just interested in a solution… ?? Because… geometry nodes… (:wink: you know ??)

Thanks , yes i need python code

Too bad… just saw this the other day :person_shrugging:

Theres some info here that maybe of use…

Hope that helps

You can use the geo nodes setup mentioned by @Okidoki above and then use default alembic exporter bpy.ops.wm.alembic_export() to export the alembic.

If you want, you can fully automate it by importing objs and creating this geonodes setup from Python code.

Saved geo nodes setup to Python code with https://extensions.blender.org/add-ons/node-to-python/:

import bpy, mathutils

#initialize obj_sequence_player node group
def obj_sequence_player_node_group():
    obj_sequence_player = bpy.data.node_groups.new(type = 'GeometryNodeTree', name = "Obj Sequence Player")

    obj_sequence_player.color_tag = 'NONE'
    obj_sequence_player.description = ""
    obj_sequence_player.default_group_node_width = 140
    

    obj_sequence_player.is_modifier = True

    #obj_sequence_player interface
    #Socket Geometry
    geometry_socket = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='OUTPUT', socket_type = 'NodeSocketGeometry')
    geometry_socket.attribute_domain = 'POINT'

    #Socket Geometry
    geometry_socket_1 = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='INPUT', socket_type = 'NodeSocketGeometry')
    geometry_socket_1.attribute_domain = 'POINT'

    #Socket Obj Sequene Collection
    obj_sequene_collection_socket = obj_sequence_player.interface.new_socket(name = "Obj Sequene Collection", in_out='INPUT', socket_type = 'NodeSocketCollection')
    obj_sequene_collection_socket.attribute_domain = 'POINT'

    #Socket Starting Frame
    starting_frame_socket = obj_sequence_player.interface.new_socket(name = "Starting Frame", in_out='INPUT', socket_type = 'NodeSocketFloat')
    starting_frame_socket.default_value = 0.0
    starting_frame_socket.min_value = -3.4028234663852886e+38
    starting_frame_socket.max_value = 3.4028234663852886e+38
    starting_frame_socket.subtype = 'NONE'
    starting_frame_socket.attribute_domain = 'POINT'


    #initialize obj_sequence_player nodes
    #node Group Input
    group_input = obj_sequence_player.nodes.new("NodeGroupInput")
    group_input.name = "Group Input"

    #node Group Output
    group_output = obj_sequence_player.nodes.new("NodeGroupOutput")
    group_output.name = "Group Output"
    group_output.is_active_output = True

    #node Mesh Line
    mesh_line = obj_sequence_player.nodes.new("GeometryNodeMeshLine")
    mesh_line.name = "Mesh Line"
    mesh_line.count_mode = 'TOTAL'
    mesh_line.mode = 'OFFSET'
    #Count
    mesh_line.inputs[0].default_value = 1
    #Start Location
    mesh_line.inputs[2].default_value = (0.0, 0.0, 0.0)
    #Offset
    mesh_line.inputs[3].default_value = (0.0, 0.0, 1.0)

    #node Mesh to Points
    mesh_to_points = obj_sequence_player.nodes.new("GeometryNodeMeshToPoints")
    mesh_to_points.name = "Mesh to Points"
    mesh_to_points.mode = 'VERTICES'
    #Selection
    mesh_to_points.inputs[1].default_value = True
    #Position
    mesh_to_points.inputs[2].default_value = (0.0, 0.0, 0.0)
    #Radius
    mesh_to_points.inputs[3].default_value = 0.05000000074505806

    #node Instance on Points
    instance_on_points = obj_sequence_player.nodes.new("GeometryNodeInstanceOnPoints")
    instance_on_points.name = "Instance on Points"
    #Selection
    instance_on_points.inputs[1].default_value = True
    #Pick Instance
    instance_on_points.inputs[3].default_value = True
    #Rotation
    instance_on_points.inputs[5].default_value = (0.0, 0.0, 0.0)
    #Scale
    instance_on_points.inputs[6].default_value = (1.0, 1.0, 1.0)

    #node Collection Info
    collection_info = obj_sequence_player.nodes.new("GeometryNodeCollectionInfo")
    collection_info.name = "Collection Info"
    collection_info.transform_space = 'ORIGINAL'
    #Separate Children
    collection_info.inputs[1].default_value = True
    #Reset Children
    collection_info.inputs[2].default_value = False

    #node Scene Time
    scene_time = obj_sequence_player.nodes.new("GeometryNodeInputSceneTime")
    scene_time.name = "Scene Time"

    #node Math
    math = obj_sequence_player.nodes.new("ShaderNodeMath")
    math.name = "Math"
    math.operation = 'SUBTRACT'
    math.use_clamp = False

    #node Realize Instances
    realize_instances = obj_sequence_player.nodes.new("GeometryNodeRealizeInstances")
    realize_instances.name = "Realize Instances"
    #Selection
    realize_instances.inputs[1].default_value = True
    #Realize All
    realize_instances.inputs[2].default_value = True
    #Depth
    realize_instances.inputs[3].default_value = 0





    #Set locations
    group_input.location = (-452.95648193359375, -143.3688201904297)
    group_output.location = (674.0150756835938, 41.04326629638672)
    mesh_line.location = (-112.06446838378906, 99.2362289428711)
    mesh_to_points.location = (87.45671844482422, 83.96038818359375)
    instance_on_points.location = (314.2950744628906, 61.222389221191406)
    collection_info.location = (-64.86137390136719, -267.17193603515625)
    scene_time.location = (-380.25958251953125, -514.1887817382812)
    math.location = (-99.42635345458984, -497.7755432128906)
    realize_instances.location = (494.0000305175781, 77.88040161132812)

    #Set dimensions
    group_input.width, group_input.height = 199.26803588867188, 100.0
    group_output.width, group_output.height = 140.0, 100.0
    mesh_line.width, mesh_line.height = 140.0, 100.0
    mesh_to_points.width, mesh_to_points.height = 140.0, 100.0
    instance_on_points.width, instance_on_points.height = 140.0, 100.0
    collection_info.width, collection_info.height = 140.0, 100.0
    scene_time.width, scene_time.height = 140.0, 100.0
    math.width, math.height = 140.0, 100.0
    realize_instances.width, realize_instances.height = 140.0, 100.0

    #initialize obj_sequence_player links
    #realize_instances.Geometry -> group_output.Geometry
    obj_sequence_player.links.new(realize_instances.outputs[0], group_output.inputs[0])
    #mesh_line.Mesh -> mesh_to_points.Mesh
    obj_sequence_player.links.new(mesh_line.outputs[0], mesh_to_points.inputs[0])
    #mesh_to_points.Points -> instance_on_points.Points
    obj_sequence_player.links.new(mesh_to_points.outputs[0], instance_on_points.inputs[0])
    #collection_info.Instances -> instance_on_points.Instance
    obj_sequence_player.links.new(collection_info.outputs[0], instance_on_points.inputs[2])
    #group_input.Obj Sequene Collection -> collection_info.Collection
    obj_sequence_player.links.new(group_input.outputs[1], collection_info.inputs[0])
    #scene_time.Frame -> math.Value
    obj_sequence_player.links.new(scene_time.outputs[1], math.inputs[0])
    #math.Value -> instance_on_points.Instance Index
    obj_sequence_player.links.new(math.outputs[0], instance_on_points.inputs[4])
    #group_input.Starting Frame -> math.Value
    obj_sequence_player.links.new(group_input.outputs[2], math.inputs[1])
    #instance_on_points.Instances -> realize_instances.Geometry
    obj_sequence_player.links.new(instance_on_points.outputs[0], realize_instances.inputs[0])
    return obj_sequence_player

obj_sequence_player = obj_sequence_player_node_group()
1 Like

Ohh… maybe the main/real question is: How to convert several numbered objects into an animation? (After this any export with an animation aware format should be the lesser problem.)

Other alternative, besides geo nodes setup, is to import objs as a shape key animation.

Thanks @Andrej
I mix different scripts with help of chatgpt and claude.ai
To create this script obj_sequence_to_abc.py and run it from cmd

import bpy
import os
import sys
import mathutils

def import_obj_sequence(directory_path):
    # Get sorted list of OBJ files
    obj_files = sorted([f for f in os.listdir(directory_path) if f.endswith('.obj')])
    if not obj_files:
        print(f"No OBJ files found in {directory_path}")
        return None, None

    # Remove existing collections if they exist
    for col_name in ['firstframe', 'animation']:
        if col_name in bpy.data.collections:
            collection = bpy.data.collections[col_name]
            for scn in bpy.data.scenes:
                if collection in scn.collection.children:
                    scn.collection.children.unlink(collection)
            bpy.data.collections.remove(collection)

    # Create new collections
    firstframe_collection = bpy.data.collections.new('firstframe')
    animation_collection = bpy.data.collections.new('animation')

    # Link collections to the scene
    bpy.context.scene.collection.children.link(firstframe_collection)
    bpy.context.scene.collection.children.link(animation_collection)

    # Hide the animation collection in the viewport
    animation_collection.hide_viewport = True

    # Import first frame
    first_obj_path = os.path.join(directory_path, obj_files[0])
    try:
        print(f"Importing first frame: {first_obj_path}")
        # Use low-level mesh creation
        mesh = bpy.data.meshes.new(name="FirstFrameMesh")
        with open(first_obj_path, 'r') as f:
            vertices = []
            faces = []
            for line in f:
                if line.startswith('v '):
                    # Parse vertex coordinates
                    _, x, y, z = line.split()
                    vertices.append((float(x), float(y), float(z)))
                elif line.startswith('f '):
                    # Parse face indices (subtract 1 for 0-based indexing)
                    face = [int(v.split('/')[0])-1 for v in line.split()[1:]]
                    faces.append(face)
        
        # Create mesh from vertices and faces
        mesh.from_pydata(vertices, [], faces)
        mesh.update()

        # Create object and link to scene and first frame collection
        obj = bpy.data.objects.new("FirstFrameObject", mesh)
        firstframe_collection.objects.link(obj)
        bpy.context.scene.collection.objects.link(obj)

    except Exception as e:
        print(f"Error importing first frame: {e}")
        return None, None

    # Import remaining frames similarly
    for obj_file in obj_files[0:]:
        try:
            obj_path = os.path.join(directory_path, obj_file)
            print(f"Importing frame: {obj_path}")
            
            # Create mesh
            mesh = bpy.data.meshes.new(name=f"AnimationMesh_{obj_file}")
            with open(obj_path, 'r') as f:
                vertices = []
                faces = []
                for line in f:
                    if line.startswith('v '):
                        _, x, y, z = line.split()
                        vertices.append((float(x), float(y), float(z)))
                    elif line.startswith('f '):
                        face = [int(v.split('/')[0])-1 for v in line.split()[1:]]
                        faces.append(face)
            
            # Create mesh from vertices and faces
            mesh.from_pydata(vertices, [], faces)
            mesh.update()

            # Create object and link to animation collection
            obj = bpy.data.objects.new(f"AnimationObject_{obj_file}", mesh)
            animation_collection.objects.link(obj)
            bpy.context.scene.collection.objects.link(obj)

        except Exception as e:
            print(f"Error importing frame {obj_file}: {e}")

    print(f"Imported {len(obj_files)} OBJ files")
    return firstframe_collection, animation_collection

def obj_sequence_player_node_group():
    # Check if the node group already exists
    if "Obj Sequence Player" in bpy.data.node_groups:
        print("Node group 'Obj Sequence Player' already exists.")
        return bpy.data.node_groups["Obj Sequence Player"]
    
    obj_sequence_player = bpy.data.node_groups.new(type = 'GeometryNodeTree', name = "Obj Sequence Player")

    obj_sequence_player.color_tag = 'NONE'
    obj_sequence_player.description = ""
    obj_sequence_player.default_group_node_width = 140
    

    obj_sequence_player.is_modifier = True

    #obj_sequence_player interface
    #Socket Geometry
    geometry_socket = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='OUTPUT', socket_type = 'NodeSocketGeometry')
    geometry_socket.attribute_domain = 'POINT'

    #Socket Geometry
    geometry_socket_1 = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='INPUT', socket_type = 'NodeSocketGeometry')
    geometry_socket_1.attribute_domain = 'POINT'


    #initialize obj_sequence_player nodes
    #node Group Output
    group_output = obj_sequence_player.nodes.new("NodeGroupOutput")
    group_output.name = "Group Output"
    group_output.is_active_output = True

    #node Mesh Line
    mesh_line = obj_sequence_player.nodes.new("GeometryNodeMeshLine")
    mesh_line.name = "Mesh Line"
    mesh_line.count_mode = 'TOTAL'
    mesh_line.mode = 'OFFSET'
    #Count
    mesh_line.inputs[0].default_value = 1
    #Start Location
    mesh_line.inputs[2].default_value = (0.0, 0.0, 0.0)
    #Offset
    mesh_line.inputs[3].default_value = (0.0, 0.0, 1.0)

    #node Mesh to Points
    mesh_to_points = obj_sequence_player.nodes.new("GeometryNodeMeshToPoints")
    mesh_to_points.name = "Mesh to Points"
    mesh_to_points.mode = 'VERTICES'
    #Selection
    mesh_to_points.inputs[1].default_value = True
    #Position
    mesh_to_points.inputs[2].default_value = (0.0, 0.0, 0.0)
    #Radius
    mesh_to_points.inputs[3].default_value = 0.05000000074505806

    #node Instance on Points
    instance_on_points = obj_sequence_player.nodes.new("GeometryNodeInstanceOnPoints")
    instance_on_points.name = "Instance on Points"
    #Selection
    instance_on_points.inputs[1].default_value = True
    #Pick Instance
    instance_on_points.inputs[3].default_value = True
    #Rotation
    instance_on_points.inputs[5].default_value = (0.0, 0.0, 0.0)
    #Scale
    instance_on_points.inputs[6].default_value = (1.0, 1.0, 1.0)

    #node Collection Info
    collection_info = obj_sequence_player.nodes.new("GeometryNodeCollectionInfo")
    collection_info.name = "Collection Info"
    collection_info.transform_space = 'ORIGINAL'
    if "animation" in bpy.data.collections:
        collection_info.inputs[0].default_value = bpy.data.collections["animation"]
    #Separate Children
    collection_info.inputs[1].default_value = True
    #Reset Children
    collection_info.inputs[2].default_value = False

    #node Scene Time
    scene_time = obj_sequence_player.nodes.new("GeometryNodeInputSceneTime")
    scene_time.name = "Scene Time"

    #node Math
    math = obj_sequence_player.nodes.new("ShaderNodeMath")
    math.name = "Math"
    math.operation = 'SUBTRACT'
    math.use_clamp = False
    #Value_001
    math.inputs[1].default_value = 1.0

    #node Realize Instances
    realize_instances = obj_sequence_player.nodes.new("GeometryNodeRealizeInstances")
    realize_instances.name = "Realize Instances"
    #Selection
    realize_instances.inputs[1].default_value = True
    #Realize All
    realize_instances.inputs[2].default_value = True
    #Depth
    realize_instances.inputs[3].default_value = 0

    #Set locations
    group_output.location = (674.0150756835938, 41.04326629638672)
    mesh_line.location = (-112.06446838378906, 99.2362289428711)
    mesh_to_points.location = (87.45671844482422, 83.96038818359375)
    instance_on_points.location = (314.2950744628906, 61.222389221191406)
    collection_info.location = (-64.86137390136719, -267.17193603515625)
    scene_time.location = (-332.7658386230469, -545.5699462890625)
    math.location = (-99.42635345458984, -497.7755432128906)
    realize_instances.location = (494.0000305175781, 77.88040161132812)

    #Set dimensions
    group_output.width, group_output.height = 140.0, 100.0
    mesh_line.width, mesh_line.height = 140.0, 100.0
    mesh_to_points.width, mesh_to_points.height = 140.0, 100.0
    instance_on_points.width, instance_on_points.height = 140.0, 100.0
    collection_info.width, collection_info.height = 140.0, 100.0
    scene_time.width, scene_time.height = 140.0, 100.0
    math.width, math.height = 140.0, 100.0
    realize_instances.width, realize_instances.height = 140.0, 100.0

    #initialize obj_sequence_player links
    #realize_instances.Geometry -> group_output.Geometry
    obj_sequence_player.links.new(realize_instances.outputs[0], group_output.inputs[0])
    #mesh_line.Mesh -> mesh_to_points.Mesh
    obj_sequence_player.links.new(mesh_line.outputs[0], mesh_to_points.inputs[0])
    #mesh_to_points.Points -> instance_on_points.Points
    obj_sequence_player.links.new(mesh_to_points.outputs[0], instance_on_points.inputs[0])
    #collection_info.Instances -> instance_on_points.Instance
    obj_sequence_player.links.new(collection_info.outputs[0], instance_on_points.inputs[2])
    #math.Value -> instance_on_points.Instance Index
    obj_sequence_player.links.new(math.outputs[0], instance_on_points.inputs[4])
    #instance_on_points.Instances -> realize_instances.Geometry
    obj_sequence_player.links.new(instance_on_points.outputs[0], realize_instances.inputs[0])
    #scene_time.Frame -> math.Value
    obj_sequence_player.links.new(scene_time.outputs[1], math.inputs[0])
    return obj_sequence_player

def apply_modifier_to_firstframe_objects(firstframe_collection, node_group):
    # Apply Geometry Node Modifier to first frame objects
    modifier_name = "ObjSequencePlayerModifier"
    for obj in firstframe_collection.objects:
        if modifier_name not in obj.modifiers:
            geom_nodes_modifier = obj.modifiers.new(name=modifier_name, type='NODES')
            geom_nodes_modifier.node_group = node_group
        else:
            print(f"Modifier '{modifier_name}' already exists on {obj.name}")

def main():
    # Check if we have the correct number of arguments
    if len(sys.argv) < 5:
        print("Usage: blender -b --python script.py -- <input_directory> <output_path>")
        sys.exit(1)

    # Get input directory and output path from command line arguments
    input_directory = sys.argv[sys.argv.index('--') + 1]
    output_path = sys.argv[sys.argv.index('--') + 2]

    # Ensure absolute paths
    input_directory = os.path.abspath(input_directory)
    output_path = os.path.abspath(output_path)

    print(f"Input Directory: {input_directory}")
    print(f"Output Path: {output_path}")

    # Import OBJ sequence
    firstframe_collection, animation_collection = import_obj_sequence(input_directory)

    if firstframe_collection is None or animation_collection is None:
        print("Failed to import OBJ sequence")
        sys.exit(1)

    # Create Obj Sequence Player node group
    node_group = obj_sequence_player_node_group()

    # Apply modifier to first frame objects
    apply_modifier_to_firstframe_objects(firstframe_collection, node_group)

    # Deselect all objects
    bpy.ops.object.select_all(action='DESELECT')

    # Ensure there is only one object in the collection and select it
    if len(firstframe_collection.objects) == 1:
        obj = firstframe_collection.objects[0]
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj

        # Set scene start and end frames
        bpy.context.scene.frame_start = 1
        bpy.context.scene.frame_end = len(animation_collection.objects)

        # Export to Alembic
        try:
            bpy.ops.wm.alembic_export(
                filepath=output_path,
                start=bpy.context.scene.frame_start,
                end=bpy.context.scene.frame_end,
                selected=True
            )
            print(f"Successfully exported '{obj.name}' to {output_path}")
        except Exception as e:
            print(f"Error exporting to Alembic: {e}")
            sys.exit(1)
    else:
        print("Error: Collection 'firstframe' does not contain exactly one object.")
        sys.exit(1)

if __name__ == "__main__":
    main()

to use the script without opening blender

& "C:\Program Files\Blender Foundation\Blender 4.3\blender.exe" --background --python obj_sequence_to_abc.py -- "C:\sequence_folder" result.abc

claude.ai create manual import because from cmd we can’t use import_obj , i don’t know why

Could not enable OBJ import addon: Error: Add-on not loaded: "io_scene_obj", cause: No module named 'io_scene_obj'

The code import the first frame in a collection called: firstframe
and import all frames with duplicated first frame obj in a collection called animation and hide it.
and it check if these collections already exist and have objects or not
then it create obj sequence player as new node and apply geometry node modifier to the first frame with preselected node and collection and finally
the code select the first frame object and export the file to adc
i try to activate this:
visible_objects_only=True
But the hidden collection objects always exported

It’s really a lot to process. Can you please narrow issue down as much as possible?

Which issue? error of import obj or alembic export?

I guess both separately.