From 0d6f3429f8d4cc8d4f0d0ab1c0e2dcf66c6735ef Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Mon, 3 Mar 2025 12:15:04 -0800 Subject: [PATCH 01/14] add recursive hide/unhide function --- loco-graphics-helper/frame.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/loco-graphics-helper/frame.py b/loco-graphics-helper/frame.py index e8040e1..47edc3e 100644 --- a/loco-graphics-helper/frame.py +++ b/loco-graphics-helper/frame.py @@ -14,6 +14,14 @@ # Representation of a frame that is to be rendered +def recursive_hide_children(object, hide, type = 'NONE'): + object.hide_render = hide + for child in object.children: + if child.loco_graphics_helper_object_properties.object_type != 'NONE': + if type != 'NONE': + continue + recursive_hide_children(child, hide) + class Frame: def __init__(self, frame_index, task, view_angle, bank_angle=0, vertical_angle=0, mid_angle=0): self.frame_index = frame_index @@ -91,13 +99,9 @@ def prepare_scene(self): continue if o.loco_graphics_helper_object_properties.object_type == 'NONE': continue - o.hide_render = True - for c in o.children: - c.hide_render = True + recursive_hide_children(o,True) - self.target_object.hide_render = False - for c in self.target_object.children: - c.hide_render = False + recursive_hide_children(self.target_object,False, self.target_object.loco_graphics_helper_object_properties.object_type) object.location = self.target_object.matrix_world.translation From b6c4eee2f777c88c856ecf93b38d5514f3b89378 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Mon, 3 Mar 2025 12:16:09 -0800 Subject: [PATCH 02/14] clarify tilt --- .../operators/vehicle_render_operator.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/loco-graphics-helper/operators/vehicle_render_operator.py b/loco-graphics-helper/operators/vehicle_render_operator.py index bd85698..600f5ad 100644 --- a/loco-graphics-helper/operators/vehicle_render_operator.py +++ b/loco-graphics-helper/operators/vehicle_render_operator.py @@ -42,8 +42,7 @@ def create_task(self, context): self.add_render_angles(body_object) if bodies[0].loco_graphics_helper_vehicle_properties.is_airplane: - self.task_builder.set_cast_shadows( - True) + self.task_builder.set_cast_shadows(True) self.task_builder.set_palette(self.palette_manager.get_shadow_palette()) self.add_airplane_shadow_render_angles(bodies[0]) else: @@ -77,7 +76,7 @@ def add_render_angles(self, object): is_bogie = object.loco_graphics_helper_object_properties.object_type == "BOGIE" target_object = object animation_frames = props.number_of_animation_frames - roll_frames = 1 if props.roll_angle == 0 else 3 + tilt_frames = 1 if props.roll_angle == 0 else 3 for i in range(len(track_angle_sections_names)): key = track_angle_sections_names[i] @@ -118,10 +117,10 @@ def add_render_angles(self, object): target_object = object for i in range(num_viewing_angles): - if roll_frames != 1: + if tilt_frames != 1: roll_angles = [0, props.roll_angle, -props.roll_angle] for j, roll_angle in enumerate(roll_angles): - frame_index = start_output_index + i * roll_frames + j + frame_index = start_output_index + i * tilt_frames + j self.task_builder.set_rotation( base_view_angle, roll_angle, vertical_angle=track_section[2]) self.task_builder.add_frame( From 64018507213017439a1a9b21808d034c9eb4ebc7 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 4 Mar 2025 00:39:04 -0800 Subject: [PATCH 03/14] yep those improvements are assorted alright --- loco-graphics-helper/__init__.py | 5 +- loco-graphics-helper/builders/task_builder.py | 19 ++ loco-graphics-helper/frame.py | 5 + .../loco_object_helper_panel.py | 52 +++-- .../operators/init_operator.py | 7 + .../operators/vehicle_render_operator.py | 16 +- .../properties/file_versioning.py | 81 +++++++ .../properties/general_properties.py | 25 +++ .../properties/preferences.py | 17 +- .../properties/vehicle_properties.py | 50 +++-- .../rct_graphics_helper_panel.py | 207 ++++++++++++++---- .../res/palettes/base_loco_palette.png | Bin 0 -> 1236 bytes .../res/palettes/custom_palette.bmp | Bin 0 -> 430 bytes .../res/palettes/recolor_1_loco_palette.bmp | Bin 0 -> 190 bytes .../res/palettes/recolour_2_loco_palette.png | Bin 0 -> 379 bytes loco-graphics-helper/vehicle.py | 173 +++++++++------ 16 files changed, 502 insertions(+), 155 deletions(-) create mode 100644 loco-graphics-helper/properties/file_versioning.py create mode 100644 loco-graphics-helper/res/palettes/base_loco_palette.png create mode 100644 loco-graphics-helper/res/palettes/custom_palette.bmp create mode 100644 loco-graphics-helper/res/palettes/recolor_1_loco_palette.bmp create mode 100644 loco-graphics-helper/res/palettes/recolour_2_loco_palette.png diff --git a/loco-graphics-helper/__init__.py b/loco-graphics-helper/__init__.py index 214fd61..67b71a0 100644 --- a/loco-graphics-helper/__init__.py +++ b/loco-graphics-helper/__init__.py @@ -16,6 +16,7 @@ from .properties.general_properties import register_general_properties, unregister_general_properties from .properties.track_properties import register_track_properties, unregister_track_properties from .properties.object_properties import register_object_properties, unregister_object_properties +from .properties.file_versioning import register_file_updater, unregister_file_updater from .rct_graphics_helper_panel import GraphicsHelperPanel from . import developer_utils import importlib @@ -25,7 +26,7 @@ "name": "Loco Graphics Helper", "description": "Render tool to replicate Locomotion graphics (based on RCT Graphics Helper)", "author": "Olivier Wervers & OpenLoco Team", - "version": (0, 1, 6), + "version": (0, 1, 10), "blender": (2, 79, 0), "location": "Render", "support": "COMMUNITY", @@ -54,6 +55,7 @@ def register(): register_walls_properties() register_track_properties() register_object_properties() + register_file_updater() print("Registered {} with {} modules".format( bl_info["name"], len(modules))) @@ -71,5 +73,6 @@ def unregister(): unregister_walls_properties() unregister_track_properties() unregister_object_properties() + unregister_file_updater() print("Unregistered {}".format(bl_info["name"])) diff --git a/loco-graphics-helper/builders/task_builder.py b/loco-graphics-helper/builders/task_builder.py index 32ffec7..ac9c0b9 100644 --- a/loco-graphics-helper/builders/task_builder.py +++ b/loco-graphics-helper/builders/task_builder.py @@ -7,12 +7,24 @@ RCT Graphics Helper is licensed under the GNU General Public License version 3. ''' +import bpy from operator import length_hint from ..frame import Frame from ..render_task import RenderTask +from ..vehicle import get_vehicle_y_offset # Builder for creating render tasks procedurally +def get_offset_y(): + properties = bpy.context.scene.loco_graphics_helper_general_properties + if properties.render_mode == "TILES": + return 0 + elif properties.render_mode == "VEHICLE": + return get_vehicle_y_offset() + elif properties.render_mode == "WALLS": + return 0 + elif properties.render_mode == "TRACK": + return 0 class TaskBuilder: @@ -82,9 +94,14 @@ def add_frame(self, frame_index, number_of_viewing_angles, angle_index, animatio frame.set_target_object(target_object) + frame.set_offset_y(get_offset_y()) + self.angles.append(frame) self.output_index = self.output_index + 1 + def add_null_frames(self, number): + self.output_index += number + # Adds render angles for the given number of viewing angles relative to the currently configured rotation def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, animation_frames=1, rotational_symmetry=False): @@ -121,6 +138,8 @@ def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, frame.set_occlusion_layers(self.occlusion_layers) + frame.set_offset_y(get_offset_y()) + if self.occlusion_layers > 0: output_indices = [] for k in range(self.occlusion_layers): diff --git a/loco-graphics-helper/frame.py b/loco-graphics-helper/frame.py index 47edc3e..5c02aeb 100644 --- a/loco-graphics-helper/frame.py +++ b/loco-graphics-helper/frame.py @@ -104,6 +104,8 @@ def prepare_scene(self): recursive_hide_children(self.target_object,False, self.target_object.loco_graphics_helper_object_properties.object_type) object.location = self.target_object.matrix_world.translation + if self.target_object.loco_graphics_helper_vehicle_properties.bounding_box_override: + object.location = self.target_object.loco_graphics_helper_vehicle_properties.bounding_box_override.matrix_world.translation # This is a little hacky... if self.layer == 'Top Down Shadow': @@ -130,6 +132,9 @@ def set_offset(self, offset_x, offset_y): self.offset_x = offset_x self.offset_y = offset_y + def set_offset_y(self, offset_y): + self.offset_y = offset_y + def set_multi_tile_size(self, width, length, invert_tile_positions): self.width = width self.length = length diff --git a/loco-graphics-helper/loco_object_helper_panel.py b/loco-graphics-helper/loco_object_helper_panel.py index 370d1c7..2f777db 100644 --- a/loco-graphics-helper/loco_object_helper_panel.py +++ b/loco-graphics-helper/loco_object_helper_panel.py @@ -60,6 +60,15 @@ def draw_bogie_panel(self, context, layout): vehicle_properties = context.object.loco_graphics_helper_vehicle_properties + row.prop(vehicle_properties, "null_component") + row = layout.row() + + if vehicle_properties.null_component: + return + + row.prop(vehicle_properties, "index") + row = layout.row() + row.prop(vehicle_properties, "is_clone") row = layout.row() @@ -67,14 +76,15 @@ def draw_bogie_panel(self, context, layout): row = layout.row() if vehicle_properties.is_clone: - row.prop(vehicle_properties, "index",text="Clone of bogie index:") - row = layout.row() return + row.prop(vehicle_properties, "render_sprite") + row = layout.row() + box = layout.box() row = box.row() - row.label("Track Properties:") + row.label("Sprite:") split = box.split(.50) columns = [split.column(), split.column()] @@ -92,15 +102,14 @@ def draw_bogie_panel(self, context, layout): row.label("Sloped Viewing Angles: 32") row = layout.row() - row.prop(vehicle_properties, "index") - row = layout.row() - row.prop(vehicle_properties, "number_of_animation_frames") row = layout.row() row.prop(vehicle_properties, "rotational_symmetry") row = layout.row() + row.prop(vehicle_properties, "bounding_box_override") + def draw_body_panel(self, context, layout): scene = context.scene general_properties = scene.loco_graphics_helper_general_properties @@ -112,6 +121,15 @@ def draw_body_panel(self, context, layout): vehicle_properties = context.object.loco_graphics_helper_vehicle_properties + row.prop(vehicle_properties, "null_component") + row = layout.row() + + if vehicle_properties.null_component: + return + + row.prop(vehicle_properties, "index") + row = layout.row() + row.prop(vehicle_properties, "is_clone") row = layout.row() @@ -119,14 +137,15 @@ def draw_body_panel(self, context, layout): row = layout.row() if vehicle_properties.is_clone: - row.prop(vehicle_properties, "index",text="Clone of body index:") - row = layout.row() return - + + row.prop(vehicle_properties, "render_sprite") + row = layout.row() + box = layout.box() row = box.row() - row.label("Track Properties:") + row.label("Sprites:") split = box.split(.50) columns = [split.column(), split.column()] @@ -148,17 +167,14 @@ def draw_body_panel(self, context, layout): row.prop(vehicle_properties, "sloped_viewing_angles", text="") row = layout.row() - row.prop(vehicle_properties, "roll_angle") - row = layout.row() - - row.prop(vehicle_properties, "index") + row.prop(vehicle_properties, "tilt_angle") row = layout.row() row.prop(vehicle_properties, "number_of_animation_frames") row = layout.row() - if vehicle_properties.number_of_animation_frames != 1 and vehicle_properties.roll_angle != 0: - row.label("WARNING CANNOT HAVE BOTH ANIMATION FRAMES AND ROLL ANGLE SET") + if vehicle_properties.number_of_animation_frames != 1 and vehicle_properties.tilt_angle != 0: + row.label("WARNING: cannot have tilt frames and animation frames") row = layout.row() row.prop(vehicle_properties, "rotational_symmetry") @@ -167,8 +183,10 @@ def draw_body_panel(self, context, layout): row.prop(vehicle_properties, "braking_lights") row = layout.row() if vehicle_properties.braking_lights and vehicle_properties.roll_angle != 0: - row.label("WARNING CANNOT HAVE BOTH BRAKING LIGHTS AND ROLL ANGLE SET") + row.label("WARNING: cannot have brake lights and tilt frames") row = layout.row() row.prop(vehicle_properties, "is_airplane") row = layout.row() + + row.prop(vehicle_properties, "bounding_box_override") diff --git a/loco-graphics-helper/operators/init_operator.py b/loco-graphics-helper/operators/init_operator.py index 949d2be..eb3a2fb 100644 --- a/loco-graphics-helper/operators/init_operator.py +++ b/loco-graphics-helper/operators/init_operator.py @@ -78,6 +78,13 @@ def execute(self, context): compositorBuilder = CompositorBuilder() compositorBuilder.build(context) + # Set project properties + properties = context.scene.loco_graphics_helper_general_properties + addon_prefs = context.user_preferences.addons["loco-graphics-helper"].preferences + + properties.RCTPluginVersion = addon_prefs.RCTPluginVersion + properties.RCTPluginName = addon_prefs.printable_idname + return {'FINISHED'} def delete_default_render_layer(self, context): diff --git a/loco-graphics-helper/operators/vehicle_render_operator.py b/loco-graphics-helper/operators/vehicle_render_operator.py index 600f5ad..aaf2e30 100644 --- a/loco-graphics-helper/operators/vehicle_render_operator.py +++ b/loco-graphics-helper/operators/vehicle_render_operator.py @@ -13,6 +13,7 @@ from ..operators.render_operator import RCTRender from ..angle_sections.track import track_angle_sections, track_angle_sections_names +from ..vehicle import get_number_of_sprites class RenderVehicle(RCTRender, bpy.types.Operator): @@ -36,7 +37,7 @@ def create_task(self, context): self.task_builder.set_palette(self.palette_manager.get_base_palette( general_props.palette, general_props.number_of_recolorables, "FULL")) - bodies = [x for x in context.scene.objects if x.loco_graphics_helper_object_properties.object_type == "BODY" and not x.loco_graphics_helper_vehicle_properties.is_clone] + bodies = [x for x in context.scene.objects if x.loco_graphics_helper_object_properties.object_type == "BODY" and not x.loco_graphics_helper_vehicle_properties.is_clone and not x.loco_graphics_helper_vehicle_properties.null_component] bodies = sorted(bodies, key=lambda x: x.loco_graphics_helper_vehicle_properties.index) for body_object in bodies: self.add_render_angles(body_object) @@ -46,7 +47,7 @@ def create_task(self, context): self.task_builder.set_palette(self.palette_manager.get_shadow_palette()) self.add_airplane_shadow_render_angles(bodies[0]) else: - bogies = [x for x in context.scene.objects if x.loco_graphics_helper_object_properties.object_type == "BOGIE" and not x.loco_graphics_helper_vehicle_properties.is_clone] + bogies = [x for x in context.scene.objects if x.loco_graphics_helper_object_properties.object_type == "BOGIE" and not x.loco_graphics_helper_vehicle_properties.is_clone and not x.loco_graphics_helper_vehicle_properties.null_component] bogies = sorted(bogies, key=lambda x: x.loco_graphics_helper_vehicle_properties.index) for bogie_object in bogies: self.add_render_angles(bogie_object) @@ -73,10 +74,13 @@ def should_render_feature(self, key, props): def add_render_angles(self, object): props = object.loco_graphics_helper_vehicle_properties + if not props.render_sprite: + self.task_builder.add_null_frames(get_number_of_sprites(object)) + return is_bogie = object.loco_graphics_helper_object_properties.object_type == "BOGIE" target_object = object animation_frames = props.number_of_animation_frames - tilt_frames = 1 if props.roll_angle == 0 else 3 + tilt_frames = 1 if props.tilt_angle == 0 else 3 for i in range(len(track_angle_sections_names)): key = track_angle_sections_names[i] @@ -118,11 +122,11 @@ def add_render_angles(self, object): for i in range(num_viewing_angles): if tilt_frames != 1: - roll_angles = [0, props.roll_angle, -props.roll_angle] - for j, roll_angle in enumerate(roll_angles): + tilt_angles = [0, props.tilt_angle, -props.tilt_angle] + for j, tilt_angle in enumerate(tilt_angles): frame_index = start_output_index + i * tilt_frames + j self.task_builder.set_rotation( - base_view_angle, roll_angle, vertical_angle=track_section[2]) + base_view_angle, tilt_angle, vertical_angle=track_section[2]) self.task_builder.add_frame( frame_index, num_viewing_angles, i, j, rotation_range, target_object) else: diff --git a/loco-graphics-helper/properties/file_versioning.py b/loco-graphics-helper/properties/file_versioning.py new file mode 100644 index 0000000..6244bc6 --- /dev/null +++ b/loco-graphics-helper/properties/file_versioning.py @@ -0,0 +1,81 @@ +''' +Copyright (c) 2024 RCT Graphics Helper developers + +For a complete list of all authors, please refer to the addon's meta info. +Interested in contributing? Visit https://github.com/oli414/Blender-RCT-Graphics + +RCT Graphics Helper is licensed under the GNU General Public License version 3. +''' + +import bpy +from bpy.app.handlers import persistent + +# Updating a project file to a newer version + +# each function updates the file to the version in the name +# if only new features were added, copy the format of version0 + +current_file_version = 4 + +def getAllComponents(): + return [x for x in bpy.context.scene.objects if x.loco_graphics_helper_object_properties.object_type != 'NONE'] + +class FileVersionUpdater: + # plugin version 0.1.6 + def version0(): + pass + + # plugin version 0.1.7 + # move component index from 1-index to 0-index + def version1(): + for object in getAllComponents(): + object.loco_graphics_helper_vehicle_properties.index -= 1 + + # plugin version 0.1.8 + # rename roll_angle to tilt_angle + def version2(): + for object in getAllComponents(): + if "roll_angle" in object.loco_graphics_helper_vehicle_properties: + object.loco_graphics_helper_vehicle_properties.tilt_angle = object.loco_graphics_helper_vehicle_properties["roll_angle"] + + # plugin version 0.1.9 + # add null component boolean + def version3(): + for object in getAllComponents(): + if object.loco_graphics_helper_vehicle_properties.index >= 255: + object.loco_graphics_helper_vehicle_properties.null_component = True + + # plugin version 0.1.10 + # add specific Y-offsets per entity type + def version4(): + bpy.context.scene.loco_graphics_helper_general_properties.y_offset += 17 + +update_functions = [getattr(FileVersionUpdater, func) for func in dir(FileVersionUpdater) if callable(getattr(FileVersionUpdater, func)) and not func.startswith("__")] + +def apply_update(): + print("loco-graphics-helper: Applying updates to file") + general_properties = bpy.context.scene.loco_graphics_helper_general_properties + addon_prefs = bpy.context.user_preferences.addons["loco-graphics-helper"].preferences + assert len(update_functions) == bpy.context.user_preferences.addons["loco-graphics-helper"].preferences.RCTPluginVersion + 1 + for i in range(int(general_properties.RCTPluginVersion)+1, int(addon_prefs.RCTPluginVersion)+1): + update_functions[i]() + general_properties.RCTPluginVersion = i + general_properties.RCTPluginName = addon_prefs.printable_idname + +@persistent +def check_for_update(_=None): + print("loco-graphics-helper: Checking for update") + general_properties = bpy.context.scene.loco_graphics_helper_general_properties + addon_prefs = bpy.context.user_preferences.addons["loco-graphics-helper"].preferences + if general_properties.RCTPluginName != addon_prefs.printable_idname: + return + if int(general_properties.RCTPluginVersion) >= int(addon_prefs.RCTPluginVersion): + return + print("File version is {}, plugin version is {}".format(general_properties.RCTPluginVersion, addon_prefs.RCTPluginVersion)) + apply_update() + +def register_file_updater(): + bpy.app.handlers.load_post.append(check_for_update) + +def unregister_file_updater(): + bpy.app.handlers.load_post.remove(check_for_update) diff --git a/loco-graphics-helper/properties/general_properties.py b/loco-graphics-helper/properties/general_properties.py index 19d3986..e2862c8 100644 --- a/loco-graphics-helper/properties/general_properties.py +++ b/loco-graphics-helper/properties/general_properties.py @@ -25,6 +25,16 @@ class GeneralProperties(bpy.types.PropertyGroup): script_file = os.path.realpath(__file__) directory = os.path.dirname(script_file) + RCTPluginName = bpy.props.StringProperty( + name="RCT Tools Type", + description="Which fork of RCTTools is this project for.", + default="unk") + + RCTPluginVersion = bpy.props.IntProperty( + name="RCT Tools Version", + description="What version of the fork this project is for. Number updates when a backwards-incompatible change is introduced.", + default=-1) + number_of_animation_frames = bpy.props.IntProperty( name="Animation Frames", description="Number of animation frames. For example in use for swinging, rotating or animated ride vehicles, animated rides, and animated scenery", @@ -112,26 +122,41 @@ class GeneralProperties(bpy.types.PropertyGroup): description="Whether or not the RCT add-on is currently rendering.", default=False) + transport_mode = bpy.props.EnumProperty( + name="Transport Mode", + items=( + ("RAIL","Rail","Railway vehicle",0), + ("ROAD","Road","Road or tram vehicle", 1), + ("AIR","Air","Aircraft", 2), + ("WATER","Water","Watercraft", 3) + ) + ) + + # currently unused build_gx = bpy.props.BoolProperty( name="Generate GX (optimized sprite file)", description="Whether or not to create a .dat sprite file. Having GXC installed is required.", default=False) + # currently unused build_assetpack = bpy.props.BoolProperty( name="Generate the asset pack file", description="Whether or not to the ORCT2 asset pack file", default=False) + # currently unused copy_assetpack_to_orct2 = bpy.props.BoolProperty( name="Copy to OpenRCT2", description="Copy the generated .graphics file to the ORCT2 assetpack folder.", default=False) + # currently unused build_parkobj = bpy.props.BoolProperty( name="Generate .parkobj file", description="Automatically build the .parkobj file. An object.json file with the object description is required in the output folder.", default=False) + # currently unused copy_parkobj_to_orct2 = bpy.props.BoolProperty( name="Copy to OpenRCT2", description="Copy the generated .parkobj file to the ORCT2 objects folder. Linking your OpenRCT2 Documents folder is required in the add-on preferences.", diff --git a/loco-graphics-helper/properties/preferences.py b/loco-graphics-helper/properties/preferences.py index 5c045de..1cc94d2 100644 --- a/loco-graphics-helper/properties/preferences.py +++ b/loco-graphics-helper/properties/preferences.py @@ -9,11 +9,19 @@ import bpy from bpy.types import AddonPreferences - +from .file_versioning import current_file_version class RCTGraphicsHelperPreferences(AddonPreferences): - bl_idname = "loco-graphics-helper" + printable_idname = "loco-graphics-helper" + bl_idname = printable_idname + + # make sure to add an updater to file_updater.py + RCTPluginVersion = bpy.props.IntProperty( + name="RCT Tools Version", + description="What version of the fork this project is for. Number updates when a backwards-incompatible change is introduced.", + default=current_file_version) + # currently unused orct2_directory = bpy.props.StringProperty( name="OpenRCT2 Path", description="The path to OpenRCT2. This should point to the directory that contains the object folder.", @@ -21,6 +29,7 @@ class RCTGraphicsHelperPreferences(AddonPreferences): subtype='DIR_PATH', default="") + # currently unused opengraphics_directory = bpy.props.StringProperty( name="OpenGraphics Repository Path", description="Root directory for the OpenGraphics repository, if available.", @@ -30,5 +39,5 @@ class RCTGraphicsHelperPreferences(AddonPreferences): def draw(self, context): layout = self.layout - layout.prop(self, "orct2_directory") - layout.prop(self, "opengraphics_directory") + col = layout.column() + col.label("""RCT Graphics Helper, "{}" Fork, file version {}""".format(self.bl_idname, self.RCTPluginVersion)) diff --git a/loco-graphics-helper/properties/vehicle_properties.py b/loco-graphics-helper/properties/vehicle_properties.py index b8e6166..2d34496 100644 --- a/loco-graphics-helper/properties/vehicle_properties.py +++ b/loco-graphics-helper/properties/vehicle_properties.py @@ -80,54 +80,70 @@ class VehicleProperties(bpy.types.PropertyGroup): default="32" ) - roll_angle = bpy.props.IntProperty( - name="Roll/Tilt Angle", - description="If non-zero will render a +angle -angle roll image", - default=0, - min=0) + tilt_angle = bpy.props.FloatProperty( + name="Tilt Angle", + description="Renders a left and right tilting sprite at the specified angle if non-zero", + default=0) index = bpy.props.IntProperty( - name="Body/Bogie Index", - description="Controls the order of the bodies/bogies", - default=1, - min=1) + name="Component Index", + description="Car/sub-component's index", + default=0, + min=0, + max=179) number_of_animation_frames = bpy.props.IntProperty( name="Animation Frames", - description="Number of animation frames. For example in use for animated wheels or cargo sprites", + description="Number of keyframed animation frames. Used for animated wheels and cargo", default=1, min=1) rotational_symmetry = bpy.props.BoolProperty( name="Rotational Symmetry", - description="If model is symmetrical when rotated around z access this will half the number of sprites rendered", + description="Component has 180-degree rotational symmetry. Reduces sprite count by half", default=False ) braking_lights = bpy.props.BoolProperty( name="Has Braking Lights", - description="If model has braking lights (located in layer 1) will render them", + description="Renders brake lights (layer 1)", default=False ) is_airplane = bpy.props.BoolProperty( name="Is an airplane", - description="If airplane will render airplane shadows (bogie)", + description="Renders airplane shadows", default=False ) is_clone = bpy.props.BoolProperty( - name="Is a clone of another bogie/body", - description="Clones will not be rendered and here just for show/location/meta data", + name="Is a duplicate of another sub-component", + description="Prevents rendering duplicate sprites for sub-components that are identical", default=False ) is_inverted = bpy.props.BoolProperty( - name="Direction is inverted", - description="Useful for clones to mark an inverted clone", + name="Component is reversed", + description="The car draws this sub-component facing backwards", + default=False + ) + + render_sprite = bpy.props.BoolProperty( + name="Render component", + description="Include this sub-component when batch rendering", + default=True + ) + + null_component = bpy.props.BoolProperty( + name="Null component", + description="This sub-component is not rendered in the game", default=False ) + bounding_box_override = bpy.props.PointerProperty( + type=bpy.types.Object, + name="BBox override", + description="Object to use when determining center of rotation and body parameters") def register_vehicles_properties(): bpy.types.Object.loco_graphics_helper_vehicle_properties = bpy.props.PointerProperty( diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index a456663..dcc26cf 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -22,9 +22,11 @@ from .operators.render_tiles_operator import RenderTiles +from .properties.file_updater import apply_update + from .models.palette import palette_colors, palette_colors_details -from .vehicle import get_car_components, VehicleComponent, SubComponent +from .vehicle import get_car_components, VehicleComponent, SubComponent, get_number_of_sprites, get_half_width class RepairConfirmOperator(bpy.types.Operator): """This action will clear out the default camera and light. Changes made to the rig object, compositor nodes and recolorable materials will be lost.""" @@ -43,6 +45,23 @@ def execute(self, context): def invoke(self, context, event): return context.window_manager.invoke_confirm(self, event) +class UpdateConfirmOperator(bpy.types.Operator): + """This action will perform the necessary updates to upgrade the file from plugin version 0.1.6 to current.""" + bl_idname = "loco_graphics_helper.update_from_prehistoric" + bl_label = "Perform updates" + bl_options = {'REGISTER', 'INTERNAL'} + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + apply_update() + return {'FINISHED'} + + def invoke(self, context, event): + return context.window_manager.invoke_confirm(self, event) + class GraphicsHelperPanel(bpy.types.Panel): bl_label = "Loco Graphics Helper" bl_idname = "VIEW3D_PT_loco_graphics_helper" @@ -64,6 +83,27 @@ def draw(self, context): # General properties properties = scene.loco_graphics_helper_general_properties + addon_prefs = context.user_preferences.addons["loco-graphics-helper"].preferences + + col = layout.column() + col.label("File made with {}".format(properties.RCTPluginName)) + col.label("File version {}".format(properties.RCTPluginVersion)) + if properties.RCTPluginName == addon_prefs.printable_idname and properties.RCTPluginVersion > addon_prefs.RCTPluginVersion: + box = layout.box() + col = box.column() + col.label("WARNING: file was made with a") + col.label("newer version of this plugin!".format(properties.RCTPluginVersion)) + col.label("This plugin version: {}".format(addon_prefs.RCTPluginVersion)) + if properties.RCTPluginVersion == -1 and properties.RCTPluginName == "unk": + box = layout.box() + col = box.row() + col.label("Update from {} version 0.1.6?".format(addon_prefs.printable_idname)) + col.operator("loco_graphics_helper.update_from_prehistoric", text="Update") + elif properties.RCTPluginName != addon_prefs.printable_idname: + box = layout.box() + col = box.column() + col.label("WARNING: file was made for {} plugin".format(properties.RCTPluginName)) + col.label("this is the {} plugin".format(addon_prefs.printable_idname)) row = layout.row() row.separator() @@ -137,24 +177,6 @@ def draw(self, context): elif properties.render_mode == "TRACK": self.draw_track_panel(scene, box) - row = layout.row() - row.prop(properties, "build_gx") - - if properties.build_gx: - box = layout.box() - box.prop(properties, "build_assetpack") - - if properties.build_assetpack: - box2 = box.box() - box2.prop(properties, "copy_assetpack_to_orct2") - - row = layout.row() - row.prop(properties, "build_parkobj") - - if properties.build_parkobj: - box = layout.box() - box.prop(properties, "copy_parkobj_to_orct2") - def draw_tiles_panel(self, scene, layout): properties = scene.loco_graphics_helper_static_properties general_properties = scene.loco_graphics_helper_general_properties @@ -219,19 +241,27 @@ def draw_track_panel(self, scene, layout): def blender_to_loco_dist(dist): return int(dist * 32 + 0.5) + @staticmethod + def calculatePrecision(x): + return [y for y in range(8) if (1 << y) == int(x)][0] - 2 + def draw_vehicle_panel(self, scene, layout): general_properties = scene.loco_graphics_helper_general_properties + row = layout.row() + row.prop(general_properties,"transport_mode") + cars = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "CAR"] cars = sorted(cars, key=lambda x: x.loco_graphics_helper_vehicle_properties.index) total_number_of_sprites = 0 + renderable_sprites = 0 components = get_car_components(cars) if len(components) == 0: col = layout.column() col.label(text="No cars detected.") - col.label(text="Ensure at least one BODY is parented to a CAR") + col.label(text="Ensure at least one body is parented to a car") return row = layout.row() row.label("Car(s) details:") @@ -325,37 +355,120 @@ def draw_vehicle_panel(self, scene, layout): car = None sub_component = None for component in components: - if component.front == bogie: - car = component - sub_component = SubComponent.FRONT - break - if component.back == bogie: - car = component - sub_component = SubComponent.BACK - break - if car is None: - continue - - number_of_sprites = car.get_number_of_sprites(sub_component) - total_number_of_sprites = total_number_of_sprites + number_of_sprites + front = component.get_object(SubComponent.FRONT) + back = component.get_object(SubComponent.BACK) + body = component.get_object(SubComponent.BODY) + + front_position = -1.0/32 + back_position = -1.0/32 + body_idx = component.get_component_index(SubComponent.BODY) + front_idx = component.get_component_index(SubComponent.FRONT) + back_idx = component.get_component_index(SubComponent.BACK) + warning = None + anim_location = 0 + front_name = '' if front is None else front.name + back_name = '' if back is None else back.name + mid_point_x = component.get_preferred_body_midpoint() + if body.loco_graphics_helper_vehicle_properties.bounding_box_override is None and not math.isclose(body.matrix_world.translation[0], mid_point_x, rel_tol=1e-4): + warning = "Body location is not at midpoint, off by {}".format(mid_point_x) + + if not front is None: + front_position = component.get_bogie_position(SubComponent.FRONT) + if not back is None: + back_position = component.get_bogie_position(SubComponent.BACK) + + anim_location = component.get_emitter_x() + if not anim_location is None and (anim_location > 255 or anim_location < 0): + warning = "Emitter is too far from bogies" + anim_location = 255 + elif body.loco_graphics_helper_vehicle_properties.is_airplane: + front_idx = 0 + back_idx = 255 + front_position = 0 + back_position = 0 + + box = layout.box() + box.label("Car {}: {}".format(component.car.loco_graphics_helper_vehicle_properties.index, component.car.name)) + col = box.column() + col.label("{}, {}, {}".format(body.name, front_name, back_name)) + col.label(" Front Position: {}".format(self.blender_to_loco_dist(front_position))) + col.label(" Back Position: {}".format(self.blender_to_loco_dist(back_position))) + col.label(" Front Bogie Sprite Index: {}".format(front_idx)) + col.label(" Back Bogie Sprite Index: {}".format(back_idx)) + col.label(" Body Sprite Index: {}".format(body_idx)) + if not anim_location is None: + col.label(" Emitter Horizontal Position: {}".format(anim_location)) + + if not warning is None: + row = box.row() + row.label(" WARNING: {},".format(warning)) + + bodies = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "BODY" and not x.loco_graphics_helper_vehicle_properties.is_clone and get_number_of_sprites(x) > 0] + bodies = sorted(bodies, key=lambda x: x.loco_graphics_helper_vehicle_properties.index) + + if len(bodies) > 0: + for body in bodies: + number_of_sprites = get_number_of_sprites(body) + total_number_of_sprites += number_of_sprites + + half_width = -1.0/32 + car = None + if body.loco_graphics_helper_vehicle_properties.bounding_box_override: + half_width = get_half_width(body.loco_graphics_helper_vehicle_properties.bounding_box_override) + for component in components: + if component.body == body: + car = component + half_width = component.get_half_width() + break + emitter_z = car.get_emitter_z() + + if number_of_sprites == 0: + continue + + if body.loco_graphics_helper_vehicle_properties.render_sprite: + renderable_sprites += number_of_sprites + + box = layout.box() + row = box.row() + row.label("Body {}: {}".format(body.loco_graphics_helper_vehicle_properties.index, body.name)) + row.prop(body.loco_graphics_helper_vehicle_properties, "render_sprite") + col = box.column() + col.label(" Flat Rotation Frames: {}".format(body.loco_graphics_helper_vehicle_properties.flat_viewing_angles)) + col.label(" Sloped Rotation Frames: {}".format(body.loco_graphics_helper_vehicle_properties.sloped_viewing_angles)) + col.label(" Tilt Frames: {}".format(3 if body.loco_graphics_helper_vehicle_properties.tilt_angle != 0 else 1)) + col.label(" Half-Length: {}{}".format(self.blender_to_loco_dist(half_width), "" if body.loco_graphics_helper_vehicle_properties.bounding_box_override else " using bounding box override")) + col.label(" Flat Yaw Accuracy: {}".format(self.calculatePrecision(body.loco_graphics_helper_vehicle_properties.flat_viewing_angles))) + col.label(" Sloped Yaw Accuracy: {}".format(self.calculatePrecision(body.loco_graphics_helper_vehicle_properties.sloped_viewing_angles))) + col.label(" Frames per Viewing Angle: {}".format(0)) + col.label(" Number of sprites: {}".format(number_of_sprites)) + if not emitter_z is None: + col.label(" Emitter Vertical Position: {}".format(emitter_z)) + + bogies = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "BOGIE" and not x.loco_graphics_helper_vehicle_properties.is_clone and get_number_of_sprites(x) > 0] + bogies = sorted(bogies, key=lambda x: x.loco_graphics_helper_vehicle_properties.index) - if number_of_sprites == 0: - continue + if len(bogies) > 0: + for bogie in bogies: + number_of_sprites = get_number_of_sprites(bogie) + total_number_of_sprites += number_of_sprites + + if bogie.loco_graphics_helper_vehicle_properties.render_sprite: + renderable_sprites += number_of_sprites + + box = layout.box() + row = box.row() + row.label("Bogie {}: {}".format(bogie.loco_graphics_helper_vehicle_properties.index, bogie.name)) + row.prop(bogie.loco_graphics_helper_vehicle_properties, "render_sprite") + col = box.column() + col.label(" Number of sprites: {}".format(number_of_sprites)) - half_width = component.get_half_width() - row = layout.row() - row.label("{}. {}".format(bogie.loco_graphics_helper_vehicle_properties.index, bogie.name)) - row = layout.row() - row.label(" Number of sprites: {}".format(number_of_sprites)) - row = layout.row() row.label("Total number of sprites: {}".format(total_number_of_sprites)) - - if total_number_of_sprites == 0: - row = layout.row() - row.label("NO BODIES OR BOGIES SET!") - row = layout.row() - row.label("NOTHING WILL BE RENDERED!") + row = layout.row() + if renderable_sprites > 0: + row.label("Sprites to render: {}".format(renderable_sprites)) + else: + row.label("WARNING: 0 sprites to render") row = layout.row() text = "Render" diff --git a/loco-graphics-helper/res/palettes/base_loco_palette.png b/loco-graphics-helper/res/palettes/base_loco_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..21380ed345e69a6fc3a767ce9ee00c35b9c982cd GIT binary patch literal 1236 zcmZ`&Z*UV;5a0600}2KS8YN=D2$7yOp$7&U^k}a&EulT=)dm_gl!Hc$c!diM5F|jV zDMURX)=&dRjSw|L!~#*GMh#jZV8kjxPl;NGpo|m8eCUig^St`a559fB{q1jO_s#6= zyj@EgjT6RA83zCp>g#HnB>qW4$(WInSIwG#R-#e1s)j0nqeAK5ZKEYUjjd~H0N64U zV0#z9Us7uORe+5;fZtXERPF;%bnHL5bOFGKLrWGn*GdHnwOUOOT8bhW#>BD~p65Is zPaqJA#p0>dfG8GoWv`bR=d?B>8m4mm3UIIA%7Kawdh_ ztP)sFh$s3yR5D;r$2hUyn-{}Fg_KbVRverp2n-hCu%u6xjmV2>CCY12(Tc7M2&L2j zG=LSuIGN0?R03*9n>vmrYg-|l7B;;yRp`}#@tH#LTW;ekK20lo6q9%WW z38$@b(WcTXwMMmp!A-2*!cm-;_J*vXI2X^jQ=&hUkK~4uG7{trC~Tl|fkwdeA%!uf zu_Q?@WA^3va6w3+J{p&^dZp8d`^<(2OC@+F<6*M_elY4OBm+a4C>l)3X*-B!*#l@%kwwRkpOHl3lx~b8Bn2U=N17j-5Pk{=$hXS2GA{OvW1M9zC^A5@S2+mP^x4atx#7 za=U7il*AnM4Yk;>BPLBQn>T$dBmD=M$hI|hN<3adiG+X8<)Ph^rQrAanyTh8*^-}O zDuR*`BS($C0UI-RT*Z*x2dZw0Vnb>yz8u+js2rbUpR7w|m!a-!soXCp^Dr zum6Rf7X$lxUkdI&@N(#tS6>Uie(+G_jW^$lzJ2&e?46_U_8mKZBL3d{Cleoh_)+rX zPd-hZ`t0-m(`U}6&z=7w^W}xF2EP90V)oLv--+MvWZK=TuhA$E850t2kYE>OT$ZxYcXhH~)VF)$_;j PCZN8yvF1qi%ANlJI0EXA literal 0 HcmV?d00001 diff --git a/loco-graphics-helper/res/palettes/custom_palette.bmp b/loco-graphics-helper/res/palettes/custom_palette.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fab08c1d5acf23dfe29e8de2b379eacfa929905d GIT binary patch literal 430 zcmYk0KS*0~5XUbl26Ye&A}AOkqEd$k{szG!Px6MK!9fECnu0?hA{jD7bWqF?MbY7* z4k3s*WXK?zp+!p_guX$GEm8_~$l#!;;2;>p!QtaC{(*kE@7;Zm?{ViJ+I7hbmUW)h z9MCL}T+%8Py0&RCS^r8c-+;(#lr{dqNYeiC&^i3ArJ-fv)}@s4#A2~_&8loRTisW4 zR?+#{?tb4c*UJBe?tQ*Sl=5L&2BDBiG#VtCjF3(z$YjzK3R^hNKE>i+N~Js8T^6+; z!!!mHc*7|=qBbhIg_Zt7ICDs%aKhqIkzDZ_=k5_{4j^4)h&hcrWaCccsarY3lRu;R zq=az|Vra5 zb1jfDc}t&2M}xDP$Z1_x9;siP*Zy0w$UHl32-6LsS&%tEwlFvUe_d&n|882A{}U{I s|5th@{hu6B_J4U!_y28e^Zy^4zxn^woyY$_xpe3Mmp31PwlgpQ05yy<*Z=?k literal 0 HcmV?d00001 diff --git a/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png b/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..31b07aee6d4dedf1a0e333e1b129f36b40e34e32 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp@K+MR(3?#p^TbckVmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5l+X(B32_C|_6*YL493;m z_Wk1ii{<0DtLNXg?tdS>`g8g2@6%8J-hTi8_4hz!i{AUa0aA=5L4Lsu4$p3+fjCLt z?k)@+tg;?J4rhT!WHAGSPzeY#-b<6O01C2~c>21szhq`%7Z*CF@y-k=B#@lwR|cec zfS3`8!QkHaE)gI@z|+MsgyVX$fDz~F8C%;K88{{}W%Dgc*$9+VEpd$~Nl7e8wMs5Z z1yT$~21cg32F5@XVq{=tXl!L@qHSPcWnf^pQ@smCLvDUbW?Cg~4gOE&gIvbo>FVdQ I&MBb@07<4{RsaA1 literal 0 HcmV?d00001 diff --git a/loco-graphics-helper/vehicle.py b/loco-graphics-helper/vehicle.py index 6bf383f..5132b88 100644 --- a/loco-graphics-helper/vehicle.py +++ b/loco-graphics-helper/vehicle.py @@ -17,6 +17,76 @@ class SubComponent(Enum): BACK = 1 BODY = 2 +def get_vehicle_y_offset(): + additional_offsets = { + "RAIL":0, + "ROAD":0, + "AIR":0, + "WATER":0, + } + return -17 + additional_offsets[bpy.context.scene.loco_graphics_helper_general_properties.transport_mode] + +def get_number_of_sprites(object): + + is_bogie = object.loco_graphics_helper_object_properties.object_type == "BOGIE" + props = object.loco_graphics_helper_vehicle_properties + + if props.null_component: + return 0 + + multiplier = props.number_of_animation_frames + if props.tilt_angle != 0: + multiplier = 3 + elif props.braking_lights: + multiplier = multiplier + 1 + if props.rotational_symmetry: + multiplier = multiplier / 2 + + num_transition_sprites = 0 if is_bogie else 4 + 4 + num_sprites = 0 + if props.sprite_track_flags[0]: + num_sprites = int(props.flat_viewing_angles) * multiplier + if props.sprite_track_flags[1]: + num_sprites = num_sprites + (int(props.sloped_viewing_angles) * 2 + num_transition_sprites) * multiplier + if props.sprite_track_flags[2]: + num_sprites = num_sprites + (int(props.sloped_viewing_angles) * 2 + num_transition_sprites) * multiplier + + if props.is_airplane: + num_sprites = num_sprites + int(props.flat_viewing_angles) * multiplier / 2 + return int(num_sprites) + +def _get_min_max_axis_bound_box_corners(object, axis): + bbox_corners = [object.matrix_world * Vector(corner) for corner in object.bound_box] + min_x = min([x[axis] for x in bbox_corners]) + max_x = max([x[axis] for x in bbox_corners]) + return (min_x, max_x) + +def _get_min_max_axis_bound_box_corners_with_children(object, axis): + mins = [] + maxs = [] + min_x, max_x = _get_min_max_axis_bound_box_corners(object, axis) + # This can happen if there are no dimensions to this object (or if its 0 width) + if min_x != max_x: + mins.append(min_x) + maxs.append(max_x) + + for c in object.children: + min_x, max_x = _get_min_max_axis_bound_box_corners_with_children(c, axis) + if min_x != max_x: + mins.append(min_x) + maxs.append(max_x) + if len(mins) == 0 or len(maxs) == 0: + return (0, 0) + + return (min(mins), max(maxs)) + +def get_half_width(object): + body_min_x, body_max_x = _get_min_max_axis_bound_box_corners_with_children(object, 0) + min_x = object.matrix_world.translation[0] - body_min_x + max_x = body_max_x - object.matrix_world.translation[0] + + return max(min_x, max_x) + class VehicleComponent: def __init__(self, car, front, back, body, animations = None): self.car = car @@ -35,8 +105,16 @@ def get_object(self, sub_component: SubComponent): SubComponent.BODY.value:self.body, } return object_mapping[sub_component.value] - - + + def get_component_index(self, sub_component: SubComponent): + object = self.get_object(sub_component) + if object == None: + return 255 + props = object.loco_graphics_helper_vehicle_properties + if props.null_component: + return 255 + return props.index + 180 * props.is_inverted + def has_sprites(self, sub_component: SubComponent): object = self.get_object(sub_component) if object is None: @@ -44,7 +122,8 @@ def has_sprites(self, sub_component: SubComponent): props = object.loco_graphics_helper_vehicle_properties if props.is_clone: return False - + # render_sprites == False does NOT return false here + if all(v == 0 for v in props.sprite_track_flags): return False @@ -52,61 +131,13 @@ def has_sprites(self, sub_component: SubComponent): def get_number_of_sprites(self, sub_component: SubComponent): object = self.get_object(sub_component) - - is_bogie = object.loco_graphics_helper_object_properties.object_type == "BOGIE" - props = object.loco_graphics_helper_vehicle_properties - - multiplier = props.number_of_animation_frames - if props.roll_angle != 0: - multiplier = 3 - elif props.braking_lights: - multiplier = multiplier + 1 - if props.rotational_symmetry: - multiplier = multiplier / 2 - - num_transition_sprites = 0 if is_bogie else 4 + 4 - num_sprites = 0 - if props.sprite_track_flags[0]: - num_sprites = int(props.flat_viewing_angles) * multiplier - if props.sprite_track_flags[1]: - num_sprites = num_sprites + (int(props.sloped_viewing_angles) * 2 + num_transition_sprites) * multiplier - if props.sprite_track_flags[2]: - num_sprites = num_sprites + (int(props.sloped_viewing_angles) * 2 + num_transition_sprites) * multiplier - - if props.is_airplane: - num_sprites = num_sprites + int(props.flat_viewing_angles) * multiplier / 2 - return int(num_sprites) + return get_number_of_sprites(object) def _get_min_max_x_bound_box_corners_with_children(self, object): - return self._get_min_max_axis_bound_box_corners_with_children(object, 0) + return _get_min_max_axis_bound_box_corners_with_children(object, 0) def _get_min_max_z_bound_box_corners_with_children(self, object): - return self._get_min_max_axis_bound_box_corners_with_children(object, 2) - - def _get_min_max_axis_bound_box_corners_with_children(self, object, axis): - mins = [] - maxs = [] - min_x, max_x = self._get_min_max_axis_bound_box_corners(object, axis) - # This can happen if there are no dimensions to this object (or if its 0 width) - if min_x != max_x: - mins.append(min_x) - maxs.append(max_x) - - for c in object.children: - min_x, max_x = self._get_min_max_axis_bound_box_corners_with_children(c, axis) - if min_x != max_x: - mins.append(min_x) - maxs.append(max_x) - if len(mins) == 0 or len(maxs) == 0: - return (0, 0) - - return (min(mins), max(maxs)) - - def _get_min_max_axis_bound_box_corners(self, object, axis): - bbox_corners = [object.matrix_world * Vector(corner) for corner in object.bound_box] - min_x = min([x[axis] for x in bbox_corners]) - max_x = max([x[axis] for x in bbox_corners]) - return (min_x, max_x) + return _get_min_max_axis_bound_box_corners_with_children(object, 2) def get_half_width(self): mins = [] @@ -143,22 +174,38 @@ def get_preferred_body_midpoint(self): def get_bogie_position(self, sub_component: SubComponent): assert sub_component != SubComponent.BODY - body_x = self.body.location[0] + body_x = self.body.matrix_world.translation[0] + half_width = self.get_half_width() + bounding_box = self.body.loco_graphics_helper_vehicle_properties.bounding_box_override + if bounding_box: + body_x = bounding_box.matrix_world.translation[0] + half_width = get_half_width(bounding_box) bogie = self.get_object(sub_component) - bogie_x = bogie.location[0] + bogie_x = bogie.matrix_world.translation[0] position_from_centre = max(body_x, bogie_x) - min(body_x, bogie_x) - return self.get_half_width() - position_from_centre - - def get_animation_location(self): + return half_width - position_from_centre + + def get_emitter_x(self): if len(self.animations) == 0: - return 0 - x_diff = self.front.location[0] - self.back.location[0] - print("front_x {} back_x {} anim_x {}".format(self.front.location[0], self.back.location[0], self.animations[0].location[0])) + return None + x_diff = self.front.matrix_world.translation[0] - self.back.matrix_world.translation[0] + # print("front_x {} back_x {} anim_x {}".format(self.front.location[0], self.back.location[0], self.animations[0].location[0])) x_factor = (1 / x_diff) - anim_diff = self.front.location[0] - self.animations[0].location[0] + anim_diff = self.front.matrix_world.translation[0] - self.animations[0].matrix_world.translation[0] anim_factor = (anim_diff * x_factor) * 128 return int(anim_factor) + 64 + def get_emitter_z(self): + if len(self.animations) == 0: + return None + target_object = self.front if not self.front is None else self.body + body_z = target_object.matrix_world.translation[2] + bounding_box = target_object.loco_graphics_helper_vehicle_properties.bounding_box_override + if bounding_box: + body_z = bounding_box.matrix_world.translation[2] + emitter_z = self.animations[0].matrix_world.translation[2] + return int((emitter_z - body_z) * 8) + 8 # I don't know if this is steam specific + def get_car_components(cars) -> List[VehicleComponent]: components = [] From 406d323751e6f64189a07a3c8386c31092ff9cbf Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 4 Mar 2025 01:03:11 -0800 Subject: [PATCH 04/14] forgot to rename that --- loco-graphics-helper/rct_graphics_helper_panel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index dcc26cf..f085215 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -22,7 +22,7 @@ from .operators.render_tiles_operator import RenderTiles -from .properties.file_updater import apply_update +from .properties.file_versioning import apply_update from .models.palette import palette_colors, palette_colors_details From 24466d743cf04046fe22c0a095234bc604ab86d7 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 4 Mar 2025 01:04:42 -0800 Subject: [PATCH 05/14] fix rebase errors part 2 --- .../rct_graphics_helper_panel.py | 93 +------------------ 1 file changed, 1 insertion(+), 92 deletions(-) diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index f085215..64007c5 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -262,98 +262,7 @@ def draw_vehicle_panel(self, scene, layout): col = layout.column() col.label(text="No cars detected.") col.label(text="Ensure at least one body is parented to a car") - return - row = layout.row() - row.label("Car(s) details:") - - for component in components: - front = component.get_object(SubComponent.FRONT) - back = component.get_object(SubComponent.BACK) - body = component.get_object(SubComponent.BODY) - idx = body.loco_graphics_helper_vehicle_properties.index - - front_position = 0 - back_position = 0 - body_idx = idx - 1 + 180 if body.loco_graphics_helper_vehicle_properties.is_inverted else idx - 1 - front_idx = 255 - back_idx = 255 - warning = None - anim_location = 0 - front_name = '' if front is None else front.name - back_name = '' if back is None else back.name - mid_point_x = component.get_preferred_body_midpoint() - if not math.isclose(body.matrix_world.translation[0], mid_point_x, rel_tol=1e-4): - warning = "BODY LOCATION IS NOT AT PREFERRED MID X POINT! {}".format(round(mid_point_x,1)) - - if not front is None: - front_position = component.get_bogie_position(SubComponent.FRONT) - back_position = component.get_bogie_position(SubComponent.BACK) - - if component.get_number_of_sprites(SubComponent.FRONT) != 0: - front_idx = front.loco_graphics_helper_vehicle_properties.index - 1 - front_idx = front_idx + 180 if front.loco_graphics_helper_vehicle_properties.is_inverted else front_idx - - if component.get_number_of_sprites(SubComponent.BACK) != 0: - back_idx = back.loco_graphics_helper_vehicle_properties.index - 1 - back_idx = back_idx + 180 if front.loco_graphics_helper_vehicle_properties.is_inverted else back_idx - - anim_location = component.get_animation_location() - if anim_location > 255 or anim_location < 0: - warning = "Animation is too far from bogies" - anim_location = 255 - elif body.loco_graphics_helper_vehicle_properties.is_airplane: - front_idx = 0 - - row = layout.row() - row.label("{}. {}, {}, {}, {}".format(component.car.loco_graphics_helper_vehicle_properties.index - 1, component.car.name, body.name, front_name, back_name)) - row = layout.row() - row.label(" Front Position: {}".format(self.blender_to_loco_dist(front_position))) - row = layout.row() - row.label(" Back Position: {}".format(self.blender_to_loco_dist(back_position))) - row = layout.row() - row.label(" Front Bogie Sprite Index: {}".format(front_idx)) - row = layout.row() - row.label(" Back Bogie Sprite Index: {}".format(back_idx)) - row = layout.row() - row.label(" Body Sprite Index: {}".format(body_idx)) - row = layout.row() - row.label(" Animation Position: {}".format(anim_location)) - - if not warning is None: - row = layout.row() - row.label(" WARNING: {},".format(warning)) - - row = layout.row() - row.label("Body(s) details:") - components = sorted(components, key=lambda x: x.body.loco_graphics_helper_vehicle_properties.index) - for component in components: - body = component.body - if body is None: - continue - if body.loco_graphics_helper_vehicle_properties.is_clone: - continue - number_of_sprites = component.get_number_of_sprites(SubComponent.BODY) - total_number_of_sprites = total_number_of_sprites + number_of_sprites - - if number_of_sprites == 0: - continue - - half_width = component.get_half_width() - row = layout.row() - row.label("{}. {}".format(body.loco_graphics_helper_vehicle_properties.index, body.name)) - row = layout.row() - row.label(" Half-Width: {}".format(self.blender_to_loco_dist(half_width))) - row = layout.row() - row.label(" Number of sprites: {}".format(number_of_sprites)) - - bogies = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "BOGIE" and not x.loco_graphics_helper_vehicle_properties.is_clone] - bogies = sorted(bogies, key=lambda x: x.loco_graphics_helper_vehicle_properties.index) - - row = layout.row() - row.label("Bogie(s) details:") - for bogie in bogies: - car = None - sub_component = None + else: for component in components: front = component.get_object(SubComponent.FRONT) back = component.get_object(SubComponent.BACK) From 2d04f6d0d06b50b1dcc3a9039ae14efaa2a23539 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 4 Mar 2025 01:17:28 -0800 Subject: [PATCH 06/14] tweak UI some more --- .../rct_graphics_helper_panel.py | 3 --- .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes 2 files changed, 3 deletions(-) diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index 64007c5..d1ddeaf 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -342,13 +342,10 @@ def draw_vehicle_panel(self, scene, layout): row.label("Body {}: {}".format(body.loco_graphics_helper_vehicle_properties.index, body.name)) row.prop(body.loco_graphics_helper_vehicle_properties, "render_sprite") col = box.column() - col.label(" Flat Rotation Frames: {}".format(body.loco_graphics_helper_vehicle_properties.flat_viewing_angles)) - col.label(" Sloped Rotation Frames: {}".format(body.loco_graphics_helper_vehicle_properties.sloped_viewing_angles)) col.label(" Tilt Frames: {}".format(3 if body.loco_graphics_helper_vehicle_properties.tilt_angle != 0 else 1)) col.label(" Half-Length: {}{}".format(self.blender_to_loco_dist(half_width), "" if body.loco_graphics_helper_vehicle_properties.bounding_box_override else " using bounding box override")) col.label(" Flat Yaw Accuracy: {}".format(self.calculatePrecision(body.loco_graphics_helper_vehicle_properties.flat_viewing_angles))) col.label(" Sloped Yaw Accuracy: {}".format(self.calculatePrecision(body.loco_graphics_helper_vehicle_properties.sloped_viewing_angles))) - col.label(" Frames per Viewing Angle: {}".format(0)) col.label(" Number of sprites: {}".format(number_of_sprites)) if not emitter_z is None: col.label(" Emitter Vertical Position: {}".format(emitter_z)) diff --git a/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png b/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png index 31b07aee6d4dedf1a0e333e1b129f36b40e34e32..9bb893cfcdbfd5070413e270b907cb4565b696b5 100644 GIT binary patch delta 17 Zcmey(^qXnIId)D~g_^04@+V$h1^`2Z2mAm4 delta 17 Zcmey(^qXnIId*n&p<^2F%qCu41^_~G2d)4B From 446b8f356a64298e7ac08174e4c185b3ac6d280f Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Wed, 5 Mar 2025 01:32:53 -0800 Subject: [PATCH 07/14] fix issue wwith half length reporting --- .../rct_graphics_helper_panel.py | 6 +++--- .../res/palettes/custom_palette.bmp | Bin 430 -> 190 bytes .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index d1ddeaf..b030ec4 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -322,13 +322,13 @@ def draw_vehicle_panel(self, scene, layout): half_width = -1.0/32 car = None - if body.loco_graphics_helper_vehicle_properties.bounding_box_override: - half_width = get_half_width(body.loco_graphics_helper_vehicle_properties.bounding_box_override) for component in components: if component.body == body: car = component half_width = component.get_half_width() break + if not body.loco_graphics_helper_vehicle_properties.bounding_box_override is None: + half_width = get_half_width(body.loco_graphics_helper_vehicle_properties.bounding_box_override) emitter_z = car.get_emitter_z() if number_of_sprites == 0: @@ -343,7 +343,7 @@ def draw_vehicle_panel(self, scene, layout): row.prop(body.loco_graphics_helper_vehicle_properties, "render_sprite") col = box.column() col.label(" Tilt Frames: {}".format(3 if body.loco_graphics_helper_vehicle_properties.tilt_angle != 0 else 1)) - col.label(" Half-Length: {}{}".format(self.blender_to_loco_dist(half_width), "" if body.loco_graphics_helper_vehicle_properties.bounding_box_override else " using bounding box override")) + col.label(" Half-Length: {}{}".format(self.blender_to_loco_dist(half_width), "" if body.loco_graphics_helper_vehicle_properties.bounding_box_override is None else " using bounding box override")) col.label(" Flat Yaw Accuracy: {}".format(self.calculatePrecision(body.loco_graphics_helper_vehicle_properties.flat_viewing_angles))) col.label(" Sloped Yaw Accuracy: {}".format(self.calculatePrecision(body.loco_graphics_helper_vehicle_properties.sloped_viewing_angles))) col.label(" Number of sprites: {}".format(number_of_sprites)) diff --git a/loco-graphics-helper/res/palettes/custom_palette.bmp b/loco-graphics-helper/res/palettes/custom_palette.bmp index fab08c1d5acf23dfe29e8de2b379eacfa929905d..5ea8d167dc73f4f80cd626a919d81f68810a73a4 100644 GIT binary patch delta 49 pcmZ3-ypK`E$#)+E1atwZ8X)EcVn#4lU|01d~WKk63Hy zX&1fNz`xfa(0C@;9byb1KLqy&Jisu8m?b2vF*5TLEEgszmu7Ky7N{TDt6W`d@%V7e z$NQzK_mDsYJru)lBneqL%(P8vX&u{fajJEy%{EQ1ho=s`s39??k$4Wtln}duY@8zd Ww@Bj^X@hwOi%(E{gZnSlSNH`B1DW0c diff --git a/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png b/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png index 9bb893cfcdbfd5070413e270b907cb4565b696b5..c67d5f42ca5b1fee59f61dd8ff6e4339cefde414 100644 GIT binary patch delta 17 Zcmey(^qXnIId%~VsX*@ZM-#6u0{}m12Uh?9 delta 17 Zcmey(^qXnIId)D~g_^04@+V$h1^`2Z2mAm4 From 3c3e143b71027023942cd2e2432db1b642a064b2 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Mon, 10 Mar 2025 02:51:07 -0700 Subject: [PATCH 08/14] implement properties for track --- loco-graphics-helper/__init__.py | 3 + loco-graphics-helper/angle_sections/track.py | 223 +++++++++++++++++- .../frame_processors/post_processor.py | 2 + .../properties/file_versioning.py | 7 +- .../properties/object_properties.py | 7 + .../properties/track_properties.py | 60 ++++- .../rct_graphics_helper_panel.py | 18 +- loco-graphics-helper/renderer.py | 2 +- .../res/palettes/base_loco_palette.png | Bin 1236 -> 1284 bytes .../res/palettes/custom_palette.bmp | Bin 190 -> 238 bytes .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes 11 files changed, 300 insertions(+), 22 deletions(-) diff --git a/loco-graphics-helper/__init__.py b/loco-graphics-helper/__init__.py index 67b71a0..cc1bbd3 100644 --- a/loco-graphics-helper/__init__.py +++ b/loco-graphics-helper/__init__.py @@ -16,6 +16,7 @@ from .properties.general_properties import register_general_properties, unregister_general_properties from .properties.track_properties import register_track_properties, unregister_track_properties from .properties.object_properties import register_object_properties, unregister_object_properties +from .properties.track_piece_properties import register_track_piece_properties, unregister_track_piece_properties from .properties.file_versioning import register_file_updater, unregister_file_updater from .rct_graphics_helper_panel import GraphicsHelperPanel from . import developer_utils @@ -55,6 +56,7 @@ def register(): register_walls_properties() register_track_properties() register_object_properties() + register_track_piece_properties() register_file_updater() print("Registered {} with {} modules".format( @@ -73,6 +75,7 @@ def unregister(): unregister_walls_properties() unregister_track_properties() unregister_object_properties() + unregister_track_piece_properties() unregister_file_updater() print("Unregistered {}".format(bl_info["name"])) diff --git a/loco-graphics-helper/angle_sections/track.py b/loco-graphics-helper/angle_sections/track.py index 726e9dd..c73b735 100644 --- a/loco-graphics-helper/angle_sections/track.py +++ b/loco-graphics-helper/angle_sections/track.py @@ -1,12 +1,14 @@ ''' -Copyright (c) 2022 RCT Graphics Helper developers +Copyright (c) 2025 Loco Graphics Helper developers For a complete list of all authors, please refer to the addon's meta info. -Interested in contributing? Visit https://github.com/oli414/Blender-RCT-Graphics +Interested in contributing? Visit https://github.com/OpenLoco/Blender-Loco-Graphics -RCT Graphics Helper is licensed under the GNU General Public License version 3. +Loco Graphics Helper is licensed under the GNU General Public License version 3. ''' +from enum import Enum + track_angle_sections_names = [ "VEHICLE_SPRITE_FLAG_FLAT", "VEHICLE_SPRITE_FLAG_GENTLE_SLOPES", @@ -31,3 +33,218 @@ [False, 4, -22.2052] ], } + +# note to self: images are ordered by sequence FIRST then angle SECOND +""" +layer_names = [ + ["Model"], + ["Pickup Icon", "Placement Icon"], + ["Ballast", "Ties", "Rails"] +] + +layer_descriptions = [ + ["Icon Model"], + ["Icon, drawn as train pickup button", "Icon, drawn as train placement button"], + ["Ballast, drawn above terrain or bridge. Sloped tracks use only this layer, sloped tram track still use all three.", "Ties, drawn above ballast", "Rails, drawn above ties"], +] +""" + +# these are not the OpenLoco track piece IDs. These are the index of the piece in the manifest +class TrackPieceType(Enum): + PREVIEW = 0 + PICKUP = 1 + PLACE = 2 + STRAIGHT = 3 + CURVE_SMALL = 7 + SMALL_CURVE_SLOPE_SHALLOW = 8 + SMALL_CURVE_SLOPE_STEEP = 9 + CURVE = 10 + SLOPE_SHALLOW = 11 + SLOPE_STEEP = 12 + CURVE_DIAG = 13 + DIAG_STRAIGHT = 14 + S_BEND = 15 + VERY_SMALL_CURVE = 16 + +class RoadPieceType(Enum): + PREVIEW = 0 + PICKUP = 1 + PLACE = 2 + STRAIGHT = 3 + VERY_SMALL_CURVE = 4 + T_INTERSECTION = 5 + FOUR_WAY_INTERSECTION = 6 + SMALL_CURVE = 7 + SLOPE_SHALLOW = 11 + SLOPE_STEEP = 12 + + +default_manifest = { + "name": "null track type", + "angles": 4, + "symmetric": False, + "render_mirror": False, + "grid_size": [1,1], + "subposition_order": [0], + "track_type": "NULL", + "canvas_size": None, + "track": False, + "road": False +} + +class TrackPieceManifest: + name= "null track type" + angles= 4 + symmetric= False + render_mirror= False + grid_size= [1,1] + subposition_order= [0] + track_type= "NULL" + canvas_size= None + track= False + road= False + + def __init__(self, input): + for key in default_manifest.keys(): + if key in input: + setattr(self, key, input[key]) + if self.canvas_size == None: + self.canvas_size = [max(self.grid_size[0],self.grid_size[1])*64 for _ in range(2)] + +track_piece_mappings = [ + { # Tab icon when building track type + "name": "Spinning Preview", + "angles": 32, + "symmetric": True, + "grid_size": [1,1], + "subposition_order": [0], + "track_type": "UI", + "canvas_size": [29,22], + "track": True, + "road": True + }, + { # vehicle UI icons for picking up and placing vehicle + "name": "Pickup icon", + "angles": 1, + "grid_size": [1,1], + "subposition_order": [0], + "track_type": "UI", + "canvas_size": [20,20], + "track": True, + "road": True + }, + { # vehicle UI icons for picking up and placing vehicle + "name": "Placement icon", + "angles": 1, + "grid_size": [1,1], + "subposition_order": [0], + "track_type": "UI", + "canvas_size": [20,20], + "track": True, + "road": True + }, + { + "name": "Straight", + "track_type": "FLAT", + "symmetric": True, + "track": True, + "road": True + }, + { # Roads put this here in the order + "name": "Very Small Curve", + "track_type": "FLAT", + "grid_size": [1,1], + "subposition_order": [0], + "road": True + }, + { + "name": "T-Intersection", + "track_type": "FLAT", + "grid_size": [1,1], + "subposition_order": [0], + "road": True + }, + { + "name": "4-Way Intersection", + "track_type": "FLAT", + "angles": 1, + "grid_size": [1,1], + "subposition_order": [0], + "road": True + }, + { + "name": "Small Curve", + "track_type": "FLAT", + "grid_size": [2,2], + "subposition_order": [2,3,0,1], + "track": True, + "road": True + }, + { + "name": "Small Curve Gentle Slope", + "track_type": "SLOPE", + "grid_size": [2,2], + "subposition_order": [2,3,0,1], + "track": True + }, + { + "name": "Small Curve Steep Slope", + "track_type": "SLOPE", + "grid_size": [2,2], + "subposition_order": [2,3,0,1], + "track": True + }, + { + "name": "Medium Curve", + "track_type": "FLAT", + "grid_size": [3,3], + "subposition_order": [6,3,4,1,2], + "track": True + }, + { + "name": "Gentle Slope", + "track_type": "SLOPE", + "grid_size": [1,2], + "subposition_order": [1,0], + "track": True, + "road": True + }, + { + "name": "Steep Slope", + "track_type": "SLOPE", + "track": True, + "road": True + }, + { + "name": "Diagonal Curve", + "track_type": "FLAT", + "render_mirror": True, + "grid_size": [2,3], + "subposition_order": [4,2,3,0,1], + "track": True + }, + { + "name": "Diagonal Straight", + "track_type": "FLAT", + "grid_size": [2,2], + "subposition_order": [2,0,3,1], + "track": True + }, + { + "name": "S-Bend", + "track_type": "FLAT", + "symmetric": True, + "grid_size": [2,3], + "subposition_order": [], + "render_mirror": True, + "track": True + }, + { # Railways put it here in the order + "name": "Very Small Curve", + "track_type": "FLAT", + "track": True + } +] + +for i in range(len(track_piece_mappings)): + track_piece_mappings[i] = TrackPieceManifest(track_piece_mappings[i]) \ No newline at end of file diff --git a/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py b/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py index 6d33803..308e20c 100644 --- a/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py +++ b/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py @@ -21,6 +21,8 @@ def __init__(self): self.index = 0 self.offset_x = 0 self.offset_y = 0 + self.flags = 0 + self.zoomOffset = 0 # Frame processor for masking, dithering and cropping the final image diff --git a/loco-graphics-helper/properties/file_versioning.py b/loco-graphics-helper/properties/file_versioning.py index 6244bc6..d693850 100644 --- a/loco-graphics-helper/properties/file_versioning.py +++ b/loco-graphics-helper/properties/file_versioning.py @@ -15,7 +15,7 @@ # each function updates the file to the version in the name # if only new features were added, copy the format of version0 -current_file_version = 4 +current_file_version = 5 def getAllComponents(): return [x for x in bpy.context.scene.objects if x.loco_graphics_helper_object_properties.object_type != 'NONE'] @@ -49,6 +49,11 @@ def version3(): # add specific Y-offsets per entity type def version4(): bpy.context.scene.loco_graphics_helper_general_properties.y_offset += 17 + + # plugin version 0.2.0 + # add track rendering + def version5(): + pass update_functions = [getattr(FileVersionUpdater, func) for func in dir(FileVersionUpdater) if callable(getattr(FileVersionUpdater, func)) and not func.startswith("__")] diff --git a/loco-graphics-helper/properties/object_properties.py b/loco-graphics-helper/properties/object_properties.py index 1a05b70..ed75715 100644 --- a/loco-graphics-helper/properties/object_properties.py +++ b/loco-graphics-helper/properties/object_properties.py @@ -27,6 +27,12 @@ def object_type_update_func(self, context): props.flat_viewing_angles = "64" props.sloped_viewing_angles = "32" + # Reset to default for track pieces + if object.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE": + props = object.loco_graphics_helper_track_piece_properties + props.flat_viewing_angles = "64" + props.sloped_viewing_angles = "32" + class ObjectProperties(bpy.types.PropertyGroup): object_type = bpy.props.EnumProperty( @@ -38,6 +44,7 @@ class ObjectProperties(bpy.types.PropertyGroup): ("CARGO", "Cargo", "", 3), ("CAR", "Car", "", 4), ("ANIMATION", "Animation position", "", 5), + ("TRACK_PIECE","Track piece","",6), ), default="NONE", update=object_type_update_func diff --git a/loco-graphics-helper/properties/track_properties.py b/loco-graphics-helper/properties/track_properties.py index 0aa208c..118d2eb 100644 --- a/loco-graphics-helper/properties/track_properties.py +++ b/loco-graphics-helper/properties/track_properties.py @@ -1,25 +1,69 @@ ''' -Copyright (c) 2022 RCT Graphics Helper developers +Copyright (c) 2025 Loco Graphics Helper developers For a complete list of all authors, please refer to the addon's meta info. -Interested in contributing? Visit https://github.com/oli414/Blender-RCT-Graphics +Interested in contributing? Visit https://github.com/OpenLoco/Blender-Loco-Graphics -RCT Graphics Helper is licensed under the GNU General Public License version 3. +Loco Graphics Helper is licensed under the GNU General Public License version 3. ''' import bpy import math import os - from ..builders.task_builder import TaskBuilder from ..operators.render_operator import RCTRender +def object_type_update_func(self, context): + object = context.object + props = object.loco_graphics_helper_track_properties + type = props.track_type + if type == "TRACK": + props.layers_flat = 3 + props.layers_slope = 1 + if type == "ROAD": + props.layers_flat = 1 + props.layers_slope = 1 + if type == "TRAM": + props.layers_flat = 3 + props.layers_slope = 3 + + class TrackProperties(bpy.types.PropertyGroup): - placeholder = bpy.props.BoolProperty( - name="Placeholder", - description="Test.", - default=False) + track_type = bpy.props.EnumProperty( + name="track_type", + items=( + ("TRACK", "Railway", "", 0), + ("ROAD", "Road", "", 1), + ("TRAM", "Tramway", "", 2), + ), + default="TRACK", + update=object_type_update_func + ) + layers_flat = bpy.props.IntProperty( + name="layers_flat", + default = 3) + layers_slope = bpy.props.IntProperty( + name="layers_slope", + default = 3) + one_way = bpy.props.BoolProperty( + name="one_way", + description="Models for both directions required", + default = False) + + # for determining how many layers each track piece has + def get_num_layers(self, track_type): + if track_type == "FLAT": + return self.layers_flat + if track_type == "SLOPE": + return self.layers_slope + return 1 + + # for determining which track pieces to render + def get_object_type(self): + if track_type == "TRAM": + return "ROAD" + return track_type def register_track_properties(): diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index b030ec4..b663a58 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -226,16 +226,16 @@ def draw_track_panel(self, scene, layout): #row = layout.row() #row.operator("render.loco_track", text="Generate Splines") - # - #row = layout.row() - #row.prop(properties, "placeholder") + + row = layout.row() + row.prop(properties, "track_type") # - #if "Rig" in context.scene.objects: - # row = layout.row() - # text = "Render" - # if general_properties.rendering: - # text = "Failed" - # row.operator("render.loco_track", text=text) + if "Rig" in context.scene.objects: + row = layout.row() + text = "Render" + if general_properties.rendering: + text = "Failed" + row.operator("render.loco_track", text=text) @staticmethod def blender_to_loco_dist(dist): diff --git a/loco-graphics-helper/renderer.py b/loco-graphics-helper/renderer.py index 34b0197..00f8d1d 100644 --- a/loco-graphics-helper/renderer.py +++ b/loco-graphics-helper/renderer.py @@ -38,7 +38,7 @@ def __init__(self, context, palette_manager): self.context = context self.magick_path = "magick" - self.floyd_steinberg_diffusion = 5 + self.floyd_steinberg_diffusion = 35 self.palette_manager = palette_manager diff --git a/loco-graphics-helper/res/palettes/base_loco_palette.png b/loco-graphics-helper/res/palettes/base_loco_palette.png index 21380ed345e69a6fc3a767ce9ee00c35b9c982cd..0c2594b2f8dc84d994942dc3552d452907188396 100644 GIT binary patch delta 260 zcmcb@*}|pR8Q|y6%O%Cdz`(%k>ERLtq~8KDBL_2(WEH*XGEvc<@zlmdcP15k2I+JL z<7#gEesTZB^6}f%^KV=CzmH!1xqSEc>8F2hzyJUG`^hVql36z7E$5ib&uq!b%*mo* zdisL+)5(F%^4vc?T^vI=t~0)45S(1i{HXrnqsLF4K70P+<*V0k7`(RS+ACyq-2>XH zTH+c}l9E`GYL#4+3Zxi}42(>54UB;(#K^$P(Adh*MBBi?%D}*Gr+OEPhTQy=%(P0} W8vLKkHv?*5@O1TaVOi&t&;$VHHdaai delta 210 zcmZqSy27d08Q|y6%O%Cdz`(%k>ERLtq#pt?BL_2(l-IO8HBr%?aoNU1cc#f_n37qh zZjG8b*_hdqlSxQK%R-SexMgxFvpn}(PZ!4!j_Zt%7-T2UXMR-95VJI?(7?IoGSCp! z64!{5l*E!$tK_0oAjM#0U}UOmV5Dnc7-C>xWolw&WT_2g7#J9x@Hm2^AvZrIGp!Q0 V2Ir|CDu5aoJYD@<);T3K0RXajIK2P> diff --git a/loco-graphics-helper/res/palettes/custom_palette.bmp b/loco-graphics-helper/res/palettes/custom_palette.bmp index 5ea8d167dc73f4f80cd626a919d81f68810a73a4..33a1a1645868ac1c110d5c58b3e5e2b726c4f5e1 100644 GIT binary patch delta 96 zcmdnT_>NK8$@d)t1atwZ8X%SgVn#4lU|`9HzZ_kX2l o(*Ma3W&fAwbpPMhHvj*z`J4Y=-Ff`~lS_C0e|huaKTsb70KSHv38_Ht^hXn~E&~8U^aoo2 From 4bada1ef787041192f64859e73814cc09f07b5f2 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Mon, 10 Mar 2025 23:15:38 -0700 Subject: [PATCH 09/14] woohoo it works --- loco-graphics-helper/__init__.py | 2 +- loco-graphics-helper/angle_sections/track.py | 269 +++++++++++------- loco-graphics-helper/builders/task_builder.py | 67 +++-- loco-graphics-helper/frame.py | 97 ++++++- .../loco_object_helper_panel.py | 108 +++++-- .../operators/track_render_operator.py | 66 ++++- .../frame_processors/post_processor.py | 7 +- .../sprites_manifest_processor.py | 22 +- .../properties/object_properties.py | 1 + .../properties/track_properties.py | 42 +-- .../rct_graphics_helper_panel.py | 61 +++- .../res/palettes/custom_palette.bmp | Bin 238 -> 190 bytes .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes loco-graphics-helper/vehicle.py | 2 +- 14 files changed, 535 insertions(+), 209 deletions(-) diff --git a/loco-graphics-helper/__init__.py b/loco-graphics-helper/__init__.py index cc1bbd3..4557797 100644 --- a/loco-graphics-helper/__init__.py +++ b/loco-graphics-helper/__init__.py @@ -27,7 +27,7 @@ "name": "Loco Graphics Helper", "description": "Render tool to replicate Locomotion graphics (based on RCT Graphics Helper)", "author": "Olivier Wervers & OpenLoco Team", - "version": (0, 1, 10), + "version": (0, 2, 0), "blender": (2, 79, 0), "location": "Render", "support": "COMMUNITY", diff --git a/loco-graphics-helper/angle_sections/track.py b/loco-graphics-helper/angle_sections/track.py index c73b735..b59c64c 100644 --- a/loco-graphics-helper/angle_sections/track.py +++ b/loco-graphics-helper/angle_sections/track.py @@ -49,202 +49,275 @@ ] """ +max_layers = 3 + +track_layer_names = [ + ("Spinning Icon","Icon used for tab icon in track building and vehicle purchase windows"), + ("Pickup Icon", "Icon for pick up vehicle button"), + ("Placement Icon", "Icon for place vehicle button"), + ("Ballast","Ballast is drawn above surface and below all other layers"), + ("Ties","Ties is drawn above ballast and below rail and road layer"), + ("Rails", "Rails is drawn above ties and road layer"), + ("Combined", "") +] + +road_layer_names = [ + ("Spinning Icon","Icon used for tab icon in track building and vehicle purchase windows"), + ("Pickup Icon", "Icon for pick up vehicle button"), + ("Placement Icon", "Icon for place vehicle button"), + ("Road", "Road is drawn above ties and below rails layer"), + ("Road", "Road is drawn above ties and below rails layer"), + ("Road", "Road is drawn above ties and below rails layer"), + ("Road", "Road is drawn above ties and below rails layer") +] + +class TrackType(Enum): + RAIL = 0 + ROAD = 1 + TRAM = 2 + # these are not the OpenLoco track piece IDs. These are the index of the piece in the manifest +# the enum names and number of enum items must be the same between the two +# Values of -1 do not show up in blender enum properties. This is used to filter invalid entries class TrackPieceType(Enum): - PREVIEW = 0 - PICKUP = 1 - PLACE = 2 - STRAIGHT = 3 - CURVE_SMALL = 7 - SMALL_CURVE_SLOPE_SHALLOW = 8 - SMALL_CURVE_SLOPE_STEEP = 9 - CURVE = 10 - SLOPE_SHALLOW = 11 - SLOPE_STEEP = 12 - CURVE_DIAG = 13 - DIAG_STRAIGHT = 14 - S_BEND = 15 - VERY_SMALL_CURVE = 16 + NONE = 0 + PREVIEW = 1 + PICKUP = 2 + PLACE = 3 + STRAIGHT = 4 + ROAD_CURVE_VERY_SMALL = -1 + T_INTERSECTION = -1 + FOUR_WAY_INTERSECTION = -1 + CURVE_SMALL = 8 + CURVE_SMALL_SLOPE_SHALLOW_UP = 9 + CURVE_SMALL_SLOPE_SHALLOW_DOWN = 10 + CURVE_SMALL_SLOPE_STEEP_UP = 11 + CURVE_SMALL_SLOPE_STEEP_DOWN = 12 + CURVE = 13 + SLOPE_SHALLOW = 14 + SLOPE_STEEP = 15 + CURVE_DIAG = 16 + DIAG_STRAIGHT = 17 + S_BEND = 18 + TRACK_CURVE_VERY_SMALL = 19 class RoadPieceType(Enum): - PREVIEW = 0 - PICKUP = 1 - PLACE = 2 - STRAIGHT = 3 - VERY_SMALL_CURVE = 4 - T_INTERSECTION = 5 - FOUR_WAY_INTERSECTION = 6 - SMALL_CURVE = 7 - SLOPE_SHALLOW = 11 - SLOPE_STEEP = 12 - + NONE = 0 + PREVIEW = 1 + PICKUP = 2 + PLACE = 3 + STRAIGHT = 4 + ROAD_CURVE_VERY_SMALL = 5 + T_INTERSECTION = 6 + FOUR_WAY_INTERSECTION = 7 + CURVE_SMALL = 8 + CURVE_SMALL_SLOPE_SHALLOW_UP = -1 + CURVE_SMALL_SLOPE_SHALLOW_DOWN = -1 + CURVE_SMALL_SLOPE_STEEP_UP = -1 + CURVE_SMALL_SLOPE_STEEP_DOWN = -1 + CURVE = -1 + SLOPE_SHALLOW = 14 + SLOPE_STEEP = 15 + CURVE_DIAG = -1 + DIAG_STRAIGHT = -1 + S_BEND = -1 + TRACK_CURVE_VERY_SMALL = -1 default_manifest = { - "name": "null track type", + "name": "default track type", "angles": 4, "symmetric": False, "render_mirror": False, "grid_size": [1,1], "subposition_order": [0], - "track_type": "NULL", + "subposition_y_offset": None, + "sprite_type": "NULL", "canvas_size": None, - "track": False, - "road": False + "camera_world_offset": [0.5, 0 , 0], # tile X, tile Y, smallZ + "base_layer_name": 3 } class TrackPieceManifest: + track_piece = 0 name= "null track type" - angles= 4 - symmetric= False - render_mirror= False - grid_size= [1,1] - subposition_order= [0] - track_type= "NULL" - canvas_size= None - track= False - road= False + angles = 4 + symmetric = False + render_mirror = False + grid_size= [1,1] + subposition_order = [0] + subposition_y_offset = [0] + sprite_type = "NULL" + canvas_size = None + camera_world_offset = [0,0,0] + base_layer_name = 3 - def __init__(self, input): + def __init__(self, input, piece): + self.track_piece = piece for key in default_manifest.keys(): if key in input: setattr(self, key, input[key]) if self.canvas_size == None: self.canvas_size = [max(self.grid_size[0],self.grid_size[1])*64 for _ in range(2)] + if self.subposition_y_offset == None: + self.subposition_y_offset = [0 for _ in range(len(self.subposition_order))] + def get_sprites_per_layer(self): + sprites = self.angles / (2 if self.symmetric else 1) + sprites *= len(self.subposition_order) + sprites *= 2 if self.render_mirror else 1 + return int(sprites) + def get_output_order(self): + order = [] + for i in range(self.grid_size[0] * self.grid_size[1]): + if i in self.subposition_order: + order.append(int(self.subposition_order.index(i))) + else: + # a negative number so small no project file would ever hit it + order.append(-99999) + return order -track_piece_mappings = [ +track_piece_manifest = [ + { + "angles": 0, + "name": "None" + }, { # Tab icon when building track type "name": "Spinning Preview", "angles": 32, "symmetric": True, "grid_size": [1,1], "subposition_order": [0], - "track_type": "UI", + "sprite_type": "UI", "canvas_size": [29,22], - "track": True, - "road": True + "camera_world_offset": [0, 0, 0], + "base_layer_name": 0 }, { # vehicle UI icons for picking up and placing vehicle "name": "Pickup icon", "angles": 1, "grid_size": [1,1], "subposition_order": [0], - "track_type": "UI", + "sprite_type": "UI", "canvas_size": [20,20], - "track": True, - "road": True + "camera_world_offset": [0, 0, 0], + "base_layer_name": 1 }, { # vehicle UI icons for picking up and placing vehicle "name": "Placement icon", "angles": 1, "grid_size": [1,1], "subposition_order": [0], - "track_type": "UI", + "sprite_type": "UI", "canvas_size": [20,20], - "track": True, - "road": True + "camera_world_offset": [0, 0, 0], + "base_layer_name": 2 }, { "name": "Straight", - "track_type": "FLAT", - "symmetric": True, - "track": True, - "road": True + "sprite_type": "FLAT", + "symmetric": True }, { # Roads put this here in the order "name": "Very Small Curve", - "track_type": "FLAT", - "grid_size": [1,1], - "subposition_order": [0], - "road": True + "sprite_type": "FLAT" }, { "name": "T-Intersection", - "track_type": "FLAT", - "grid_size": [1,1], - "subposition_order": [0], - "road": True + "sprite_type": "FLAT" }, { "name": "4-Way Intersection", - "track_type": "FLAT", - "angles": 1, - "grid_size": [1,1], - "subposition_order": [0], - "road": True + "sprite_type": "FLAT", + "angles": 1 }, { "name": "Small Curve", - "track_type": "FLAT", + "sprite_type": "FLAT", + "grid_size": [2,2], + "subposition_order": [2,0,3,1], + "camera_world_offset": [1, -0.5, 0] + }, + { + "name": "Small Curve Gentle Slope Up", + "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,3,0,1], - "track": True, - "road": True + "subposition_order": [2,0,3,1], + "camera_world_offset": [1, -0.5, 0], + "base_layer_name": 6 }, { - "name": "Small Curve Gentle Slope", - "track_type": "SLOPE", + "name": "Small Curve Gentle Slope Down", + "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,3,0,1], - "track": True + "subposition_order": [2,0,3,1], + "camera_world_offset": [1, -0.5, 0], + "base_layer_name": 6 }, { - "name": "Small Curve Steep Slope", - "track_type": "SLOPE", + "name": "Small Curve Steep Slope Up", + "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,3,0,1], - "track": True + "subposition_order": [2,0,3,1], + "camera_world_offset": [1, -0.5, 0], + "base_layer_name": 6 + }, + { + "name": "Small Curve Steep Slope Down", + "sprite_type": "SLOPE", + "grid_size": [2,2], + "subposition_order": [2,0,3,1], + "camera_world_offset": [1, -0.5, 0], + "base_layer_name": 6 }, { "name": "Medium Curve", - "track_type": "FLAT", + "sprite_type": "FLAT", "grid_size": [3,3], "subposition_order": [6,3,4,1,2], - "track": True + "camera_world_offset": [1.5, -1, 0] }, { "name": "Gentle Slope", - "track_type": "SLOPE", + "sprite_type": "SLOPE", "grid_size": [1,2], "subposition_order": [1,0], - "track": True, - "road": True + "camera_world_offset": [1, 0, 0], + "base_layer_name": 6 }, { "name": "Steep Slope", - "track_type": "SLOPE", - "track": True, - "road": True + "sprite_type": "SLOPE", + "base_layer_name": 6 }, { "name": "Diagonal Curve", - "track_type": "FLAT", + "sprite_type": "FLAT", "render_mirror": True, "grid_size": [2,3], "subposition_order": [4,2,3,0,1], - "track": True + "camera_world_offset": [1.5, -0.5, 0] }, { "name": "Diagonal Straight", - "track_type": "FLAT", + "symmetric": True, + "sprite_type": "FLAT", "grid_size": [2,2], "subposition_order": [2,0,3,1], - "track": True + "camera_world_offset": [0.5, -.5, 0] }, { "name": "S-Bend", - "track_type": "FLAT", + "sprite_type": "FLAT", "symmetric": True, - "grid_size": [2,3], - "subposition_order": [], "render_mirror": True, - "track": True + "grid_size": [2,3], + "subposition_order": [5, 3, 2, 0], + "camera_world_offset": [1.5, 0.5, 0] }, { # Railways put it here in the order "name": "Very Small Curve", - "track_type": "FLAT", - "track": True + "sprite_type": "FLAT" } ] -for i in range(len(track_piece_mappings)): - track_piece_mappings[i] = TrackPieceManifest(track_piece_mappings[i]) \ No newline at end of file +for i in range(len(track_piece_manifest)): + track_piece_manifest[i] = TrackPieceManifest(track_piece_manifest[i], i) \ No newline at end of file diff --git a/loco-graphics-helper/builders/task_builder.py b/loco-graphics-helper/builders/task_builder.py index ac9c0b9..c5e7c18 100644 --- a/loco-graphics-helper/builders/task_builder.py +++ b/loco-graphics-helper/builders/task_builder.py @@ -45,6 +45,9 @@ def __init__(self): self.use_anti_aliasing = True self.anti_alias_with_background = False self.maintain_aliased_silhouette = True + self.mirror_x = False + + self.target_object = None self.output_index = 0 @@ -58,7 +61,10 @@ def __init__(self): self.offset_x = 0 self.offset_y = 0 + self.output_flags = 0 + self.output_zoomOffset = 0 + self.output_prefix = "Sprite" self.occlusion_layers = 0 self.task = RenderTask(None) @@ -87,6 +93,11 @@ def add_frame(self, frame_index, number_of_viewing_angles, angle_index, animatio frame.set_base_palette(self.palette) + frame.output_prefix = self.output_prefix + frame.set_output_flags(self.output_flags) + frame.set_output_zoomOffset(self.output_zoomOffset) + frame.set_mirror_x(self.mirror_x) + frame.set_anti_aliasing_with_background( self.use_anti_aliasing, self.anti_alias_with_background, self.maintain_aliased_silhouette) @@ -103,7 +114,7 @@ def add_null_frames(self, number): self.output_index += number # Adds render angles for the given number of viewing angles relative to the currently configured rotation - def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, animation_frames=1, rotational_symmetry=False): + def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, animation_frames=1, rotational_symmetry=False, oversize_order = "SCENERY"): start_output_index = self.output_index @@ -111,12 +122,13 @@ def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, number_of_viewing_angles = int(number_of_viewing_angles / 2) rotation_range = 180 if rotational_symmetry else 360 + frames = 0 + for viewing_angle_index in range(number_of_viewing_angles): + for animation_frame in range(animation_frames): + num_sprites = 1 + angle = rotation_range / number_of_viewing_angles * viewing_angle_index - for i in range(number_of_viewing_angles): - for j in range(animation_frames): - angle = rotation_range / number_of_viewing_angles * i - - frame_index = start_output_index + i * animation_frames + j + frame_index = start_output_index + viewing_angle_index * animation_frames + animation_frame frame = Frame(frame_index, self.task, angle + self.view_angle, self.bank_angle, self.vertical_angle, self.mid_angle) frame.set_multi_tile_size(self.width, self.length, self.invert_tile_positions) @@ -128,13 +140,20 @@ def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, frame.set_cast_shadows(self.cast_shadows) frame.set_layer(self.layer) + + frame.set_target_object(self.target_object) frame.set_base_palette(self.palette) + + frame.output_prefix = self.output_prefix + frame.set_output_flags(self.output_flags) + frame.set_output_zoomOffset(self.output_zoomOffset) + frame.set_mirror_x(self.mirror_x) frame.set_anti_aliasing_with_background( self.use_anti_aliasing, self.anti_alias_with_background, self.maintain_aliased_silhouette) - frame.animation_frame_index = animation_frame_index + j + frame.animation_frame_index = animation_frame_index + animation_frame frame.set_occlusion_layers(self.occlusion_layers) @@ -144,27 +163,25 @@ def add_viewing_angles(self, number_of_viewing_angles, animation_frame_index=0, output_indices = [] for k in range(self.occlusion_layers): output_indices.append( - start_output_index + k * animation_frames * number_of_viewing_angles + j * number_of_viewing_angles + i) + start_output_index + k * animation_frames * number_of_viewing_angles + animation_frame * number_of_viewing_angles + viewing_angle_index) frame.set_output_indices(output_indices) + num_sprites = self.occlusion_layers if frame.oversized: - output_indices = [] - for k in range(frame.width * frame.length): - tile_index = k - if frame.invert_tile_positions: - tile_index = (frame.width * frame.length - k - 1) - output_indices.append( - start_output_index + tile_index * animation_frames * number_of_viewing_angles + j * number_of_viewing_angles + i) - - frame.set_output_indices(output_indices) - + if oversize_order == "SCENERY": + num_sprites = frame.oversize_order_scenery(start_output_index, number_of_viewing_angles, viewing_angle_index, animation_frames, animation_frame) + elif oversize_order == "TRACK": + num_sprites = frame.oversize_order_track(start_output_index, number_of_viewing_angles, viewing_angle_index, animation_frames, animation_frame) + else: + raise Exception("Invalid oversize order type") + frames += num_sprites self.angles.append(frame) - + """ frames = number_of_viewing_angles * \ animation_frames * self.width * self.length if self.occlusion_layers > 0: frames *= self.occlusion_layers - + """ self.output_index += frames # Sets the number of recolorable materials @@ -200,6 +217,9 @@ def set_size(self, width, length, invert_tile_positions): self.length = length self.invert_tile_positions = invert_tile_positions + def set_mirror_scale(self, scale): + self.scale = scale + # Sets the rotation applied to future render angles def set_rotation(self, view_angle, bank_angle=0, vertical_angle=0, mid_angle=0): self.view_angle = view_angle @@ -213,6 +233,7 @@ def reset_rotation(self): self.bank_angle = 0 self.vertical_angle = 0 self.mid_angle = 0 + self.mirror_x = False # Sets the number of occlusion layers def set_occlusion_layers(self, layers): @@ -231,9 +252,11 @@ def clear(self): self.width = 1 self.length = 1 + self.output_flags = 0 + self.output_zoomOffset = 0 self.set_offset(0, 0) - + self.target_object = None self.set_occlusion_layers(0) self.recolorables = 0 @@ -241,3 +264,5 @@ def clear(self): self.task = RenderTask(None) self.reset_rotation() + self.output_prefix = "Sprite" + diff --git a/loco-graphics-helper/frame.py b/loco-graphics-helper/frame.py index 5c02aeb..3d0c369 100644 --- a/loco-graphics-helper/frame.py +++ b/loco-graphics-helper/frame.py @@ -26,7 +26,6 @@ class Frame: def __init__(self, frame_index, task, view_angle, bank_angle=0, vertical_angle=0, mid_angle=0): self.frame_index = frame_index self.output_indices = [frame_index] - self.task = task self.view_angle = view_angle self.bank_angle = bank_angle @@ -52,8 +51,13 @@ def __init__(self, frame_index, task, view_angle, bank_angle=0, vertical_angle=0 self.cast_shadows = True + self.output_prefix = "Sprite" self.offset_x = 0 self.offset_y = 0 + self.output_flags = 0 + self.output_zoomOffset = 0 + self.scale = (1, 1, 1) + self.view_angle_offset = -45 self.base_palette = None @@ -84,15 +88,13 @@ def get_final_output_paths(self): output_paths = [] for output_index in self.output_indices: output_paths.append(os.path.join( - self.task.get_output_folder(), "sprites", "sprite_{}.png".format(output_index))) + self.task.get_output_folder(), "sprites", "{}_{}.png".format(self.output_prefix, int(output_index)))) return output_paths else: - return [os.path.join(self.task.get_output_folder(), "sprites", "sprite_{}.png".format(self.frame_index))] + return [os.path.join(self.task.get_output_folder(), "sprites", "{}_{}.png".format(self.output_prefix,self.frame_index))] - def prepare_scene(self): - object = bpy.data.objects['Rig'] - if object is None: - return + def prepare_scene_vehicle(self): + location = None if not self.target_object is None: for o in bpy.data.scenes[0].objects: if o == self.target_object: @@ -100,23 +102,47 @@ def prepare_scene(self): if o.loco_graphics_helper_object_properties.object_type == 'NONE': continue recursive_hide_children(o,True) - recursive_hide_children(self.target_object,False, self.target_object.loco_graphics_helper_object_properties.object_type) - object.location = self.target_object.matrix_world.translation + location = self.target_object.matrix_world.translation if self.target_object.loco_graphics_helper_vehicle_properties.bounding_box_override: - object.location = self.target_object.loco_graphics_helper_vehicle_properties.bounding_box_override.matrix_world.translation - + location = self.target_object.loco_graphics_helper_vehicle_properties.bounding_box_override.matrix_world.translation # This is a little hacky... if self.layer == 'Top Down Shadow': bpy.data.objects['AirplaneShadowLight'].hide_render = False else: bpy.data.objects['AirplaneShadowLight'].hide_render = True + return location + + def prepare_scene_track(self): + for o in bpy.data.scenes[0].objects: + if o.loco_graphics_helper_object_properties.object_type == 'NONE': + continue + recursive_hide_children(o,True) + if self.target_object is not None: + for o in self.target_object.objects: + recursive_hide_children(o,False) + return self.target_object.location + return [0,0,0] + + def prepare_scene(self): + object = bpy.data.objects['Rig'] + general_properties = bpy.context.scene.loco_graphics_helper_general_properties + render_mode = general_properties.render_mode + if object is None: + return + + if render_mode == "VEHICLE": + object.location = self.prepare_scene_vehicle() + elif render_mode == "TRACK": + object.location = self.prepare_scene_track() + object.rotation_euler = (math.radians(self.bank_angle), math.radians(self.vertical_angle), math.radians(self.mid_angle)) vJoint = object.children[0] - vJoint.rotation_euler = (0, 0, math.radians(self.view_angle - 45)) + vJoint.scale = self.scale + vJoint.rotation_euler = (0, 0, math.radians(self.view_angle + self.view_angle_offset )) def set_anti_aliasing_with_background(self, use_anti_aliasing, anti_alias_with_background, maintain_aliased_silhouette): self.use_anti_aliasing = use_anti_aliasing @@ -164,9 +190,54 @@ def set_output_indices(self, indices): if len(self.output_indices) != self.width * self.length * layers: raise Exception( "The number of output indices does not match the number of expected output sprites for this frame") - + + def oversize_order_scenery(self, start_output_index, number_of_viewing_angles, viewing_angle_index, animation_frames, animation_frame): + output_indices = [] + for k in range(self.width * self.length): + tile_index = k + if self.invert_tile_positions: + tile_index = (self.width * self.length - k - 1) + output_indices.append( + start_output_index + tile_index * animation_frames * number_of_viewing_angles + animation_frame * number_of_viewing_angles + viewing_angle_index) + self.set_output_indices(output_indices) + return self.width * self.length + + def oversize_order_track(self, start_output_index, number_of_viewing_angles, viewing_angle_index, animation_frames, animation_frame): + if self.target_object is None: + self.set_output_indices([start_output_index + viewing_angle_index * animation_frames * self.width * self.length + x for x in range(self.width * self.length)]) + return + offset_order = self.target_object.manifest.get_output_order() + num_sprites = len(self.target_object.manifest.subposition_order) + output_indices = [] + for offset in offset_order: + frame_number = offset + start_output_index + frame_number += viewing_angle_index * num_sprites # do I bother with animation frames? + output_indices.append(frame_number) + self.set_output_indices(output_indices) + return num_sprites + def set_target_object(self, object): self.target_object = object def set_cast_shadows(self, cast_shadows): self.cast_shadows = cast_shadows + + def set_output_flags(self, flags): + self.output_flags = flags + + def set_output_zoomOffset(self, offset): + if offset < 0: + raise Exception("Zoom Offset must be positive") + if offset > self.frame_index: + raise Exception("Zoom Offset may not be larger than the current sprite number") + self.output_zoomOffset = offset + + def set_mirror_x(self, mirror): + if mirror: + self.scale = (-1, 1, 1) + self.view_angle_offset = -90-45 + self.view_angle = -abs(self.view_angle) + else: + self.scale = (1, 1, 1) + self.view_angle_offset = -45 + self.view_angle = abs(self.view_angle) diff --git a/loco-graphics-helper/loco_object_helper_panel.py b/loco-graphics-helper/loco_object_helper_panel.py index 2f777db..1bdf562 100644 --- a/loco-graphics-helper/loco_object_helper_panel.py +++ b/loco-graphics-helper/loco_object_helper_panel.py @@ -8,6 +8,8 @@ ''' import bpy +from .track import is_road, is_rail, TrackPiece, get_layer_names, get_num_layers +from .angle_sections.track import TrackPieceType, RoadPieceType, track_piece_manifest, TrackType class LocoObjectHelperPanel(bpy.types.Panel): bl_label = "Loco Graphics" @@ -25,23 +27,102 @@ def draw(self, context): row.label("Tool is not intialised.") return row.prop(object_properties, "object_type") + object_type = object_properties.object_type - if object_properties.object_type == "BODY": + if object_type == "BODY": self.draw_body_panel(context, layout) - if object_properties.object_type == "BOGIE": + if object_type == "BOGIE": self.draw_bogie_panel(context, layout) - if object_properties.object_type == "CAR": + if object_type == "CAR": self.draw_car_panel(context, layout) + + if object_properties.object_type == "TRACK_PIECE": + self.draw_piece_panel(context, layout) + + if object_type == "TRACK_LAYER": + self.draw_layer_panel(context, layout) - def draw_car_panel(self, context, layout): + @staticmethod + def wrong_render_mode(context, layout, mode): scene = context.scene general_properties = scene.loco_graphics_helper_general_properties + + if general_properties.render_mode != mode: + row = layout.row() + row.label("{} render mode required".format(mode)) + return True + return False + + def draw_piece_panel(self, context, layout): + row = layout.row() + + if self.wrong_render_mode(context, layout, "TRACK"): + return + + track_piece_properties = context.object.loco_graphics_helper_track_piece_properties + if is_road(): + row = layout.row() + row.prop(track_piece_properties,"road_piece") + row = layout.row() + row.prop(track_piece_properties,"reversed") + else: + row = layout.row() + row.prop(track_piece_properties,"track_piece") + + piece_name = track_piece_properties.road_piece if is_road() else track_piece_properties.track_piece + piece_type = RoadPieceType[piece_name] if is_road() else TrackPieceType[piece_name] + + if piece_type.value <= 0: + return + manifest = track_piece_manifest[piece_type.value] + track_piece = TrackPiece(manifest, context.object) + box = layout.box() + box.label("Layers:") + split = box.split(.50) + columns = [split.column(), split.column()] + for i in range(get_num_layers(manifest.sprite_type)): + names = get_layer_names(i + manifest.base_layer_name) + columns[i % 2].row().prop(track_piece_properties, "layers", + index=i, text=names[0]) + box = layout.box() + col = box.column() + for i in range(track_piece.num_layers): + col.label("{} layer:".format(track_piece.layer_names[i])) + if len(track_piece.layer_objects[i]) == 0: + col.label(" No model set. Sprites will be ignored.") + else: + col.label(" "+", ".join([x.name for x in track_piece.layer_objects[i]])) + + def draw_layer_panel(self, context, layout): row = layout.row() - if not general_properties.render_mode == "VEHICLE": - row.label("Vehicle Render Mode Required") + if self.wrong_render_mode(context, layout, "TRACK"): + return + + track_piece_properties = context.object.loco_graphics_helper_track_piece_properties + + parent = context.object.parent + parent_properties = parent.loco_graphics_helper_track_piece_properties + piece_name = parent_properties.road_piece if is_road() else parent_properties.track_piece + piece_type = RoadPieceType[piece_name] if is_road() else TrackPieceType[piece_name] + if piece_type.value <= 0: + return + manifest = track_piece_manifest[piece_type.value] + box = layout.box() + box.label("Layers:") + split = box.split(.50) + columns = [split.column(), split.column()] + for i in range(get_num_layers(manifest.sprite_type)): + names = get_layer_names(i + manifest.base_layer_name) + columns[i % 2].row().prop(track_piece_properties, "layers", + index=i, text=names[0]) + + def draw_car_panel(self, context, layout): + row = layout.row() + + if self.wrong_render_mode(context, layout, "VEHICLE"): return vehicle_properties = context.object.loco_graphics_helper_vehicle_properties @@ -50,12 +131,8 @@ def draw_car_panel(self, context, layout): row = layout.row() def draw_bogie_panel(self, context, layout): - scene = context.scene - general_properties = scene.loco_graphics_helper_general_properties - row = layout.row() - if not general_properties.render_mode == "VEHICLE": - row.label("Vehicle Render Mode Required") + if self.wrong_render_mode(context, layout, "VEHICLE"): return vehicle_properties = context.object.loco_graphics_helper_vehicle_properties @@ -111,12 +188,8 @@ def draw_bogie_panel(self, context, layout): row.prop(vehicle_properties, "bounding_box_override") def draw_body_panel(self, context, layout): - scene = context.scene - general_properties = scene.loco_graphics_helper_general_properties - row = layout.row() - if not general_properties.render_mode == "VEHICLE": - row.label("Vehicle Render Mode Required") + if self.wrong_render_mode(context, layout, "VEHICLE"): return vehicle_properties = context.object.loco_graphics_helper_vehicle_properties @@ -144,8 +217,7 @@ def draw_body_panel(self, context, layout): box = layout.box() - row = box.row() - row.label("Sprites:") + box.label("Sprites:") split = box.split(.50) columns = [split.column(), split.column()] diff --git a/loco-graphics-helper/operators/track_render_operator.py b/loco-graphics-helper/operators/track_render_operator.py index 33e495c..a57369e 100644 --- a/loco-graphics-helper/operators/track_render_operator.py +++ b/loco-graphics-helper/operators/track_render_operator.py @@ -1,10 +1,10 @@ ''' -Copyright (c) 2022 RCT Graphics Helper developers +Copyright (c) 2025 Loco Graphics Helper developers For a complete list of all authors, please refer to the addon's meta info. -Interested in contributing? Visit https://github.com/oli414/Blender-RCT-Graphics +Interested in contributing? Visit https://github.com/OpenLoco/Blender-Loco-Graphics -RCT Graphics Helper is licensed under the GNU General Public License version 3. +Loco Graphics Helper is licensed under the GNU General Public License version 3. ''' import bpy @@ -12,8 +12,15 @@ import os from .render_operator import RCTRender +from ..track import get_valid_pieces, get_track_pieces +class TrackPieceLayer(): + def __init__(self, location, objects, manifest): + self.location = location + self.manifest = manifest + self.objects = objects + class RenderTrack(RCTRender, bpy.types.Operator): bl_idname = "render.loco_track" bl_label = "Render Loco Track" @@ -22,7 +29,56 @@ def create_task(self, context): scene = context.scene props = scene.loco_graphics_helper_track_properties general_props = scene.loco_graphics_helper_general_properties - - # Create the list of frames with our parameters + self.task_builder.clear() + + self.task_builder.set_anti_aliasing_with_background( + context.scene.render.use_antialiasing, general_props.anti_alias_with_background, general_props.maintain_aliased_silhouette) + + self.task_builder.set_output_index(general_props.out_start_index) + self.task_builder.set_recolorables(general_props.number_of_recolorables) + self.task_builder.set_cast_shadows(general_props.cast_shadows) + + self.task_builder.set_palette(self.palette_manager.get_base_palette( + general_props.palette, general_props.number_of_recolorables, "FULL")) + + valid_pieces = get_valid_pieces() + + track_pieces = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE" and not x.loco_graphics_helper_track_piece_properties.reversed] + + ndot_pieces = get_track_pieces(track_pieces) + for i in valid_pieces: + self.add_track_piece(ndot_pieces[i]) + + if props.one_way: + reverse_pieces = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE" and x.loco_graphics_helper_track_piece_properties.reversed] + rdot_pieces = get_track_pieces(reverse_pieces) + for i in valid_pieces: + self.add_track_piece(rdot_pieces[i]) + return self.task_builder.create_task(context) + + def add_track_piece(self, track_piece): + num_frames = track_piece.manifest.get_sprites_per_layer() * track_piece.num_layers + if track_piece.render_sprite: + for i in range(track_piece.num_layers): + self.add_layer(track_piece, i) + else: + self.task_builder.output_flags = 0 + self.task_builder.add_null_frames(num_frames) + + def add_layer(self, track_piece, layer): + """track_piece.location""" + manifest = track_piece.manifest + target_object = TrackPieceLayer([0,0,0],track_piece.layer_objects[layer], manifest) + self.task_builder.output_flags = 1 + # self.task_builder.output_prefix = "{}_{}".format(manifest.name, track_piece.layer_names[layer]) + self.task_builder.target_object = target_object + self.task_builder.mirror_x = False + self.task_builder.set_size(manifest.grid_size[0], manifest.grid_size[1], False) + self.task_builder.add_viewing_angles(manifest.angles, 0, 1, manifest.symmetric, "TRACK") + + if manifest.render_mirror: + self.task_builder.mirror_x = True + self.task_builder.add_viewing_angles(manifest.angles, 0, 1, manifest.symmetric, "TRACK") + \ No newline at end of file diff --git a/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py b/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py index 308e20c..2c30450 100644 --- a/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py +++ b/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py @@ -113,6 +113,8 @@ def _process_default(self, magick_command, frame): output_info.offset_x += frame.offset_x output_info.offset_y += frame.offset_y + output_info.flags = frame.output_flags + output_info.zoomOffset = frame.output_zoomOffset frame.task.output_info.append(output_info) @@ -131,6 +133,8 @@ def _process_oversized(self, magick_command, frame): for j in range(frame.length): tile_index = j * frame.width + i final_output_index = frame.output_indices[tile_index] + if final_output_index < 0: + continue final_output_path = frame.get_final_output_paths()[tile_index] tile_magic_command = MagickCommand(quantized_output_path) @@ -154,7 +158,6 @@ def _process_oversized(self, magick_command, frame): 2), (j - (frame.length - 1) / 2) rot = round(frame.view_angle / 90) % 4 - if rot == 1: x, y = (-y, x) if rot == 2: @@ -173,6 +176,8 @@ def _process_oversized(self, magick_command, frame): output_info.offset_x += frame.offset_x output_info.offset_y += frame.offset_y + output_info.flags = frame.output_flags + output_info.zoomOffset = frame.output_zoomOffset output_infos.append(output_info) diff --git a/loco-graphics-helper/processors/sub_processes/sprites_manifest_processor.py b/loco-graphics-helper/processors/sub_processes/sprites_manifest_processor.py index e29d4bf..7459835 100644 --- a/loco-graphics-helper/processors/sub_processes/sprites_manifest_processor.py +++ b/loco-graphics-helper/processors/sub_processes/sprites_manifest_processor.py @@ -30,24 +30,40 @@ def process(self, master_context, callback=None): file_path = os.path.join( task.get_output_folder(), "sprites.json") - output_info_list = task.output_info + def frame_filter(a): + return a.index >= 0 and a.offset_x >= -128 and a.offset_x <= 127 and a.offset_y >= -128 and a.offset_y < 127 + + output_info_list = [a for a in task.output_info if frame_filter(a)] def get_index(output_info): return output_info.index output_info_list.sort(key=get_index) + + images = [] + if os.path.exists(file_path): + try: + with open(file_path, "r") as images_file: + images = json.loads(images_file.read(), + object_pairs_hook=OrderedDict) + images_file.close() + except Exception as e: + print("Error when reading sprites.json",e) with open(file_path, "w") as images_file: - images = [] for output_info in output_info_list: while len(images) <= output_info.index: - images.append("") + images.append({}) image_dict = OrderedDict() image_dict["path"] = "sprites/" + \ os.path.basename(output_info.path) image_dict["x"] = output_info.offset_x image_dict["y"] = output_info.offset_y + if output_info.flags != 0: + image_dict["flags"] = output_info.flags + if output_info.zoomOffset != 0: + image_dict["zoomSprite"] = output_info.zoomSprite images[output_info.index] = image_dict diff --git a/loco-graphics-helper/properties/object_properties.py b/loco-graphics-helper/properties/object_properties.py index ed75715..1f61b33 100644 --- a/loco-graphics-helper/properties/object_properties.py +++ b/loco-graphics-helper/properties/object_properties.py @@ -45,6 +45,7 @@ class ObjectProperties(bpy.types.PropertyGroup): ("CAR", "Car", "", 4), ("ANIMATION", "Animation position", "", 5), ("TRACK_PIECE","Track piece","",6), + ("TRACK_LAYER","Track layer","",7), ), default="NONE", update=object_type_update_func diff --git a/loco-graphics-helper/properties/track_properties.py b/loco-graphics-helper/properties/track_properties.py index 118d2eb..0012291 100644 --- a/loco-graphics-helper/properties/track_properties.py +++ b/loco-graphics-helper/properties/track_properties.py @@ -14,57 +14,21 @@ from ..operators.render_operator import RCTRender -def object_type_update_func(self, context): - object = context.object - props = object.loco_graphics_helper_track_properties - type = props.track_type - if type == "TRACK": - props.layers_flat = 3 - props.layers_slope = 1 - if type == "ROAD": - props.layers_flat = 1 - props.layers_slope = 1 - if type == "TRAM": - props.layers_flat = 3 - props.layers_slope = 3 - - class TrackProperties(bpy.types.PropertyGroup): track_type = bpy.props.EnumProperty( - name="track_type", + name="Track Type", items=( - ("TRACK", "Railway", "", 0), + ("RAIL", "Railway", "", 0), ("ROAD", "Road", "", 1), ("TRAM", "Tramway", "", 2), ), - default="TRACK", - update=object_type_update_func + default="RAIL" ) - layers_flat = bpy.props.IntProperty( - name="layers_flat", - default = 3) - layers_slope = bpy.props.IntProperty( - name="layers_slope", - default = 3) one_way = bpy.props.BoolProperty( name="one_way", description="Models for both directions required", default = False) - # for determining how many layers each track piece has - def get_num_layers(self, track_type): - if track_type == "FLAT": - return self.layers_flat - if track_type == "SLOPE": - return self.layers_slope - return 1 - - # for determining which track pieces to render - def get_object_type(self): - if track_type == "TRAM": - return "ROAD" - return track_type - def register_track_properties(): bpy.types.Scene.loco_graphics_helper_track_properties = bpy.props.PointerProperty( diff --git a/loco-graphics-helper/rct_graphics_helper_panel.py b/loco-graphics-helper/rct_graphics_helper_panel.py index b663a58..cb300b1 100644 --- a/loco-graphics-helper/rct_graphics_helper_panel.py +++ b/loco-graphics-helper/rct_graphics_helper_panel.py @@ -28,6 +28,9 @@ from .vehicle import get_car_components, VehicleComponent, SubComponent, get_number_of_sprites, get_half_width +from .track import is_rail, is_road, get_track_pieces, TrackPiece, get_valid_pieces +from .angle_sections.track import track_piece_manifest + class RepairConfirmOperator(bpy.types.Operator): """This action will clear out the default camera and light. Changes made to the rig object, compositor nodes and recolorable materials will be lost.""" bl_idname = "loco_graphics_helper.repair_confirm" @@ -217,25 +220,65 @@ def draw_walls_panel(self, scene, layout): text = "Failed" row.operator("render.loco_walls", text=text) + @staticmethod + def create_track_box(track_piece, layout): + box = layout.box() + row = box.row() + manifest = track_piece_manifest[track_piece.track_piece] + row.label(manifest.name) + if track_piece.root_object is not None: + row.prop(track_piece.root_object.loco_graphics_helper_track_piece_properties, "render_sprite") + col = box.column() + for i in range(track_piece.num_layers): + col.label("{} layer:".format(track_piece.layer_names[i])) + if len(track_piece.layer_objects[i]) == 0: + col.label(" No model set. Sprites will be blank.") + else: + col.label(" "+", ".join([x.name for x in track_piece.layer_objects[i]])) + def draw_track_panel(self, scene, layout): properties = scene.loco_graphics_helper_track_properties general_properties = scene.loco_graphics_helper_general_properties - - row = layout.row() - row.label("Work in progress") #row = layout.row() #row.operator("render.loco_track", text="Generate Splines") row = layout.row() row.prop(properties, "track_type") -# - if "Rig" in context.scene.objects: + + if is_road(): row = layout.row() - text = "Render" - if general_properties.rendering: - text = "Failed" - row.operator("render.loco_track", text=text) + row.prop(properties,"one_way") + + track_pieces = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE" and not x.loco_graphics_helper_track_piece_properties.reversed] + reverse_pieces = [x for x in scene.objects if x.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE" and x.loco_graphics_helper_track_piece_properties.reversed] + + col = layout.column() + valid_pieces = get_valid_pieces() + ndot_pieces = get_track_pieces(track_pieces) + if is_road(): + col.label("Road pieces:") + box = col.box() + for piece_number in valid_pieces: + self.create_track_box(ndot_pieces[piece_number],box) + if properties.one_way: + col.label("Reverse road pieces:") + rdot_pieces = get_track_pieces(reverse_pieces) + box = col.box() + for piece_number in valid_pieces: + self.create_track_box(rdot_pieces[piece_number],box) + else: + col.label("Track pieces:") + box = col.box() + for piece_number in valid_pieces: + self.create_track_box(ndot_pieces[piece_number],box) + + + row = layout.row() + text = "Render" + if general_properties.rendering: + text = "Failed" + row.operator("render.loco_track", text=text) @staticmethod def blender_to_loco_dist(dist): diff --git a/loco-graphics-helper/res/palettes/custom_palette.bmp b/loco-graphics-helper/res/palettes/custom_palette.bmp index 33a1a1645868ac1c110d5c58b3e5e2b726c4f5e1..5ea8d167dc73f4f80cd626a919d81f68810a73a4 100644 GIT binary patch delta 48 ocmaFIxQ|iU$#)+E1atwZ8X)EcVn#4lU|NK8$@d)t1atwZ8X%SgVn#4lU|`9HzZ_kX2l o(*Ma3W&fAwbpPMhHvj*z`J4Y=-Ff`~lS_C0e|huaKTsb70KS Date: Tue, 11 Mar 2025 02:24:17 -0700 Subject: [PATCH 10/14] get mirror rendering working --- loco-graphics-helper/angle_sections/track.py | 10 +++++----- loco-graphics-helper/frame.py | 8 ++++++-- .../frame_processors/post_processor.py | 17 +++++++++++++---- .../res/palettes/custom_palette.bmp | Bin 190 -> 238 bytes .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/loco-graphics-helper/angle_sections/track.py b/loco-graphics-helper/angle_sections/track.py index b59c64c..b3763c4 100644 --- a/loco-graphics-helper/angle_sections/track.py +++ b/loco-graphics-helper/angle_sections/track.py @@ -233,14 +233,14 @@ def get_output_order(self): "name": "Small Curve", "sprite_type": "FLAT", "grid_size": [2,2], - "subposition_order": [2,0,3,1], + "subposition_order": [2,3,0,1], "camera_world_offset": [1, -0.5, 0] }, { "name": "Small Curve Gentle Slope Up", "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,0,3,1], + "subposition_order": [2,3,0,1], "camera_world_offset": [1, -0.5, 0], "base_layer_name": 6 }, @@ -248,7 +248,7 @@ def get_output_order(self): "name": "Small Curve Gentle Slope Down", "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,0,3,1], + "subposition_order": [2,3,0,1], "camera_world_offset": [1, -0.5, 0], "base_layer_name": 6 }, @@ -256,7 +256,7 @@ def get_output_order(self): "name": "Small Curve Steep Slope Up", "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,0,3,1], + "subposition_order": [2,3,0,1], "camera_world_offset": [1, -0.5, 0], "base_layer_name": 6 }, @@ -264,7 +264,7 @@ def get_output_order(self): "name": "Small Curve Steep Slope Down", "sprite_type": "SLOPE", "grid_size": [2,2], - "subposition_order": [2,0,3,1], + "subposition_order": [2,3,0,1], "camera_world_offset": [1, -0.5, 0], "base_layer_name": 6 }, diff --git a/loco-graphics-helper/frame.py b/loco-graphics-helper/frame.py index 3d0c369..391d5a1 100644 --- a/loco-graphics-helper/frame.py +++ b/loco-graphics-helper/frame.py @@ -57,6 +57,7 @@ def __init__(self, frame_index, task, view_angle, bank_angle=0, vertical_angle=0 self.output_flags = 0 self.output_zoomOffset = 0 self.scale = (1, 1, 1) + self.mirror_x = False self.view_angle_offset = -45 self.base_palette = None @@ -233,11 +234,14 @@ def set_output_zoomOffset(self, offset): self.output_zoomOffset = offset def set_mirror_x(self, mirror): + if mirror == self.mirror_x: + return + self.mirror_x = mirror if mirror: self.scale = (-1, 1, 1) self.view_angle_offset = -90-45 - self.view_angle = -abs(self.view_angle) + self.view_angle = 360-self.view_angle else: self.scale = (1, 1, 1) self.view_angle_offset = -45 - self.view_angle = abs(self.view_angle) + self.view_angle = 360-self.view_angle diff --git a/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py b/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py index 2c30450..386a46e 100644 --- a/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py +++ b/loco-graphics-helper/processors/sub_processes/frame_processors/post_processor.py @@ -154,19 +154,27 @@ def _process_oversized(self, magick_command, frame): result, final_output_index, final_output_path) # Modify the output offsets for the sub tile we're processing - x, y = (i - (frame.width - 1) / - 2), (j - (frame.length - 1) / 2) + x, y = (i - (frame.width - 1) / 2), (j - (frame.length - 1) / 2) rot = round(frame.view_angle / 90) % 4 + + a = output_info.offset_x + b = output_info.offset_y + + if frame.mirror_x: + x = -x + rot = 4 - rot + if rot == 1: x, y = (-y, x) if rot == 2: x, y = (-x, -y) if rot == 3: x, y = (y, -x) - + dx = -int((x * 32) - (y * 32)) - dy = -int((y * 16) + (x * 16)) + dy = -int((x * 16) + (y * 16)) + output_info.offset_x += dx output_info.offset_y += dy @@ -176,6 +184,7 @@ def _process_oversized(self, magick_command, frame): output_info.offset_x += frame.offset_x output_info.offset_y += frame.offset_y + print("FRAME NUMBER {}: Mirrored: {}, rot = {}, x = {}, y = {}, dx = {}, dy = {}, offset_x = {}, offset_y = {}".format(final_output_index,frame.mirror_x, rot, x, y, dx, dy, a, b, output_info.offset_x, output_info.offset_y)) output_info.flags = frame.output_flags output_info.zoomOffset = frame.output_zoomOffset diff --git a/loco-graphics-helper/res/palettes/custom_palette.bmp b/loco-graphics-helper/res/palettes/custom_palette.bmp index 5ea8d167dc73f4f80cd626a919d81f68810a73a4..a71399838b9059978a35df3cc746d756772ed54b 100644 GIT binary patch delta 95 zcmdnT_>NK8$@d)t1atwZ8X%SgVn#4lU|CnsKA1^`1(2k8I+ delta 17 Zcmey(^qXnIId(R7rO%VLt(tgs830Dd2!H?p From 17e2ab9a7a4991adda670f3b04f6d8834a088d63 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 11 Mar 2025 12:23:12 -0700 Subject: [PATCH 11/14] make it easier to set up track pieces --- loco-graphics-helper/__init__.py | 2 +- .../loco_object_helper_panel.py | 43 ++++++++++++------ .../properties/file_versioning.py | 9 +++- .../properties/object_properties.py | 1 - .../res/palettes/base_loco_palette.png | Bin 1284 -> 1284 bytes 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/loco-graphics-helper/__init__.py b/loco-graphics-helper/__init__.py index 4557797..14d7ce6 100644 --- a/loco-graphics-helper/__init__.py +++ b/loco-graphics-helper/__init__.py @@ -27,7 +27,7 @@ "name": "Loco Graphics Helper", "description": "Render tool to replicate Locomotion graphics (based on RCT Graphics Helper)", "author": "Olivier Wervers & OpenLoco Team", - "version": (0, 2, 0), + "version": (0, 2, 1), "blender": (2, 79, 0), "location": "Render", "support": "COMMUNITY", diff --git a/loco-graphics-helper/loco_object_helper_panel.py b/loco-graphics-helper/loco_object_helper_panel.py index 1bdf562..3fc1386 100644 --- a/loco-graphics-helper/loco_object_helper_panel.py +++ b/loco-graphics-helper/loco_object_helper_panel.py @@ -21,13 +21,28 @@ class LocoObjectHelperPanel(bpy.types.Panel): def draw(self, context): layout = self.layout object_properties = context.object.loco_graphics_helper_object_properties + object_type = object_properties.object_type + + # this connects a track layer object to its parent object + track_object = context.object + scene = context.scene + while track_object.parent is not None: + track_object = track_object.parent + if track_object.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE": + break + if track_object == scene or track_object == context.object: + track_object = None row = layout.row() if not "Rig" in context.scene.objects: row.label("Tool is not intialised.") return - row.prop(object_properties, "object_type") - object_type = object_properties.object_type + + # if it's a track layer object, force the type to NONE and don't show the property + if track_object and track_object.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE": + pass + else: + row.prop(object_properties, "object_type") if object_type == "BODY": self.draw_body_panel(context, layout) @@ -37,12 +52,12 @@ def draw(self, context): if object_type == "CAR": self.draw_car_panel(context, layout) - - if object_properties.object_type == "TRACK_PIECE": - self.draw_piece_panel(context, layout) - - if object_type == "TRACK_LAYER": - self.draw_layer_panel(context, layout) + + # if it's a track layer object, show the track properties of the track piece object + if track_object and track_object.loco_graphics_helper_object_properties.object_type == "TRACK_PIECE": + self.draw_piece_panel(context, track_object, layout) + elif object_type == "TRACK_PIECE": + self.draw_piece_panel(context, context.object, layout) @staticmethod def wrong_render_mode(context, layout, mode): @@ -55,13 +70,14 @@ def wrong_render_mode(context, layout, mode): return True return False - def draw_piece_panel(self, context, layout): + def draw_piece_panel(self, context, track_object, layout): row = layout.row() if self.wrong_render_mode(context, layout, "TRACK"): return - track_piece_properties = context.object.loco_graphics_helper_track_piece_properties + # track piece's properties + track_piece_properties = track_object.loco_graphics_helper_track_piece_properties if is_road(): row = layout.row() row.prop(track_piece_properties,"road_piece") @@ -77,21 +93,22 @@ def draw_piece_panel(self, context, layout): if piece_type.value <= 0: return manifest = track_piece_manifest[piece_type.value] - track_piece = TrackPiece(manifest, context.object) + track_piece = TrackPiece(manifest, track_object) box = layout.box() box.label("Layers:") split = box.split(.50) columns = [split.column(), split.column()] for i in range(get_num_layers(manifest.sprite_type)): names = get_layer_names(i + manifest.base_layer_name) - columns[i % 2].row().prop(track_piece_properties, "layers", + # track layer's properties + columns[i % 2].row().prop(context.object.loco_graphics_helper_track_piece_properties, "layers", index=i, text=names[0]) box = layout.box() col = box.column() for i in range(track_piece.num_layers): col.label("{} layer:".format(track_piece.layer_names[i])) if len(track_piece.layer_objects[i]) == 0: - col.label(" No model set. Sprites will be ignored.") + col.label(" No model set. Sprites will be blank.") else: col.label(" "+", ".join([x.name for x in track_piece.layer_objects[i]])) diff --git a/loco-graphics-helper/properties/file_versioning.py b/loco-graphics-helper/properties/file_versioning.py index d693850..06e4a32 100644 --- a/loco-graphics-helper/properties/file_versioning.py +++ b/loco-graphics-helper/properties/file_versioning.py @@ -13,9 +13,9 @@ # Updating a project file to a newer version # each function updates the file to the version in the name -# if only new features were added, copy the format of version0 +# if only new features were added, use pass argument for an empty function -current_file_version = 5 +current_file_version = 6 def getAllComponents(): return [x for x in bpy.context.scene.objects if x.loco_graphics_helper_object_properties.object_type != 'NONE'] @@ -55,6 +55,11 @@ def version4(): def version5(): pass + # plugin version 0.2.1 + # remove TRACK_LAYER object type + def version6(): + pass + update_functions = [getattr(FileVersionUpdater, func) for func in dir(FileVersionUpdater) if callable(getattr(FileVersionUpdater, func)) and not func.startswith("__")] def apply_update(): diff --git a/loco-graphics-helper/properties/object_properties.py b/loco-graphics-helper/properties/object_properties.py index 1f61b33..ed75715 100644 --- a/loco-graphics-helper/properties/object_properties.py +++ b/loco-graphics-helper/properties/object_properties.py @@ -45,7 +45,6 @@ class ObjectProperties(bpy.types.PropertyGroup): ("CAR", "Car", "", 4), ("ANIMATION", "Animation position", "", 5), ("TRACK_PIECE","Track piece","",6), - ("TRACK_LAYER","Track layer","",7), ), default="NONE", update=object_type_update_func diff --git a/loco-graphics-helper/res/palettes/base_loco_palette.png b/loco-graphics-helper/res/palettes/base_loco_palette.png index 0c2594b2f8dc84d994942dc3552d452907188396..b990bd9501d93583b4de9322848cb6ae1843438c 100644 GIT binary patch delta 21 ccmZqSYT??D%gn(oEXLI@9CLefDRVy~06*6T7XSbN delta 21 ccmZqSYT??D%gn*aqG5Xag80+TrOf?|07u#f0RR91 From 7390e938ffde0cfa0e9c02ee4dd186b31e53c704 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 11 Mar 2025 15:35:33 -0700 Subject: [PATCH 12/14] sadly this is uselelss --- loco-graphics-helper/frame.py | 2 +- .../operators/track_render_operator.py | 2 +- .../res/palettes/base_loco_palette.png | Bin 1284 -> 1284 bytes .../res/palettes/custom_palette.bmp | Bin 238 -> 190 bytes .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes 5 files changed, 2 insertions(+), 2 deletions(-) diff --git a/loco-graphics-helper/frame.py b/loco-graphics-helper/frame.py index 391d5a1..e31c8f9 100644 --- a/loco-graphics-helper/frame.py +++ b/loco-graphics-helper/frame.py @@ -124,7 +124,7 @@ def prepare_scene_track(self): for o in self.target_object.objects: recursive_hide_children(o,False) return self.target_object.location - return [0,0,0] + return self.target_object.location def prepare_scene(self): object = bpy.data.objects['Rig'] diff --git a/loco-graphics-helper/operators/track_render_operator.py b/loco-graphics-helper/operators/track_render_operator.py index a57369e..af9391b 100644 --- a/loco-graphics-helper/operators/track_render_operator.py +++ b/loco-graphics-helper/operators/track_render_operator.py @@ -70,7 +70,7 @@ def add_track_piece(self, track_piece): def add_layer(self, track_piece, layer): """track_piece.location""" manifest = track_piece.manifest - target_object = TrackPieceLayer([0,0,0],track_piece.layer_objects[layer], manifest) + target_object = TrackPieceLayer((0,0,0),track_piece.layer_objects[layer], manifest) self.task_builder.output_flags = 1 # self.task_builder.output_prefix = "{}_{}".format(manifest.name, track_piece.layer_names[layer]) self.task_builder.target_object = target_object diff --git a/loco-graphics-helper/res/palettes/base_loco_palette.png b/loco-graphics-helper/res/palettes/base_loco_palette.png index b990bd9501d93583b4de9322848cb6ae1843438c..2f01760dc5b19876ce0f59e5900c22efd03c6cfe 100644 GIT binary patch delta 20 bcmZqSYT??D$ILFmq0qW)Px$6i=6*&1K^X>m delta 20 bcmZqSYT??D$ILD)#?>zzb9-|sb3Y>hJp~2~ diff --git a/loco-graphics-helper/res/palettes/custom_palette.bmp b/loco-graphics-helper/res/palettes/custom_palette.bmp index a71399838b9059978a35df3cc746d756772ed54b..5ea8d167dc73f4f80cd626a919d81f68810a73a4 100644 GIT binary patch delta 47 mcmaFIxQ|iU$#)+E1atwZ8X)EcVn#4lU|NK8$@d)t1atwZ8X%SgVn#4lU|CnsKA1^`1(2k8I+ From afb553b64b97fd91357d1701fa1911c17b736c87 Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 11 Mar 2025 23:11:14 -0700 Subject: [PATCH 13/14] fix some bugs --- .../loco_object_helper_panel.py | 5 +++-- .../res/palettes/recolour_2_loco_palette.png | Bin 379 -> 379 bytes 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/loco-graphics-helper/loco_object_helper_panel.py b/loco-graphics-helper/loco_object_helper_panel.py index 3fc1386..e461b3c 100644 --- a/loco-graphics-helper/loco_object_helper_panel.py +++ b/loco-graphics-helper/loco_object_helper_panel.py @@ -71,7 +71,6 @@ def wrong_render_mode(context, layout, mode): return False def draw_piece_panel(self, context, track_object, layout): - row = layout.row() if self.wrong_render_mode(context, layout, "TRACK"): return @@ -154,6 +153,7 @@ def draw_bogie_panel(self, context, layout): vehicle_properties = context.object.loco_graphics_helper_vehicle_properties + row = layout.row() row.prop(vehicle_properties, "null_component") row = layout.row() @@ -211,12 +211,13 @@ def draw_body_panel(self, context, layout): vehicle_properties = context.object.loco_graphics_helper_vehicle_properties - row.prop(vehicle_properties, "null_component") row = layout.row() + row.prop(vehicle_properties, "null_component") if vehicle_properties.null_component: return + row = layout.row() row.prop(vehicle_properties, "index") row = layout.row() diff --git a/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png b/loco-graphics-helper/res/palettes/recolour_2_loco_palette.png index 1fb9f4a6895739424ae2251f6c1031dabd3f3f36..844cf9c0591a5a5861c543ad657a4fff06cfdc72 100644 GIT binary patch delta 18 acmey(^qXnISq>gHE)#uwSN@4tmjM7jW(Ix$ delta 18 acmey(^qXnISq^S7Dc;?jF7qc|T?PO|9tT?h From a39d71e0120e915e573f5afe5990b67a21995c7e Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Tue, 11 Mar 2025 23:11:41 -0700 Subject: [PATCH 14/14] apparently these were never added whoops --- .../properties/track_piece_properties.py | 49 ++++++++++ loco-graphics-helper/track.py | 90 +++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 loco-graphics-helper/properties/track_piece_properties.py create mode 100644 loco-graphics-helper/track.py diff --git a/loco-graphics-helper/properties/track_piece_properties.py b/loco-graphics-helper/properties/track_piece_properties.py new file mode 100644 index 0000000..def30bf --- /dev/null +++ b/loco-graphics-helper/properties/track_piece_properties.py @@ -0,0 +1,49 @@ +''' +Copyright (c) 2025 Loco Graphics Helper developers + +For a complete list of all authors, please refer to the addon's meta info. +Interested in contributing? Visit https://github.com/OpenLoco/Blender-Loco-Graphics + +Loco Graphics Helper is licensed under the GNU General Public License version 3. +''' + +import bpy +from ..angle_sections.track import track_piece_manifest, TrackPieceType, RoadPieceType, max_layers + +class TrackPieceProperties(bpy.types.PropertyGroup): + track_piece = bpy.props.EnumProperty( + name="Track Piece", + items=tuple((enum_item.name,track_piece_manifest[enum_item.value].name,"",enum_item.value) for enum_item in TrackPieceType), + default="NONE" + ) + road_piece = bpy.props.EnumProperty( + name="Road Piece", + items=tuple((enum_item.name,track_piece_manifest[enum_item.value].name,"",enum_item.value) for enum_item in RoadPieceType), + default="NONE" + ) + + reversed = bpy.props.BoolProperty( + name="Reverse Road", + description="If set, the normal direction of travel is toward the image Southwest. If unset, normal direction of travel is toward the image Northeast, or is two-way", + default=False) + + layers = bpy.props.BoolVectorProperty( + name="Track Layers", + default=[False for _ in range(max_layers)], + description="Which layers this model is rendered as", + size=max_layers + ) + + render_sprite = bpy.props.BoolProperty( + name="Render Track piece", + description="Include this track piece when batch rendering", + default=True + ) + +def register_track_piece_properties(): + bpy.types.Object.loco_graphics_helper_track_piece_properties = bpy.props.PointerProperty( + type=TrackPieceProperties) + + +def unregister_track_piece_properties(): + del bpy.types.Object.loco_graphics_helper_track_piece_properties diff --git a/loco-graphics-helper/track.py b/loco-graphics-helper/track.py new file mode 100644 index 0000000..2da097f --- /dev/null +++ b/loco-graphics-helper/track.py @@ -0,0 +1,90 @@ +''' +Copyright (c) 2025 Loco Graphics Helper developers + +For a complete list of all authors, please refer to the addon's meta info. +Interested in contributing? Visit https://github.com/OpenLoco/Blender-Loco-Graphics + +Loco Graphics Helper is licensed under the GNU General Public License version 3. +''' + +import bpy +from mathutils import Vector +from typing import List +from .angle_sections.track import * + +def track_properties(): + return bpy.context.scene.loco_graphics_helper_track_properties + +def get_num_layers(sprite_type): + if sprite_type == "UI" or sprite_type == "NULL": + return 1 + object_type = TrackType[track_properties().track_type] + map = { + TrackType.RAIL: {"FLAT": 3, "SLOPE": 1}, + TrackType.ROAD: {"FLAT": 1, "SLOPE": 1}, + TrackType.TRAM: {"FLAT": 3, "SLOPE": 3} + } + return map[object_type][sprite_type] + +# used to determine available pieces +def is_road(): + object_type = TrackType[track_properties().track_type] + if object_type == TrackType.TRAM: + return True + return object_type == TrackType.ROAD + +# used to determine layer names +def is_rail(): + object_type = TrackType[track_properties().track_type] + if object_type == TrackType.TRAM: + return True + return object_type == TrackType.RAIL + +def get_layer_names(layer): + if is_rail(): + return track_layer_names[layer] + else: + return road_layer_names[layer] + +class TrackPiece(): + track_piece = 0 + root_object = None + num_layers = 0 + layer_objects = [] + layer_names = [] + manifest = None + render_sprite = False + location = [0,0,0] + def __init__(self, manifest, root_object = None): + self.manifest = manifest + self.track_piece = manifest.track_piece + self.num_layers = get_num_layers(manifest.sprite_type) + self.layer_names = [get_layer_names(manifest.base_layer_name + i)[0] for i in range(self.num_layers)] + self.layer_objects = [[] for i in range(self.num_layers)] + if root_object is not None: + self.set_root_object(root_object) + + def set_root_object(self, object): + self.root_object = object + self.location = object.matrix_world.translation + self.render_sprite = object.loco_graphics_helper_track_piece_properties.render_sprite + objects = object.children + for i in range(self.num_layers): + self.layer_objects[i] = [x for x in objects if x.loco_graphics_helper_track_piece_properties.layers[i]] + if (object.loco_graphics_helper_track_piece_properties.layers[i]): + self.layer_objects[i].append(object) + +def get_valid_pieces(): + piece_list = RoadPieceType if is_road() else TrackPieceType # the enum with the list of pieces + return [a.value for a in piece_list if a.value > 0] + +def get_track_pieces(objects) -> List[TrackPiece]: + pieces = [TrackPiece(a) for a in track_piece_manifest] + piece_type_property = "road_piece" if is_road() else "track_piece" # the property in track_piece_properties + for object in objects: + if piece_type_property in object.loco_graphics_helper_track_piece_properties: + piece_number = object.loco_graphics_helper_track_piece_properties[piece_type_property] + if piece_number > 0: + pieces[piece_number].set_root_object(object) + + return pieces \ No newline at end of file