diff --git a/cwxml/ymap.py b/cwxml/ymap.py index 0262804..2b0dde1 100644 --- a/cwxml/ymap.py +++ b/cwxml/ymap.py @@ -1,6 +1,7 @@ from abc import ABC as AbstractClass, abstractmethod from typing import Union, Type from xml.etree import ElementTree as ET +from ..sollumz_properties import SollumzGame from .element import ( AttributeProperty, ElementProperty, @@ -18,6 +19,7 @@ Vector4Property, ) +current_game = SollumzGame.GTA class YMAP: @@ -25,6 +27,11 @@ class YMAP: @staticmethod def from_xml_file(filepath): + global current_game + if ".rsc" in filepath: + current_game = SollumzGame.RDR + else: + current_game = SollumzGame.GTA return CMapData.from_xml_file(filepath) @staticmethod @@ -344,6 +351,51 @@ def __init__(self): self.max_z_offset = ValueProperty("maxZOffset") self.object_hash = ValueProperty("objectHash") self.flags = ValueProperty("flags") + + +class StepInstance(ElementTree): + tag_name = "Item" + + def __init__(self): + super().__init__() + self.position = VectorProperty("position") + self.width = ValueProperty("width") + self.depth = ValueProperty("depth") + self.height = ValueProperty("height") + self.forward = VectorProperty("forward") + self.right = VectorProperty("right") + + +class StepInstanceList(ListProperty): + list_type = StepInstance + tag_name = "steps" + item_type = "qbgDfAA_0xA15F529D" + + def __init__(self, tag_name=None, value=None): + super().__init__(tag_name, value) + self.item_type = AttributeProperty("itemType", self.item_type) + + @staticmethod + def from_xml(element: ET.Element): + new = StepInstanceList() + for child in element.iter(): + if len(child.attrib) <= 0: + step = StepInstance + new.value.append(step.from_xml(child)) + + return new + + +class ExtensionStairs(Extension): + type = "CExtensionDefStairs" + + def __init__(self): + super().__init__() + self.bottom = VectorProperty("bottom") + self.top = VectorProperty("top") + self.bound_min = VectorProperty("boundMin") + self.bound_max = VectorProperty("boundMax") + self.steps = StepInstanceList() class ExtensionsList(ListProperty): @@ -382,6 +434,8 @@ def get_extension_xml_class_from_type(ext_type: str) -> Union[Type[Extension], N return ExtensionProcObject elif ext_type == ExtensionScriptEntityId.type: return ExtensionScriptEntityId + elif ext_type == ExtensionStairs.type: + return ExtensionStairs return None @@ -431,10 +485,22 @@ def __init__(self): self.tint_value = ValueProperty("tintValue", 0) +class EntityRDR(Entity): + def __init__(self): + super().__init__() + self.blend_age_layer = ValueProperty("blendAgeLayer", 0) + self.blend_age_dirt = ValueProperty("blendAgeDirt", 0) + + class EntityList(ListPropertyRequired): list_type = Entity tag_name = "entities" + def __init__(self, current_game): + if current_game == SollumzGame.RDR: + self.list_type = Entity + super().__init__() + class ContainerLodsList(ElementTree): """This is not used by GTA5 but added for completion""" @@ -636,7 +702,7 @@ def __init__(self): self.streaming_extents_max = VectorProperty("streamingExtentsMax") self.entities_extents_min = VectorProperty("entitiesExtentsMin") self.entities_extents_max = VectorProperty("entitiesExtentsMax") - self.entities = EntityList() + self.entities = EntityList(current_game) self.container_lods = ContainerLodsList() self.box_occluders = BoxOccludersList() self.occlude_models = OccludeModelsList() diff --git a/cwxml/ytyp.py b/cwxml/ytyp.py index 9585539..b2a0e8e 100644 --- a/cwxml/ytyp.py +++ b/cwxml/ytyp.py @@ -61,7 +61,7 @@ def __init__(self): self.extensions = ExtensionsList() if current_game == SollumzGame.RDR: self.guid = ValueProperty("guid") - self.unknown_1 = TextProperty("iypiqkia_0x07d164a8") + self.unknown_1 = TextProperty("zqNiUDA_0x07D164A8") class TimeArchetype(BaseArchetype): def __init__(self): @@ -260,7 +260,7 @@ def __init__(self): super().__init__() self.name = TextProperty("name") self.locations = LocationsBuffer() - self.entities = EntityList() + self.entities = EntityList(current_game) class EntitySetsList(ListProperty): @@ -298,7 +298,7 @@ def __init__(self): super().__init__() self.type = AttributeProperty("type", "CMloArchetypeDef") self.mlo_flags = ValueProperty("mloFlags") - self.entities = EntityList() + self.entities = EntityList(current_game) self.rooms = RoomsList() self.portals = PortalsList() self.entity_sets = EntitySetsList() diff --git a/sollumz_preferences.py b/sollumz_preferences.py index 047fd79..82aaf13 100644 --- a/sollumz_preferences.py +++ b/sollumz_preferences.py @@ -8,7 +8,7 @@ import os import ast from typing import Any -from .sollumz_properties import SollumType +from .sollumz_properties import SollumType, SollumzGame, items_from_enums from configparser import ConfigParser from typing import Optional @@ -363,6 +363,14 @@ class SollumzAddonPreferences(bpy.types.AddonPreferences): update=_save_preferences ) + default_game: bpy.props.EnumProperty( + items=items_from_enums(SollumzGame), + name="Default game:", + description="Sets the game with which Blender will start", + default=SollumzGame.GTA, + update=_save_preferences + ) + shared_textures_directories: CollectionProperty( name="Shared Textures", type=SzSharedTexturesDirectory, @@ -390,6 +398,7 @@ def draw(self, context): layout.prop(self, "extra_color_swatches") layout.prop(self, "sollumz_icon_header") layout.prop(self, "use_text_name_as_mat_name") + layout.prop(self, "default_game") from .sollumz_ui import draw_list_with_add_remove layout.separator() @@ -463,7 +472,12 @@ def _apply_preferences(data_block: bpy.types.ID, config: ConfigParser, section: continue value_str = config.get(section, key) - value = ast.literal_eval(value_str) + + #it crashes for enum + if key == "default_game": + value = value_str + else: + value = ast.literal_eval(value_str) if key == "shared_textures_directories": # Special case to handle CollectionProperty diff --git a/sollumz_properties.py b/sollumz_properties.py index 5222759..1a0d217 100644 --- a/sollumz_properties.py +++ b/sollumz_properties.py @@ -610,6 +610,11 @@ class EntityProperties: artificial_ambient_occlusion: bpy.props.FloatProperty( name="Artificial Ambient Occlusion", default=255) tint_value: bpy.props.FloatProperty(name="Tint Value") + #RDR + blend_age_layer: bpy.props.FloatProperty( + name="Blend Age Layer", default=255) + blend_age_dirt: bpy.props.FloatProperty( + name="Blend Age Dirt", default=255) class ObjectEntityProperties(bpy.types.PropertyGroup, EntityProperties): @@ -621,17 +626,19 @@ def updateSceneSollumzGame(self, context): context.scene.sollum_collision_material_game_type = context.scene.sollum_game_type def register(): + from .sollumz_preferences import get_addon_preferences + preferences = get_addon_preferences(bpy.context) bpy.types.Object.sollum_game_type = bpy.props.EnumProperty( items=items_from_enums(SollumzGame), name="Sollumz Game", - default=SollumzGame.GTA, + default=preferences.default_game, options={"HIDDEN"} ) bpy.types.Scene.sollum_game_type = bpy.props.EnumProperty( items=items_from_enums(SollumzGame), name="Sollumz Game", - default=SollumzGame.GTA, + default=preferences.default_game, options={"HIDDEN"}, update=updateSceneSollumzGame ) diff --git a/tests/test_obj_reader.py b/tests/test_obj_reader.py index 440546a..ea815bd 100644 --- a/tests/test_obj_reader.py +++ b/tests/test_obj_reader.py @@ -91,6 +91,7 @@ def test_obj_as_vertices_only(): "../ytyp/gizmos/models/SpawnPoint.obj", "../ytyp/gizmos/models/SpawnPointOverride.obj", "../ytyp/gizmos/models/WindDisturbance.obj", + "../ytyp/gizmos/models/Stair.obj", )) def test_obj_read_sollumz_builtin_asset(obj_relative_file_path: str): obj_path = Path(__file__).parent.joinpath(obj_relative_file_path) diff --git a/ybn/flag_presets.xml b/ybn/flag_presets.xml index 5845766..8f47359 100644 --- a/ybn/flag_presets.xml +++ b/ybn/flag_presets.xml @@ -9,6 +9,18 @@ cf_map_type_horse, cf_cover_type, cf_map_type_mover, cf_map_type_vehicle, cf_map_type_weapon cf_vehicle_non_bvh_type, cf_vehicle_bvh_type, cf_ped_type, cf_ragdoll_type, cf_horse_type, cf_horse_ragdoll_type, cf_object_type, cf_plant_type, cf_projectile_type, cf_explosion_type, cf_forklift_forks_type, cf_weapon_test, cf_camera_test, cf_ai_test, cf_script_test, cf_wheel_test, cf_glass_type + + cf_horse_avoidance + cf_horse_type, cf_horse_ragdoll_type, cf_ai_test + + + cf_stair_slope_type + cf_ped_type, cf_horse_type, cf_weapon_test, cf_camera_test, cf_ai_test, cf_script_test, cf_wheel_test + + + cf_river_type + cf_weapon_test, cf_camera_test, cf_ai_test, cf_script_test, cf_wheel_test + map_animal, map_cover, map_dynamic, map_vehicle vehicle_not_bvh, vehicle_bvh, ped, ragdoll, animal, animal_ragdoll, object, plant, projectile, explosion, forklift_forks, test_weapon, test_camera, test_ai, test_script, test_vehicle_wheel, glass diff --git a/ybn/operators.py b/ybn/operators.py index b68e786..289233d 100644 --- a/ybn/operators.py +++ b/ybn/operators.py @@ -356,6 +356,8 @@ class SOLLUMZ_OT_delete_flag_preset(SOLLUMZ_OT_base, bpy.types.Operator): "Stair plane", "Stair mesh", "Deep surface", + "Horse Avoidance", + "River Type", ] def run(self, context): @@ -430,6 +432,8 @@ def run(self, context): flag_preset = FlagPreset() flag_preset.name = self.name + flag_preset.game = obj.sollum_game_type + for prop in dir(obj.composite_flags1): value = getattr(obj.composite_flags1, prop) if value is True: @@ -472,11 +476,13 @@ def run(self, context): preset = flag_presets.presets[index] if obj.sollum_game_type == SollumzGame.RDR: - preset = flag_presets.presets[1] + if not preset or preset.game != obj.sollum_game_type: + preset = flag_presets.presets[1] flags_class = RDRBoundFlags type_flags, include_flags = obj.type_flags, obj.include_flags else: - preset = flag_presets.presets[0] + if not preset or preset.game != obj.sollum_game_type: + preset = flag_presets.presets[0] flags_class = BoundFlags type_flags, include_flags = obj.composite_flags1, obj.composite_flags2 @@ -486,7 +492,6 @@ def run(self, context): type_flags[flag_name] = flag_in_preset1 include_flags[flag_name] = flag_in_preset2 - include_flags[flag_name] = flag_in_preset2 tag_redraw(context) self.message( @@ -514,9 +519,14 @@ def run(self, context): return False if aobj.sollum_type in BOUND_TYPES: - for flag_name in BoundFlags.__annotations__.keys(): - aobj.composite_flags1[flag_name] = False - aobj.composite_flags2[flag_name] = False + if aobj.sollum_game_type == SollumzGame.RDR: + for flag_name in RDRBoundFlags.__annotations__.keys(): + aobj.type_flags[flag_name] = False + aobj.include_flags[flag_name] = False + else: + for flag_name in BoundFlags.__annotations__.keys(): + aobj.composite_flags1[flag_name] = False + aobj.composite_flags2[flag_name] = False tag_redraw(context) diff --git a/ytyp/gizmos/extensions.py b/ytyp/gizmos/extensions.py index 95b4ee4..47ed71e 100644 --- a/ytyp/gizmos/extensions.py +++ b/ytyp/gizmos/extensions.py @@ -65,6 +65,7 @@ def _load_extension_model(extension_type: ExtensionType, file_name: str): (ExtensionType.SPAWN_POINT, "SpawnPoint.obj"), (ExtensionType.SPAWN_POINT_OVERRIDE, "SpawnPointOverride.obj"), (ExtensionType.WIND_DISTURBANCE, "WindDisturbance.obj"), + (ExtensionType.Stairs, "Stair.obj") ): _load_extension_model(ext_type, model_file_name) return shapes @@ -91,6 +92,45 @@ def get_cube_shape() -> object: (0.5, 0.5, -0.5), (0.5, 0.5, 0.5), )) +@functools.cache +def get_bound_box_shape(min_b, max_b) -> object: + """A cube as a bounding box with origin in the middle""" + return bpy.types.Gizmo.new_custom_shape("LINES", ( + # bottom face + (min_b[0],min_b[1],min_b[2]),(min_b[0],max_b[1],min_b[2]), + (min_b[0],min_b[1],min_b[2]),(max_b[0],min_b[1],min_b[2]), + (max_b[0],min_b[1],min_b[2]),(max_b[0],max_b[1],min_b[2]), + (min_b[0],max_b[1],min_b[2]),(max_b[0],max_b[1],min_b[2]), + # top face + (min_b[0],min_b[1],max_b[2]),(min_b[0],max_b[1],max_b[2]), + (min_b[0],min_b[1],max_b[2]),(max_b[0],min_b[1],max_b[2]), + (max_b[0],min_b[1],max_b[2]),(max_b[0],max_b[1],max_b[2]), + (min_b[0],max_b[1],max_b[2]),(max_b[0],max_b[1],max_b[2]), + # connect top and bottom faces + (min_b[0],min_b[1],min_b[2]),(min_b[0],min_b[1],max_b[2]), + (min_b[0],max_b[1],min_b[2]),(min_b[0],max_b[1],max_b[2]), + (max_b[0],min_b[1],min_b[2]),(max_b[0],min_b[1],max_b[2]), + (max_b[0],max_b[1],min_b[2]),(max_b[0],max_b[1],max_b[2]), + )) + +@functools.cache +def get_axis_shape() -> object: + """A representation off the axis on the origin""" + return bpy.types.Gizmo.new_custom_shape("LINES", ( + (0, 0, -0.5), (0, 0, 0.5), + (0, -0.5, 0), (0, 0.5, 0), + (-0.5, 0, 0), (0.5, 0, 0), + )) + +@functools.cache +def get_arrow_shape() -> object: + """A representation off the axis on the origin""" + return bpy.types.Gizmo.new_custom_shape("LINES", ( + (0, 0, 0), (0.5, 0, 0), + (0.5, 0, 0), (0.4, 0.05, 0), + (0.5, 0, 0), (0.4, -0.05, 0), + )) + @functools.cache def get_square_shape() -> object: @@ -291,6 +331,7 @@ def draw_select(self, context, select_id=None): is_active = selected_archetype == archetype and selected_extension == ext is_ladder = ext.extension_type == ExtensionType.LADDER is_light_shaft = ext.extension_type == ExtensionType.LIGHT_SHAFT + is_stair = ext.extension_type == ExtensionType.Stairs theme = context.preferences.themes[0] @@ -338,6 +379,25 @@ def draw_select(self, context, select_id=None): self.draw_ladder_gizmo(context, select_id) elif is_light_shaft: self.draw_light_shaft_gizmo(context, select_id) + elif is_stair: + self.draw_custom_shape(get_bound_box_shape(tuple(ext_props.bound_min), tuple(ext_props.bound_max)), matrix=gizmo_matrix, select_id=select_id) + self.draw_custom_shape(get_axis_shape(), matrix=gizmo_matrix @ Matrix.Translation(ext_props.top), select_id=select_id) + self.draw_custom_shape(get_axis_shape(), matrix=gizmo_matrix @ Matrix.Translation(ext_props.bottom), select_id=select_id) + for step in ext_props.steps: + step_prop = step.step_properties + self.color = theme.view_3d.object_selected if step != ext_props.selected_step or not is_active else theme.view_3d.object_active + self.color_highlight = self.color + self.draw_custom_shape(get_cube_shape(), + matrix=gizmo_matrix @ Matrix.Translation(step_prop.position) + @ Matrix.Rotation(step_prop.rotation, 4, Vector([0,0,1])) + @ Matrix.Scale(step_prop.depth, 4, Vector([1,0,0])) + @ Matrix.Scale(step_prop.width, 4, Vector([0,1,0])) + @ Matrix.Scale(step_prop.height, 4, Vector([0,0,1])), + select_id=select_id) + self.draw_custom_shape(get_arrow_shape(), + matrix=gizmo_matrix @ Matrix.Translation(step_prop.position) + @ Matrix.Rotation(step_prop.rotation, 4, Vector([0,0,1])), + select_id=select_id) def draw_ladder_gizmo(self, context, select_id): archetype = self.linked_archetype diff --git a/ytyp/gizmos/models/FakeDoor.obj b/ytyp/gizmos/models/FakeDoor.obj new file mode 100644 index 0000000..cde5ffe --- /dev/null +++ b/ytyp/gizmos/models/FakeDoor.obj @@ -0,0 +1,270 @@ +# Blender 4.3.0 +# www.blender.org +o DefFakeDoor +v 0.027470 -0.312138 0.197172 +v 0.027470 0.423864 0.144484 +v -0.027470 0.134895 0.144484 +v -0.027470 0.134895 -0.144484 +v 0.027470 -0.312138 -0.197172 +v 0.027470 0.423864 -0.144484 +v 0.027470 0.134895 -0.144484 +v 0.027470 0.134895 0.144484 +v -0.027470 -0.312138 0.197172 +v -0.027470 0.423864 0.144484 +v -0.027470 0.423864 -0.144484 +v -0.027470 -0.312138 -0.197172 +v -0.027470 0.082207 0.197172 +v -0.027470 0.082207 -0.197172 +v 0.027470 0.082207 -0.197172 +v 0.027470 0.082207 0.197172 +v 0.027470 0.476551 -0.197172 +v 0.027470 0.476551 0.197172 +v -0.027470 0.476551 -0.197172 +v -0.027470 0.476551 0.197172 +v -0.027470 0.134895 -0.000000 +v 0.027470 0.134895 -0.000000 +v 0.027470 0.423864 -0.000000 +v -0.027470 0.279379 -0.144484 +v -0.027470 0.279379 0.144484 +v 0.027470 0.279379 -0.144484 +v -0.027470 0.423864 -0.000000 +v 0.027470 0.279379 0.144484 +v 0.027470 0.279379 -0.000000 +v -0.027470 0.279379 -0.000000 +v -0.027470 0.292116 0.012737 +v -0.027470 0.292116 0.131747 +v -0.027470 0.411127 0.131747 +v -0.027470 0.411127 0.012737 +v 0.027470 0.292116 0.012737 +v 0.027470 0.411127 0.012737 +v 0.027470 0.411127 0.131747 +v 0.027470 0.292116 0.131747 +v 0.027470 0.147632 0.012737 +v 0.027470 0.266642 0.012737 +v 0.027470 0.266642 0.131747 +v 0.027470 0.147632 0.131747 +v 0.027470 0.147632 -0.131747 +v 0.027470 0.266642 -0.131747 +v 0.027470 0.266642 -0.012737 +v 0.027470 0.147632 -0.012737 +v 0.027470 0.292116 -0.131747 +v 0.027470 0.411127 -0.131747 +v 0.027470 0.411127 -0.012737 +v 0.027470 0.292116 -0.012737 +v -0.027470 0.292116 -0.131747 +v -0.027470 0.292116 -0.012737 +v -0.027470 0.411127 -0.012737 +v -0.027470 0.411127 -0.131747 +v -0.027470 0.147632 -0.131747 +v -0.027470 0.147632 -0.012737 +v -0.027470 0.266642 -0.012737 +v -0.027470 0.266642 -0.131747 +v -0.027470 0.147632 0.012737 +v -0.027470 0.147632 0.131747 +v -0.027470 0.266642 0.131747 +v -0.027470 0.266642 0.012737 +v -0.043514 0.095534 -0.028027 +v -0.043514 0.064967 0.003005 +v -0.043514 0.126785 0.002754 +v -0.043514 0.096218 0.033787 +v 0.043514 0.095534 -0.028027 +v 0.043514 0.064967 0.003005 +v 0.043514 0.126785 0.002754 +v 0.043514 0.096218 0.033787 +v -0.043514 0.441530 -0.379296 +v -0.043514 0.472781 -0.348515 +v 0.043514 0.472781 -0.348515 +v 0.043514 0.441530 -0.379296 +v -0.043514 -0.255735 -0.374024 +v 0.043514 -0.255735 -0.374024 +v 0.043514 -0.286302 -0.342991 +v -0.043514 -0.286302 -0.342991 +v -0.043514 0.478054 0.348751 +v -0.043514 0.447487 0.379784 +v 0.043514 0.447487 0.379784 +v 0.043514 0.478054 0.348751 +v 0.043514 -0.249778 0.385056 +v -0.043514 -0.249778 0.385056 +v -0.043514 -0.281029 0.354275 +v 0.043514 -0.281029 0.354275 +s 0 +f 40 59 39 +f 9 14 12 +f 39 60 42 +f 5 9 12 +f 15 1 5 +f 17 14 19 +f 16 9 1 +f 15 12 14 +f 19 18 17 +f 18 13 16 +f 21 14 13 +f 22 16 15 +f 23 17 18 +f 24 19 14 +f 25 13 20 +f 26 15 17 +f 27 20 19 +f 28 18 16 +f 42 61 41 +f 41 62 40 +f 32 35 31 +f 31 36 34 +f 34 37 33 +f 33 38 32 +f 25 31 30 +f 10 32 25 +f 27 33 10 +f 30 34 27 +f 23 35 29 +f 2 36 23 +f 28 37 2 +f 29 38 28 +f 29 39 22 +f 28 40 29 +f 28 42 41 +f 22 42 8 +f 26 43 7 +f 29 44 26 +f 29 46 45 +f 22 43 46 +f 6 47 26 +f 23 48 6 +f 29 49 23 +f 29 47 50 +f 30 51 24 +f 27 52 30 +f 27 54 53 +f 24 54 11 +f 21 55 4 +f 30 56 21 +f 30 58 57 +f 24 55 58 +f 3 59 21 +f 25 60 3 +f 30 61 25 +f 30 59 62 +f 45 58 44 +f 46 57 45 +f 43 56 46 +f 44 55 43 +f 49 54 48 +f 50 53 49 +f 47 52 50 +f 48 51 47 +f 40 62 59 +f 9 13 14 +f 39 59 60 +f 5 1 9 +f 15 16 1 +f 17 15 14 +f 16 13 9 +f 15 5 12 +f 19 20 18 +f 18 20 13 +f 13 3 21 +f 21 4 14 +f 15 7 22 +f 22 8 16 +f 18 2 23 +f 23 6 17 +f 14 4 24 +f 24 11 19 +f 20 10 25 +f 25 3 13 +f 17 6 26 +f 26 7 15 +f 19 11 27 +f 27 10 20 +f 16 8 28 +f 28 2 18 +f 42 60 61 +f 41 61 62 +f 32 38 35 +f 31 35 36 +f 34 36 37 +f 33 37 38 +f 25 32 31 +f 10 33 32 +f 27 34 33 +f 30 31 34 +f 23 36 35 +f 2 37 36 +f 28 38 37 +f 29 35 38 +f 29 40 39 +f 28 41 40 +f 28 8 42 +f 22 39 42 +f 26 44 43 +f 29 45 44 +f 29 22 46 +f 22 7 43 +f 6 48 47 +f 23 49 48 +f 29 50 49 +f 29 26 47 +f 30 52 51 +f 27 53 52 +f 27 11 54 +f 24 51 54 +f 21 56 55 +f 30 57 56 +f 30 24 58 +f 24 4 55 +f 3 60 59 +f 25 61 60 +f 30 62 61 +f 30 21 59 +f 45 57 58 +f 46 56 57 +f 43 55 56 +f 44 58 55 +f 49 53 54 +f 50 52 53 +f 47 51 52 +f 48 54 51 +f 66 63 64 +f 70 67 69 +f 72 74 71 +f 65 71 63 +f 73 65 69 +f 74 69 67 +f 71 67 63 +f 76 78 75 +f 76 63 67 +f 68 76 67 +f 78 68 64 +f 75 64 63 +f 80 82 79 +f 66 79 65 +f 81 66 70 +f 82 70 69 +f 79 69 65 +f 84 86 83 +f 84 70 66 +f 64 84 66 +f 86 64 68 +f 83 68 70 +f 66 65 63 +f 70 68 67 +f 72 73 74 +f 65 72 71 +f 73 72 65 +f 74 73 69 +f 71 74 67 +f 76 77 78 +f 76 75 63 +f 68 77 76 +f 78 77 68 +f 75 78 64 +f 80 81 82 +f 66 80 79 +f 81 80 66 +f 82 81 70 +f 79 82 69 +f 84 85 86 +f 84 83 70 +f 64 85 84 +f 86 85 64 +f 83 86 68 diff --git a/ytyp/gizmos/models/Stair.obj b/ytyp/gizmos/models/Stair.obj new file mode 100644 index 0000000..acbef80 --- /dev/null +++ b/ytyp/gizmos/models/Stair.obj @@ -0,0 +1,114 @@ +# Blender 4.3.0 +# www.blender.org +o DefStair +v -0.197290 0.065763 0.131526 +v -0.197290 0.131526 0.131526 +v -0.197290 0.000000 0.065763 +v -0.197290 0.197290 0.131526 +v -0.197290 0.065763 0.065763 +v -0.197290 -0.197290 -0.131526 +v -0.197290 0.131526 0.065763 +v -0.197290 -0.065763 0.000000 +v -0.197290 0.000000 0.000000 +v -0.197290 0.065763 -0.000000 +v -0.197290 -0.131526 -0.065763 +v -0.197290 -0.065763 -0.065763 +v -0.197290 -0.000000 -0.065763 +v -0.197290 -0.131526 -0.131526 +v -0.197290 -0.065763 -0.131526 +v -0.197290 0.131526 0.197290 +v -0.197290 -0.131526 -0.197290 +v 0.197290 -0.197290 -0.131526 +v 0.197290 -0.131526 -0.065763 +v 0.197290 0.197290 0.131526 +v 0.197290 -0.197290 -0.197290 +v 0.197290 0.197290 0.197290 +v -0.197290 -0.197290 -0.197290 +v -0.197290 0.197290 0.197290 +v 0.197290 0.131526 0.131526 +v 0.197290 0.065763 0.131526 +v 0.197290 0.131526 0.065763 +v 0.197290 0.065763 0.065763 +v 0.197290 0.000000 0.065763 +v 0.197290 0.065763 -0.000000 +v 0.197290 0.000000 0.000000 +v 0.197290 -0.065763 0.000000 +v 0.197290 -0.000000 -0.065763 +v 0.197290 -0.065763 -0.065763 +v 0.197290 -0.065763 -0.131526 +v 0.197290 -0.131526 -0.131526 +v 0.197290 0.131526 0.197290 +v 0.197290 -0.131526 -0.197290 +s 0 +f 21 6 23 +f 27 26 28 +f 29 28 26 +f 14 23 6 +f 30 29 31 +f 32 31 29 +f 33 32 34 +f 19 34 32 +f 4 22 20 +f 22 16 37 +f 20 37 25 +f 17 21 23 +f 26 25 37 +f 12 14 11 +f 11 14 6 +f 9 12 8 +f 8 12 11 +f 5 9 3 +f 3 9 8 +f 2 5 1 +f 1 5 3 +f 16 2 1 +f 35 19 36 +f 18 36 19 +f 38 18 21 +f 2 20 25 +f 7 25 27 +f 5 27 28 +f 24 2 16 +f 10 28 30 +f 9 30 31 +f 13 31 33 +f 12 33 34 +f 34 15 12 +f 18 11 6 +f 14 35 36 +f 36 17 14 +f 1 37 16 +f 26 3 29 +f 29 8 32 +f 32 11 19 +f 21 18 6 +f 27 25 26 +f 14 17 23 +f 30 28 29 +f 33 31 32 +f 4 24 22 +f 22 24 16 +f 20 22 37 +f 17 38 21 +f 12 15 14 +f 9 13 12 +f 5 10 9 +f 2 7 5 +f 35 34 19 +f 38 36 18 +f 2 4 20 +f 7 2 25 +f 5 7 27 +f 24 4 2 +f 10 5 28 +f 9 10 30 +f 13 9 31 +f 12 13 33 +f 34 35 15 +f 18 19 11 +f 14 15 35 +f 36 38 17 +f 1 26 37 +f 26 1 3 +f 29 3 8 +f 32 8 11 diff --git a/ytyp/operators/extensions.py b/ytyp/operators/extensions.py index 3c0b777..732514d 100644 --- a/ytyp/operators/extensions.py +++ b/ytyp/operators/extensions.py @@ -4,6 +4,42 @@ from ..utils import get_selected_archetype, get_selected_entity, get_selected_ytyp, get_selected_extension from ...tools.blenderhelper import tag_redraw +class SOLLUMZ_OT_add_archetype_extension_step(bpy.types.Operator): + """Add an step to the stair""" + bl_idname = "sollumz.addarchetypeextensionstep" + bl_options = {"UNDO"} + bl_label = "Add Step" + + @classmethod + def poll(cls, context): + return get_selected_extension(context) is not None + + def execute(self, context): + selected_stair = get_selected_extension(context).stairs_extension_properties + selected_stair.new_step() + tag_redraw(context, space_type="VIEW_3D", region_type="UI") + tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_PROPS") + tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_HEADER") + return {"FINISHED"} + +class SOLLUMZ_OT_delete_archetype_extension_step(bpy.types.Operator): + """Delete the selected step from the stair""" + bl_idname = "sollumz.deletearchetypeextensionstep" + bl_options = {"UNDO"} + bl_label = "Delete Step" + + @classmethod + def poll(cls, context): + selected_extension = get_selected_extension(context).stairs_extension_properties + return selected_extension is not None + + def execute(self, context): + selected_stair = get_selected_extension(context).stairs_extension_properties + selected_stair.delete_selected_stair() + tag_redraw(context, space_type="VIEW_3D", region_type="UI") + tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_PROPS") + tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_HEADER") + return {"FINISHED"} class SOLLUMZ_OT_add_archetype_extension(bpy.types.Operator): """Add an extension to the archetype""" @@ -280,3 +316,84 @@ def execute(self, context): def set_extension_props(cls, context: bpy.types.Context, verts_location: Vector): light_shaft_props = get_selected_extension(context).light_shaft_extension_properties light_shaft_props.offset_position = verts_location + +class SOLLUMZ_OT_update_bound_box_stair(bpy.types.Operator): + """Update bound box from all steps""" + bl_idname = "sollumz.updateboundboxstair" + bl_options = {"UNDO"} + bl_label = "Update Bound Box" + + @classmethod + def poll(cls, context): + return get_selected_extension(context) is not None + + def execute(self, context): + get_selected_extension(context).stairs_extension_properties.update_bound_box() + + return {"FINISHED"} + +class SOLLUMZ_OT_update_step_size_and_location(bpy.types.Operator): + """Update step size, location and rotation from selection""" + bl_idname = "sollumz.updatestepsizeandlocation" + bl_options = {"UNDO"} + bl_label = "Update Step size" + + @classmethod + def poll(cls, context): + return get_selected_extension(context) is not None + + def execute(self, context): + aobj = context.active_object + aobj.update_from_editmode() + + me = aobj.data + selected_edges = [e.key for e in me.edges if e.select] + + if len(selected_edges) != 12: + self.report({"INFO"}, "You must select exactly 12 edges.") + return {"CANCELLED"} + + get_selected_extension(context).stairs_extension_properties.selected_step.step_properties.update_step(me, selected_edges) + + return {"FINISHED"} + + +class SOLLUMZ_OT_generate_steps_from_faces(bpy.types.Operator): + """Generate steps from selected faces""" + bl_idname = "sollumz.generatestepsfromfaces" + bl_options = {"UNDO"} + bl_label = "Generate Steps from Faces" + + @classmethod + def poll(cls, context): + return get_selected_extension(context) is not None + + def execute(self, context): + aobj = context.active_object + aobj.update_from_editmode() + + me = aobj.data + selected_vertices = [v.co for v in me.vertices if v.select] + + if len(selected_vertices) < 4: + self.report({"INFO"}, "You must select atleast 4 edges.") + return {"CANCELLED"} + + get_selected_extension(context).stairs_extension_properties.generate_steps(selected_vertices) + + return {"FINISHED"} + +class SOLLUMZ_OT_mirror_step(bpy.types.Operator): + """Mirrors the selected step, the vector will point in the oposite direction""" + bl_idname = "sollumz.mirrorstep" + bl_options = {"UNDO"} + bl_label = "Mirror Step" + + @classmethod + def poll(cls, context): + return get_selected_extension(context) is not None + + def execute(self, context): + get_selected_extension(context).stairs_extension_properties.selected_step.step_properties.mirror_step() + + return {"FINISHED"} \ No newline at end of file diff --git a/ytyp/properties/extensions.py b/ytyp/properties/extensions.py index 8226c65..0d60c90 100644 --- a/ytyp/properties/extensions.py +++ b/ytyp/properties/extensions.py @@ -3,6 +3,9 @@ from enum import Enum from ...tools.utils import get_list_item from ...ydr.light_flashiness import Flashiness, LightFlashinessEnumItems +from mathutils import Vector +import math +from ..utils import get_selected_extension class ExtensionType(str, Enum): @@ -20,6 +23,7 @@ class ExtensionType(str, Enum): PROC_OBJECT = "CExtensionDefProcObject" EXPRESSION = "CExtensionDefExpression" SCRIPT_ID = "CExtensionDefScriptEntityId" + Stairs = "CExtensionDefStairs" ExtensionTypeEnumItems = ( @@ -37,6 +41,7 @@ class ExtensionType(str, Enum): (ExtensionType.PROC_OBJECT, "Procedural Object", "", 11), (ExtensionType.EXPRESSION, "Expression", "", 12), (ExtensionType.SCRIPT_ID, "Script ID", "", 13), + (ExtensionType.Stairs, "Stairs", "", 14), ) @@ -285,6 +290,218 @@ class ProcObjectExtensionProperties(bpy.types.PropertyGroup, BaseExtensionProper flags: bpy.props.IntProperty(name="Flags", subtype="UNSIGNED") +def set_float_vector_property(self, value, name, relativ_object = None): + if not relativ_object: + relativ_object = self + if relativ_object.show_relative: + self[name] = value + else: + self[name] = Vector(value) - relativ_object.offset_position + + +def get_float_vector_property(self, name, relativ_object = None): + if not relativ_object: + relativ_object = self + if relativ_object.show_relative: + return self[name] + else: + return Vector(self[name]) + relativ_object.offset_position + + +class StepsExtensionProperties(bpy.types.PropertyGroup): + position: bpy.props.FloatVectorProperty(name="Position", subtype="TRANSLATION") + position_ui: bpy.props.FloatVectorProperty(name="Position", + subtype="TRANSLATION", + get=lambda self: get_float_vector_property(self, "position", get_selected_extension(bpy.context).stairs_extension_properties), + set=lambda self, value: set_float_vector_property(self, value, "position", get_selected_extension(bpy.context).stairs_extension_properties)) + width: bpy.props.FloatProperty(name="Width") + depth: bpy.props.FloatProperty(name="Depth") + height: bpy.props.FloatProperty(name="Height") + rotation: bpy.props.FloatProperty(name="Rotation", subtype="ANGLE") + + def mirror_step(self): + if(self.rotation > math.pi): + self.rotation -= math.pi + else: + self.rotation += math.pi + + def update_step(self, mesh, edges): + stair_porperties = get_selected_extension(bpy.context).stairs_extension_properties + + selected_vertices = [v.co for v in mesh.vertices if v.select] + self.position = sum(selected_vertices, Vector()) / len(selected_vertices) - stair_porperties.offset_position + + + vertices = [v.co for v in mesh.vertices] + #t = Vector() + #t.an + verts = [] + + relevant_edges = set() + for edge in edges: + vec = vertices[edge[1]] - vertices[edge[0]] + relevant_edges.add(vec.to_tuple()) + verts += list(vertices[edge[1]], ) + + if len(relevant_edges) != 3: + print("not exactly 3 unique edges") + print(relevant_edges) + return + + relevant_edges = list(relevant_edges) + edges_length = [Vector(e).length for e in relevant_edges] + edges_vector = [Vector(e) for e in relevant_edges] + width = max(max(edges_length[0], edges_length[1]), edges_length[2]) + height = min(min(edges_length[0], edges_length[1]), edges_length[2]) + angle_vector = edges_vector[edges_length.index(width)] + edges_length.remove(width) + edges_length.remove(height) + depth = edges_length[0] + + self.width = width + self.height = height + self.depth = depth + self.rotation = -math.atan2(angle_vector.x, angle_vector.y) + + +class StepsExtensionListProperties(bpy.types.PropertyGroup): + step_properties: bpy.props.PointerProperty( + type=StepsExtensionProperties) + name: bpy.props.StringProperty(name="Name", default="Step") + + +class StairsExtensionProperties(bpy.types.PropertyGroup, BaseExtensionProperties): + show_relative: bpy.props.BoolProperty(name="Show Relative", default=False) + bottom: bpy.props.FloatVectorProperty(name="Bottom", subtype="TRANSLATION") + bottom_ui: bpy.props.FloatVectorProperty(name="Bottom", + subtype="TRANSLATION", + get=lambda self: get_float_vector_property(self, "bottom"), + set=lambda self, value: set_float_vector_property(self, value, "bottom")) + top: bpy.props.FloatVectorProperty(name="Top", subtype="TRANSLATION") + top_ui: bpy.props.FloatVectorProperty(name="Top", + subtype="TRANSLATION", + get=lambda self: get_float_vector_property(self, "top"), + set=lambda self, value: set_float_vector_property(self, value, "top")) + bound_min: bpy.props.FloatVectorProperty(name="Bound Min", subtype="TRANSLATION") + bound_min_ui: bpy.props.FloatVectorProperty(name="Bound Min", + subtype="TRANSLATION", + get=lambda self: get_float_vector_property(self, "bound_min"), + set=lambda self, value: set_float_vector_property(self, value, "bound_min")) + bound_max: bpy.props.FloatVectorProperty(name="Bound Max", subtype="TRANSLATION") + bound_max_ui: bpy.props.FloatVectorProperty(name="Bound Max", + subtype="TRANSLATION", + get=lambda self: get_float_vector_property(self, "bound_max"), + set=lambda self, value: set_float_vector_property(self, value, "bound_max")) + steps: bpy.props.CollectionProperty( + type=StepsExtensionListProperties, name="Steps") + steps_index: bpy.props.IntProperty(name="Step") + + @property + def selected_step(self) -> Union[StepsExtensionListProperties, None]: + return get_list_item(self.steps, self.steps_index) + + def new_step(self) -> StepsExtensionProperties: + item: StepsExtensionProperties = self.steps.add() + item.step_properties.position = (0.0, 0.0, 0.0) + return item + + def delete_selected_stair(self): + if not self.selected_step: + return + + self.steps.remove(self.steps_index) + self.steps_index = max(self.steps_index - 1, 0) + + def flip_forward_vector(self): + if self.step_properties.rotation > math.pi: + self.step_properties.rotation -= math.pi + else: + self.step_properties.rotation += math.pi + + def update_bound_box(self): + if len(self.steps) <= 0: + return + + bound_min = bound_max = self.steps[0].step_properties.position + step_min = step_max = self.steps[0].step_properties + + rotate = lambda x, y, angle: (x * math.sin(angle) - y * math.cos(angle), x * math.cos(angle) + y * math.sin(angle)) + for step in self.steps: + prop = step.step_properties + step_min = step_min if step_min.position[2] <= prop.position[2] else prop + step_max = step_max if step_max.position[2] >= prop.position[2] else prop + + half_width = prop.width / 2 + half_depth = prop.depth / 2 + corners = [ + rotate(-half_width, -half_depth, prop.rotation), + rotate(half_width, -half_depth, prop.rotation), + rotate(half_width, half_depth, prop.rotation), + rotate(-half_width, half_depth, prop.rotation)] + + for corner in corners: + bound_min = (min(bound_min[0], corner[0] + prop.position[0]), min(bound_min[1], corner[1] + prop.position[1]), min(bound_min[2], prop.position[2] - prop.height/2)) + bound_max = (max(bound_max[0], corner[0] + prop.position[0]), max(bound_max[1], corner[1] + prop.position[1]), step_max.position[2]) + self.bound_min = bound_min + self.bound_max = bound_max + + self.top = step_max.position + self.bottom = (step_min.position[0], step_min.position[1], bound_min[2]) + + def generate_steps(self, selected_vertices): + vertices = dict() + for vertx in selected_vertices: + if vertx.z in vertices.keys(): + vertices[vertx.z].append(vertx) + else: + vertices[vertx.z] = [vertx] + + first_step = None + heights = [] + vertices = sorted(list(vertices.values()), key=lambda v: v[0].z, reverse = False) + for i, vertx in enumerate(vertices): + if len(vertx) < 4: + print("Not 4 vertecies. Skipping...") + continue + new = self.new_step() + new.step_properties.position = sum(vertx, Vector())/len(vertx) - self.offset_position + if i != 0: + height = vertx[0].z - vertices[i-1][0].z + new.step_properties.height = height + new.step_properties.position.z -= height/2 + heights.append(height) + else: + first_step = new + edges = [] + for j in range(len(vertx) - 1): + edges.append(vertx[j] - vertx[j + 1]) + edges.append(vertx[0] - vertx[-1]) + + edges.sort(key=lambda e: e.length, reverse=True) + print(edges) + width = Vector(edges[0]) + new.step_properties.width = width.length + new.step_properties.depth = edges[-2].length + angle1 = -math.atan2(width.x, width.y) + angle2 = -math.atan2(edges[1].x, edges[1].y) + print(angle1, angle2) + new.step_properties.rotation = (angle1 + angle2)/2 + height = sum(heights)/len(heights) + first_step.step_properties.position.z -= height/2 + first_step.step_properties.height = height + + #group by z toleranz 0.025 + #find corner verts + # + #find unique edges + # controll inverted + + + + + + + class ExtensionProperties(bpy.types.PropertyGroup): def get_properties(self) -> BaseExtensionProperties: if self.extension_type == ExtensionType.DOOR: @@ -315,6 +532,8 @@ def get_properties(self) -> BaseExtensionProperties: return self.spawn_point_override_properties elif self.extension_type == ExtensionType.WIND_DISTURBANCE: return self.wind_disturbance_properties + elif self.extension_type == ExtensionType.Stairs: + return self.stairs_extension_properties extension_type: bpy.props.EnumProperty(name="Type", items=ExtensionTypeEnumItems) name: bpy.props.StringProperty(name="Name", default="Extension") @@ -347,6 +566,8 @@ def get_properties(self) -> BaseExtensionProperties: type=WindDisturbanceExtensionProperties) proc_object_extension_properties: bpy.props.PointerProperty( type=ProcObjectExtensionProperties) + stairs_extension_properties: bpy.props.PointerProperty( + type=StairsExtensionProperties) class ExtensionsContainer: @@ -371,6 +592,11 @@ def new_extension(self, ext_type=None) -> ExtensionProperties: ladder_props = item.ladder_extension_properties ladder_props.bottom = 0.0, 0.0, -2.5 + stair_props = item.stairs_extension_properties + for name, value in stair_props.__class__.__annotations__.items(): + if value.function == bpy.props.FloatVectorProperty: + setattr(stair_props, name, (0.0, 0.0, 0.0)) + return item def delete_selected_extension(self): diff --git a/ytyp/ui/entities.py b/ytyp/ui/entities.py index 1989f22..48cebb3 100644 --- a/ytyp/ui/entities.py +++ b/ytyp/ui/entities.py @@ -1,9 +1,10 @@ import bpy from ...tabbed_panels import TabbedPanelHelper, TabPanel from ...sollumz_ui import BasicListHelper, FlagsPanel, FilterListHelper, draw_list_with_add_remove +from ...sollumz_properties import SollumzGame from ..properties.ytyp import ArchetypeType from ..properties.mlo import EntityProperties, MloEntityProperties -from ..utils import get_selected_archetype, get_selected_entity +from ..utils import get_selected_archetype, get_selected_entity, get_selected_ytyp from .extensions import ExtensionsListHelper, ExtensionsPanelHelper from .mlo import SOLLUMZ_PT_MLO_PANEL @@ -133,6 +134,7 @@ def draw(self, context): layout.use_property_decorate = False selected_archetype = get_selected_archetype(context) selected_entity = get_selected_entity(context) + selected_ytyp = get_selected_ytyp(context) layout.prop(selected_entity, "linked_object") @@ -162,6 +164,8 @@ def draw(self, context): for prop_name in EntityProperties.__annotations__: if prop_name == "flags": continue + elif prop_name in {"blend_age_layer", "blend_age_dirt"} and selected_ytyp.game != SollumzGame.RDR: + continue layout.prop(selected_entity, prop_name) diff --git a/ytyp/ui/extensions.py b/ytyp/ui/extensions.py index 5271fd1..1659320 100644 --- a/ytyp/ui/extensions.py +++ b/ytyp/ui/extensions.py @@ -1,7 +1,7 @@ import bpy from ...tabbed_panels import TabPanel from ...sollumz_ui import BasicListHelper, draw_list_with_add_remove -from ..properties.extensions import ExtensionsContainer, ExtensionType +from ..properties.extensions import ExtensionsContainer, ExtensionType, ExtensionProperties from ..operators.extensions import ( SOLLUMZ_OT_update_bottom_from_selected, SOLLUMZ_OT_update_corner_a_location, @@ -13,6 +13,10 @@ SOLLUMZ_OT_update_particle_effect_location, SOLLUMZ_OT_calculate_light_shaft_center_offset_location, SOLLUMZ_OT_update_light_shaft_direction, + SOLLUMZ_OT_update_bound_box_stair, + SOLLUMZ_OT_update_step_size_and_location, + SOLLUMZ_OT_generate_steps_from_faces, + SOLLUMZ_OT_mirror_step, ) from ..utils import get_selected_archetype, get_selected_extension from .archetype import SOLLUMZ_PT_ARCHETYPE_TABS_PANEL @@ -53,8 +57,8 @@ def draw(self, context): extensions_container = self.get_extensions_container(context) _, side_col = draw_list_with_add_remove(layout, self.ADD_OPERATOR_ID, self.DELETE_OPERATOR_ID, - self.EXTENSIONS_LIST_ID, "", extensions_container, "extensions", - extensions_container, "extension_index") + self.EXTENSIONS_LIST_ID, "", extensions_container, "extensions", + extensions_container, "extension_index") side_col.separator() side_col.operator(self.DUPLICATE_OPERATOR_ID, text="", icon="DUPLICATE") @@ -105,18 +109,58 @@ def draw(self, context): elif is_light_shaft and (prop_name.startswith("flag_") or prop_name == "scale_by_sun_intensity"): # skip light shaft flag props, drawn above # and skip scale_by_sun_intensity because it is the same as flag_5 - continue + continue else: if prop_name in {'direction_amount', 'cornerA'}: layout.separator() + elif prop_name == "steps": + self.draw_steps(selected_extension) + break + elif prop_name in ["bottom", "top", "bound_min", "bound_max"]: + continue row = layout.row() row.prop(extension_properties, prop_name) + def draw_steps(self, selected_extension: ExtensionProperties): + extension_properties = selected_extension.get_properties() + + layout = self.layout + layout.separator() + row = layout.row() + row.operator(SOLLUMZ_OT_update_bound_box_stair.bl_idname) + row = layout.row() + row.operator(SOLLUMZ_OT_generate_steps_from_faces.bl_idname) + layout.separator() + + draw_list_with_add_remove(layout, "sollumz.addarchetypeextensionstep", "sollumz.deletearchetypeextensionstep", + "SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_STAIR_LIST", "", + extension_properties, "steps", extension_properties, "steps_index") + + if selected_extension.stairs_extension_properties: + if selected_extension.stairs_extension_properties.selected_step: + selected_step = selected_extension.stairs_extension_properties.selected_step + if selected_step.step_properties: + row = layout.row() + row.operator(SOLLUMZ_OT_update_step_size_and_location.bl_idname) + row = layout.row() + row.operator(SOLLUMZ_OT_mirror_step.bl_idname) + + step = selected_step.step_properties + for prop_name in step.__class__.__annotations__: + if prop_name == "position": + continue + row = layout.row() + row.prop(step, prop_name) + class SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_LIST(BasicListHelper, bpy.types.UIList): bl_idname = "SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_LIST" icon = "CON_TRACKTO" +class SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_STAIR_LIST(BasicListHelper, bpy.types.UIList): + bl_idname = "SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_STAIR_LIST" + icon = "CON_TRACKTO" + class SOLLUMZ_PT_ARCHETYPE_EXTENSIONS_PANEL(TabPanel, ExtensionsPanelHelper, bpy.types.Panel): bl_label = "Extensions" diff --git a/ytyp/ui/ytyp.py b/ytyp/ui/ytyp.py index 602b76b..087664e 100644 --- a/ytyp/ui/ytyp.py +++ b/ytyp/ui/ytyp.py @@ -1,6 +1,6 @@ import bpy from ...sollumz_ui import BasicListHelper, SollumzFileSettingsPanel, draw_list_with_add_remove -from ...sollumz_properties import ArchetypeType +from ...sollumz_properties import ArchetypeType, SOLLUMZ_UI_NAMES from ...sollumz_preferences import ( get_import_settings, get_export_settings, @@ -50,6 +50,10 @@ def draw(self, context): row = list_col.row() row.operator("sollumz.importytyp", icon="IMPORT") row.operator("sollumz.exportytyp", icon="EXPORT") + selected_ytyp = get_selected_ytyp(context) + if selected_ytyp: + row = list_col.row() + row.label(text=SOLLUMZ_UI_NAMES[selected_ytyp.game]) class SOLLUMZ_PT_import_ytyp(bpy.types.Panel, SollumzFileSettingsPanel): diff --git a/ytyp/ytypexport.py b/ytyp/ytypexport.py index 91fc0bb..ba3845b 100644 --- a/ytyp/ytypexport.py +++ b/ytyp/ytypexport.py @@ -1,6 +1,7 @@ from typing import Iterable import bpy from mathutils import Euler, Vector, Quaternion, Matrix +from math import cos, sin from ..cwxml import ytyp as ytypxml, ymap as ymapxml from ..sollumz_properties import ArchetypeType, AssetType, EntityLodLevel, EntityPriorityLevel, SollumzGame, MapEntityType @@ -92,6 +93,8 @@ def create_entity_xml(entity: MloEntityProperties) -> ymapxml.Entity: """Create xml mlo entity from an entity data-block.""" entity_xml = ymapxml.Entity() + if current_game == SollumzGame.RDR: + entity_xml = ymapxml.EntityRDR() entity_obj = entity.linked_object if entity_obj: set_entity_xml_transforms_from_object(entity_obj, entity_xml) @@ -106,6 +109,9 @@ def create_entity_xml(entity: MloEntityProperties) -> ymapxml.Entity: entity_xml.ambient_occlusion_multiplier = entity.ambient_occlusion_multiplier entity_xml.artificial_ambient_occlusion = entity.artificial_ambient_occlusion entity_xml.tint_value = entity.tint_value + if isinstance(entity_xml, ymapxml.EntityRDR): + entity_xml.blend_age_layer = entity.blend_age_layer + entity_xml.blend_age_dirt = entity.blend_age_dirt lod_level = next(name for name, value in vars( EntityLodLevel).items() if value == (entity.lod_level)) @@ -188,6 +194,12 @@ def set_extension_xml_props(extension: ExtensionProperties, extension_xml: ymapx ignored_props = getattr(extension_properties.__class__, "ignored_in_import_export", None) # see LightShaftExtensionProperties + #if type(extension_xml) == ymapxml.ExtensionStairs: + #extension_properties.bottom += extension_properties.offset_position + #extension_properties.top += extension_properties.offset_position + #extension_properties.bound_min += extension_properties.offset_position + #extension_properties.bound_max += extension_properties.offset_position + for prop_name in extension_properties.__class__.__annotations__: if ignored_props is not None and prop_name in ignored_props: continue @@ -218,6 +230,20 @@ def set_extension_xml_props(extension: ExtensionProperties, extension_xml: ymapx elif prop_name == "flashiness": # convert enum back to int prop_value = Flashiness[prop_value].value + + elif prop_name == "steps": + prop_value = ymapxml.StepInstanceList() + for step in extension_properties.steps: + step_properties = step.step_properties + step_xml = ymapxml.StepInstance() + for prop_name_step in vars(step_xml).keys(): + if hasattr(step_properties, prop_name_step): + setattr(step_xml, prop_name_step, getattr(step_properties, prop_name_step)) + step_xml.position = extension_properties.offset_position + step_xml.forward = Vector((round(cos(step_properties.rotation), 6), round(sin(step_properties.rotation), 6), 0)) + step_xml.right = Vector((-step_xml.forward.y, step_xml.forward.x, 0)) + prop_value.value.append(step_xml) + setattr(extension_xml, prop_name, prop_value) diff --git a/ytyp/ytypimport.py b/ytyp/ytypimport.py index 79b6c25..07513d4 100644 --- a/ytyp/ytypimport.py +++ b/ytyp/ytypimport.py @@ -2,6 +2,7 @@ from typing import Union from mathutils import Vector, Quaternion +from math import atan2 from ..cwxml import ytyp as ytypxml, ymap as ymapxml from ..sollumz_properties import ArchetypeType, AssetType, EntityLodLevel, EntityPriorityLevel, SollumzGame, MapEntityType @@ -36,6 +37,7 @@ def create_mlo_entity_set(entity_set_xml: ytypxml.EntitySet, archetype: Archetyp entity.attached_room_id = str(archetype.rooms[location].id) +# maybe combine with create_mlo_entity() def create_entity_set_entity(entity_xml: ymapxml.Entity, entity_set: EntitySetProperties): """Create an mlo entity from an xml for the provided archetype data-block.""" @@ -59,6 +61,9 @@ def create_entity_set_entity(entity_xml: ymapxml.Entity, entity_set: EntitySetPr entity.ambient_occlusion_multiplier = entity_xml.ambient_occlusion_multiplier entity.artificial_ambient_occlusion = entity_xml.artificial_ambient_occlusion entity.tint_value = entity_xml.tint_value + if isinstance(entity_xml, ymapxml.EntityRDR): + entity.blend_age_layer = entity_xml.blend_age_layer + entity.blend_age_dirt = entity_xml.blend_age_dirt for extension_xml in entity_xml.extensions: create_extension(extension_xml, entity) @@ -154,6 +159,9 @@ def create_mlo_entity(entity_xml: ymapxml.Entity, archetype: ArchetypeProperties entity.ambient_occlusion_multiplier = entity_xml.ambient_occlusion_multiplier entity.artificial_ambient_occlusion = entity_xml.artificial_ambient_occlusion entity.tint_value = entity_xml.tint_value + if isinstance(entity_xml, ymapxml.EntityRDR): + entity.blend_age_layer = entity_xml.blend_age_layer + entity.blend_age_dirt = entity_xml.blend_age_dirt for extension_xml in entity_xml.extensions: create_extension(extension_xml, entity) @@ -170,6 +178,12 @@ def set_extension_props(extension_xml: ymapxml.Extension, extension: ExtensionPr ignored_props = getattr(extension_properties.__class__, "ignored_in_import_export", None) # see LightShaftExtensionProperties + if type(extension_xml) == ymapxml.ExtensionStairs: + extension_xml.bottom -= extension_xml.offset_position + extension_xml.top -= extension_xml.offset_position + extension_xml.bound_min -= extension_xml.offset_position + extension_xml.bound_max -= extension_xml.offset_position + for prop_name in extension_properties.__class__.__annotations__: if ignored_props is not None and prop_name in ignored_props: continue @@ -202,6 +216,17 @@ def set_extension_props(extension_xml: ymapxml.Extension, extension: ExtensionPr # `flashiness` is now an enum property, we need the enum as string prop_value = Flashiness(prop_value).name + elif prop_name == "steps": + for step in extension_xml.steps: + new_step = extension_properties.new_step() + step.rotation = atan2(step.forward.y, step.forward.x) + step.position -= extension_xml.offset_position + for name in new_step.step_properties.__class__.__annotations__: + val = getattr(step, name) + if val: + setattr(new_step.step_properties, name, val) + continue + setattr(extension_properties, prop_name, prop_value) @@ -333,7 +358,8 @@ def create_archetype(archetype_xml: ytypxml.BaseArchetype, ytyp: CMapTypesProper archetype.name = archetype_xml.name archetype.flags.total = str(archetype_xml.flags) - archetype.special_attribute = SpecialAttribute(archetype_xml.special_attribute).name + if current_game == SollumzGame.RDR: + archetype.special_attribute = SpecialAttribute(archetype_xml.special_attribute).name archetype.hd_texture_dist = archetype_xml.hd_texture_dist archetype.texture_dictionary = archetype_xml.texture_dictionary archetype.clip_dictionary = archetype_xml.clip_dictionary @@ -347,8 +373,10 @@ def create_archetype(archetype_xml: ytypxml.BaseArchetype, ytyp: CMapTypesProper archetype.asset_type = get_asset_type_enum(archetype_xml.asset_type) if current_game == SollumzGame.RDR: - archetype.load_flags = int(archetype_xml.load_flags, 0) - archetype.guid = int(archetype_xml.guid, 0) + archetype.load_flags = archetype_xml.load_flags if isinstance( + archetype_xml.load_flags, int) else int(archetype_xml.load_flags, 0) + archetype.guid = archetype_xml.guid if isinstance( + archetype_xml.guid, int) else int(archetype_xml.guid, 0) archetype.unknown_1 = get_map_entity_type_enum(archetype_xml.unknown_1) find_and_set_archetype_asset(archetype)