From 9363fe9edcac9041fe9b473847ad973e55f89194 Mon Sep 17 00:00:00 2001 From: 8-root-grafter Date: Tue, 25 Nov 2025 22:51:48 +0100 Subject: [PATCH 1/2] v0.0.6a - not working version Only on UI side! Thruster and engine sound_event_on split into Sound_on and sound_effect. --- __init__.py | 6 ++++- blender_manifest.toml | 2 +- engine.py | 56 +++++++++++++++++++++++++++++++++++++++---- thruster.py | 52 ++++++++++++++++++++++++++++++++++------ 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/__init__.py b/__init__.py index b540b24..6a54bb8 100644 --- a/__init__.py +++ b/__init__.py @@ -21,6 +21,7 @@ # Import modules with all classes and functions from . import utils from .thruster import ( + OBJECT_PT_thruster_panel_export, ThrusterProperties, OBJECT_OT_add_thruster, OBJECT_PT_thruster_panel, @@ -31,6 +32,7 @@ EngineProperties, OBJECT_OT_add_engine, OBJECT_PT_engine_panel, + OBJECT_PT_engine_panel_export, ) from .export import ( OBJECT_OT_export_ksa_metadata, @@ -47,7 +49,7 @@ "name": "Kitten export", "description": "A Blender addon for exporting spacecraft models to Kitten Space Agency (KSA) format with support for thrusters, engines, meshes, and materials.", "author": "Marcus Zuber", - "version": (0, 0, 5), + "version": (0, 0, 6), "blender": (4, 50, 0), "location": "Add Menu > KSA folder, and File > Export > KSA Part", "category": ["Add Mesh", "Import-Export"], @@ -62,7 +64,9 @@ OBJECT_PT_thruster_panel, OBJECT_PT_thruster_panel_control, OBJECT_PT_thruster_panel_offset, + OBJECT_PT_thruster_panel_export, OBJECT_PT_engine_panel, + OBJECT_PT_engine_panel_export, OBJECT_OT_export_ksa_metadata, OBJECT_OT_export_glb_with_meta, VIEW3D_MT_ksa_add, diff --git a/blender_manifest.toml b/blender_manifest.toml index 471bc7a..ce4b776 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -3,7 +3,7 @@ schema_version = "1.0.0" # Example of manifest file for a Blender extension # Change the values according to your extension id = "kittenExport" -version = "0.0.5" +version = "0.0.6" name = "Kitten Space Agency Exporter" tagline = "Export your models to Kitten Space Agency format" maintainer = "Marcus Zuber" diff --git a/engine.py b/engine.py index 11335b6..90f86e8 100644 --- a/engine.py +++ b/engine.py @@ -74,7 +74,7 @@ def _engine_dict_to_xml_element(parent, engine_data, decimal_places=3): ET.SubElement(engine, 'VolumetricExhaust', Id=exhaust_id) # SoundEvent with Action and SoundId attributes - sound_id = engine_data.get('sound_event_action_on', 'DefaultEngineSoundBehavior') + sound_id = engine_data.get('sound_effect', 'DefaultEngineSoundBehavior') ET.SubElement(engine, 'SoundEvent', Action='On', SoundId=sound_id) @@ -113,17 +113,24 @@ class EngineProperties(bpy.types.PropertyGroup): ) volumetric_exhaust_id: bpy.props.StringProperty( - name="Volumetric exhaust", + name="Exhaust effect", description="Volumetric exhaust effect to be used by the thurster when firing.", default="ApolloCSM" ) - sound_event_action_on: bpy.props.StringProperty( - name="Sound", + sound_effect: bpy.props.StringProperty( + name="Sound effect", description="Sound effect to be used by the thurster when firing.", default="DefaultEngineSoundBehavior" ) + sound_on: bpy.props.BoolProperty( + name="Sound on", + description="Sound effect on/off.", + default=True, + ) + + exportable: bpy.props.BoolProperty( name="Export", description="Include this object in custom exports.", @@ -207,6 +214,45 @@ def draw(self, context): prop_with_unit(col, props, "thrust_kn", "kN") prop_with_unit(col, props, "specific_impulse_seconds", "s") prop_with_unit(col, props, "minimum_throttle", "%") + + col.separator() + + # Exhaust effect col.prop(props, "volumetric_exhaust_id") - col.prop(props, "sound_event_action_on") + col.separator() + # col.prop(props, "sound_event_action_on") + + # Sound effect + col.prop(props, "sound_effect") + col.prop(props, "sound_on") # Sound on/ff + col.separator() + + +class OBJECT_PT_engine_panel_export(bpy.types.Panel): + """Display engine properties in the properties panel.""" + bl_label = "Engine export" + bl_idname = "OBJECT_PT_engine_panel_export" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = 'data' # more intuitive location + + @classmethod + def poll(cls, context): + obj = getattr(context, 'object', None) + if obj is None: + return False + # Only show panel for objects that are marked as engines + return obj.get('_is_engine') is not None or obj.get('_engine_meta') is not None or obj.name.startswith('Engine') + + def draw(self, context): + layout = self.layout + obj = context.object + + # Access engine_props + props = obj.engine_props + + col = layout.column() + + col.separator() col.prop(props, "exportable") + col.separator() \ No newline at end of file diff --git a/thruster.py b/thruster.py index 4ff3ad9..773aff2 100644 --- a/thruster.py +++ b/thruster.py @@ -105,7 +105,7 @@ def _thruster_dict_to_xml_element(parent, thruster_data, decimal_places=3): ET.SubElement(thruster, 'VolumetricExhaust', Id=exhaust_id) # SoundEvent with Action and SoundId attributes - sound_id = thruster_data.get('sound_event_on', 'DefaultRcsThruster') + sound_id = thruster_data.get('sound_effect', 'DefaultRcsThruster') ET.SubElement(thruster, 'SoundEvent', Action='On', SoundId=sound_id) @@ -143,17 +143,23 @@ class ThrusterProperties(bpy.types.PropertyGroup): ) volumetric_exhaust_id: bpy.props.StringProperty( - name="VolumetricExhaust_id", + name="Exhaust effect", description="Volumetric exhaust effect to be used by the thurster when firing.", default="ApolloRCS" ) - sound_event_on: bpy.props.StringProperty( + sound_effect: bpy.props.StringProperty( name="Sound effect", - description="Sound effect to be used by the thurster when firing.", + description="Sound effect to be played when firing the thurster.", default="DefaultRcsThruster" ) + sound_on: bpy.props.BoolProperty( + name="Sound on", + description="Sound effect on/off.", + default=True, + ) + control_map_translation: bpy.props.BoolVectorProperty( name="control_map_translation", description="Set if thruster should fire on translation input. Do not select both option for the same direction.", @@ -169,7 +175,7 @@ class ThrusterProperties(bpy.types.PropertyGroup): ) fx_location: bpy.props.FloatVectorProperty( - name="FxLocation", + name="Offset", description="Offset of the thruster effect.", default=(0.0, 0.0, 0.0), size=3, # 3D vector @@ -264,11 +270,15 @@ def draw(self, context): prop_with_unit(col, props, "thrust_n", "N") prop_with_unit(col, props, "specific_impulse_seconds", "s") prop_with_unit(col, props, "minimum_pulse_time_seconds", "s") - col.separator() + # Exhaust effect col.prop(props, "volumetric_exhaust_id") - col.prop(props, "sound_event_on") + col.separator() + + # Sound effect + col.prop(props, "sound_effect") + col.prop(props, "sound_on") class OBJECT_PT_thruster_panel_control(bpy.types.Panel): @@ -344,6 +354,34 @@ def draw(self, context): col = layout.column() col.prop(props, "fx_location") + col.separator() + + +class OBJECT_PT_thruster_panel_export(bpy.types.Panel): + bl_label = "Thruster export" + bl_idname = "OBJECT_PT_thruster_panel_offset" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = 'data' + + @classmethod + def poll(cls, context): + obj = getattr(context, 'object', None) + if obj is None: + return False + # Only show panel for objects that are marked as thrusters + return obj.get('_is_thruster') is not None or obj.get('_thruster_meta') is not None or obj.name.startswith( + 'Thruster') + + def draw(self, context): + layout = self.layout + obj = context.object + + # Access thruster_props + props = obj.thruster_props + + col = layout.column() col.separator() col.prop(props, "exportable") + col.separator() \ No newline at end of file From baee70b8971a6ba0761d974e07c09b759e5a6ba5 Mon Sep 17 00:00:00 2001 From: 8-root-grafter Date: Wed, 26 Nov 2025 20:58:48 +0100 Subject: [PATCH 2/2] v0.0.6 Switchable sound on/off Engine and thurster sound switchable between on or off. --- engine.py | 9 +++++++-- export.py | 8 ++++---- thruster.py | 7 ++++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/engine.py b/engine.py index 90f86e8..9e7b4df 100644 --- a/engine.py +++ b/engine.py @@ -52,6 +52,12 @@ def _engine_dict_to_xml_element(parent, engine_data, decimal_places=3): else: ex_dir = [1.0, 0.0, 0.0] # default forward (+X) + + if engine_data.get('sound_on'): # switchable sound export on/off + engine_sound_on_off = 'On' + else: + engine_sound_on_off = 'Off' + rounded_ex_dir = [_round_coordinate(v, decimal_places) for v in ex_dir] ET.SubElement(engine, 'ExhaustDirection', X=str(rounded_ex_dir[0]), Y=str(rounded_ex_dir[1]), Z=str(rounded_ex_dir[2])) @@ -75,7 +81,7 @@ def _engine_dict_to_xml_element(parent, engine_data, decimal_places=3): # SoundEvent with Action and SoundId attributes sound_id = engine_data.get('sound_effect', 'DefaultEngineSoundBehavior') - ET.SubElement(engine, 'SoundEvent', Action='On', SoundId=sound_id) + ET.SubElement(engine, 'SoundEvent', Action=engine_sound_on_off, SoundId=sound_id) def engines_list_to_xml_str(list_of_meta): @@ -220,7 +226,6 @@ def draw(self, context): # Exhaust effect col.prop(props, "volumetric_exhaust_id") col.separator() - # col.prop(props, "sound_event_action_on") # Sound effect col.prop(props, "sound_effect") diff --git a/export.py b/export.py index 4e7c0d1..c3267ff 100644 --- a/export.py +++ b/export.py @@ -54,7 +54,7 @@ class OBJECT_OT_export_ksa_metadata(bpy.types.Operator): coordinate_decimal_places: bpy.props.IntProperty( name="Coordinate Decimal Places", - description="Number of decimal places for coordinates in the XML export (e.g., 3 for 0.001 precision)", + description="Number of decimal places for coordinates in the XML export (e.g., 3 for 0.001 precision). \n Bigger does not equal better. Recommended to not increase above 4. This is a soltuion for floating point errors for now.", default=3, min=0, max=10, @@ -98,7 +98,7 @@ def execute(self, context): 'specific_impulse_seconds': tp.specific_impulse_seconds, 'minimum_pulse_time_seconds': tp.minimum_pulse_time_seconds, 'volumetric_exhaust_id': tp.volumetric_exhaust_id, - 'sound_event_on': tp.sound_event_on, + 'sound_effect': tp.sound_effect, 'control_map_translation': _safe_vector_to_list(tp.control_map_translation), 'control_map_rotation': _safe_vector_to_list(tp.control_map_rotation), 'exportable': tp.exportable, @@ -121,7 +121,7 @@ def execute(self, context): 'specific_impulse_seconds': ep.specific_impulse_seconds, 'minimum_throttle': ep.minimum_throttle, 'volumetric_exhaust_id': ep.volumetric_exhaust_id, - 'sound_event_action_on': ep.sound_event_action_on, + 'sound_effect': ep.sound_effect, 'exportable': ep.exportable, } engines.append(entry) @@ -406,7 +406,7 @@ def execute(self, context): 'specific_impulse_seconds': kp.specific_impulse_seconds, 'minimum_pulse_time_seconds': kp.minimum_pulse_time_seconds, 'volumetric_exhaust_id': kp.volumetric_exhaust_id, - 'sound_event_on': kp.sound_event_on, + 'sound_effect': kp.sound_effect, 'control_map_translation': _safe_vector_to_list(kp.control_map_translation), 'control_map_rotation': _safe_vector_to_list(kp.control_map_rotation), 'exportable': kp.exportable, diff --git a/thruster.py b/thruster.py index 773aff2..f68d3f4 100644 --- a/thruster.py +++ b/thruster.py @@ -84,6 +84,11 @@ def _thruster_dict_to_xml_element(parent, thruster_data, decimal_places=3): if enabled and i < len(rot_labels): csv_parts.append(rot_labels[i]) + if thruster_data.get('sound_on'): # switchable sound export on/off + thruster_sound_on_off = 'On' + else: + thruster_sound_on_off = 'Off' + # Always add the ControlMap element (even if empty) csv_value = ','.join(csv_parts) if csv_parts else '' ET.SubElement(thruster, 'ControlMap', CSV=csv_value) @@ -106,7 +111,7 @@ def _thruster_dict_to_xml_element(parent, thruster_data, decimal_places=3): # SoundEvent with Action and SoundId attributes sound_id = thruster_data.get('sound_effect', 'DefaultRcsThruster') - ET.SubElement(thruster, 'SoundEvent', Action='On', SoundId=sound_id) + ET.SubElement(thruster, 'SoundEvent', Action=thruster_sound_on_off, SoundId=sound_id) def thrusters_list_to_xml_str(list_of_meta):