Skip to content

Commit

Permalink
Merge pull request #338 from google-research/25EE4EAB8473AEDCD1F328C3…
Browse files Browse the repository at this point in the history
…5065F392

Adds the ability to use parenting instead of joining when loading GLB.
  • Loading branch information
burrussmp authored Nov 29, 2024
2 parents a9e4113 + d9b547e commit 0ee21e2
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 24 deletions.
18 changes: 18 additions & 0 deletions kubric/core/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,22 @@ 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
#
# Parenting avoids destroying things like animations and can preven
# accidental deletion of objects from the scene.
#
# This will break certain features of kubric like assigning meterials to
# assets or other functions that expect the asset to be a mesh. For
# example, when assigning segmentation_ids you may want to reconstruct
# kb.Asset recursively on the top-level empty and set the `segmentation_id`.
# Also when used with PyBullet for simulations, the non-animated collision
# mesh is used which can lead to unexpected behavior when an animated object
# is used in a physics simulation.
use_parenting_instead_of_join = tl.Bool(False)

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


def add_top_level_empty_parent(name: str = "Empty") -> bpy.types.Object:
"""Adds an empty parent to scene and makes it the parent of all objects.
Args:
name: The name of the empty parent.
Returns:
The newly created empty parent.
"""
parent_obj = bpy.data.objects.new(name, 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 +440,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(obj.uid)
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 +502,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 0ee21e2

Please sign in to comment.