import bpy from bpy.app.handlers import persistent from ..ui.preferences import get_project_preferences from .. import ui_icons from ..utils.callbacks import on_release from ..utils.custom_object import CustomObject from .enums import WoWSceneTypes from typing import Type class WBS_PT_object_properties_common: """ Common base for all bpy.types.Object property panels. """ bl_region_type = 'WINDOW' bl_space_type = 'PROPERTIES' __wbs_custom_object_type__: Type[CustomObject] """ Type of custom object associated with the panel. """ __wbs_scene_type__: WoWSceneTypes """ Type of WoW scene. """ _required = {'bl_context', 'bl_label', '__wbs_custom_object_type__', '__wbs_scene_type__'} """ Required fields to override in derived classes. """ def __init_subclass__(cls, **kwargs): for requirement in cls._required: if not hasattr(cls, requirement): raise NotImplementedError(f'"{cls.__name__}" must override "{requirement}".') super().__init_subclass__(**kwargs) @classmethod def poll(cls, context: bpy.types.Context): """ Used to identify if the panel should be rendered in a given context. :param context: Current context. :return: True if should be rendered, else False. """ return (context.scene is not None and context.scene.wow_scene.type == cls.__wbs_scene_type__.name and context.object and cls.__wbs_custom_object_type__.match(context.object)) class WBS_PT_wow_scene(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" bl_label = "WoW Scene" def draw(self, context): layout = self.layout col = layout.column() col.prop(context.scene.wow_scene, 'version') col.prop(context.scene.wow_scene, 'type') col.prop(context.scene.wow_scene, 'game_path') col.prop(context.scene, 'wow_screen_3d', text='Main workspace') @classmethod def poll(cls, context): return context.scene is not None class WowScenePropertyGroup(bpy.types.PropertyGroup): version: bpy.props.EnumProperty( name='Client version', items=[('2', 'WotLK', "", ui_icons['WOTLK'], 0), ('6', 'Legion', "", ui_icons['LEGION'], 1)], default='2' ) type: bpy.props.EnumProperty( name='Scene type', description='Sets up the UI to work with a specific WoW game format', items=[ ('M2', 'M2', 'M2 model', 'FILE_VOLUME', 0), ('WMO', 'WMO', 'World Map Object (WMO)', 'FILE_3D', 1)], default='WMO' ) doodadset_mode: bpy.props.BoolProperty( name='Doodadset Mode', description='Enters doodadset editing mode', default=False ) game_path: bpy.props.StringProperty( name='Game path', description='A path to the model in WoW filesystem.' ) class WOW_PT_render_settings(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" bl_label = "WoW Render Settings" def draw(self, context): layout = self.layout layout.use_property_split = True col = layout.column() col.label(text='Lighting:') col.prop(context.scene.wow_render_settings, "ext_ambient_color") col.prop(context.scene.wow_render_settings, "ext_dir_color") col.prop(context.scene.wow_render_settings, "sidn_scalar") col.label(text='Fog:') col.prop(context.scene.wow_render_settings, "fog_color") col.prop(context.scene.wow_render_settings, "fog_start") col.prop(context.scene.wow_render_settings, "fog_end") col.label(text='Sun Direction:') col.prop(context.scene.wow_render_settings, "sun_direction", text='') @classmethod def poll(cls, context): return context.scene is not None @on_release() def update_ext_ambient_color(self, context): properties = bpy.data.node_groups.get('MO_Properties') if properties: properties.nodes['extLightAmbientColor'].outputs[0].default_value = self.ext_ambient_color @on_release() def update_ext_dir_color(self, context): properties = bpy.data.node_groups.get('MO_Properties') if properties: properties.nodes['extLightDirColor'].outputs[0].default_value = self.ext_dir_color @on_release() def update_sidn_scalar(self, context): properties = bpy.data.node_groups.get('MO_Properties') if properties: properties.nodes['SIDNScalar'].outputs[0].default_value = self.sidn_scalar @on_release() def update_sun_direction(self, context): properties = bpy.data.node_groups.get('MO_Properties') if properties: properties.nodes['SunDirection'].inputs[1].default_value = self.sun_direction class WoWRenderSettingsPropertyGroup(bpy.types.PropertyGroup): ext_ambient_color: bpy.props.FloatVectorProperty( name="Ext. Ambient Color", subtype='COLOR', default=(1, 1, 1, 1), size=4, min=0.0, max=1.0, update=update_ext_ambient_color) ext_dir_color: bpy.props.FloatVectorProperty( name="Ext. Dir Color", subtype='COLOR', default=(0.991, 0.246, 0, 1), size=4, min=0.0, max=1.0, update=update_ext_dir_color) sidn_scalar: bpy.props.FloatProperty( name='SIDN intensity', min=0.0, max=1.0, update=update_sidn_scalar) sun_direction: bpy.props.FloatVectorProperty( name='Sun Direction', default=(0.2, 0.7, 0.6), size=3, subtype='DIRECTION', update=update_sun_direction) fog_color: bpy.props.FloatVectorProperty( name='Fog Color', subtype='COLOR', default=(0.5, 0.5, 0.5), size=3, min=0.0, max=1.0) fog_start: bpy.props.FloatProperty(name='Fog Start', min=0.1, default=30.0) fog_end: bpy.props.FloatProperty(name='Fog End', min=0.1, default=1000.0) def update_screen_3d(self, context): if not self.wow_screen_3d: return viewport = None for area in bpy.context.scene.wow_screen_3d.areas: if area.type == 'VIEW_3D': rv3d = area.spaces[0].region_3d if rv3d is not None: viewport = rv3d break bpy.app.driver_namespace["wow_viewport"] = viewport @persistent def _apply_pref_scene_type(_): prefs = get_project_preferences() if not prefs: return for scene in bpy.data.scenes: scene.wow_scene.type = prefs.scene_type # Force UI refresh wm = bpy.context.window_manager if wm: for win in wm.windows: scr = win.screen if scr: for area in scr.areas: area.tag_redraw() def register_wow_scene_properties(): bpy.types.Scene.wow_scene = bpy.props.PointerProperty(type=WowScenePropertyGroup) bpy.types.Scene.wow_screen_3d = bpy.props.PointerProperty(type=bpy.types.Screen, update=update_screen_3d) bpy.types.Scene.wow_render_settings = bpy.props.PointerProperty(type=WoWRenderSettingsPropertyGroup) def unregister_wow_scene_properties(): del bpy.types.Scene.wow_scene del bpy.types.Scene.wow_screen_3d del bpy.types.Scene.wow_render_settings def render_top_bar(self, context): if context.region.alignment == 'TOP': return layout = self.layout row = layout.row(align=True) doodadset_mode = context.scene.wow_scene.doodadset_mode if proj_prefs := get_project_preferences(): row.prop(proj_prefs, 'export_method_enum', text='Export Folder', icon='FILE_TICK') if context.scene.wow_scene.type == 'WMO': icon = 'CHECKBOX_HLT' if doodadset_mode else 'CHECKBOX_DEHLT' text = "Doodadset Mode ON" if doodadset_mode else "Doodadset Mode OFF" row.operator("scene.doodadset_mode", text=text, icon=icon) row.operator("scene.save_current_wmo_collection", text="Save current WMO", icon='FILE_TICK') if context.scene.wow_scene.type == 'M2': row.operator("scene.save_current_m2", text="Save current M2", icon='FILE_TICK') row.label(text='WoW Scene:') row.prop(context.scene.wow_scene, 'version', text='') row.prop(context.scene.wow_scene, 'type', text='') row.operator("scene.reload_wow_filesystem", text="", icon='FILE_REFRESH') def render_viewport_toggles_left(self, context): layout = self.layout row = layout.row(align=True) row.operator("wow.toggle_image_alpha", icon='NODE_TEXTURE', text='') menu_import_wmo = lambda self, ctx: self.layout.operator("import_mesh.wmo", text="WoW WMO (.wmo)") menu_export_wmo = lambda self, ctx: self.layout.operator("export_mesh.wmo", text="WoW WMO (.wmo)") menu_import_m2 = lambda self, ctx: self.layout.operator("import_mesh.m2", text="WoW M2 (.m2)") menu_export_m2 = lambda self, ctx: self.layout.operator("export_mesh.m2", text="WoW M2 (.m2)") menu_convert_bones = lambda self, ctx: self.layout.operator("convert_bones.m2", text="Convert Bones To WoW") menu_print_m2_warnings = lambda self, ctx: self.layout.operator("print_warnings.m2", text="Print M2 Warnings") def register(): register_wow_scene_properties() bpy.app.handlers.load_post.append(_apply_pref_scene_type) bpy.types.TOPBAR_HT_upper_bar.append(render_top_bar) bpy.types.VIEW3D_HT_header.append(render_viewport_toggles_left) bpy.types.TOPBAR_MT_file_import.append(menu_import_wmo) bpy.types.TOPBAR_MT_file_import.append(menu_import_m2) bpy.types.TOPBAR_MT_file_export.append(menu_export_wmo) bpy.types.TOPBAR_MT_file_export.append(menu_export_m2) bpy.types.TOPBAR_MT_file_export.append(menu_convert_bones) bpy.types.TOPBAR_MT_file_export.append(menu_print_m2_warnings) def unregister(): unregister_wow_scene_properties() bpy.app.handlers.load_post.remove(_apply_pref_scene_type) bpy.types.TOPBAR_MT_file_import.remove(menu_import_wmo) bpy.types.TOPBAR_MT_file_import.remove(menu_import_m2) bpy.types.TOPBAR_MT_file_export.remove(menu_export_wmo) bpy.types.TOPBAR_MT_file_export.remove(menu_export_m2) bpy.types.TOPBAR_HT_upper_bar.remove(render_top_bar) bpy.types.VIEW3D_HT_header.remove(render_viewport_toggles_left) bpy.types.TOPBAR_MT_file_export.remove(menu_convert_bones) bpy.types.TOPBAR_MT_file_export.remove(menu_print_m2_warnings)