0

Before exporting to FBX I modify the object's name, location, parent, etc.

All the exporting happens in a for loop of selected objects.

So I need to restore it after every export.

Example script:

import bpy
from bpy import context
import os
import random

filepath = "C:/Users/karan/Desktop/"

override = context.copy()

for object in context.selected_objects: # It's a simple modification, but I will have some complex modifications. object.location = (random.random(), random.random(), random.random())

# export
override['selected_objects'] = [object]
with context.temp_override(**override):
    bpy.ops.export_scene.fbx(filepath=os.path.join(filepath, f"{object.name}.fbx"), check_existing=False, use_selection=True)

# restore
bpy.ops.wm.revert_mainfile()

I get this error

ReferenceError: StructRNA of type Object has been removed

How to fix this?

And how to add a progress bar while exporting?

enter image description here

import bpy
import os
import random

class EXPORT_OT_export_fbx(bpy.types.Operator): """Tooltip""" bl_idname = "export.fbx" bl_label = "Export FBX"

def execute(self, context):
    filepath = "C:/Users/karan/Desktop/"
    names = [obj.name for obj in context.selected_objects]

    for name in names:
        object = bpy.data.objects[name]
        object.location = (random.random(), random.random(), random.random())

        override = bpy.context.copy()
        override['selected_objects'] = [object]
        with context.temp_override(**override):
            bpy.ops.export_scene.fbx(filepath=os.path.join(filepath, f"{object.name}.fbx"), use_selection=True)

        # restore
        bpy.ops.wm.revert_mainfile()

    return {'FINISHED'}


class EXPORT_PT_export_fbx(bpy.types.Panel): bl_label = "Export" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Export"

def draw(self, context):
    layout = self.layout

    if context.scene.export_progress >= 0:
        layout.enabled = False
        layout.prop(context.scene, 'export_progress', slider=True)
    else:
        layout.operator("export.fbx")


def register(): bpy.utils.register_class(EXPORT_OT_export_fbx) bpy.utils.register_class(EXPORT_PT_export_fbx)

bpy.types.Scene.export_progress = bpy.props.IntProperty(name = 'Exporting...', subtype = 'PERCENTAGE', min = -1, soft_min = 0, soft_max = 100, max = 100, default = -1)


def unregister(): bpy.utils.unregister_class(EXPORT_OT_export_fbx) bpy.utils.unregister_class(EXPORT_PT_export_fbx)

del bpy.types.Scene.export_progress


if name == "main": register()

Karan
  • 1,984
  • 5
  • 21
  • After restore, you will get ReferenceError on some pointer, see: https://blender.stackexchange.com/questions/268787/how-does-the-undo-operation-work/268869#268869 – X Y Jul 01 '23 at 02:51
  • I don't want to use the undo system. – Karan Jul 01 '23 at 03:01
  • This post can explain why the ReferneceError occurs, so just put object = bpy.data.objects.get('Cube') in the forloop to fix that. override also need. – X Y Jul 01 '23 at 03:18
  • I want to loop over context.selected_objects, I updated my question. – Karan Jul 01 '23 at 03:31
  • @XY how to add progress bar while exporting? – Karan Jul 01 '23 at 05:04
  • I know there are two methods, the panel method and the blf method. I only know the blf method. For the panel method, you can see this post: https://blender.stackexchange.com/questions/3219/how-to-show-to-the-user-a-progression-in-a-script
    The panel method may not work because you restore the file every time.
    – X Y Jul 01 '23 at 05:21
  • Sorry, I can't understand how to implement it in my script. – Karan Jul 01 '23 at 05:31
  • It is hard and tricky for both method because when you restore the file, it restore everything, like operator model, panel, some handler... If you want to do the blf method, I can try to help you, but I'm going out now, and you need to wait until I'm free. – X Y Jul 01 '23 at 05:39
  • Yeah, I will wait until you are free, I don't want the blf method. – Karan Jul 01 '23 at 05:43

1 Answers1

2

This should work, if not tell me I'll fix it

import bpy
from bpy import context
import os
import random

filepath = "C:/Users/karan/Desktop/" names = [obj.name for obj in context.selected_objects]

for name in names: object = bpy.data.objects[name] object.name += ".fbx" object.location = (random.random(), random.random(), random.random())

override = bpy.context.copy()
override['selected_objects'] = [object]
with context.temp_override(**override):
    bpy.ops.export_scene.fbx(filepath=os.path.join(filepath, object.name), check_existing=False, use_selection=True)

# restore
bpy.ops.wm.revert_mainfile()


To show the progress after reloading the blend file, you can refer to this

import bpy
import os
import random
from bpy.app.handlers import persistent

is_button_press = [False] data = {}

@persistent def when_new_file(dummy): print("new file detected") do_job()

def init_job(): print("init job") data["job_ind"] = 0 data["job_ind_end"] = 100 bpy.context.scene.export_progress = 0 bpy.app.handlers.load_post.append(when_new_file) bpy.ops.wm.revert_mainfile()

def do_job(): print("do_job, ind= ", data["job_ind"]) # do something you want here

if data["job_ind"] == data["job_ind_end"]:
    end_job()
else:
    bpy.app.timers.register(next_job)

def next_job(): data["job_ind"] += 1 bpy.ops.wm.revert_mainfile() if "job_ind" in data: bpy.context.scene.export_progress = data["job_ind"] * 100 // (data["job_ind_end"] + 1)

def end_job(): print("job done") bpy.app.handlers.load_post.remove(when_new_file) is_button_press[0] = False data.clear() bpy.context.scene.export_progress = -1 bpy.ops.wm.revert_mainfile()

class Progress_test(bpy.types.Operator): bl_idname = "wm.test" bl_label = "Progress_test"

# not possible running in operator using forloop
# because after running bpy.ops.wm.revert_mainfile(), operator cannot access, blender will crash
def execute(self, context):
    if not is_button_press[0]:
        is_button_press[0] = True
        bpy.app.timers.register(init_job)
    return {'FINISHED'}


class TEST_PT_PANEL(bpy.types.Panel): bl_label = "Test Panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Item"

def draw(self, context):
    layout = self.layout

    if is_button_press[0]:
        layout.enabled = False
        layout.prop(context.scene, 'export_progress', slider=True)
    else:
        layout.operator("wm.test")


def register(): bpy.utils.register_class(Progress_test) bpy.utils.register_class(TEST_PT_PANEL)

bpy.types.Scene.export_progress = bpy.props.IntProperty(name = 'Exporting...', subtype = 'PERCENTAGE', min = -1, soft_min = 0, soft_max = 100, max = 100, default = -1)


def unregister(): bpy.utils.unregister_class(Progress_test) bpy.utils.unregister_class(TEST_PT_PANEL)

del bpy.types.Scene.export_progress


if name == "main": register()

enter image description here

After you testing above script, change the init_job and do_job function

filepath = "..."

def init_job(): print("init job") data["job_ind"] = 0 data["names"] = [obj.name for obj in bpy.context.selected_objects] data["job_ind_end"] = len(data["names"]) - 1 bpy.context.scene.export_progress = 0 bpy.app.handlers.load_post.append(when_new_file) bpy.ops.wm.revert_mainfile()

def do_job(): print("do_job, ind= ", data["job_ind"])

name = data["names"][data["job_ind"]]
object = bpy.data.objects[name]
object.location = (random.random(), random.random(), random.random())

override = bpy.context.copy()
override['selected_objects'] = [object]
with bpy.context.temp_override(**override):
    bpy.ops.export_scene.fbx(filepath=os.path.join(filepath, f"{object.name}.fbx"), use_selection=True)

if data["job_ind"] == data["job_ind_end"]:
    end_job()
else:
    bpy.app.timers.register(next_job)

X Y
  • 5,234
  • 1
  • 6
  • 20
  • It works but will it work in a background instance? – Karan Jul 01 '23 at 04:01
  • background instance? Do you mean allow the user modify the scene when exporting? – X Y Jul 01 '23 at 04:07
  • No, I will run the blender instance in the background using subprocess.Popon(). In the background this exporting will happen. – Karan Jul 01 '23 at 04:09
  • 1
    Not sure, you need test it. I guess it can't because you need wait to restore the file. – X Y Jul 01 '23 at 04:16
  • Can you implement the wait to restore the file in your answer? – Karan Jul 01 '23 at 04:18
  • No idea, As the docs say:
    So far, no work has been done to make Blender’s Python integration thread safe, so until it’s properly supported, it’s best not make use of this.

    Note

    Python threads only allow concurrency and won’t speed up your scripts on multiprocessor systems, the subprocess and multiprocess modules can be used with Blender to make use of multiple CPUs too.

    https://docs.blender.org/api/3.6/info_gotcha.html

    – X Y Jul 01 '23 at 04:40
  • @Karan Please add all the relevant information in your question at once. It is unfair to the person who responded if you keep adding new conditions to your question. Please post only one question per thread. Also a little bit of gratitude goes a long way to make someone's time worthwile. If you don't know how to express it, you can try just saying "thank you". Cheers – Gorgious Jul 01 '23 at 08:16
  • Sorry, @Gorgious but the 2 questions are related to each other. – Karan Jul 01 '23 at 14:00
  • It doesn't change the fact that you're only allowed to ask one per thread – Gorgious Jul 01 '23 at 15:16
  • the objects are not in their previous location after export – Karan Jul 01 '23 at 16:11
  • I get this error Traceback (most recent call last): File "C:\Users\karan\Desktop\untitled.blend\Text", line 13, in when_new_file File "C:\Users\karan\Desktop\untitled.blend\Text", line 36, in do_job NameError: name 'context' is not defined – Karan Jul 01 '23 at 16:16
  • change to bpy.context. From the script, it exports a random location, then restores your saved file and exports a random location again, Did you find that it not restore to the original location? – X Y Jul 01 '23 at 16:23
  • If you need keep the location, store the location to a global variable and get it back. – X Y Jul 01 '23 at 16:27
  • when we are restoring using bpy.ops.wm.revert_mainfile() then why we need to store the location to a global variable? – Karan Jul 01 '23 at 16:31
  • still getting NameError: name 'context' is not defined error – Karan Jul 01 '23 at 16:33
  • I think after end_job() it should restore once again. – Karan Jul 01 '23 at 16:35
  • yup, I tested it success after this fix – X Y Jul 01 '23 at 16:45