Skip to content

Commit

Permalink
Adds the ability to use parenting instead of joining when loading GLB.
Browse files Browse the repository at this point in the history
NOTE: Intentionally left out "glb_" prefix to option as this could eventually be applied to other filetypes on load, for example, USD.
PiperOrigin-RevId: 700816151
  • Loading branch information
Qwlouse authored and burrussmp committed Nov 27, 2024
1 parent a9e4113 commit 766b6da
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 24 deletions.
7 changes: 7 additions & 0 deletions kubric/core/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,11 @@ class FileBasedObject(PhysicalObject):
# UI, minus a 90 degree X-axis rotation applied after loading.
glb_do_transform_apply_after_import = tl.Bool(False)

# If true, uses a parenting approach instead of a join so that the asset
# represents a top-level Empty node in the scene. All existing objects
# without parents will be parented to this object. Any transform applied to
# this object will be applied to all children. For more details, see
# https://docs.blender.org/manual/en/latest/scene_layout/object/editing/parent.html
use_parenting_instead_of_join = tl.Bool(False)

# TODO: trigger error when changing filenames or asset-id after the fact
71 changes: 47 additions & 24 deletions kubric/renderer/blender.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@
logger = logging.getLogger(__name__)


def add_top_level_empty_parent() -> Any:
"""Adds an empty parent to scene and makes it the parent of all objects."""
parent_obj = bpy.data.objects.new("Empty", None)
parent_obj.rotation_mode = "QUATERNION"
bpy.context.scene.collection.objects.link(parent_obj)
for obj in bpy.context.scene.objects:
if obj != parent_obj and obj.parent is None:
obj.parent = parent_obj
return parent_obj


# noinspection PyUnresolvedReferences
class Blender(core.View):
""" An implementation of a rendering backend in Blender/Cycles."""
Expand Down Expand Up @@ -422,29 +433,40 @@ def _add_asset(self, obj: core.FileBasedObject):
location=True, rotation=True, scale=True
)

# gltf files often contain "Empty" objects as placeholders for camera / lights etc.
# here we are interested only in the meshes, we filter these out and join all meshes into one.
mesh = [m for m in bpy.context.selected_objects if m.type == "MESH"]
assert mesh
for ob in mesh:
ob.select_set(state=True)
bpy.context.view_layer.objects.active = ob

# make sure one of the objects is active, otherwise join() fails.
# see https://blender.stackexchange.com/questions/132266/joining-all-meshes-in-any-context-gets-error
bpy.context.view_layer.objects.active = mesh[0]
bpy.ops.object.join()

# Make sure to delete all remaining non-mesh objects. Note that for
# some reason deleting the non-mesh objets before joining removes
# parts of the meshes in some cases.
non_mesh_objects = [
obj
for obj in bpy.context.selected_objects
if obj.type != "MESH"
]
with bpy.context.temp_override(selected_objects=non_mesh_objects):
bpy.ops.object.delete()
if obj.use_parenting_instead_of_join:
parent_obj = add_top_level_empty_parent()
bpy.ops.object.select_all(action="DESELECT")
parent_obj.select_set(state=True)
else:
# Legacy loader which relies on JOIN. NOTE: This will destroy
# things like animations.
# gltf files often contain "Empty" objects as placeholders for
# camera / lights etc.
# here we are interested only in the meshes, we filter these out
# and join all meshes into one.
mesh = [
m for m in bpy.context.selected_objects if m.type == "MESH"
]
assert mesh
for ob in mesh:
ob.select_set(state=True)
bpy.context.view_layer.objects.active = ob

# make sure one of the objects is active, otherwise join() fails.
# see https://blender.stackexchange.com/questions/132266/joining-all-meshes-in-any-context-gets-error
bpy.context.view_layer.objects.active = mesh[0]
bpy.ops.object.join()

# Make sure to delete all remaining non-mesh objects. Note that
# for some reason deleting the non-mesh objets before joining
# removes parts of the meshes in some cases.
non_mesh_objects = [
obj
for obj in bpy.context.selected_objects
if obj.type != "MESH"
]
with bpy.context.temp_override(selected_objects=non_mesh_objects):
bpy.ops.object.delete()

assert len(bpy.context.selected_objects) == 1
blender_obj = bpy.context.selected_objects[0]
Expand Down Expand Up @@ -473,7 +495,8 @@ def _add_asset(self, obj: core.FileBasedObject):

# deactivate auto_smooth because for some reason it lead to no smoothing at all
# TODO: make smoothing configurable
blender_obj.data.use_auto_smooth = False
if hasattr(blender_obj.data, "use_auto_smooth"):
blender_obj.data.use_auto_smooth = False

register_object3d_setters(obj, blender_obj)
obj.observe(AttributeSetter(blender_obj, "active_material",
Expand Down

0 comments on commit 766b6da

Please sign in to comment.