Skip to content

Commit

Permalink
Restore scene grabbables/physics support
Browse files Browse the repository at this point in the history
Scene grabbables support was reverted (Hubs-Foundation/hubs#6515) in the Hubs client due to bugs and is now only present for the Hubs Client's addons branch and its behavior graphs add-on.  This commit restores the scene grabbables and physics support (along with fixes/improvements that were added to it in the main Blender add-on) to the behavior graphs Blender add-on to allow the functionality to be used with the Hubs Client's addons branch and its behavior graphs add-on.  A corresponding commit to the main Blender add-on removes support for scene grabbables/physics in the main Blender add-on so it maintains compatibility with the current Hubs Client.
  • Loading branch information
Exairnous committed Nov 26, 2024
1 parent f155dc5 commit 56d8452
Show file tree
Hide file tree
Showing 6 changed files with 432 additions and 3 deletions.
10 changes: 9 additions & 1 deletion components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from . import custom_tags, networked_behavior, networked_animation, networked_material, networked_object_material, networked_object_properties
from . import custom_tags, grabbable, rigid_body, physics_shape, networked_behavior, networked_transform, networked_animation, networked_material, networked_object_material, networked_object_properties


def register():
custom_tags.register()
grabbable.register()
rigid_body.register()
physics_shape.register()
networked_behavior.register()
networked_transform.register()
networked_animation.register()
networked_material.register()
networked_object_material.register()
Expand All @@ -12,8 +16,12 @@ def register():

def unregister():
networked_behavior.unregister()
networked_transform.unregister()
networked_animation.unregister()
networked_material.unregister()
networked_object_material.unregister()
rigid_body.unregister()
physics_shape.unregister()
grabbable.unregister()
custom_tags.unregister()
networked_object_properties.unregister()
53 changes: 53 additions & 0 deletions components/grabbable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from bpy.props import BoolProperty
from io_hubs_addon.components.hubs_component import HubsComponent
from io_hubs_addon.components.types import NodeType, PanelType, Category
from io_hubs_addon.components.utils import remove_component, add_component
from .networked_transform import NetworkedTransform
from ..utils import do_register, do_unregister


class Grabbable(HubsComponent):
_definition = {
'name': 'grabbable',
'display_name': 'Grabbable',
'category': Category.OBJECT,
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT],
'icon': 'VIEW_PAN',
'deps': ['rigidbody', 'networked-transform'],
'version': (1, 0, 1)
}

cursor: BoolProperty(
name="By Cursor", description="Can be grabbed by a cursor", default=True)

hand: BoolProperty(
name="By Hand", description="Can be grabbed by VR hands", default=True)

@classmethod
def init(cls, obj):
obj.hubs_component_list.items.get('rigidbody').isDependency = True

def migrate(self, migration_type, panel_type, instance_version, host, migration_report, ob=None):
migration_occurred = False
if instance_version <= (1, 0, 0):
migration_occurred = True

# This was a component that has disappeared but it was usually added together with grababble so we try to remove those instances.
if "capturable" in host.hubs_component_list.items:
remove_component(host, "capturable")

if "networked-object-properties" in host.hubs_component_list.items:
remove_component(host, "networked-object-properties")

add_component(host, NetworkedTransform.get_name())

return migration_occurred


def register():
do_register(Grabbable)


def unregister():
do_unregister(Grabbable)
23 changes: 23 additions & 0 deletions components/networked_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from io_hubs_addon.components.hubs_component import HubsComponent
from io_hubs_addon.components.types import NodeType, PanelType
from ..utils import do_register, do_unregister


class NetworkedTransform(HubsComponent):
_definition = {
'name': 'networked-transform',
'display_name': 'Networked Transform',
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT],
'icon': 'EMPTY_AXIS',
'deps': ['networked'],
'version': (1, 0, 0)
}


def register():
do_register(NetworkedTransform)


def unregister():
do_unregister(NetworkedTransform)
143 changes: 143 additions & 0 deletions components/physics_shape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from bpy.props import BoolProperty, FloatProperty, EnumProperty, FloatVectorProperty
from io_hubs_addon.components.hubs_component import HubsComponent
from io_hubs_addon.components.types import NodeType, PanelType, Category
from mathutils import Vector
from io_hubs_addon.io.utils import import_component, assign_property
from ..utils import do_register, do_unregister


class PhysicsShape(HubsComponent):
_definition = {
'name': 'physics-shape',
'display_name': 'Physics Shape',
'category': Category.OBJECT,
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT, PanelType.BONE],
'icon': 'SCENE_DATA',
'version': (1, 0, 1)
}

type: EnumProperty(
name="Type", description="Type",
items=[("box", "Box Collider", "A box-shaped primitive collision shape"),
("sphere", "Sphere Collider", "A primitive collision shape which represents a sphere"),
("hull", "Convex Hull",
"A convex hull wrapped around the object's vertices. A good analogy for a convex hull is an elastic membrane or balloon under pressure which is placed around a given set of vertices. When released the membrane will assume the shape of the convex hull"),
("mesh", "Mesh Collider",
"A shape made of the actual vertices of the object. This can be expensive for large meshes")],
default="hull")

fit: EnumProperty(
name="Fit Mode",
description="Shape fitting mode",
items=[("all", "Automatic fit all", "Automatically match the shape to fit the object's vertices"),
("manual", "Manual", "Use the manually specified dimensions to define the shape, ignoring the object's vertices")],
default="all")

halfExtents: FloatVectorProperty(
name="Half Extents",
description="Half dimensions of the collider. (Only used when fit is set to \"manual\" and type is set to \"box\")",
unit='LENGTH',
subtype="XYZ",
default=(0.5, 0.5, 0.5))

minHalfExtent: FloatProperty(
name="Min Half Extent",
description="The minimum size to use when automatically generating half extents. (Only used when fit is set to \"all\" and type is set to \"box\")",
unit="LENGTH",
default=0.0)

maxHalfExtent: FloatProperty(
name="Max Half Extent",
description="The maximum size to use when automatically generating half extents. (Only used when fit is set to \"all\" and type is set to \"box\")",
unit="LENGTH",
default=1000.0)

sphereRadius: FloatProperty(
name="Sphere Radius",
description="Radius of the sphere collider. (Only used when fit is set to \"manual\" and type is set to \"sphere\")",
unit="LENGTH", default=0.5)

offset: FloatVectorProperty(
name="Offset", description="An offset to apply to the collider relative to the object's origin",
unit='LENGTH',
subtype="XYZ",
default=(0.0, 0.0, 0.0))

includeInvisible: BoolProperty(
name="Include Invisible",
description="Include invisible objects when generating a collider. (Only used if \"fit\" is set to \"all\")",
default=False)

def draw(self, context, layout, panel):
layout.prop(self, "type")
layout.prop(self, "fit")
if self.fit == "manual":
if self.type == "box":
layout.prop(self, "halfExtents")
elif self.type == "sphere":
layout.prop(self, "sphereRadius")
else:
if self.type == "box":
layout.prop(self, "minHalfExtent")
layout.prop(self, "maxHalfExtent")
layout.prop(self, "includeInvisible")
layout.prop(self, "offset")

if self.fit == "manual" and (self.type == "mesh" or self.type == "hull"):
col = layout.column()
col.alert = True
col.label(
text="'Hull' and 'Mesh' do not support 'manual' fit mode", icon='ERROR')

def gather(self, export_settings, object):
props = super().gather(export_settings, object)
props['offset'] = {
'x': self.offset.x,
'y': self.offset.z if export_settings['gltf_yup'] else self.offset.y,
'z': self.offset.y if export_settings['gltf_yup'] else self.offset.z,
}
props['halfExtents'] = {
'x': self.halfExtents.x,
'y': self.halfExtents.z if export_settings['gltf_yup'] else self.halfExtents.y,
'z': self.halfExtents.y if export_settings['gltf_yup'] else self.halfExtents.z,
}
return props

def migrate(self, migration_type, panel_type, instance_version, host, migration_report, ob=None):
migration_occurred = False
if instance_version <= (1, 0, 0):
migration_occurred = True

offset = self.offset.copy()
offset = Vector((offset.x, offset.z, offset.y))
self.offset = offset

halfExtents = self.halfExtents.copy()
halfExtents = Vector((halfExtents.x, halfExtents.z, halfExtents.y))
self.halfExtents = halfExtents

return migration_occurred

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
gltf_yup = gltf.import_settings.get('gltf_yup', True)

blender_component = import_component(component_name, blender_host)
for property_name, property_value in component_value.items():
if property_name == 'offset' and gltf_yup:
property_value['y'], property_value['z'] = property_value['z'], property_value['y']

elif property_name == 'halfExtents' and gltf_yup:
property_value['y'], property_value['z'] = property_value['z'], property_value['y']

assign_property(gltf.vnodes, blender_component,
property_name, property_value)


def register():
do_register(PhysicsShape)


def unregister():
do_unregister(PhysicsShape)
Loading

0 comments on commit 56d8452

Please sign in to comment.