# copyright (c) 2018- polygoniq xyz s.r.o. import typing import bpy import os import tempfile import shutil from . import polib telemetry = polib.get_telemetry("memsaver") MODULE_CLASSES: typing.List[typing.Type] = [] IMAGE_SIZES_ENUM = [ ("32", "32", "32"), ("64", "64", "64"), ("128", "128", "128"), ("256", "256", "256"), ("512", "512", "512"), ("1024", "1024", "1024"), ("2048", "2048", "2048"), ("4096", "4096", "4096"), ("8192", "8192", "8192"), ("16384", "16384", "16384"), ] @polib.log_helpers_bpy.logged_operator class PackLogs(bpy.types.Operator): bl_idname = "memsaver.pack_logs" bl_label = "Pack Logs" bl_description = "Archives polygoniq logs as zip file and opens its location" bl_options = {'REGISTER'} def execute(self, context): packed_logs_directory_path = polib.log_helpers_bpy.pack_logs(telemetry) polib.utils_bpy.xdg_open_file(packed_logs_directory_path) return {'FINISHED'} MODULE_CLASSES.append(PackLogs) @polib.log_helpers_bpy.logged_operator class OpenCacheFolder(bpy.types.Operator): bl_idname = "memsaver.open_cache_folder" bl_label = "Open Cache Folder" bl_description = "Opens the directory with cached derivative images" bl_options = {'REGISTER'} def execute(self, context): prefs = get_preferences(context) polib.utils_bpy.xdg_open_file(prefs.get_cache_path()) return {'FINISHED'} MODULE_CLASSES.append(OpenCacheFolder) @polib.log_helpers_bpy.logged_preferences class Preferences(bpy.types.AddonPreferences): bl_idname = __package__ cache_path: bpy.props.StringProperty( name="Cache Path", default=os.path.expanduser("~/memsaver_cache"), description="Where derivatives (of various sizes) of images will be cached.", subtype='DIR_PATH', update=lambda self, context: polib.utils_bpy.absolutize_preferences_path( self, context, "cache_path" ), ) # Used to choose target of image sizer operator operator_target: bpy.props.EnumProperty( name="Target", description="Choose to what objects/images the operator should apply to", items=[ ('SELECTED_OBJECTS', "Selected Objects", "All selected objects"), ('SCENE_OBJECTS', "Scene Objects", "All objects in current scene"), ('ALL_OBJECTS', "All Objects", "All objects in the .blend file"), (None), ( 'ALL_IMAGES_EXCEPT_HDR_EXR', "All Images except HDR and EXR", "All images except HDR and EXR, even those not used in any objects", ), ( 'ALL_HDR_EXR_IMAGES', "All HDR and EXR Images", "All HDR and EXR images, even those not used in any objects", ), ('ALL_IMAGES', "All Images", "All images, even those not used in any objects"), ], default='SCENE_OBJECTS', ) adaptive_image_enabled: bpy.props.BoolProperty( name="Optimize Images", default=True, ) adaptive_image_quality_factor: bpy.props.FloatProperty( name="Quality Factor", description="Object 2D bounds are multiplied by this to figure out image side size. \n\n" "Texture sizes are calculated based on how many pixels the objects that use them take " "either horizontally or vertically in the camera view. For example if an object is bigger " "vertically and it takes 10% of a FHD resolution then it takes 108 px and so its textures " "(if not used anywhere else) have to be at least 256px if the quality factor is set to " "2.00. Please note that if a big texture is mapped onto an object in a way that only " "a fraction of it is actually used, the downscaling might have a detrimental effect since " "even though the object is let's say 970 px wide it uses only a small part of the entire " "texture which then gets downscaled based on the bounds and not based on actual mapping " "density", default=1.0, min=0.001, ) adaptive_image_minimum_size: bpy.props.EnumProperty( name="Minimum Image Size", description="Minimal image size, the algorithm will not choose a smaller size than this", items=IMAGE_SIZES_ENUM, default="256", ) adaptive_image_maximum_size: bpy.props.EnumProperty( name="Maximum Image Size", description="Maximal image size, the algorithm will not choose a larger size than this", items=IMAGE_SIZES_ENUM, default="4096", # 4k ) adaptive_mesh_enabled: bpy.props.BoolProperty( name="Decimate Meshes", default=False, ) adaptive_mesh_full_quality_distance: bpy.props.FloatProperty( name="Full Quality Max Distance", description="We won't apply any decimation on meshes closer to the camera than this value", default=20.0, min=0.0, ) adaptive_mesh_lowest_quality_distance: bpy.props.FloatProperty( name="Lowest Quality Distance", description="Distance at which we will apply the lowest quality decimation", default=200.0, min=0.0, ) adaptive_mesh_lowest_quality_decimation_ratio: bpy.props.FloatProperty( name="Lowest Quality Decimation Ratio", description="Which decimation ratio do we apply at the lowest quality distance", default=0.2, min=0.0, max=1.0, ) adaptive_mesh_lowest_face_count: bpy.props.IntProperty( name="Lowest Face Count", description="Ignore meshes with face count lower than this value", default=5000, min=0, ) adaptive_animation_mode: bpy.props.BoolProperty( name="Animation Mode", description="Instead of figuring out distances from current frame, consider all frames", default=False, ) overlay_text_size_px: bpy.props.FloatProperty( name="Overlay Text Size", description="Size of the overlay text in pixels", default=16.0, min=1.0, ) overlay_text_color: bpy.props.FloatVectorProperty( name="Overlay Text Color", description="Color of the text overlay. " "Useful when the default value would blend with background", min=0.0, max=1.0, default=(1.0, 1.0, 1.0, 1.0), size=4, subtype='COLOR', ) change_size_desired_size: bpy.props.EnumProperty( name="Desired Size", description="Desired Maximum Side Size of Textures", items=IMAGE_SIZES_ENUM + [("CUSTOM", "CUSTOM", "CUSTOM")], default="2048", ) change_size_custom_size: bpy.props.IntProperty( name="Custom Size", description="Desired Maximum Side Size of Textures", default=3072, ) def get_cache_path(self) -> str: if not os.path.isdir(self.cache_path): os.makedirs(self.cache_path) return self.cache_path def draw(self, context: bpy.types.Context): row = self.layout.row() row.prop(self, "cache_path") row = self.layout.row() row.operator(OpenCacheFolder.bl_idname, icon='FILE_CACHE') row = self.layout.row() row.operator(PackLogs.bl_idname, icon='EXPERIMENTAL') polib.ui_bpy.draw_settings_footer(self.layout) MODULE_CLASSES.append(Preferences) def get_preferences(context: bpy.types.Context) -> Preferences: return context.preferences.addons[__package__].preferences def register(): for cls in MODULE_CLASSES: bpy.utils.register_class(cls) def unregister(): for cls in reversed(MODULE_CLASSES): bpy.utils.unregister_class(cls)