diff --git a/Arma3ObjectBuilder/CHANGELOG.md b/Arma3ObjectBuilder/CHANGELOG.md index edea7f3..86356f2 100644 --- a/Arma3ObjectBuilder/CHANGELOG.md +++ b/Arma3ObjectBuilder/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [v2.4.1](https://github.com/MrClock8163/Arma3ObjectBuilder/releases/tag/v2.4.1) (Blender 2.90 -> 4.2) + +### Changes + +- internal handling of the P3D data was simplified and improved +- internal code structure and dependencies were cleaned up +- improved add-on reloading + +### Fixed + +- named properties were not properly lowercased with the Force Lowercase export option during P3D export +- animation action property classes were not properly unregistered when the add-on was deactivated or reloaded + ## [v2.4.0](https://github.com/MrClock8163/Arma3ObjectBuilder/releases/tag/v2.4.0) (Blender 2.90 -> 4.2) ### Added diff --git a/Arma3ObjectBuilder/__init__.py b/Arma3ObjectBuilder/__init__.py index fb0e3bf..6686a51 100644 --- a/Arma3ObjectBuilder/__init__.py +++ b/Arma3ObjectBuilder/__init__.py @@ -2,31 +2,56 @@ "name": "Arma 3 Object Builder", "description": "Collection of tools for editing Arma 3 content", "author": "MrClock (present add-on), Hans-Joerg \"Alwarren\" Frieden (original ArmaToolbox add-on)", - "version": (2, 4, 0), + "version": (2, 4, 1), "blender": (2, 90, 0), "location": "Object Builder panels", - "warning": "", + "warning": "Development", "doc_url": "https://mrcmodding.gitbook.io/arma-3-object-builder/home", "tracker_url": "https://github.com/MrClock8163/Arma3ObjectBuilder/issues", "category": "Import-Export" } +import os + if "bpy" in locals(): - import importlib + from importlib import reload - importlib.reload(props) - importlib.reload(ui) - importlib.reload(flagutils) - -else: - from . import props - from . import ui - from .utilities import flags as flagutils + if "utilities" in locals(): + reload(utilities) + if "io" in locals(): + reload(io) + if "props" in locals(): + reload(props) + if "ui" in locals(): + reload(ui) import bpy +addon_prefs = None +addon_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +addon_icons = {} + +def get_icon(name): + icon = 0 + try: + icon = addon_icons[addon_prefs.icon_theme.lower()][name].icon_id + except Exception: + pass + + return icon + +def get_prefs(): + return addon_prefs + + +from . import utilities +from . import io +from . import props +from . import ui + + def outliner_enable_update(self, context): if self.outliner == 'ENABLED' and ui.tool_outliner.depsgraph_update_post_handler not in bpy.app.handlers.depsgraph_update_post: bpy.app.handlers.depsgraph_update_post.append(ui.tool_outliner.depsgraph_update_post_handler) @@ -52,8 +77,7 @@ def execute(self, context): from winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER key = OpenKey(HKEY_CURRENT_USER, r"software\bohemia interactive\arma 3 tools") value, _type = QueryValueEx(key, "path") - prefs = context.preferences.addons[__package__].preferences - prefs.a3_tools = value + addon_prefs.a3_tools = value except Exception: self.report({'ERROR'}, "The Arma 3 Tools installation could not be found, it has to be set manually") @@ -124,13 +148,13 @@ def poll(cls, context): def invoke(self, context, event): prefs = context.preferences.addons[__package__].preferences - flagutils.set_flag_vertex(self, prefs.flag_vertex) + utilities.flags.set_flag_vertex(self, prefs.flag_vertex) return context.window_manager.invoke_props_dialog(self) def execute(self, context): prefs = context.preferences.addons[__package__].preferences - prefs.flag_vertex = flagutils.get_flag_vertex(self) + prefs.flag_vertex = utilities.flags.get_flag_vertex(self) return {'FINISHED'} @@ -176,13 +200,13 @@ def poll(cls, context): def invoke(self, context, event): prefs = context.preferences.addons[__package__].preferences - flagutils.set_flag_face(self, prefs.flag_face) + utilities.flags.set_flag_face(self, prefs.flag_face) return context.window_manager.invoke_props_dialog(self) def execute(self, context): prefs = context.preferences.addons[__package__].preferences - prefs.flag_face = flagutils.get_flag_face(self) + prefs.flag_face = utilities.flags.get_flag_face(self) return {'FINISHED'} @@ -345,30 +369,54 @@ def draw(self, context): ) +def register_icons(): + import bpy.utils.previews + + themes_dir = os.path.join(addon_dir, "icons") + for theme in os.listdir(themes_dir): + theme_icons = bpy.utils.previews.new() + + icons_dir = os.path.join(themes_dir, theme) + for filename in os.listdir(icons_dir): + theme_icons.load(os.path.splitext(os.path.basename(filename))[0].lower(), os.path.join(icons_dir, filename), 'IMAGE') + + addon_icons[theme.lower()] = theme_icons + + +def unregister_icons(): + import bpy.utils.previews + + for icon in addon_icons.values(): + bpy.utils.previews.remove(icon) + + addon_icons.clear() + + def register(): from bpy.utils import register_class - from .utilities import generic - + print("Registering Arma 3 Object Builder ( '" + __package__ + "' )") for cls in classes: register_class(cls) + global addon_prefs + addon_prefs = bpy.context.preferences.addons[__package__].preferences + for mod in modules: mod.register() - generic.register_icons() - + register_icons() + print("Register done") def unregister(): from bpy.utils import unregister_class - from .utilities import generic print("Unregistering Arma 3 Object Builder ( '" + __package__ + "' )") - generic.unregister_icons() + unregister_icons() for mod in reversed(modules): mod.unregister() diff --git a/Arma3ObjectBuilder/blender_manifest.toml b/Arma3ObjectBuilder/blender_manifest.toml index f8f8ef2..f4e0e5a 100644 --- a/Arma3ObjectBuilder/blender_manifest.toml +++ b/Arma3ObjectBuilder/blender_manifest.toml @@ -5,7 +5,7 @@ type = "add-on" id = "Arma3ObjectBuilder" name = "Arma 3 Object Builder" tagline = "Comprehensive add-on for modding Arma 3" -version = "2.4.0" +version = "2.4.1" blender_version_min = "4.2.0" website = "https://mrcmodding.gitbook.io/arma-3-object-builder/home" tags = ["Import-Export", "Game Engine", "Object"] diff --git a/Arma3ObjectBuilder/io/__init__.py b/Arma3ObjectBuilder/io/__init__.py index 033507d..f44d379 100644 --- a/Arma3ObjectBuilder/io/__init__.py +++ b/Arma3ObjectBuilder/io/__init__.py @@ -1,4 +1,46 @@ +if "binary_handler" in locals(): + from importlib import reload + + if "binary_handler" in locals(): + reload(binary_handler) + if "compression" in locals(): + reload(compression) + if "data_asc" in locals(): + reload(data_asc) + if "data_p3d" in locals(): + reload(data_p3d) + if "data_rap" in locals(): + reload(data_rap) + if "data_rtm" in locals(): + reload(data_rtm) + if "data_tbcsv" in locals(): + reload(data_tbcsv) + if "export_asc" in locals(): + reload(export_asc) + if "export_mcfg" in locals(): + reload(export_mcfg) + if "export_p3d" in locals(): + reload(export_p3d) + if "export_rtm" in locals(): + reload(export_rtm) + if "export_tbcsv" in locals(): + reload(export_tbcsv) + if "import_armature" in locals(): + reload(import_armature) + if "import_asc" in locals(): + reload(import_asc) + if "import_mcfg" in locals(): + reload(import_mcfg) + if "import_p3d" in locals(): + reload(import_p3d) + if "import_rtm" in locals(): + reload(import_rtm) + if "import_tbcsv" in locals(): + reload(import_tbcsv) + + from . import binary_handler +from . import compression from . import data_asc from . import data_p3d from . import data_rap @@ -8,8 +50,10 @@ from . import export_mcfg from . import export_p3d from . import export_rtm +from . import export_tbcsv from . import import_armature from . import import_asc from . import import_mcfg from . import import_p3d from . import import_rtm +from . import import_tbcsv diff --git a/Arma3ObjectBuilder/io/binary_handler.py b/Arma3ObjectBuilder/io/binary_handler.py index 1b78e38..1307f3f 100644 --- a/Arma3ObjectBuilder/io/binary_handler.py +++ b/Arma3ObjectBuilder/io/binary_handler.py @@ -4,6 +4,8 @@ import struct +import functools +import itertools def read_byte(file): @@ -76,33 +78,21 @@ def read_char(file, count = 1): # In theory all strings in BI files should be strictly ASCII, # but on the off chance that a corrupt character is present, the method would fail. # Therefore using UTF-8 decoding is more robust, and gives the same result for valid ASCII values. +# https://stackoverflow.com/a/32775270 def read_asciiz(file): - res = b'' - - while True: - a = file.read(1) - if a == b'\x00' or a == b'': - break - - res += a - - return res.decode('utf8', errors="replace") + eof = iter(functools.partial(file.read, 1), "") + return b"".join(itertools.takewhile(b"\x00".__ne__, eof)).decode('utf8', errors="replace") def read_asciiz_field(file, field_len): field = file.read(field_len) if len(field) < field_len: raise EOFError("ASCIIZ field ran into unexpected EOF") - result = bytearray() - for value in field: - if value == 0: - break - - result.append(value) - else: + parts = field.split(b"\x00") + if len(parts) < 2: raise ValueError("ASCIIZ field length overflow") - return result.decode('utf8', errors="replace") + return parts[0].decode('utf8', errors="replace") def read_lascii(file): length = read_byte(file) diff --git a/Arma3ObjectBuilder/io/data_p3d.py b/Arma3ObjectBuilder/io/data_p3d.py index fd787d7..c082fe3 100644 --- a/Arma3ObjectBuilder/io/data_p3d.py +++ b/Arma3ObjectBuilder/io/data_p3d.py @@ -7,7 +7,6 @@ import struct import math import re -from decimal import Decimal, Context from . import binary_handler as binary @@ -22,7 +21,7 @@ def __str__(self): class P3D_TAGG_DataEmpty(): @classmethod def read(cls, file, length): - file.read(length) + file.seek(length, 1) return cls() def length(self): @@ -40,12 +39,16 @@ def __init__(self): def read(cls, file, length = 0): output = cls() - for i in range(length // 8): - point_1, point_2 = binary.read_ulongs(file, 2) - + count_values = length // 4 + data = binary.read_ulongs(file, count_values) + + for i in range(0, count_values, 2): + point_1 = data[i] + point_2 = data[i + 1] + if point_1 == point_2: continue - + output.edges.append((point_1, point_2)) return output @@ -85,12 +88,12 @@ def write(self, file): class P3D_TAGG_DataMass(): def __init__(self): - self.masses = {} + self.masses = () @classmethod def read(cls, file, count_verts): output = cls() - output.masses = {i: value for i, value in enumerate(binary.read_floats(file, count_verts))} + output.masses = binary.read_floats(file, count_verts) return output @@ -98,22 +101,21 @@ def length(self): return len(self.masses) * 4 def write(self, file): - for value in self.masses.values(): - binary.write_float(file, value) + binary.write_float(file, *self.masses) class P3D_TAGG_DataUVSet(): def __init__(self): self.id = 0 - self.uvs = {} + self.uvs = [] @classmethod def read(cls, file, length = 0): output = cls() - count_values = (length - 4) // 4 output.id = binary.read_ulong(file) + count_values = (length - 4) // 4 data = binary.read_floats(file, count_values) - output.uvs = {i: (data[i * 2], 1 - data[i * 2 + 1]) for i in range(count_values // 2)} + output.uvs = [(data[i], 1 - data[i + 1]) for i in range(0, count_values, 2)] return output @@ -122,38 +124,30 @@ def length(self): def write(self, file): binary.write_ulong(file, self.id) - for value in self.uvs.values(): - binary.write_float(file, value[0], 1 - value[1]) + for u, v in self.uvs: + binary.write_float(file, u, 1 - v) class P3D_TAGG_DataSelection(): def __init__(self): self.count_verts = 0 self.count_faces = 0 - self.weight_verts = {} - self.weight_faces = {} + self.weight_verts = [] + self.weight_faces = [] @classmethod def decode_weight(cls, weight): - if weight == 0 or weight == 1: + if weight in (0, 1): return weight - - value = (256 - weight) / 255 - if value > 1: - return 0 - - return value + return (255 - weight) / 254 @classmethod def encode_weight(cls, weight): - if weight == 0 or weight == 1: + if weight in (0, 1): return int(weight) - value = round(256 - 255 * weight) - - if value == 256: - return 0 + value = round(255 - 254 * weight) return value @@ -165,9 +159,10 @@ def read(cls, file, count_verts, count_faces): output.count_faces = count_faces data_verts = bytearray(file.read(count_verts)) - - output.weight_verts = {i: cls.decode_weight(value) for i, value in enumerate(data_verts) if value > 0} - file.read(count_faces) + output.weight_verts = [(i, cls.decode_weight(value)) for i, value in enumerate(data_verts) if value > 0] + file.seek(count_faces, 1) # skip face selection data + # data_faces = bytearray(file.read(count_faces)) + # output.weight_faces = [(i, cls.decode_weight(value)) for i, value in enumerate(data_faces) if value > 0] return output @@ -176,12 +171,12 @@ def length(self): def write(self, file): bytes_verts = bytearray(self.count_verts) - for idx in self.weight_verts: - bytes_verts[idx] = self.encode_weight(self.weight_verts[idx]) + for idx, weight in self.weight_verts: + bytes_verts[idx] = self.encode_weight(weight) bytes_faces = bytearray(self.count_faces) - for idx in self.weight_faces: - bytes_faces[idx] = self.encode_weight(self.weight_faces[idx]) + for idx, weight in self.weight_faces: + bytes_faces[idx] = self.encode_weight(weight) file.write(bytes_verts) file.write(bytes_faces) @@ -209,28 +204,29 @@ def read(cls, file, count_verts, count_faces): output.name = binary.read_asciiz(file) length = binary.read_ulong(file) - if output.name == "#EndOfFile#": if length != 0: raise P3D_Error("Invalid EOF") output.active = False elif output.name == "#SharpEdges#": + if length % 8 != 0: + raise P3D_Error("Invalid sharp edges length: %d" % length) + output.data = P3D_TAGG_DataSharpEdges.read(file, length) elif output.name == "#Property#": if length != 128: - raise P3D_Error("Invalid name property length: %d" % length) + raise P3D_Error("Invalid named property length: %d" % length) output.data = P3D_TAGG_DataProperty.read(file) elif output.name == "#Mass#": output.data = P3D_TAGG_DataMass.read(file, count_verts) elif output.name == "#UVSet#": output.data = P3D_TAGG_DataUVSet.read(file, length) - elif not output.name.startswith("#") and not output.name.endswith("#"): + elif output.is_selection(): output.data = P3D_TAGG_DataSelection.read(file, count_verts, count_faces) else: - # Consume unneeded TAGGs - file.read(length) + file.seek(length, 1) # Skip unneeded TAGG data output.active = False return output @@ -252,7 +248,7 @@ def is_proxy(self): if not self.name.startswith("proxy:"): return False - regex_proxy = "proxy:.*\.\d+" + regex_proxy = r"proxy:.*\.\d+" return re.match(regex_proxy, self.name) def is_selection(self): @@ -276,7 +272,7 @@ class P3D_LOD_Resolution(): HITPOINTS = 13 VIEW_GEOMETRY = 14 FIRE_GEOMETRY = 15 - VIEW_CARGO_GEOMERTRY = 16 + VIEW_CARGO_GEOMETRY = 16 VIEW_CARGO_FIRE_GEOMETRY = 17 VIEW_COMMANDER = 18 VIEW_COMMANDER_GEOMETRY = 19 @@ -290,57 +286,47 @@ class P3D_LOD_Resolution(): SHADOW_VIEW_PILOT = 27 SHADOW_VIEW_GUNNER = 28 WRECKAGE = 29 - UNDERGROUND = 30 # Geometry PhysX Old for Arma 3 + UNDERGROUND = 30 GROUNDLAYER = 31 NAVIGATION = 32 - # SHADOWBUFFER = ... UNKNOWN = -1 - INDEX_MAP = { - (0.0, 0): VISUAL, # Visual - (1.0, 3): VIEW_GUNNER, # View Gunner - (1.1, 3): VIEW_PILOT, # View Pilot - (1.2, 3): VIEW_CARGO, # View Cargo - (1.0, 4): SHADOW, # Shadow - # (1.1, 4): SHADOWBUFFER, - (1.3, 4): GROUNDLAYER, # GroundLayer (VBS) - (2.0, 4): EDIT, # Edit - (1.0, 13): GEOMETRY, # Geometry - (2.0, 13): GEOMETRY_BUOY, # Geometry Buoyancy - (3.0, 13): UNDERGROUND, # Underground (VBS), Geometry PhysX (old) for Arma 3 - (4.0, 13): GEOMETRY_PHYSX, # Geometry PhysX - (5.0, 13): NAVIGATION, # Navigation (VBS) - (1.0, 15): MEMORY, # Memory - (2.0, 15): LANDCONTACT, # Land Contact - (3.0, 15): ROADWAY, # Roadway - (4.0, 15): PATHS, # Paths - (5.0, 15): HITPOINTS, # Hit Points - (6.0, 15): VIEW_GEOMETRY, # View Geometry - (7.0, 15): FIRE_GEOMETRY, # Fire Geometry - (8.0, 15): VIEW_CARGO_GEOMERTRY, # View Cargo Geometry - (9.0, 15): VIEW_CARGO_FIRE_GEOMETRY, # View Cargo Fire Geometry - (1.0, 16): VIEW_COMMANDER, # View Commander - (1.1, 16): VIEW_COMMANDER_GEOMETRY, # View Commander Geometry - (1.2, 16): VIEW_COMMANDER_FIRE_GEOMETRY, # View Commander Fire Geometry - (1.3, 16): VIEW_PILOT_GEOMETRY, # View Pilot Geometry - (1.4, 16): VIEW_PILOT_FIRE_GEOMETRY, # View Pilot Fire Geometry - (1.5, 16): VIEW_GUNNER_GEOMETRY, # View Gunner Geometry - (1.6, 16): VIEW_GUNNER_FIRE_GEOMETRY, # View Gunner Fire Geometry - (1.7, 16): SUBPARTS, # Sub Parts - (1.8, 16): SHADOW_VIEW_CARGO, # Cargo View Shadow Volume - (1.9, 16): SHADOW_VIEW_PILOT, # Pilot View Shadow Volume - (2.0, 16): SHADOW_VIEW_GUNNER, # Gunner View Shadow Volume - (2.1, 16): WRECKAGE, # Wreckage - (-1.0, 0): UNKNOWN # Unknown - } - - RESOLUTION_POS = { # decimal places in normalized format - VIEW_CARGO: 3, - SHADOW: 4, - # SHADOWBUFFER: 4, - EDIT: 4, - VIEW_CARGO_GEOMERTRY: 2, - SHADOW_VIEW_CARGO: 3 + SIGNATURE_MAP = { + # "0.000e+00": VISUAL, # (+ resolution) + "1.000e+03": VIEW_GUNNER, + "1.100e+03": VIEW_PILOT, + # "1.200e+03": VIEW_CARGO, # (+ resolution) + # "1.000e+04": SHADOW, # (+ resolution) + # "1.100e+04": SHADOWBUFFER, + "1.300e+04": GROUNDLAYER, # Shadow Volume 3000 for Arma 3 + # "2.000e+04": EDIT, # (+ resolution) + "1.000e+13": GEOMETRY, + "2.000e+13": GEOMETRY_BUOY, + "3.000e+13": UNDERGROUND, # Geometry PhysX (old) for Arma 3 + "4.000e+13": GEOMETRY_PHYSX, + "5.000e+13": NAVIGATION, # Resolution 5e13 for Arma 3 + "1.000e+15": MEMORY, + "2.000e+15": LANDCONTACT, + "3.000e+15": ROADWAY, + "4.000e+15": PATHS, + "5.000e+15": HITPOINTS, + "6.000e+15": VIEW_GEOMETRY, + "7.000e+15": FIRE_GEOMETRY, + # "8.000e+15": VIEW_CARGO_GEOMETRY, (+ resolution * 1e13) + "9.000e+15": VIEW_CARGO_FIRE_GEOMETRY, + "1.000e+16": VIEW_COMMANDER, + "1.100e+16": VIEW_COMMANDER_GEOMETRY, + "1.200e+16": VIEW_COMMANDER_FIRE_GEOMETRY, + "1.300e+16": VIEW_PILOT_GEOMETRY, + "1.400e+16": VIEW_PILOT_FIRE_GEOMETRY, + "1.500e+16": VIEW_GUNNER_GEOMETRY, + "1.600e+16": VIEW_GUNNER_FIRE_GEOMETRY, + "1.700e+16": SUBPARTS, + # "1.800e+16": SHADOW_VIEW_CARGO, # (+ resolution * 1e13) + "1.900e+16": SHADOW_VIEW_PILOT, + "2.000e+16": SHADOW_VIEW_GUNNER, + "2.100e+16": WRECKAGE, + # (-1.0, 0): UNKNOWN } def __init__(self, lod = 0, res = 0): @@ -356,40 +342,49 @@ def __float__(self): @classmethod def encode(cls, lod, resolution): - if lod == cls.VISUAL or lod == cls.UNKNOWN: - return resolution - - lookup = {v: k for k, v in cls.INDEX_MAP.items()} - - coef, exp = lookup[lod] - pos = cls.RESOLUTION_POS.get(lod, None) - - resolution_sign = (resolution * 10**(exp - pos)) if pos is not None else 0 - - return coef * 10**exp + resolution_sign - + if lod in (cls.VISUAL, cls.UNKNOWN): + return resolution + + lookup = {v: k for k, v in cls.SIGNATURE_MAP.items()} + signature = lookup.get(lod) + if signature is not None: + return float(signature) + + if lod == cls.VIEW_CARGO: + return 1.2e3 + resolution + elif lod == cls.SHADOW: + return 1.0e4 + resolution + elif lod == cls.EDIT: + return 2.0e4 + resolution + elif lod == cls.VIEW_CARGO_GEOMETRY: + return 8.0e15 + resolution * 1e13 + elif lod == cls.SHADOW_VIEW_CARGO: + return 1.8e16 + resolution * 1e13 + @classmethod def decode(cls, signature): if signature < 1e3: return cls.VISUAL, round(signature) - elif 1e4 <= signature < 1.2e4: + elif 1.2e3 <= signature < 1.3e3: + return cls.VIEW_CARGO, round(signature - 1.2e3) + elif 1.0e4 <= signature < 1.2e4: return cls.SHADOW, round(signature - 1e4) + elif 2e4 <= signature < 3e4: + return cls.EDIT, round(signature - 2e4) - num = Decimal(signature) - exp = num.normalize(Context(2)).adjusted() - - coef = float((num / 10**exp)) - base = round(coef, 1) if exp in (3, 4, 16) else round(coef) + string = "%.3e" % signature + lod = cls.SIGNATURE_MAP.get(string) + if lod is not None: + return lod, 0 - lod = cls.INDEX_MAP.get((base, exp), cls.UNKNOWN) - pos = cls.RESOLUTION_POS.get(lod, None) + exp = int(string[-2:]) + if exp == 15: + return cls.VIEW_CARGO_GEOMETRY, int(string[2:4]) + elif exp == 16: + return cls.SHADOW_VIEW_CARGO, int(string[3:5]) - if lod == cls.UNKNOWN: - return lod, round(signature) - - resolution = int(round((coef - base) * 10**pos, pos)) if pos is not None else 0 - - return lod, resolution + print(signature, string) + return cls.UNKNOWN, round(signature) @classmethod def from_float(cls, value): @@ -416,47 +411,51 @@ def __init__(self): self.flags = 0x00000000 self.resolution = P3D_LOD_Resolution() - self.verts = {} - self.normals = {} - self.faces = {} + self.verts = [] + self.normals = [] + self.faces = [] self.taggs = [] + struct_vert = struct.Struct(' 0 and path[0] != "\\": path = "\\" + path diff --git a/Arma3ObjectBuilder/scripts/convert_atbx_to_a3ob.py b/Arma3ObjectBuilder/scripts/convert_atbx_to_a3ob.py index f31a72b..4208aba 100644 --- a/Arma3ObjectBuilder/scripts/convert_atbx_to_a3ob.py +++ b/Arma3ObjectBuilder/scripts/convert_atbx_to_a3ob.py @@ -37,15 +37,16 @@ class Settings: import bpy name = None -for addon in bpy.context.preferences.addons.keys(): - if addon.endswith("Arma3ObjectBuilder"): - name = addon +for addon in bpy.context.preferences.addons: + if addon.module.endswith("Arma3ObjectBuilder"): + name = addon.module break else: raise Exception("Arma 3 Object Builder could not be found") -a3ob_utils = importlib.import_module(addon).utilities -a3ob_io = importlib.import_module(addon).io +a3ob = importlib.import_module(name) +a3ob_utils = a3ob.utilities +a3ob_io = a3ob.io utils = a3ob_utils.generic structutils = a3ob_utils.structure @@ -75,7 +76,7 @@ class Settings: '5.000e+15': str(LOD.HITPOINTS), '6.000e+15': str(LOD.VIEW_GEOMETRY), '7.000e+15': str(LOD.FIRE_GEOMETRY), - '8.000e+15': str(LOD.VIEW_CARGO_GEOMERTRY), + '8.000e+15': str(LOD.VIEW_CARGO_GEOMETRY), '9.000e+15': str(LOD.VIEW_CARGO_FIRE_GEOMETRY), '1.000e+16': str(LOD.VIEW_COMMANDER), '1.100e+16': str(LOD.VIEW_COMMANDER_GEOMETRY), diff --git a/Arma3ObjectBuilder/scripts/convert_bmtr_to_rtm.py b/Arma3ObjectBuilder/scripts/convert_bmtr_to_rtm.py index d83d861..655259a 100644 --- a/Arma3ObjectBuilder/scripts/convert_bmtr_to_rtm.py +++ b/Arma3ObjectBuilder/scripts/convert_bmtr_to_rtm.py @@ -41,16 +41,16 @@ class Settings: import bpy name = None -for addon in bpy.context.preferences.addons.keys(): - if addon.endswith("Arma3ObjectBuilder"): - name = addon +for addon in bpy.context.preferences.addons: + if addon.module.endswith("Arma3ObjectBuilder"): + name = addon.module break else: raise Exception("Arma 3 Object Builder could not be found") -a3ob_utils = importlib.import_module(addon).utilities -a3ob_io = importlib.import_module(addon).io - +a3ob = importlib.import_module(name) +a3ob_utils = a3ob.utilities +a3ob_io = a3ob.io rtm = a3ob_io.data_rtm data = a3ob_utils.data ProcessLogger = a3ob_utils.logger.ProcessLogger diff --git a/Arma3ObjectBuilder/scripts/import_p3d_batch.py b/Arma3ObjectBuilder/scripts/import_p3d_batch.py index 6664b6e..f46f1c9 100644 --- a/Arma3ObjectBuilder/scripts/import_p3d_batch.py +++ b/Arma3ObjectBuilder/scripts/import_p3d_batch.py @@ -59,14 +59,15 @@ class Settings: import bpy name = None -for addon in bpy.context.preferences.addons.keys(): - if addon.endswith("Arma3ObjectBuilder"): - name = addon +for addon in bpy.context.preferences.addons: + if addon.module.endswith("Arma3ObjectBuilder"): + name = addon.module break else: raise Exception("Arma 3 Object Builder could not be found") -read_file = importlib.import_module(addon).io.import_p3d.read_file +a3ob = importlib.import_module(name) +read_file = a3ob.io.import_p3d.read_file def main(): diff --git a/Arma3ObjectBuilder/scripts/import_rtm_batch.py b/Arma3ObjectBuilder/scripts/import_rtm_batch.py index 6a0fb77..dfe3837 100644 --- a/Arma3ObjectBuilder/scripts/import_rtm_batch.py +++ b/Arma3ObjectBuilder/scripts/import_rtm_batch.py @@ -50,14 +50,15 @@ class Settings: import bpy name = None -for addon in bpy.context.preferences.addons.keys(): - if addon.endswith("Arma3ObjectBuilder"): - name = addon +for addon in bpy.context.preferences.addons: + if addon.module.endswith("Arma3ObjectBuilder"): + name = addon.module break else: raise Exception("Arma 3 Object Builder could not be found") -import_file = importlib.import_module(addon).io.import_rtm.import_file +a3ob = importlib.import_module(name) +import_file = a3ob.io.import_rtm.import_file def main(): diff --git a/Arma3ObjectBuilder/scripts/ofp2_manskeleton.py b/Arma3ObjectBuilder/scripts/ofp2_manskeleton.py index bf145d8..33b39aa 100644 --- a/Arma3ObjectBuilder/scripts/ofp2_manskeleton.py +++ b/Arma3ObjectBuilder/scripts/ofp2_manskeleton.py @@ -27,14 +27,15 @@ class Settings: import bpy name = None -for addon in bpy.context.preferences.addons.keys(): - if addon.endswith("Arma3ObjectBuilder"): - name = addon +for addon in bpy.context.preferences.addons: + if addon.module.endswith("Arma3ObjectBuilder"): + name = addon.module break else: raise Exception("Arma 3 Object Builder could not be found") -data = importlib.import_module(name).utilities.data +a3ob = importlib.import_module(name) +data = a3ob.utilities.data def main(): diff --git a/Arma3ObjectBuilder/ui/__init__.py b/Arma3ObjectBuilder/ui/__init__.py index 6a07273..5653fa3 100644 --- a/Arma3ObjectBuilder/ui/__init__.py +++ b/Arma3ObjectBuilder/ui/__init__.py @@ -1,3 +1,46 @@ +if "props_object_mesh" in locals(): + from importlib import reload + + if "import_export_armature" in locals(): + reload(import_export_armature) + if "import_export_asc" in locals(): + reload(import_export_asc) + if "import_export_mcfg" in locals(): + reload(import_export_mcfg) + if "import_export_p3d" in locals(): + reload(import_export_p3d) + if "import_export_rtm" in locals(): + reload(import_export_rtm) + if "import_export_tbcsv" in locals(): + reload(import_export_tbcsv) + if "props_action" in locals(): + reload(props_action) + if "props_material" in locals(): + reload(props_material) + if "props_object_mesh" in locals(): + reload(props_object_mesh) + if "tool_outliner" in locals(): + reload(tool_outliner) + if "tool_hitpoint" in locals(): + reload(tool_hitpoint) + if "tool_mass" in locals(): + reload(tool_mass) + if "tool_materials" in locals(): + reload(tool_materials) + if "tool_paths" in locals(): + reload(tool_paths) + if "tool_proxies" in locals(): + reload(tool_proxies) + if "tool_rigging" in locals(): + reload(tool_rigging) + if "tool_scripts" in locals(): + reload(tool_scripts) + if "tool_utilities" in locals(): + reload(tool_utilities) + if "tool_validation" in locals(): + reload(tool_validation) + + from . import import_export_armature from . import import_export_asc from . import import_export_mcfg diff --git a/Arma3ObjectBuilder/ui/import_export_armature.py b/Arma3ObjectBuilder/ui/import_export_armature.py index 4845cb2..97e5f26 100644 --- a/Arma3ObjectBuilder/ui/import_export_armature.py +++ b/Arma3ObjectBuilder/ui/import_export_armature.py @@ -1,5 +1,3 @@ -import traceback - import bpy import bpy_extras @@ -49,11 +47,7 @@ def execute(self, context): utils.op_report(self, {'ERROR'}, "Invalid skeleton definiton, run skeleton validation for RTM for more info") return {'FINISHED'} - try: - arm.import_armature(self, skeleton) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + arm.import_armature(self, skeleton) return {'FINISHED'} diff --git a/Arma3ObjectBuilder/ui/import_export_asc.py b/Arma3ObjectBuilder/ui/import_export_asc.py index 7a073ed..ab39700 100644 --- a/Arma3ObjectBuilder/ui/import_export_asc.py +++ b/Arma3ObjectBuilder/ui/import_export_asc.py @@ -1,5 +1,3 @@ -import traceback - import bpy import bpy_extras @@ -37,14 +35,8 @@ def draw(self, context): def execute(self, context): with open(self.filepath) as file: - try: - import_asc.read_file(self, context, file) - utils.op_report(self, {'INFO'}, "Successfully imported DEM") - except import_asc.asc.ASC_Error as ex: - utils.op_report(self, {'ERROR'}, ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, ex) - traceback.print_exc() + import_asc.read_file(self, context, file) + utils.op_report(self, {'INFO'}, "Successfully imported DTM") return {'FINISHED'} @@ -119,24 +111,12 @@ def poll(cls, context): def draw(self, context): pass - def execute(self, context): - if not utils.OutputManager.can_access_path(self.filepath): - utils.op_report(self, {'ERROR'}, "Cannot write to target file (file likely in use by another blocking process)") - return {'FINISHED'} - + def execute(self, context): obj = context.active_object - output = utils.OutputManager(self.filepath, "w") - with output as file: - try: - export_asc.write_file(self, context, file, obj) - output.success = True - utils.op_report(self, {'INFO'}, "Successfuly exported DTM") - except export_asc.asc.ASC_Error: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + with utils.ExportFileHandler(self.filepath, "wt") as file: + export_asc.write_file(self, context, file, obj) + utils.op_report(self, {'INFO'}, "Successfuly exported DTM") return {'FINISHED'} diff --git a/Arma3ObjectBuilder/ui/import_export_mcfg.py b/Arma3ObjectBuilder/ui/import_export_mcfg.py index 187c5de..5667a87 100644 --- a/Arma3ObjectBuilder/ui/import_export_mcfg.py +++ b/Arma3ObjectBuilder/ui/import_export_mcfg.py @@ -1,4 +1,3 @@ -import traceback import os import bpy @@ -35,20 +34,13 @@ class A3OB_OP_import_mcfg(bpy.types.Operator, bpy_extras.io_utils.ImportHelper): @classmethod def poll(cls, context): - return os.path.isfile(utils.get_cfg_convert()) + return os.path.isfile(import_mcfg.get_cfg_convert()) def draw(self, context): pass def execute(self, context): - count_skeletons = 0 - try: - count_skeletons = import_mcfg.read_file(self, context) - except import_mcfg.rap.RAP_Error as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + count_skeletons = import_mcfg.read_file(self, context) if count_skeletons > 0: utils.op_report(self, {'INFO'}, "Successfully imported %d skeleton(s)" % count_skeletons) @@ -109,27 +101,17 @@ def poll(cls, context): def draw(self, context): pass - def execute(self, context): - if not utils.OutputManager.can_access_path(self.filepath): - utils.op_report(self, {'ERROR'}, "Cannot write to target file (file likely in use by another blocking process)") - return {'FINISHED'} - + def execute(self, context): scene_props = context.scene.a3ob_rigging skeleton = scene_props.skeletons[self.skeleton_index] - output = utils.OutputManager(self.filepath, "w") validator = Validator(ProcessLoggerNull()) if not validator.validate_skeleton(skeleton, False, True): utils.op_report(self, {'ERROR'}, "Invalid skeleton definiton, run skeleton validation for more info") return {'FINISHED'} - with output as file: - try: - export_mcfg.write_file(self, skeleton, file) - output.success = True - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + with utils.ExportFileHandler(self.filepath, "w") as file: + export_mcfg.write_file(self, skeleton, file) return {'FINISHED'} diff --git a/Arma3ObjectBuilder/ui/import_export_p3d.py b/Arma3ObjectBuilder/ui/import_export_p3d.py index 02d6322..c481be8 100644 --- a/Arma3ObjectBuilder/ui/import_export_p3d.py +++ b/Arma3ObjectBuilder/ui/import_export_p3d.py @@ -1,6 +1,3 @@ -import traceback -import struct - import bpy import bpy_extras @@ -100,17 +97,8 @@ def draw(self, context): def execute(self, context): with open(self.filepath, "rb") as file: - try: - lod_objects = import_p3d.read_file(self, context, file) - utils.op_report(self, {'INFO'}, "Successfully imported %d LODs (check the logs in the system console)" % len(lod_objects)) - except struct.error as ex: - utils.op_report(self, {'ERROR'}, "Unexpected EndOfFile (check the system console)") - traceback.print_exc() - except import_p3d.p3d.P3D_Error as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + lod_objects = import_p3d.read_file(self, context, file) + utils.op_report(self, {'INFO'}, "Successfully imported %d LODs (check the logs in the system console)" % len(lod_objects)) return {'FINISHED'} @@ -328,33 +316,22 @@ class A3OB_OP_export_p3d(bpy.types.Operator, bpy_extras.io_utils.ExportHelper): def draw(self, context): pass - def execute(self, context): - if not utils.OutputManager.can_access_path(self.filepath): - utils.op_report(self, {'ERROR'}, "Cannot write to target file (file likely in use by another blocking process)") + def execute(self, context): + if not export_p3d.can_export(self, context): + utils.op_report(self, {'ERROR'}, "There are no LODs to export") return {'FINISHED'} - if export_p3d.can_export(self, context): - output = utils.OutputManager(self.filepath, "wb") - temp_collection = export_p3d.create_temp_collection(context) - with output as file: - try: - lod_count, exported_count = export_p3d.write_file(self, context, file, temp_collection) - if lod_count == exported_count: - utils.op_report(self, {'INFO'}, "Successfully exported all %d LODs (check the logs in the system console)" % exported_count) - else: - utils.op_report(self, {'WARNING'}, "Only exported %d/%d LODs (check the logs in the system console)" % (exported_count, lod_count)) - output.success = True - except export_p3d.p3d.P3D_Error as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() - - if not utils.get_addon_preferences().preserve_preprocessed_lods: - export_p3d.cleanup_temp_collection(temp_collection) - - else: - utils.op_report(self, {'ERROR'}, "There are no LODs to export") + temp_collection = export_p3d.create_temp_collection(context) + + with utils.ExportFileHandler(self.filepath, "wb") as file: + lod_count, exported_count = export_p3d.write_file(self, context, file, temp_collection) + if lod_count == exported_count: + utils.op_report(self, {'INFO'}, "Successfully exported all %d LODs (check the logs in the system console)" % exported_count) + else: + utils.op_report(self, {'WARNING'}, "Only exported %d/%d LODs (check the logs in the system console)" % (exported_count, lod_count)) + + if not utils.AddonInfo.prefs.preserve_preprocessed_lods: + export_p3d.cleanup_temp_collection(temp_collection) return {'FINISHED'} diff --git a/Arma3ObjectBuilder/ui/import_export_rtm.py b/Arma3ObjectBuilder/ui/import_export_rtm.py index e4a4725..ec2a28c 100644 --- a/Arma3ObjectBuilder/ui/import_export_rtm.py +++ b/Arma3ObjectBuilder/ui/import_export_rtm.py @@ -1,5 +1,3 @@ -import traceback - import bpy import bpy_extras @@ -83,11 +81,7 @@ def invoke(self, context, event): return super().invoke(context, event) - def execute(self, context): - if not utils.OutputManager.can_access_path(self.filepath): - utils.op_report(self, {'ERROR'}, "Cannot write to target file (file likely in use by another blocking process)") - return {'FINISHED'} - + def execute(self, context): obj = context.object action = None if obj.animation_data: @@ -107,23 +101,13 @@ def execute(self, context): utils.op_report(self, {'ERROR'}, "Invalid skeleton definiton, run skeleton validation for RTM for more info") return {'FINISHED'} - output = utils.OutputManager(self.filepath, "wb") - with output as file: - try: - static, frame_count = export_rtm.write_file(self, context, file, obj, action) - - if not self.static_pose and static: - utils.op_report(self, {'INFO'}, "Exported as static pose") - else: - utils.op_report(self, {'INFO'}, "Exported %d frame(s)" % frame_count) - - output.success = True - - except export_rtm.rtm.RTM_Error as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + with utils.ExportFileHandler(self.filepath, "wb") as file: + static, frame_count = export_rtm.write_file(self, context, file, obj, action) + + if not self.static_pose and static: + utils.op_report(self, {'INFO'}, "Exported as static pose") + else: + utils.op_report(self, {'INFO'}, "Exported %d frame(s)" % frame_count) return {'FINISHED'} @@ -274,15 +258,8 @@ def invoke(self, context, event): return super().invoke(context, event) def execute(self, context): - count_frames = 0 with open(self.filepath, "rb") as file: - try: - count_frames = import_rtm.import_file(self, context, file) - except (import_rtm.rtm.RTM_Error, import_rtm.rtm.BMTR_Error) as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + count_frames = import_rtm.import_file(self, context, file) if count_frames > 0: utils.op_report(self, {'INFO'}, "Successfully imported %d frame(s)" % count_frames) diff --git a/Arma3ObjectBuilder/ui/import_export_tbcsv.py b/Arma3ObjectBuilder/ui/import_export_tbcsv.py index ba8cc50..41640f9 100644 --- a/Arma3ObjectBuilder/ui/import_export_tbcsv.py +++ b/Arma3ObjectBuilder/ui/import_export_tbcsv.py @@ -1,5 +1,3 @@ -import traceback - import bpy import bpy_extras @@ -60,17 +58,12 @@ def draw(self, context): def execute(self, context): with open(self.filepath, "rt") as file: - try: - count_read, count_found = import_tbcsv.read_file(self, context, file) - if count_found > 0: - utils.op_report(self, {'INFO'}, "Successfully imported %d/%d object positions (check the logs in the system console)" % (count_found, count_read)) - else: - utils.op_report(self, {'WARNING'}, "Could not spawn any objects, template objects were not found (check the logs in the system console)") - except import_tbcsv.tb.TBCSV_Error as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + count_read, count_found = import_tbcsv.read_file(self, context, file) + + if count_found > 0: + utils.op_report(self, {'INFO'}, "Successfully imported %d/%d object positions (check the logs in the system console)" % (count_found, count_read)) + else: + utils.op_report(self, {'WARNING'}, "Could not spawn any objects, template objects were not found (check the logs in the system console)") return {'FINISHED'} @@ -147,25 +140,14 @@ def execute(self, context): if not self.collection and self.name_source == 'COLLECTION': utils.op_report(self, {'ERROR'}, "Collection name can only be used when exporting a collection") return {'FINISHED'} - - if not utils.OutputManager.can_access_path(self.filepath): - utils.op_report(self, {'ERROR'}, "Cannot write to target file (file likely in use by another blocking process)") - return {'FINISHED'} - output = utils.OutputManager(self.filepath, "wt") - with output as file: - try: - count = export_tbcsv.write_file(self, context, file) - if count > 0: - utils.op_report(self, {'INFO'}, "Successfully exported %d object positions (check the logs in the system console)" % count) - else: - utils.op_report(self, {'WARNING'}, "Could not export any object positions (check the logs in the system console)") - output.success = True - except import_tbcsv.tb.TBCSV_Error as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - except Exception as ex: - utils.op_report(self, {'ERROR'}, "%s (check the system console)" % ex) - traceback.print_exc() + with utils.ExportFileHandler(self.filepath, "wt") as file: + count = export_tbcsv.write_file(self, context, file) + + if count > 0: + utils.op_report(self, {'INFO'}, "Successfully exported %d object positions (check the logs in the system console)" % count) + else: + utils.op_report(self, {'WARNING'}, "Could not export any object positions (check the logs in the system console)") return {'FINISHED'} diff --git a/Arma3ObjectBuilder/ui/props_object_mesh.py b/Arma3ObjectBuilder/ui/props_object_mesh.py index ac3393b..c899866 100644 --- a/Arma3ObjectBuilder/ui/props_object_mesh.py +++ b/Arma3ObjectBuilder/ui/props_object_mesh.py @@ -1,5 +1,6 @@ import bpy +from .. import get_prefs, get_icon from ..utilities import generic as utils from ..utilities import data from ..utilities import proxy as proxyutils @@ -269,7 +270,7 @@ def execute(self, context): flag_props = obj.a3ob_properties_object_flags item = flag_props.vertex.add() item.name = "New Flag Group" - item.set_flag(utils.get_addon_preferences().flag_vertex) + item.set_flag(get_prefs().flag_vertex) flag_props.vertex_index = len(flag_props.vertex) - 1 return {'FINISHED'} @@ -398,7 +399,7 @@ def execute(self, context): flag_props = obj.a3ob_properties_object_flags item = flag_props.face.add() item.name = "New Flag Group" - item.set_flag(utils.get_addon_preferences().flag_face) + item.set_flag(get_prefs().flag_face) flag_props.face_index = len(flag_props.face) - 1 return {'FINISHED'} @@ -900,7 +901,7 @@ def draw(self, context): def menu_func(self, context): self.layout.separator() - self.layout.operator(A3OB_OT_proxy_add.bl_idname, text="Arma 3 Proxy", icon_value=utils.get_icon("op_proxy_add")) + self.layout.operator(A3OB_OT_proxy_add.bl_idname, text="Arma 3 Proxy", icon_value=get_icon("op_proxy_add")) classes = ( diff --git a/Arma3ObjectBuilder/ui/tool_hitpoint.py b/Arma3ObjectBuilder/ui/tool_hitpoint.py index 65863e7..a744e33 100644 --- a/Arma3ObjectBuilder/ui/tool_hitpoint.py +++ b/Arma3ObjectBuilder/ui/tool_hitpoint.py @@ -1,5 +1,6 @@ import bpy +from .. import get_icon from ..utilities import generic as utils from ..utilities import clouds as cloudutils @@ -65,7 +66,7 @@ def draw(self, context): col_selection = layout.column(align=True, heading="Selection:") col_selection.prop(scene_props, "selection", text="", icon='MESH_DATA') - layout.operator("a3ob.hitpoints_generate", text="Generate", icon_value=utils.get_icon("op_hitpoints_generate")) + layout.operator("a3ob.hitpoints_generate", text="Generate", icon_value=get_icon("op_hitpoints_generate")) classes = ( diff --git a/Arma3ObjectBuilder/ui/tool_mass.py b/Arma3ObjectBuilder/ui/tool_mass.py index 4e7eed4..de9d337 100644 --- a/Arma3ObjectBuilder/ui/tool_mass.py +++ b/Arma3ObjectBuilder/ui/tool_mass.py @@ -1,5 +1,6 @@ import bpy +from .. import get_icon from ..utilities import generic as utils from ..utilities import masses as massutils @@ -155,14 +156,14 @@ def draw(self, context): col = layout.column(align=True) if scene_props.source == 'MASS': col.prop(scene_props, "mass") - col.operator("a3ob.vertex_mass_set", icon_value=utils.get_icon("op_mass_set")) - col.operator("a3ob.vertex_mass_distribute", icon_value=utils.get_icon("op_mass_distribute")) + col.operator("a3ob.vertex_mass_set", icon_value=get_icon("op_mass_set")) + col.operator("a3ob.vertex_mass_distribute", icon_value=get_icon("op_mass_distribute")) elif scene_props.source == 'DENSITY': col.prop(scene_props, "density") - col.operator("a3ob.vertex_mass_set_density", icon_value=utils.get_icon("op_mass_set_density")) + col.operator("a3ob.vertex_mass_set_density", icon_value=get_icon("op_mass_set_density")) col.separator() - col.operator("a3ob.vertex_mass_clear", icon_value=utils.get_icon("op_mass_clear")) + col.operator("a3ob.vertex_mass_clear", icon_value=get_icon("op_mass_clear")) class A3OB_PT_vertex_mass_analyze(bpy.types.Panel): @@ -204,8 +205,8 @@ def draw(self, context): row_method = layout.row(align=True) row_method.prop(scene_props, "method", text="Method", expand=True) - layout.operator("a3ob.vertex_mass_visualize", icon_value=utils.get_icon("op_visualize")) - layout.operator("a3ob.vertex_mass_center", icon_value=utils.get_icon("op_mass_center")) + layout.operator("a3ob.vertex_mass_visualize", icon_value=get_icon("op_visualize")) + layout.operator("a3ob.vertex_mass_center", icon_value=get_icon("op_mass_center")) layout.label(text="Stats:") col_stats = layout.column(align=True) diff --git a/Arma3ObjectBuilder/ui/tool_materials.py b/Arma3ObjectBuilder/ui/tool_materials.py index c49d74a..d827919 100644 --- a/Arma3ObjectBuilder/ui/tool_materials.py +++ b/Arma3ObjectBuilder/ui/tool_materials.py @@ -2,6 +2,7 @@ import bpy +from .. import get_prefs, get_icon from ..utilities import generic as utils from ..utilities import materials as matutils @@ -27,7 +28,7 @@ def execute(self, context): path = scene_props.templates[scene_props.templates_index].path template = matutils.RVMATTemplate(path) - success = template.write_output(utils.get_addon_preferences().project_root, scene_props.folder, scene_props.basename, scene_props.check_files_exist) + success = template.write_output(get_prefs().project_root, scene_props.folder, scene_props.basename, scene_props.check_files_exist) if success: self.report({'INFO'}, "Successfully generated %s.rvmat" % scene_props.basename) @@ -149,7 +150,7 @@ def draw(self, context): col_list = layout.column(align=True) col_list.template_list("A3OB_UL_materials_templates", "A3OB_materials_templates", scene_props, "templates", scene_props, "templates_index", item_dyntip_propname="path") - col_list.operator("a3ob.materials_templates_reload", icon_value=utils.get_icon("op_refresh")) + col_list.operator("a3ob.materials_templates_reload", icon_value=get_icon("op_refresh")) layout.prop(scene_props, "folder") layout.prop(scene_props, "basename") diff --git a/Arma3ObjectBuilder/ui/tool_outliner.py b/Arma3ObjectBuilder/ui/tool_outliner.py index 1605f29..2b78ed0 100644 --- a/Arma3ObjectBuilder/ui/tool_outliner.py +++ b/Arma3ObjectBuilder/ui/tool_outliner.py @@ -1,6 +1,7 @@ import bpy from bpy.app.handlers import persistent +from .. import get_prefs from ..utilities import generic as utils from ..utilities import outliner as linerutils @@ -108,7 +109,7 @@ class A3OB_PT_outliner(bpy.types.Panel): @classmethod def poll(cls, context): - return utils.get_addon_preferences().outliner == 'ENABLED' + return get_prefs().outliner == 'ENABLED' def draw_header(self, context): utils.draw_panel_header(self) @@ -176,7 +177,7 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - if utils.get_addon_preferences().outliner == 'ENABLED': + if get_prefs().outliner == 'ENABLED': bpy.app.handlers.depsgraph_update_post.append(depsgraph_update_post_handler) print("\t" + "UI: Outliner") diff --git a/Arma3ObjectBuilder/ui/tool_paths.py b/Arma3ObjectBuilder/ui/tool_paths.py index 3909f17..1fb57ab 100644 --- a/Arma3ObjectBuilder/ui/tool_paths.py +++ b/Arma3ObjectBuilder/ui/tool_paths.py @@ -1,5 +1,6 @@ import bpy +from .. import get_icon from ..utilities import generic as utils from ..utilities import renaming as renameutils @@ -109,7 +110,7 @@ def draw(self, context): col_list.template_list("A3OB_UL_renamable_paths", "A3OB_bulk_rename", scene_props, "path_list", scene_props, "path_list_index") row_filter = col_list.row(align=True) - row_filter.operator("a3ob.rename_list_refresh", text="", icon_value=utils.get_icon("op_refresh")) + row_filter.operator("a3ob.rename_list_refresh", text="", icon_value=get_icon("op_refresh")) row_filter.prop(scene_props, "source_filter") if utils.is_valid_idx(scene_props.path_list_index, scene_props.path_list): @@ -117,7 +118,7 @@ def draw(self, context): col_edit.prop(scene_props.path_list[scene_props.path_list_index], "path", text="") col_edit.prop(scene_props, "new_path", text="") col_edit.separator() - col_edit.operator("a3ob.rename_path_item", icon_value=utils.get_icon("op_replace")) + col_edit.operator("a3ob.rename_path_item", icon_value=get_icon("op_replace")) class A3OB_PT_renaming_paths_root(bpy.types.Panel): @@ -139,7 +140,7 @@ def draw(self, context): row_filter = layout.row(align=True) row_filter.prop(scene_props, "source_filter") - layout.operator("a3ob.rename_path_root", icon_value=utils.get_icon("op_replace")) + layout.operator("a3ob.rename_path_root", icon_value=get_icon("op_replace")) class A3OB_PT_renaming_vertex_groups(bpy.types.Panel): @@ -158,7 +159,7 @@ def draw(self, context): col_edit.prop(scene_props, "vgroup_old") col_edit.prop(scene_props, "vgroup_new") layout.prop(scene_props, "vgroup_match_whole") - layout.operator("a3ob.rename_vertex_groups", icon_value=utils.get_icon("op_replace")) + layout.operator("a3ob.rename_vertex_groups", icon_value=get_icon("op_replace")) classes = ( diff --git a/Arma3ObjectBuilder/ui/tool_proxies.py b/Arma3ObjectBuilder/ui/tool_proxies.py index ad9cf50..c9c1df9 100644 --- a/Arma3ObjectBuilder/ui/tool_proxies.py +++ b/Arma3ObjectBuilder/ui/tool_proxies.py @@ -5,6 +5,7 @@ import bpy import mathutils +from .. import get_icon from ..utilities import generic as utils from ..utilities import lod as lodutils from ..utilities import compat as computils @@ -335,14 +336,14 @@ def draw(self, context): layout = self.layout col_align = layout.column(align=True) - col_align.operator("a3ob.proxy_align", icon_value=utils.get_icon("op_proxy_align")) - col_align.operator("a3ob.proxy_align_object", icon_value=utils.get_icon("op_proxy_align_object")) - layout.operator("a3ob.proxy_realign_ocs", icon_value=utils.get_icon("op_proxy_realign")) - layout.operator("a3ob.proxy_extract", icon_value=utils.get_icon("op_proxy_extract")) + col_align.operator("a3ob.proxy_align", icon_value=get_icon("op_proxy_align")) + col_align.operator("a3ob.proxy_align_object", icon_value=get_icon("op_proxy_align_object")) + layout.operator("a3ob.proxy_realign_ocs", icon_value=get_icon("op_proxy_realign")) + layout.operator("a3ob.proxy_extract", icon_value=get_icon("op_proxy_extract")) col_move = layout.column(align=True) - col_move.operator("a3ob.proxy_copy", icon_value=utils.get_icon("op_proxy_copy")) - col_move.operator("a3ob.proxy_copy_all", icon_value=utils.get_icon("op_proxy_copy_all")) - col_move.operator("a3ob.proxy_transfer", icon_value=utils.get_icon("op_proxy_transfer")) + col_move.operator("a3ob.proxy_copy", icon_value=get_icon("op_proxy_copy")) + col_move.operator("a3ob.proxy_copy_all", icon_value=get_icon("op_proxy_copy_all")) + col_move.operator("a3ob.proxy_transfer", icon_value=get_icon("op_proxy_transfer")) class A3OB_UL_lod_objects_selector(bpy.types.UIList): diff --git a/Arma3ObjectBuilder/ui/tool_rigging.py b/Arma3ObjectBuilder/ui/tool_rigging.py index 6c2b6fe..82d4956 100644 --- a/Arma3ObjectBuilder/ui/tool_rigging.py +++ b/Arma3ObjectBuilder/ui/tool_rigging.py @@ -1,5 +1,6 @@ import bpy +from .. import get_icon from ..utilities import generic as utils from ..utilities import rigging as riggingutils from ..utilities import data @@ -635,7 +636,7 @@ def draw(self, context): col_bones_operators.operator("a3ob.rigging_skeletons_bones_move", text="", icon='TRIA_DOWN').direction = 'DOWN' - layout.operator("a3ob.rigging_pivots_from_armature", icon_value=utils.get_icon("op_pivots_from_armature")) + layout.operator("a3ob.rigging_pivots_from_armature", icon_value=get_icon("op_pivots_from_armature")) class A3OB_PT_rigging_weights(bpy.types.Panel): @@ -655,15 +656,15 @@ def draw(self, context): layout = self.layout col_select = layout.column(align=True) - col_select.operator("a3ob.rigging_weights_select_overdetermined", icon_value=utils.get_icon("op_weights_select_overdetermined")) - col_select.operator("a3ob.rigging_weights_select_unnormalized", icon_value=utils.get_icon("op_weights_select_unnormalized")) + col_select.operator("a3ob.rigging_weights_select_overdetermined", icon_value=get_icon("op_weights_select_overdetermined")) + col_select.operator("a3ob.rigging_weights_select_unnormalized", icon_value=get_icon("op_weights_select_unnormalized")) col_edit = layout.column(align=True) - col_edit.operator("a3ob.rigging_weights_prune", icon_value=utils.get_icon("op_weights_prune")) - col_edit.operator("a3ob.rigging_weights_prune_overdetermined", icon_value=utils.get_icon("op_weights_prune_overdetermined")) - col_edit.operator("a3ob.rigging_weights_normalize", icon_value=utils.get_icon("op_weights_normalize")) + col_edit.operator("a3ob.rigging_weights_prune", icon_value=get_icon("op_weights_prune")) + col_edit.operator("a3ob.rigging_weights_prune_overdetermined", icon_value=get_icon("op_weights_prune_overdetermined")) + col_edit.operator("a3ob.rigging_weights_normalize", icon_value=get_icon("op_weights_normalize")) - layout.operator("a3ob.rigging_weights_cleanup", icon_value=utils.get_icon("op_weights_cleanup")) + layout.operator("a3ob.rigging_weights_cleanup", icon_value=get_icon("op_weights_cleanup")) layout.prop(scene_props, "prune_threshold") diff --git a/Arma3ObjectBuilder/ui/tool_scripts.py b/Arma3ObjectBuilder/ui/tool_scripts.py index a747d8a..be6de91 100644 --- a/Arma3ObjectBuilder/ui/tool_scripts.py +++ b/Arma3ObjectBuilder/ui/tool_scripts.py @@ -2,7 +2,7 @@ import bpy -from ..utilities import generic as utils +from .. import get_icon, addon_dir scripts = { @@ -28,7 +28,7 @@ def get_scripts_directory(): - return os.path.join(utils.get_addon_directory(), "scripts") + return os.path.join(addon_dir, "scripts") def add_operators(layout, files): @@ -92,7 +92,7 @@ def draw(self, context): def draw_scripts_menu(self, context): self.layout.separator() - self.layout.menu("A3OB_MT_scripts", text="Object Builder", icon_value=utils.get_icon("addon")) + self.layout.menu("A3OB_MT_scripts", text="Object Builder", icon_value=get_icon("addon")) def register(): diff --git a/Arma3ObjectBuilder/ui/tool_utilities.py b/Arma3ObjectBuilder/ui/tool_utilities.py index f2b3af0..c7212a6 100644 --- a/Arma3ObjectBuilder/ui/tool_utilities.py +++ b/Arma3ObjectBuilder/ui/tool_utilities.py @@ -2,8 +2,8 @@ import bpy +from .. import get_icon, addon_dir from ..utilities import structure as structutils -from ..utilities import generic as utils from ..utilities import data @@ -238,7 +238,7 @@ def poll(cls, context): return True def execute(self, context): - path = os.path.join(utils.get_addon_directory(), "CHANGELOG.md") + path = os.path.join(addon_dir, "CHANGELOG.md") bpy.ops.text.open(filepath=path, internal=True) self.report({'INFO'}, "See CHANGELOG.md text block") @@ -525,19 +525,19 @@ def menu_func(self, context): self.layout.separator() col = self.layout.column() col.ui_units_x = 5.2 - col.menu("A3OB_MT_object_builder", icon_value=utils.get_icon("addon")) + col.menu("A3OB_MT_object_builder", icon_value=get_icon("addon")) def vertex_groups_func(self, context): layout = self.layout row = layout.row(align=True) row.alignment = 'RIGHT' - row.menu("A3OB_MT_vertex_groups", text="", icon_value=utils.get_icon("addon")) + row.menu("A3OB_MT_vertex_groups", text="", icon_value=get_icon("addon")) def menu_help_func(self, context): self.layout.separator() - self.layout.menu("A3OB_MT_help", icon_value=utils.get_icon("addon")) + self.layout.menu("A3OB_MT_help", icon_value=get_icon("addon")) def register(): diff --git a/Arma3ObjectBuilder/ui/tool_validation.py b/Arma3ObjectBuilder/ui/tool_validation.py index e3751cb..ad86d06 100644 --- a/Arma3ObjectBuilder/ui/tool_validation.py +++ b/Arma3ObjectBuilder/ui/tool_validation.py @@ -1,5 +1,6 @@ import bpy +from .. import get_icon from ..utilities import generic as utils from ..utilities.validator import Validator from ..utilities.logger import ProcessLogger @@ -78,7 +79,7 @@ def draw(self, context): layout.prop(scene_props, "relative_paths") layout.separator() - layout.operator("a3ob.validate_for_lod", text="Validate", icon_value=utils.get_icon("op_validate")) + layout.operator("a3ob.validate_for_lod", text="Validate", icon_value=get_icon("op_validate")) classes = ( diff --git a/Arma3ObjectBuilder/utilities/__init__.py b/Arma3ObjectBuilder/utilities/__init__.py index ebd8615..be69e03 100644 --- a/Arma3ObjectBuilder/utilities/__init__.py +++ b/Arma3ObjectBuilder/utilities/__init__.py @@ -1,15 +1,51 @@ +if "data" in locals(): + from importlib import reload + + if "data" in locals(): + reload(data) + if "logger" in locals(): + reload(logger) + if "compat" in locals(): + reload(compat) + if "clouds" in locals(): + reload(clouds) + if "colors" in locals(): + reload(colors) + if "proxy" in locals(): + reload(proxy) + if "renaming" in locals(): + reload(renaming) + if "generic" in locals(): + reload(generic) + if "validator" in locals(): + reload(validator) + if "flags" in locals(): + reload(flags) + if "lod" in locals(): + reload(lod) + if "masses" in locals(): + reload(masses) + if "outliner" in locals(): + reload(outliner) + if "rigging" in locals(): + reload(rigging) + if "structure" in locals(): + reload(structure) + + +# In order of dependency +from . import data +from . import logger +from . import compat from . import clouds from . import colors -from . import compat -from . import data -from . import flags +from . import proxy +from . import renaming from . import generic +from . import validator +from . import flags from . import lod -from . import logger from . import masses from . import outliner -from . import proxy -from . import renaming from . import rigging from . import structure -from . import validator diff --git a/Arma3ObjectBuilder/utilities/data.py b/Arma3ObjectBuilder/utilities/data.py index b23a16d..a9ca99c 100644 --- a/Arma3ObjectBuilder/utilities/data.py +++ b/Arma3ObjectBuilder/utilities/data.py @@ -1,9 +1,6 @@ # Various hard coded data used in I/O and throughout the UI. -from ..io.data_p3d import P3D_LOD_Resolution as LOD - - flags_vertex_surface = { 'NORMAL': 0x00000000, 'SURFACE_ON': 0x00000001, @@ -67,13 +64,49 @@ flag_face_user_mask = 0xfe000000 +class LOD(): + VISUAL = 0 + VIEW_GUNNER = 1 + VIEW_PILOT = 2 + VIEW_CARGO = 3 + SHADOW = 4 + EDIT = 5 + GEOMETRY = 6 + GEOMETRY_BUOY = 7 + GEOMETRY_PHYSX = 8 + MEMORY = 9 + LANDCONTACT = 10 + ROADWAY = 11 + PATHS = 12 + HITPOINTS = 13 + VIEW_GEOMETRY = 14 + FIRE_GEOMETRY = 15 + VIEW_CARGO_GEOMETRY = 16 + VIEW_CARGO_FIRE_GEOMETRY = 17 + VIEW_COMMANDER = 18 + VIEW_COMMANDER_GEOMETRY = 19 + VIEW_COMMANDER_FIRE_GEOMETRY = 20 + VIEW_PILOT_GEOMETRY = 21 + VIEW_PILOT_FIRE_GEOMETRY = 22 + VIEW_GUNNER_GEOMETRY = 23 + VIEW_GUNNER_FIRE_GEOMETRY = 24 + SUBPARTS = 25 + SHADOW_VIEW_CARGO = 26 + SHADOW_VIEW_PILOT = 27 + SHADOW_VIEW_GUNNER = 28 + WRECKAGE = 29 + UNDERGROUND = 30 + GROUNDLAYER = 31 + NAVIGATION = 32 + UNKNOWN = -1 + + lod_has_resolution = { LOD.VISUAL, LOD.VIEW_CARGO, LOD.SHADOW, - # LOD.SHADOWBUFFER, LOD.EDIT, - LOD.VIEW_CARGO_GEOMERTRY, + LOD.VIEW_CARGO_GEOMETRY, LOD.SHADOW_VIEW_CARGO } @@ -90,7 +123,6 @@ lod_shadows = { LOD.SHADOW, - # LOD.SHADOWBUFFER, LOD.SHADOW_VIEW_CARGO, LOD.SHADOW_VIEW_PILOT, LOD.SHADOW_VIEW_GUNNER @@ -103,7 +135,7 @@ LOD.GEOMETRY_PHYSX, LOD.VIEW_GEOMETRY, LOD.FIRE_GEOMETRY, - LOD.VIEW_CARGO_GEOMERTRY, + LOD.VIEW_CARGO_GEOMETRY, LOD.VIEW_CARGO_FIRE_GEOMETRY, LOD.VIEW_COMMANDER_GEOMETRY, LOD.VIEW_COMMANDER_FIRE_GEOMETRY, @@ -127,7 +159,6 @@ LOD.VIEW_PILOT: ("View - Pilot", "First person view"), LOD.VIEW_CARGO: ("View - Cargo", "Passenger first person view"), LOD.SHADOW: ("Shadow Volume", "Shadow casting geometry"), - # LOD.SHADOWBUFFER: ("Shadow Buffer", "Shadow buffer geometry"), LOD.EDIT: ("Edit", "Temporary layer"), LOD.GEOMETRY: ("Geometry", "Object collision geometry and occluders"), LOD.GEOMETRY_BUOY: ("Geometry Buoyancy", "Buoyant object geometry (Displacement for VBS)"), @@ -139,7 +170,7 @@ LOD.HITPOINTS: ("Hit-points", "Hit point cloud"), LOD.VIEW_GEOMETRY: ("View Geometry", "View occlusion for AI"), LOD.FIRE_GEOMETRY: ("Fire Geometry", "Hitbox geometry"), - LOD.VIEW_CARGO_GEOMERTRY: ("View - Cargo Geometry", "Passenger view collision geometry and occluders"), + LOD.VIEW_CARGO_GEOMETRY: ("View - Cargo Geometry", "Passenger view collision geometry and occluders"), LOD.VIEW_CARGO_FIRE_GEOMETRY: ("View - Cargo Fire Geometry", "Passenger view hitbox geometry"), LOD.VIEW_COMMANDER: ("View - Commander", "Commander first person view"), LOD.VIEW_COMMANDER_GEOMETRY: ("View - Commander Geometry", "Commander view collision geometry and occluders"), @@ -174,7 +205,6 @@ **dict.fromkeys( [ LOD.SHADOW, - # LOD.SHADOWBUFFER, LOD.SHADOW_VIEW_CARGO, LOD.SHADOW_VIEW_PILOT, LOD.SHADOW_VIEW_GUNNER @@ -188,7 +218,7 @@ LOD.GEOMETRY_PHYSX, LOD.VIEW_GEOMETRY, LOD.FIRE_GEOMETRY, - LOD.VIEW_CARGO_GEOMERTRY, + LOD.VIEW_CARGO_GEOMETRY, LOD.VIEW_CARGO_FIRE_GEOMETRY, LOD.VIEW_COMMANDER_GEOMETRY, LOD.VIEW_COMMANDER_FIRE_GEOMETRY, @@ -236,7 +266,7 @@ LOD.VIEW_GUNNER, LOD.VIEW_PILOT, LOD.VIEW_CARGO, - LOD.VIEW_CARGO_GEOMERTRY, + LOD.VIEW_CARGO_GEOMETRY, LOD.VIEW_CARGO_FIRE_GEOMETRY, LOD.VIEW_COMMANDER, LOD.VIEW_COMMANDER_GEOMETRY, @@ -254,7 +284,6 @@ **dict.fromkeys( [ LOD.SHADOW, - # LOD.SHADOWBUFFER, LOD.GEOMETRY ], "General" @@ -737,9 +766,9 @@ def get_rvmat_templates(): import os - from ..utilities.generic import get_addon_directory + from .. import addon_dir - template_dir = os.path.join(get_addon_directory(), "scripts") + template_dir = os.path.join(addon_dir, "scripts") templates = { "PBR (VBS)": os.path.join(template_dir, "pbr_vbs.rvmat_template"), diff --git a/Arma3ObjectBuilder/utilities/generic.py b/Arma3ObjectBuilder/utilities/generic.py index e395929..8e85c69 100644 --- a/Arma3ObjectBuilder/utilities/generic.py +++ b/Arma3ObjectBuilder/utilities/generic.py @@ -3,31 +3,17 @@ import os import json -from datetime import datetime from contextlib import contextmanager +from datetime import datetime import bpy import bpy_extras.mesh_utils as meshutils import bmesh -from .. import __package__ as addon_name +from .. import get_prefs from . import data -def print_context(): - print("=======================") - for attr in dir(bpy.context): - print(attr, eval('bpy.context.%s' % attr)) - print("=======================") - - -def show_info_box(message, title = "", icon = 'INFO'): - def draw(self, context): - self.layout.label(text=message) - - bpy.context.window_manager.popup_menu(draw, title=title, icon=icon) - - # For some reason, not all operator reports are printed to the console. The behavior seems to be context dependent, # but not certain. def op_report(operator, mode, message): @@ -42,20 +28,12 @@ def abspath(path): return os.path.abspath(bpy.path.abspath(path)) -def strip_extension(path): - return os.path.splitext(path)[0] - - -def get_addon_preferences(): - return bpy.context.preferences.addons[addon_name].preferences - - def is_valid_idx(index, subscriptable): return len(subscriptable) > 0 and 0 <= index < len(subscriptable) def draw_panel_header(panel): - if not get_addon_preferences().show_info_links: + if not get_prefs().show_info_links: return row = panel.layout.row(align=True) @@ -98,55 +76,6 @@ def query_bmesh(obj): bm.free() -class OutputManager(): - def __init__(self, filepath, mode = "w"): - timestamp = datetime.now().strftime("%Y%m%d%H%M%S") - self.filepath = filepath - self.temppath = "%s.%s.temp" % (filepath, timestamp) - self.backup = "%s.%s.bak" % (filepath, timestamp) - self.mode = mode - self.file = None - self.success = False - - def __enter__(self): - file = open(self.temppath, self.mode) - self.file = file - - return file - - def __exit__(self, exc_type, exc_value, exc_tb): - self.file.close() - addon_prefs = get_addon_preferences() - - if self.success: - if os.path.isfile(self.filepath) and addon_prefs.create_backups: - self.force_rename(self.filepath, self.filepath + ".bak") - - self.force_rename(self.temppath, self.filepath) - - elif not addon_prefs.preserve_faulty_output: - os.remove(self.temppath) - - return False - - @staticmethod - def force_rename(old, new): - if os.path.isfile(new): - os.remove(new) - - os.rename(old, new) - - # Very dirty check, but works for now - @staticmethod - def can_access_path(path): - try: - open(path, "ab").close() - os.rename(path, path) - return True - except: - return False - - def get_loose_components(obj): mesh = obj.data mesh.calc_loop_triangles() @@ -232,7 +161,6 @@ def replace_slashes(path): # Attempt to restore absolute paths to the set project root (P drive by default). def restore_absolute(path, extension = ""): path = replace_slashes(path.strip().lower()) - addon_prefs = get_addon_preferences() if path == "": return "" @@ -240,7 +168,7 @@ def restore_absolute(path, extension = ""): if os.path.splitext(path)[1].lower() != extension: path += extension - root = abspath(addon_prefs.project_root).lower() + root = abspath(get_prefs().project_root).lower() if not path.startswith(root): abs_path = os.path.join(root, path) if os.path.exists(abs_path): @@ -271,22 +199,13 @@ def format_path(path, root = "", to_relative = True, extension = True): path = make_relative(path, root) if not extension: - path = strip_extension(path) + path = os.path.splitext(path)[0] return path -def get_addon_directory(): - return os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) - - -def get_cfg_convert(): - return os.path.join(get_addon_preferences().a3_tools, "cfgconvert/cfgconvert.exe") - - def load_common_data(scene): - prefs = get_addon_preferences() - custom_path = abspath(prefs.custom_data) + custom_path = abspath(get_prefs().custom_data) builtin = data.common_data json_data = {} try: @@ -309,37 +228,38 @@ def load_common_data(scene): scene_props.items_index = 0 -preview_collection = {} +class ExportFileHandler(): + def __init__(self, filepath, mode): + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + self.filepath = filepath + self.temppath = "%s.%s.temp" % (filepath, timestamp) + self.mode = mode + self.file = None + addon_pref = get_prefs() + self.backup_old = addon_pref.create_backups + self.preserve_faulty = addon_pref.preserve_faulty_output + def __enter__(self): + file = open(self.temppath, self.mode) + self.file = file -def get_icon(name): - icon = 0 - try: - icon = preview_collection[get_addon_preferences().icon_theme.lower()][name].icon_id - except: - pass - - return icon + return file + def __exit__(self, exc_type, exc_value, traceback): + self.file.close() -def register_icons(): - import bpy.utils.previews - - themes_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../icons")) - for theme in os.listdir(themes_dir): - theme_icons = bpy.utils.previews.new() - - icons_dir = os.path.join(themes_dir, theme) - for filename in os.listdir(icons_dir): - theme_icons.load(os.path.splitext(os.path.basename(filename))[0].lower(), os.path.join(icons_dir, filename), 'IMAGE') - - preview_collection[theme.lower()] = theme_icons - + if exc_type is None: + if os.path.isfile(self.filepath) and self.backup_old: + self.force_rename(self.filepath, self.filepath + ".bak") -def unregister_icons(): - import bpy.utils.previews - - for icon in preview_collection.values(): - bpy.utils.previews.remove(icon) + self.force_rename(self.temppath, self.filepath) + + elif not self.preserve_faulty: + os.remove(self.temppath) - preview_collection.clear() + @staticmethod + def force_rename(old, new): + if os.path.isfile(new): + os.remove(new) + + os.rename(old, new) diff --git a/Arma3ObjectBuilder/utilities/validator.py b/Arma3ObjectBuilder/utilities/validator.py index f9083e8..6965362 100644 --- a/Arma3ObjectBuilder/utilities/validator.py +++ b/Arma3ObjectBuilder/utilities/validator.py @@ -7,7 +7,7 @@ import bmesh import bpy -from ..io.data_p3d import P3D_LOD_Resolution as LOD +from .data import LOD class ValidatorResult(): @@ -719,7 +719,7 @@ def setup_lod_specific(self): str(LOD.GEOMETRY_PHYSX), str(LOD.VIEW_GEOMETRY), str(LOD.FIRE_GEOMETRY), - str(LOD.VIEW_CARGO_GEOMERTRY), + str(LOD.VIEW_CARGO_GEOMETRY), str(LOD.VIEW_CARGO_FIRE_GEOMETRY), str(LOD.VIEW_COMMANDER_GEOMETRY), str(LOD.VIEW_COMMANDER_FIRE_GEOMETRY),