diff --git a/__init__.py b/__init__.py index c408672..4fc5c5e 100644 --- a/__init__.py +++ b/__init__.py @@ -35,7 +35,8 @@ gui.IMPORT_OT_dff, gui.EXPORT_OT_dff, gui.EXPORT_OT_col, - gui.EXPORT_OT_ipl_cull, + gui.EXPORT_OT_ipl, + gui.EXPORT_OT_ide, gui.SCENE_OT_dff_frame_move, gui.SCENE_OT_dff_atomic_move, gui.SCENE_OT_dff_update, @@ -69,9 +70,12 @@ gui.DFFObjectProps, gui.DFFCollectionProps, gui.MapImportPanel, + gui.MapObjectPanel, gui.DFFFrameProps, gui.DFFAtomicProps, gui.DFFSceneProps, + gui.IDEObjectProps, + gui.IPLObjectProps, gui.DFF_MT_ExportChoice, gui.DFF_MT_EditArmature, gui.DFF_MT_Pose, @@ -105,6 +109,8 @@ def register(): bpy.types.TextCurve.ext_2dfx = bpy.props.PointerProperty(type=gui.RoadSign2DFXObjectProps) bpy.types.Material.dff = bpy.props.PointerProperty(type=gui.DFFMaterialProps) bpy.types.Object.dff = bpy.props.PointerProperty(type=gui.DFFObjectProps) + bpy.types.Object.ide = bpy.props.PointerProperty(type=gui.IDEObjectProps) + bpy.types.Object.ipl = bpy.props.PointerProperty(type=gui.IPLObjectProps) bpy.types.Collection.dff = bpy.props.PointerProperty(type=gui.DFFCollectionProps) bpy.types.TOPBAR_MT_file_import.append(gui.import_dff_func) @@ -125,6 +131,8 @@ def unregister(): del bpy.types.TextCurve.ext_2dfx del bpy.types.Material.dff del bpy.types.Object.dff + del bpy.types.Object.ide + del bpy.types.Object.ipl del bpy.types.Collection.dff bpy.types.TOPBAR_MT_file_import.remove(gui.import_dff_func) diff --git a/gtaLib/map.py b/gtaLib/map.py index 3e5f106..8c3f97f 100644 --- a/gtaLib/map.py +++ b/gtaLib/map.py @@ -36,6 +36,12 @@ class TextIPLData: object_instances: list cull_instances: list +####################################################### +@dataclass +class TextIDEData: + objs_instances: list + tobj_instances: list + # Base for all IPL / IDE section reader / writer classes ####################################################### class SectionUtility: @@ -436,6 +442,17 @@ def write_text_ipl_to_stream(file_stream, game_id, ipl_data:TextIPLData): section_utility = SectionUtility("mult") section_utility.write(file_stream, []) + ######################################################################## + @staticmethod + def write_text_ide_to_stream(file_stream, ide_data:TextIDEData): + file_stream.write("# IDE generated with DragonFF\n") + + section_utility = SectionUtility("objs") + section_utility.write(file_stream, ide_data.objs_instances) + + section_utility = SectionUtility("tobj") + section_utility.write(file_stream, ide_data.tobj_instances) + ######################################################################## @staticmethod def write_ipl_data(filename, game_id, ipl_data:TextIPLData): @@ -443,3 +460,11 @@ def write_ipl_data(filename, game_id, ipl_data:TextIPLData): with open(filename, 'w') as file_stream: self.write_text_ipl_to_stream(file_stream, game_id, ipl_data) + + ######################################################################## + @staticmethod + def write_ide_data(filename, ide_data:TextIDEData): + self = MapDataUtility + + with open(filename, 'w') as file_stream: + self.write_text_ide_to_stream(file_stream, ide_data) diff --git a/gui/dff_menus.py b/gui/dff_menus.py index 3708f22..5d3d2bc 100644 --- a/gui/dff_menus.py +++ b/gui/dff_menus.py @@ -8,7 +8,7 @@ COLLECTION_OT_dff_generate_bounds, \ OBJECT_OT_dff_add_collision_box, OBJECT_OT_dff_add_collision_sphere from .ext_2dfx_menus import EXT2DFXObjectProps, EXT2DFXMenus -from .map_ot import EXPORT_OT_ipl_cull +from .map_ot import EXPORT_OT_ipl, EXPORT_OT_ide from .cull_menus import CULLObjectProps, CULLMenus from ..gtaLib.data import presets @@ -243,9 +243,6 @@ def draw(self, context): op = self.layout.operator(EXPORT_OT_col.bl_idname, text="DragonFF Collision (.col)") op.use_active_collection = False - self.layout.operator(EXPORT_OT_ipl_cull.bl_idname, - text="DragonFF CULL (.ipl)") - ####################################################### def import_dff_func(self, context): diff --git a/gui/map_menus.py b/gui/map_menus.py index 7e65236..71e100f 100644 --- a/gui/map_menus.py +++ b/gui/map_menus.py @@ -20,6 +20,96 @@ from .map_ot import SCENE_OT_ipl_select, OBJECT_OT_dff_add_cull from ..gtaLib.data import map_data +####################################################### +class IDEObjectProps(bpy.types.PropertyGroup): + """IDE properties for objects""" + + obj_id: bpy.props.StringProperty( + name="Object ID", + description="Unique object ID in IDE files", + default="" + ) + + model_name: bpy.props.StringProperty( + name="Model Name", + description="Model name (should match DFF filename)", + default="" + ) + + txd_name: bpy.props.StringProperty( + name="TXD Name", + description="Texture dictionary name", + default="" + ) + + flags: bpy.props.StringProperty( + name="Flags", + description="Object flags", + default="0" + ) + + draw_distance: bpy.props.StringProperty( + name="Draw Distance", + description="Single draw distance", + default="100" + ) + + draw_distance1: bpy.props.StringProperty( + name="Draw Distance 1", + description="First draw distance (for multi-LOD objects)", + default="" + ) + + draw_distance2: bpy.props.StringProperty( + name="Draw Distance 2", + description="Second draw distance (for multi-LOD objects)", + default="" + ) + + draw_distance3: bpy.props.StringProperty( + name="Draw Distance 3", + description="Third draw distance (for multi-LOD objects)", + default="" + ) + + obj_type: bpy.props.EnumProperty( + name="Object Type", + description="IDE object type", + items=[ + ('objs', 'Regular Object', 'Standard object'), + ('tobj', 'Time Object', 'Time-based object') + ], + default='objs' + ) + + time_on: bpy.props.StringProperty( + name="Time On", + description="Hour when object appears (0-23)", + default="0" + ) + + time_off: bpy.props.StringProperty( + name="Time Off", + description="Hour when object disappears (0-23)", + default="24" + ) + +####################################################### +class IPLObjectProps(bpy.types.PropertyGroup): + """IPL properties for objects""" + + interior: bpy.props.StringProperty( + name="Interior", + description="Interior ID (0 for exterior)", + default="0" + ) + + lod: bpy.props.StringProperty( + name="LOD", + description="LOD object ID (-1 for no LOD)", + default="-1" + ) + ####################################################### class DFFFrameProps(bpy.types.PropertyGroup): obj : bpy.props.PointerProperty(type=bpy.types.Object) @@ -227,7 +317,7 @@ def atomics_active_changed(self, context): ####################################################### class MapImportPanel(bpy.types.Panel): """Creates a Panel in the scene context of the properties editor""" - bl_label = "DragonFF - Map Import" + bl_label = "DragonFF - Map I/O" bl_idname = "SCENE_PT_layout" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -274,7 +364,115 @@ def draw(self, context): row = layout.row() row.operator("scene.dragonff_map_import") -#######################################################@ + layout.separator() + + row = layout.row() + row.operator("export_scene.dff_ipl", text="Export IPL") + + row = layout.row() + row.operator("export_scene.dff_ide", text="Export IDE") + +####################################################### +class MapObjectPanel(bpy.types.Panel): + """Panel for IPL/IDE object properties""" + bl_label = "DragonFF - Map Properties" + bl_idname = "OBJECT_PT_map_props" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + + ####################################################### + @classmethod + def poll(cls, context): + return context.object is not None + + ####################################################### + def draw(self, context): + layout = self.layout + obj = context.object + + # IPL Data + box = layout.box() + box.label(text="IPL Data (Instance Placement):", icon='OUTLINER_OB_EMPTY') + + col = box.column() + + row = col.row() + row.label(text="Object ID:") + row.label(text=obj.ide.obj_id if obj.ide.obj_id else "Not Set") + + row = col.row() + row.label(text="Model Name:") + row.label(text=obj.ide.model_name if obj.ide.model_name else "Not Set") + + row = col.row() + row.prop(obj.ipl, "interior") + + if context.scene.dff.game_version_dropdown == map_data.game_version.SA: + row = col.row() + row.prop(obj.ipl, "lod") + + # Show current transform + col.separator() + col.label(text="Transform:") + col.label(text=f"Position: {obj.location.x:.3f}, {obj.location.y:.3f}, {obj.location.z:.3f}") + rot = obj.rotation_quaternion + col.label(text=f"Rotation: {rot.x:.3f}, {rot.y:.3f}, {rot.z:.3f}, {rot.w:.3f}") + + # IDE Data + box = layout.box() + box.label(text="IDE Data (Object Definition):", icon='OUTLINER_DATA_MESH') + + col = box.column() + + # Object ID + row = col.row(align=True) + row.prop(obj.ide, "obj_id") + + # Model Name + row = col.row() + row.prop(obj.ide, "model_name") + + # Object Type + row = col.row() + row.prop(obj.ide, "obj_type") + + # TXD Name + row = col.row() + row.prop(obj.ide, "txd_name") + + # Flags + row = col.row() + row.prop(obj.ide, "flags") + + # Draw Distances + col.separator() + col.label(text="Draw Distances:") + + # Check which draw distance format to use + if obj.ide.draw_distance1 or obj.ide.draw_distance2 or obj.ide.draw_distance3: + # Multiple draw distances + row = col.row() + row.prop(obj.ide, "draw_distance1") + row = col.row() + row.prop(obj.ide, "draw_distance2") + row = col.row() + row.prop(obj.ide, "draw_distance3") + else: + # Single draw distance + row = col.row() + row.prop(obj.ide, "draw_distance") + + # Time Object Properties + if obj.ide.obj_type == 'tobj': + col.separator() + col.label(text="Time Object Properties:") + + row = col.row() + row.prop(obj.ide, "time_on") + row.prop(obj.ide, "time_off") + +####################################################### class DFF_MT_AddMapObject(bpy.types.Menu): bl_label = "Map" diff --git a/gui/map_ot.py b/gui/map_ot.py index 96a0f44..b302026 100644 --- a/gui/map_ot.py +++ b/gui/map_ot.py @@ -21,7 +21,7 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper -from ..ops import map_importer, ipl_exporter +from ..ops import map_importer, ide_exporter, ipl_exporter from ..ops.cull_importer import cull_importer from ..ops.importer_common import link_object @@ -201,11 +201,65 @@ def execute(self, context): return {'FINISHED'} ####################################################### -class EXPORT_OT_ipl_cull(bpy.types.Operator, ExportHelper): +class EXPORT_OT_ide(bpy.types.Operator, ExportHelper): + """Export IDE file""" + bl_idname = "export_scene.dff_ide" + bl_description = "Export a GTA IDE File" + bl_label = "DragonFF IDE (.ide)" + filename_ext = ".ide" - bl_idname = "export_scene.dff_ipl_cull" - bl_description = "Export a GTA CULL IPL File" - bl_label = "DragonFF CULL (.ipl)" + filepath : bpy.props.StringProperty(name="File path", + maxlen=1024, + default="", + subtype='FILE_PATH') + + filter_glob : bpy.props.StringProperty(default="*.ide", + options={'HIDDEN'}) + + only_selected : bpy.props.BoolProperty( + name = "Only Selected", + description = "Export only selected objects", + default = False + ) + + ####################################################### + def draw(self, context): + layout = self.layout + + layout.prop(self, "only_selected") + + ####################################################### + def execute(self, context): + start = time.time() + try: + ide_exporter.export_ide( + { + "file_name" : self.filepath, + "only_selected" : self.only_selected, + } + ) + + total_objs_num = len(ide_exporter.ide_exporter.objs_objects) + total_tobj_num = len(ide_exporter.ide_exporter.tobj_objects) + + if not (total_objs_num + total_tobj_num): + report = "No objects with IDE data found" + self.report({"ERROR"}, report) + return {'CANCELLED'}, report + + self.report({"INFO"}, f"Finished export {total_objs_num} objs and {total_tobj_num} tobj in {time.time() - start:.2f}s") + + except Exception as e: + self.report({"ERROR"}, str(e)) + + return {'FINISHED'} + +####################################################### +class EXPORT_OT_ipl(bpy.types.Operator, ExportHelper): + """Export IPL file""" + bl_idname = "export_scene.dff_ipl" + bl_description = "Export a GTA IPL File" + bl_label = "DragonFF IPL (.ipl)" filename_ext = ".ipl" filepath : bpy.props.StringProperty(name="File path", @@ -218,6 +272,19 @@ class EXPORT_OT_ipl_cull(bpy.types.Operator, ExportHelper): only_selected : bpy.props.BoolProperty( name = "Only Selected", + description = "Export only selected objects", + default = False + ) + + export_inst : bpy.props.BoolProperty( + name = "Export INST", + description = "Export INST entries", + default = True + ) + + export_cull : bpy.props.BoolProperty( + name = "Export CULL", + description = "Export CULL entries", default = False ) @@ -228,6 +295,11 @@ def draw(self, context): layout.prop(self, "only_selected") layout.prop(context.scene.dff, "game_version_dropdown", text="Game") + box = layout.box() + box.label(text="Export Entries") + box.prop(self, "export_inst", text="INST") + box.prop(self, "export_cull", text="CULL") + ####################################################### def execute(self, context): @@ -238,17 +310,19 @@ def execute(self, context): "file_name" : self.filepath, "only_selected" : self.only_selected, "game_id" : context.scene.dff.game_version_dropdown, - "export_inst" : False, - "export_cull" : True, + "export_inst" : self.export_inst, + "export_cull" : self.export_cull, } ) - if not ipl_exporter.ipl_exporter.total_objects_num: + total_objects_num = ipl_exporter.ipl_exporter.total_objects_num + + if not total_objects_num: report = "No objects with IPL data found" self.report({"ERROR"}, report) return {'CANCELLED'}, report - self.report({"INFO"}, f"Finished export in {time.time() - start:.2f}s") + self.report({"INFO"}, f"Finished export {total_objects_num} objects in {time.time() - start:.2f}s") except Exception as e: self.report({"ERROR"}, str(e)) diff --git a/ops/ide_exporter.py b/ops/ide_exporter.py new file mode 100644 index 0000000..1cc56fe --- /dev/null +++ b/ops/ide_exporter.py @@ -0,0 +1,179 @@ +# GTA DragonFF - Blender scripts to edit basic GTA formats +# Copyright (C) 2019 Parik + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import bpy + +from ..gtaLib.map import TextIDEData, MapDataUtility + +####################################################### +class ide_exporter: + + only_selected = False + + objs_objects = [] + tobj_objects = [] + + ####################################################### + @staticmethod + def collect_objects(context): + """Collect objects that have IDE data""" + + self = ide_exporter + + self.objs_objects = [] + self.tobj_objects = [] + + ide_objects = {} # Group by ID to avoid duplicates + + for obj in context.scene.objects: + if self.only_selected and not obj.select_get(): + continue + + if obj.dff.type == 'OBJ': + if hasattr(obj, 'ide') and obj.ide.obj_id and obj.ide.txd_name: + obj_id = obj.ide.obj_id + + # Only add if we haven't seen this ID before + if obj_id not in ide_objects: + ide_objects[obj_id] = obj + + # Separate objects by type + for obj in ide_objects.values(): + if obj.ide.obj_type == 'tobj': + self.tobj_objects.append(obj) + else: + self.objs_objects.append(obj) + + ####################################################### + @staticmethod + def get_draw_distances(obj): + """Get draw distances for the object""" + distances = [] + + # Check for single draw distance + if obj.ide.draw_distance: + distances.append(obj.ide.draw_distance) + + # Check for multiple draw distances + if obj.ide.draw_distance1: + distances.append(obj.ide.draw_distance1) + if obj.ide.draw_distance2: + distances.append(obj.ide.draw_distance2) + if obj.ide.draw_distance3: + distances.append(obj.ide.draw_distance3) + + # Default to single distance of 100 if none found + if not distances: + distances = ['100'] + + return distances + + ####################################################### + @staticmethod + def format_objs_line(obj): + """Format an object as an objs line""" + + self = ide_exporter + + obj_id = obj.ide.obj_id + model_name = obj.ide.model_name or obj.name + txd_name = obj.ide.txd_name or model_name + flags = obj.ide.flags or '0' + + distances = self.get_draw_distances(obj) + + # Format based on number of draw distances + if len(distances) == 1: + # Format: ID, ModelName, TxdName, DrawDistance, Flags + return f"{obj_id}, {model_name}, {txd_name}, {distances[0]}, {flags}" + + elif len(distances) == 2: + # Format: ID, ModelName, TxdName, MeshCount, DrawDistance1, DrawDistance2, Flags + mesh_count = '1' # Default to 1 for now + return f"{obj_id}, {model_name}, {txd_name}, {mesh_count}, {distances[0]}, {distances[1]}, {flags}" + + elif len(distances) == 3: + # Format: ID, ModelName, TxdName, MeshCount, DrawDistance1, DrawDistance2, DrawDistance3, Flags + mesh_count = '1' # Default to 1 for now + return f"{obj_id}, {model_name}, {txd_name}, {mesh_count}, {distances[0]}, {distances[1]}, {distances[2]}, {flags}" + + else: + # Default format + return f"{obj_id}, {model_name}, {txd_name}, {distances[0]}, {flags}" + + ####################################################### + @staticmethod + def format_tobj_line(obj): + """Format an object as a tobj line""" + + self = ide_exporter + + obj_id = obj.ide.obj_id + model_name = obj.ide.model_name or obj.name + txd_name = obj.ide.txd_name or model_name + flags = obj.ide.flags or '0' + time_on = obj.ide.time_on or '0' + time_off = obj.ide.time_off or '24' + + distances = self.get_draw_distances(obj) + + # Format based on number of draw distances + if len(distances) == 1: + # Format: ID, ModelName, TxdName, DrawDistance, Flags, TimeOn, TimeOff + return f"{obj_id}, {model_name}, {txd_name}, {distances[0]}, {flags}, {time_on}, {time_off}" + + elif len(distances) == 2: + # Format: ID, ModelName, TxdName, MeshCount, DrawDistance1, DrawDistance2, Flags, TimeOn, TimeOff + mesh_count = '1' # Default to 1 for now + return f"{obj_id}, {model_name}, {txd_name}, {mesh_count}, {distances[0]}, {distances[1]}, {flags}, {time_on}, {time_off}" + + elif len(distances) == 3: + # Format: ID, ModelName, TxdName, MeshCount, DrawDistance1, DrawDistance2, DrawDistance3, Flags, TimeOn, TimeOff + mesh_count = '1' # Default to 1 for now + return f"{obj_id}, {model_name}, {txd_name}, {mesh_count}, {distances[0]}, {distances[1]}, {distances[2]}, {flags}, {time_on}, {time_off}" + + else: + # Default format + return f"{obj_id}, {model_name}, {txd_name}, {distances[0]}, {flags}, {time_on}, {time_off}" + + ####################################################### + @staticmethod + def export_ide(filename): + self = ide_exporter + + self.collect_objects(bpy.context) + + total_objects_num = len(self.objs_objects) + len(self.tobj_objects) + if not total_objects_num: + return + + objs_instances = [self.format_objs_line(obj) for obj in self.objs_objects] + tobj_instances = [self.format_tobj_line(obj) for obj in self.tobj_objects] + + ide_data = TextIDEData( + objs_instances, + tobj_instances, + ) + + MapDataUtility.write_ide_data(filename, ide_data) + +####################################################### +def export_ide(options): + """Main export function""" + + ide_exporter.only_selected = options['only_selected'] + + ide_exporter.export_ide(options['file_name']) diff --git a/ops/ipl_exporter.py b/ops/ipl_exporter.py index e54ceea..2b9d8cf 100644 --- a/ops/ipl_exporter.py +++ b/ops/ipl_exporter.py @@ -16,6 +16,7 @@ import bpy +from ..gtaLib.data.map_data import game_version from ..gtaLib.map import TextIPLData, MapDataUtility from ..ops.cull_exporter import cull_exporter @@ -24,7 +25,7 @@ class ipl_exporter: only_selected = False game_id = None - export_inst = False + export_inst = True export_cull = False inst_objects = [] @@ -46,7 +47,8 @@ def collect_objects(context): continue if self.export_inst and obj.dff.type == 'OBJ': - self.inst_objects.append(obj) + if hasattr(obj, 'ide') and obj.ide.obj_id and not obj.parent: + self.inst_objects.append(obj) if self.export_cull and obj.dff.type == 'CULL': self.cull_objects.append(obj) @@ -60,8 +62,39 @@ def format_inst_line(obj): self = ipl_exporter - # TODO - return "" + # Get data from ide/ipl properties + obj_id = obj.ide.obj_id + model_name = obj.ide.model_name or obj.name + interior = obj.ipl.interior or '0' + lod = obj.ipl.lod or '-1' + + # Get transformation data + loc = obj.location + rot = obj.rotation_quaternion + scale = obj.scale + + # Note: the W component is negated + rot_w = -rot.w + rot_x = rot.x + rot_y = rot.y + rot_z = rot.z + + if self.game_id == game_version.III: + # GTA III format: ID, ModelName, PosX, PosY, PosZ, ScaleX, ScaleY, ScaleZ, RotX, RotY, RotZ, RotW + return f"{obj_id}, {model_name}, {loc.x:.6f}, {loc.y:.6f}, {loc.z:.6f}, {scale.x:.6f}, {scale.y:.6f}, {scale.z:.6f}, {rot_x:.6f}, {rot_y:.6f}, {rot_z:.6f}, {rot_w:.6f}" + + elif self.game_id == game_version.VC: + # GTA VC format: ID, ModelName, Interior, PosX, PosY, PosZ, ScaleX, ScaleY, ScaleZ, RotX, RotY, RotZ, RotW + return f"{obj_id}, {model_name}, {interior}, {loc.x:.6f}, {loc.y:.6f}, {loc.z:.6f}, {scale.x:.6f}, {scale.y:.6f}, {scale.z:.6f}, {rot_x:.6f}, {rot_y:.6f}, {rot_z:.6f}, {rot_w:.6f}" + + elif self.game_id == game_version.SA: + # GTA SA format: ID, ModelName, Interior, PosX, PosY, PosZ, RotX, RotY, RotZ, RotW, LOD + # Note: SA doesn't have scale in IPL + return f"{obj_id}, {model_name}, {interior}, {loc.x:.6f}, {loc.y:.6f}, {loc.z:.6f}, {rot_x:.6f}, {rot_y:.6f}, {rot_z:.6f}, {rot_w:.6f}, {lod}" + + else: + # Default to SA format + return f"{obj_id}, {model_name}, {interior}, {loc.x:.6f}, {loc.y:.6f}, {loc.z:.6f}, {rot_x:.6f}, {rot_y:.6f}, {rot_z:.6f}, {rot_w:.6f}, {lod}" ####################################################### @staticmethod @@ -76,12 +109,12 @@ def export_ipl(filename): object_instances = [self.format_inst_line(obj) for obj in self.inst_objects] cull_instacnes = cull_exporter.export_objects(self.cull_objects, self.game_id) - ipl_Data = TextIPLData( + ipl_data = TextIPLData( object_instances, cull_instacnes, ) - MapDataUtility.write_ipl_data(filename, self.game_id, ipl_Data) + MapDataUtility.write_ipl_data(filename, self.game_id, ipl_data) ####################################################### def export_ipl(options): diff --git a/ops/map_importer.py b/ops/map_importer.py index 6e98c9d..09c18c8 100644 --- a/ops/map_importer.py +++ b/ops/map_importer.py @@ -91,6 +91,32 @@ def import_object_instance(context, inst): obj, inst ) + # Store IPL + obj.ide.obj_id = inst.id + obj.ide.model_name = self.object_data[inst.id].modelName + if hasattr(inst, 'interior'): + obj.ipl.interior = inst.interior + if hasattr(inst, 'lod'): + obj.ipl.lod = inst.lod + + # Store IDE data + ide_data = self.object_data[inst.id] + obj.ide.txd_name = ide_data.txdName + if hasattr(ide_data, 'drawDistance'): + obj.ide.draw_distance = ide_data.drawDistance + if hasattr(ide_data, 'drawDistance1'): + obj.ide.draw_distance1 = ide_data.drawDistance1 + if hasattr(ide_data, 'drawDistance2'): + obj.ide.draw_distance2 = ide_data.drawDistance2 + if hasattr(ide_data, 'drawDistance3'): + obj.ide.draw_distance3 = ide_data.drawDistance3 + obj.ide.flags = ide_data.flags + obj.ide.obj_type = 'objs' # Mark as regular object + if hasattr(ide_data, 'timeOn'): + obj.ide.obj_type = 'tobj' # Mark as time object + obj.ide.time_on = ide_data.timeOn + obj.ide.time_off = ide_data.timeOff + cached_2dfx = [obj for obj in model_cache if obj.dff.type == "2DFX"] for obj in cached_2dfx: new_obj = bpy.data.objects.new(obj.name, obj.data) @@ -167,6 +193,32 @@ def import_object_instance(context, inst): obj, inst ) + # Store IPL data + obj.ide.obj_id = inst.id + obj.ide.model_name = self.object_data[inst.id].modelName + if hasattr(inst, 'interior'): + obj.ipl.interior = inst.interior + if hasattr(inst, 'lod'): + obj.ipl.lod = inst.lod + + # Store IDE data + ide_data = self.object_data[inst.id] + obj.ide.txd_name = ide_data.txdName + if hasattr(ide_data, 'drawDistance'): + obj.ide.draw_distance = ide_data.drawDistance + if hasattr(ide_data, 'drawDistance1'): + obj.ide.draw_distance1 = ide_data.drawDistance1 + if hasattr(ide_data, 'drawDistance2'): + obj.ide.draw_distance2 = ide_data.drawDistance2 + if hasattr(ide_data, 'drawDistance3'): + obj.ide.draw_distance3 = ide_data.drawDistance3 + obj.ide.flags = ide_data.flags + obj.ide.obj_type = 'objs' # Mark as regular object + if hasattr(ide_data, 'timeOn'): + obj.ide.obj_type = 'tobj' # Mark as time object + obj.ide.time_on = ide_data.timeOn + obj.ide.time_off = ide_data.timeOff + # Set root object as 2DFX parent if root_objects: for obj in collection_objects: