Skip to content

Commit

Permalink
Merge pull request #63 from Exairnous/import
Browse files Browse the repository at this point in the history
Add support for importing Hubs components from glTF files
  • Loading branch information
keianhzo authored Apr 17, 2024
2 parents f30b386 + e7e1c96 commit f3e5aee
Show file tree
Hide file tree
Showing 65 changed files with 2,088 additions and 997 deletions.
12 changes: 9 additions & 3 deletions addons/io_hubs_addon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .utils import get_user_python_path
import sys
import bpy
from .io import gltf_exporter
from .io import gltf_exporter, gltf_importer, panels
from . import (nodes, components)
from . import preferences
from . import third_party
Expand All @@ -11,7 +11,7 @@
bl_info = {
"name": "Hubs Blender Addon",
"author": "Mozilla Hubs",
"description": "Tools for developing GLTF assets for Mozilla Hubs",
"description": "Tools for developing glTF assets for Mozilla Hubs",
"blender": (3, 1, 2),
"version": (1, 5, 0, "dev_build"),
"location": "",
Expand All @@ -32,7 +32,9 @@ def register():
preferences.register()
nodes.register()
components.register()
gltf_importer.register()
gltf_exporter.register()
panels.register_panels()
third_party.register()
debugger.register()

Expand All @@ -47,7 +49,9 @@ def registration_migration():

def unregister():
third_party.unregister()
panels.unregister_panels()
gltf_exporter.unregister()
gltf_importer.unregister()
components.unregister()
nodes.unregister()
preferences.unregister()
Expand All @@ -61,7 +65,9 @@ def unregister():
glTF2ExportUserExtension = gltf_exporter.glTF2ExportUserExtension
glTF2_pre_export_callback = gltf_exporter.glTF2_pre_export_callback
glTF2_post_export_callback = gltf_exporter.glTF2_post_export_callback
if bpy.app.version > (3, 0, 0):
glTF2ImportUserExtension = gltf_importer.glTF2ImportUserExtension


def register_panel():
return gltf_exporter.register_export_panel()
return panels.register_panels()
17 changes: 15 additions & 2 deletions addons/io_hubs_addon/components/components_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ def get_component_definitions():
]


def get_component_module_name(component_class):
relative_module_name = component_class.__module__.replace(f"{__package__}.definitions.", "")
return ".".join(relative_module_name.split(".")[:-1])


def register_component(component_class):
print("Registering component: " + component_class.get_name())
component_module_name = get_component_module_name(component_class)
if component_module_name:
print(f"Registering component: {component_module_name} - {component_class.get_name()}")
else:
print(f"Registering component: {component_class.get_name()}")
bpy.utils.register_class(component_class)

component_id = component_class.get_id()
Expand Down Expand Up @@ -97,7 +106,11 @@ def unregister_component(component_class):
from ..io.gltf_exporter import glTF2ExportUserExtension
glTF2ExportUserExtension.remove_excluded_property(component_class.get_id())

print("Component unregistered: " + component_class.get_name())
component_module_name = get_component_module_name(component_class)
if component_module_name:
print(f"Component unregistered: {component_module_name} - {component_class.get_name()}")
else:
print(f"Component unregistered: {component_class.get_name()}")


def load_components_registry():
Expand Down
23 changes: 6 additions & 17 deletions addons/io_hubs_addon/components/definitions/ammo_shape.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from bpy.props import FloatProperty, EnumProperty, FloatVectorProperty, BoolProperty
from ..hubs_component import HubsComponent
from ..types import Category, PanelType, NodeType
from ..utils import V_S1
from ..utils import get_host_or_parents_scaled


class AmmoShape(HubsComponent):
Expand Down Expand Up @@ -71,19 +71,8 @@ class AmmoShape(HubsComponent):
def draw(self, context, layout, panel):
super().draw(context, layout, panel)

parents = [context.object]
while parents:
parent = parents.pop()
if parent.scale != V_S1:
col = layout.column()
col.alert = True
col.label(
text="The ammo-shape object, and its parents' scale need to be [1,1,1]", icon='ERROR')

break

if parent.parent:
parents.insert(0, parent.parent)

if hasattr(parent, 'parent_bone') and parent.parent_bone:
parents.insert(0, parent.parent.pose.bones[parent.parent_bone])
if get_host_or_parents_scaled(context.object):
col = layout.column()
col.alert = True
col.label(
text="The ammo-shape object, and its parents' scale need to be [1,1,1]", icon='ERROR')
11 changes: 11 additions & 0 deletions addons/io_hubs_addon/components/definitions/audio_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ..types import PanelType, NodeType, MigrationType
from ..utils import is_linked, get_host_reference_message
from ..consts import DISTANCE_MODELS, MAX_ANGLE
from ...io.utils import assign_property
from math import degrees, radians

AUDIO_TYPES = [("pannernode", "Positional audio (pannernode)",
Expand Down Expand Up @@ -137,3 +138,13 @@ def draw(self, context, layout, panel):
layout.prop(data=self, property="coneInnerAngle")
layout.prop(data=self, property="coneOuterAngle")
layout.prop(data=self, property="coneOuterGain")

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
component = blender_host.hubs_component_audio_params
component.overrideAudioSettings = True
for property_name, property_value in component_value.items():
if property_name in ['coneInnerAngle', 'coneOuterAngle']:
property_value = radians(property_value)
assign_property(gltf.vnodes, component,
property_name, property_value)
10 changes: 10 additions & 0 deletions addons/io_hubs_addon/components/definitions/audio_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ..types import Category, PanelType, NodeType, MigrationType
from ..utils import is_linked
from ..consts import DISTANCE_MODELS, MAX_ANGLE
from ...io.utils import import_component, assign_property
from math import degrees, radians


Expand Down Expand Up @@ -128,3 +129,12 @@ def migrate(self, migration_type, panel_type, instance_version, host, migration_
f"Warning: The Media Cone angles may not have migrated correctly for the Audio Settings component on scene \"{host.name_full}\"")

return migration_occurred

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
component = import_component(component_name, blender_host)
for property_name, property_value in component_value.items():
if property_name in ['mediaConeInnerAngle', 'mediaConeOuterAngle']:
property_value = radians(property_value)
assign_property(gltf.vnodes, component,
property_name, property_value)
4 changes: 3 additions & 1 deletion addons/io_hubs_addon/components/definitions/billboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ class Billboard(HubsComponent):
}

onlyY: BoolProperty(
name="Vertical Axis Only", description="Locks the Vertical Axis to enable only side to side movement in world space and removes any other rotational transforms", default=False)
name="Vertical Axis Only",
description="Locks the Vertical Axis to enable only side to side movement in world space and removes any other rotational transforms",
default=False)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from ..types import Category, PanelType, NodeType
from ..utils import is_linked
from ..ui import add_link_indicator
from ...io.utils import import_component, assign_property
import bpy


TOME_MAPPING = [("NoToneMapping", "None", "No tone mapping"),
Expand Down Expand Up @@ -161,3 +163,16 @@ def gather(self, export_settings, object):
}

return output

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
component = import_component(component_name, blender_host)
for property_name, property_value in component_value.items():
if property_name == "bloom":
for subproperty_name, subproperty_value in property_value.items():
assign_property(gltf.vnodes, component,
f"bloom{subproperty_name.capitalize()}",
subproperty_value)
else:
assign_property(gltf.vnodes, component,
property_name, property_value)
44 changes: 44 additions & 0 deletions addons/io_hubs_addon/components/definitions/loop_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from bpy.types import PropertyGroup, Menu, Operator
from ..hubs_component import HubsComponent
from ..types import Category, PanelType, NodeType
from ...io.utils import import_component, assign_property
from ..utils import redraw_component_ui, get_host_reference_message
from ...utils import delayed_gather

msgbus_owners = []

Expand Down Expand Up @@ -361,6 +363,34 @@ def get_animation_name(ob, track):
return track.name


def import_tracks(tracks, ob, component):
for track_name in tracks:
try:
nla_track = ob.animation_data.nla_tracks[track_name]
track_type = "object"
except (AttributeError, KeyError):
try:
nla_track = ob.data.shape_keys.animation_data.nla_tracks[track_name]
track_type = "shape_key"
except (AttributeError, KeyError):
track = component.tracks_list.add()
track.name = track_name
continue

if not has_track(component.tracks_list, nla_track):
track = component.tracks_list.add()
strip_name = get_strip_name(nla_track)
action_name = get_action_name(nla_track)
track.name = get_display_name(
nla_track.name, strip_name)
track.track_name = nla_track.name
track.strip_name = strip_name if is_default_name(
nla_track.name) else ''
track.action_name = action_name if is_default_name(
nla_track.name) else ''
track.track_type = track_type


class TracksList(bpy.types.UIList):
bl_idname = "HUBS_UL_TRACKS_list"

Expand Down Expand Up @@ -837,6 +867,20 @@ def unregister():

unregister_msgbus()

@classmethod
@delayed_gather
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
blender_component = import_component(
component_name, blender_host)

for property_name, property_value in component_value.items():
if property_name == 'clip' and property_value != "":
tracks = property_value.split(",")
import_tracks(tracks, blender_ob, blender_component)
else:
assign_property(gltf.vnodes, blender_component,
property_name, property_value)

def migrate(self, migration_type, panel_type, instance_version, host, migration_report, ob=None):
migration_occurred = False
if instance_version < (1, 0, 0):
Expand Down
58 changes: 42 additions & 16 deletions addons/io_hubs_addon/components/definitions/media_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from ..models import box
from ..hubs_component import HubsComponent
from ..types import Category, PanelType, NodeType, MigrationType
from ..utils import V_S1, is_linked, get_host_reference_message
from ..utils import get_host_or_parents_scaled, is_linked, get_host_reference_message
from .networked import migrate_networked
from mathutils import Matrix, Vector
from ...io.utils import import_component, assign_property


def is_bone(ob):
Expand Down Expand Up @@ -200,19 +201,44 @@ def gather(self, export_settings, object):
def draw(self, context, layout, panel):
super().draw(context, layout, panel)

parents = [context.object]
while parents:
parent = parents.pop()
if parent.scale != V_S1:
col = layout.column()
col.alert = True
col.label(
text="The media-frame object, and its parents' scale need to be [1,1,1]", icon='ERROR')
if get_host_or_parents_scaled(context.object):
col = layout.column()
col.alert = True
col.label(
text="The media-frame object, and its parents' scale need to be [1,1,1]", icon='ERROR')

break

if parent.parent:
parents.insert(0, parent.parent)

if hasattr(parent, 'parent_bone') and parent.parent_bone:
parents.insert(0, parent.parent.pose.bones[parent.parent_bone])
@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
blender_component = import_component(
component_name, blender_host)

gltf_yup = gltf.import_settings.get('gltf_yup', True)

for property_name, property_value in component_value.items():
if property_name == 'bounds' 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)

elif property_name == 'align':
align = {
'x': property_value['x'],
'y': property_value['y'],
'z': property_value['z']
}
if gltf_yup:
align['y'] = "min" if property_value['z'] == "max" else "max" if property_value['z'] == "min" else property_value['z']
align['z'] = property_value['y']

blender_component.alignX = align['x']
blender_component.alignY = align['y']
blender_component.alignZ = align['z']

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

if get_host_or_parents_scaled(blender_host):
import_report.append(
f"The media-frame {blender_host.name} or one of its parents' scales isn't [1,1,1]. If this file is being imported from Spoke, then you may need to multiply the bounds parameter by the parents' scale before resetting the scale to [1,1,1].")
15 changes: 15 additions & 0 deletions addons/io_hubs_addon/components/definitions/particle_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ..utils import is_linked, get_host_reference_message
import bpy
from mathutils import Vector
from ...io.utils import import_component, assign_property


class ParticleEmitter(HubsComponent):
Expand Down Expand Up @@ -160,3 +161,17 @@ def create_gizmo(cls, ob, gizmo_group):
gizmo.alpha_highlight = 1.0

return gizmo

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
blender_component = import_component(
component_name, blender_host)

gltf_yup = gltf.import_settings.get('gltf_yup', True)

for property_name, property_value in component_value.items():
if property_name in ['startVelocity', 'endVelocity'] 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)
14 changes: 14 additions & 0 deletions addons/io_hubs_addon/components/definitions/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from bpy.props import StringProperty, BoolProperty
from ..hubs_component import HubsComponent
from ..types import Category, PanelType, NodeType
from ...io.utils import import_component, assign_property


class Spawner(HubsComponent):
Expand Down Expand Up @@ -41,3 +42,16 @@ def migrate(self, migration_type, panel_type, instance_version, host, migration_
self.applyGravity = False

return migration_occurred

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
blender_component = import_component(
component_name, blender_host)

for property_name, property_value in component_value.items():
if property_name == 'mediaOptions':
setattr(blender_component, "applyGravity",
property_value["applyGravity"])
else:
assign_property(gltf.vnodes, blender_component,
property_name, property_value)
18 changes: 18 additions & 0 deletions addons/io_hubs_addon/components/definitions/spoke/background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from ....io.utils import import_component, set_color_from_hex
from ...hubs_component import HubsComponent
from ...types import NodeType


class Background(HubsComponent):
_definition = {
'name': 'background',
'display_name': 'Background',
'node_type': NodeType.SCENE
}

@classmethod
def gather_import(cls, gltf, blender_host, component_name, component_value, import_report, blender_ob=None):
blender_component = import_component(
'environment-settings', blender_host)
blender_component.toneMapping = "LinearToneMapping"
set_color_from_hex(blender_component, "backgroundColor", component_value['color'])
Loading

0 comments on commit f3e5aee

Please sign in to comment.