Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -42,6 +44,16 @@
menu_func_export,
)

# Addon metadata
bl_info = {
"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, 6),
"blender": (4, 50, 0),
"location": "Add Menu > KSA folder, and File > Export > KSA Part",
"category": ["Add Mesh", "Import-Export"],
}

# List of all classes to register
classes = (
Expand All @@ -52,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,
Expand Down
2 changes: 1 addition & 1 deletion blender_manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
63 changes: 57 additions & 6 deletions engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -74,8 +80,8 @@ 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')
ET.SubElement(engine, 'SoundEvent', Action='On', SoundId=sound_id)
sound_id = engine_data.get('sound_effect', 'DefaultEngineSoundBehavior')
ET.SubElement(engine, 'SoundEvent', Action=engine_sound_on_off, SoundId=sound_id)


def engines_list_to_xml_str(list_of_meta):
Expand Down Expand Up @@ -113,17 +119,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.",
Expand Down Expand Up @@ -207,6 +220,44 @@ 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()

# 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()
8 changes: 4 additions & 4 deletions export.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
59 changes: 51 additions & 8 deletions thruster.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,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)
Expand All @@ -104,8 +109,8 @@ 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')
ET.SubElement(thruster, 'SoundEvent', Action='On', SoundId=sound_id)
sound_id = thruster_data.get('sound_effect', 'DefaultRcsThruster')
ET.SubElement(thruster, 'SoundEvent', Action=thruster_sound_on_off, SoundId=sound_id)


def thrusters_list_to_xml_str(list_of_meta):
Expand Down Expand Up @@ -142,17 +147,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.",
Expand All @@ -168,7 +179,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
Expand Down Expand Up @@ -263,11 +274,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):
Expand Down Expand Up @@ -343,6 +358,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()