From 112681c7f35332ff6b89c68f96fd1b3d1765c21d Mon Sep 17 00:00:00 2001 From: Zdenek Dolezal Date: Wed, 11 Dec 2024 16:13:38 +0100 Subject: [PATCH] Updated engon from monorepo commit 618d11b92726f0c7ab43a1e27ad18d2d2b0c61f8 --- __init__.py | 14 +- blender_manifest.toml | 4 +- browser/dev.py | 2 - browser/spawn.py | 6 + browser/utils.py | 3 - .../aquatiq_material_limitation_warning.py | 4 +- features/aquatiq_paint_mask.py | 1 + features/botaniq_adjustments.py | 5 +- .../botaniq_animations/botaniq_animations.py | 1 + features/colorize.py | 1 + features/emergency_lights.py | 1 + features/feature_utils.py | 1 - features/license_plates_generator.py | 72 +++++++++- features/light_adjustments.py | 1 + features/puddles.py | 1 + features/rain_generator.py | 1 + features/river_generator.py | 1 + features/road_generator/build_roads_modal.py | 1 - features/road_generator/panel.py | 1 + features/traffiq_lights_settings.py | 1 + features/traffiq_paint_adjustments.py | 1 + features/traffiq_rigs.py | 1 + features/traffiq_wear.py | 1 + features/vegetation_generator.py | 1 + features/vine_generator.py | 1 + materialiq/displacement.py | 1 - materialiq/panel.py | 1 + materialiq/textures.py | 2 - panel.py | 18 +-- preferences/__init__.py | 6 +- preferences/what_is_new_preferences.py | 2 - python_deps/polib/asset_pack_bpy.py | 62 +++++--- python_deps/polib/node_utils_bpy.py | 1 - python_deps/polib/remove_duplicates_bpy.py | 1 - python_deps/polib/ui_bpy.py | 2 +- utils/__init__.py | 32 +++++ utils/copy_nodes_mod_values.py | 135 ++++++++++++++++++ ui_utils.py => utils/show_popup.py | 2 +- 38 files changed, 330 insertions(+), 62 deletions(-) create mode 100644 utils/__init__.py create mode 100644 utils/copy_nodes_mod_values.py rename ui_utils.py => utils/show_popup.py (98%) diff --git a/__init__.py b/__init__.py index fe10767..f9f0f10 100644 --- a/__init__.py +++ b/__init__.py @@ -102,7 +102,7 @@ from . import hatchery from . import mapr - from . import ui_utils + from . import utils from . import asset_registry from . import asset_pack_installer from . import pack_info_search_paths @@ -132,12 +132,12 @@ bl_info = { "name": "engon", "author": "polygoniq xyz s.r.o.", - "version": (1, 4, 0), # bump doc_url and version in register as well! + "version": (1, 4, 1), # bump doc_url and version in register as well! "blender": (3, 6, 0), "location": "polygoniq tab in the sidebar of the 3D View window", - "description": "", + "description": "Browse assets, filter and sort, scatter, animate, adjust rigs", "category": "Object", - "doc_url": "https://docs.polygoniq.com/engon/1.4.0/", + "doc_url": "https://docs.polygoniq.com/engon/1.4.1/", "tracker_url": "https://polygoniq.com/discord/", } @@ -158,9 +158,9 @@ def _post_register(): def register(): # We pass mock "bl_info" to the updater, as from Blender 4.2.0, the "bl_info" is # no longer available in this scope. - addon_updater_ops.register({"version": (1, 4, 0)}) + addon_updater_ops.register({"version": (1, 4, 1)}) - ui_utils.register() + utils.register() pack_info_search_paths.register() convert_selection.register() panel.register() @@ -196,7 +196,7 @@ def unregister(): panel.unregister() convert_selection.unregister() pack_info_search_paths.unregister() - ui_utils.unregister() + utils.unregister() # Remove all nested modules from module cache, more reliable than importlib.reload(..) # Idea by BD3D / Jacques Lucke diff --git a/blender_manifest.toml b/blender_manifest.toml index 04da7ed..1ce5314 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -1,13 +1,13 @@ schema_version = "1.0.0" id = "engon" -version = "1.4.0" +version = "1.4.1" name = "engon" tagline = "Browse assets, filter and sort, scatter, animate, adjust rigs" maintainer = "polygoniq " type = "add-on" -website = "https://docs.polygoniq.com/engon/1.4.0/" +website = "https://docs.polygoniq.com/engon/1.4.1/" tags = ["Add Mesh", "Animation", "Material", "Mesh", "Object", "Scene", "User Interface"] blender_version_min = "4.2.0" diff --git a/browser/dev.py b/browser/dev.py index db71cd8..7eefd47 100644 --- a/browser/dev.py +++ b/browser/dev.py @@ -23,8 +23,6 @@ import typing import logging from . import filters -from . import previews -from . import utils from .. import mapr from .. import polib from .. import asset_registry diff --git a/browser/spawn.py b/browser/spawn.py index b39244b..df0317f 100644 --- a/browser/spawn.py +++ b/browser/spawn.py @@ -396,9 +396,15 @@ def execute(self, context: bpy.types.Context): for old_collection in obj.users_collection: old_collection.objects.unlink(obj) old_collection.objects.link(obj_copy) + obj_copy.parent = obj.parent obj_copy.matrix_world = obj.matrix_world obj_copy.select_set(True) + if prefs.spawn_options.make_editable: + polib.asset_pack_bpy.make_selection_editable( + context, True, keep_selection=True, keep_active=True + ) + if prefs.spawn_options.remove_duplicates: self._remove_duplicates() diff --git a/browser/utils.py b/browser/utils.py index bfde2b4..e04f160 100644 --- a/browser/utils.py +++ b/browser/utils.py @@ -18,11 +18,8 @@ # # ##### END GPL LICENSE BLOCK ##### -import bpy import logging -import typing from .. import mapr -from .. import asset_registry logger = logging.getLogger(f"polygoniq.{__name__}") diff --git a/features/aquatiq_material_limitation_warning.py b/features/aquatiq_material_limitation_warning.py index 44e10ce..1422dbb 100644 --- a/features/aquatiq_material_limitation_warning.py +++ b/features/aquatiq_material_limitation_warning.py @@ -24,7 +24,7 @@ import logging from . import feature_utils from . import asset_pack_panels -from .. import ui_utils +from .. import utils from .. import polib logger = logging.getLogger(f"polygoniq.{__name__}") @@ -108,7 +108,7 @@ def draw_material_limitations( layout.alert = True op = layout.operator( - ui_utils.ShowPopup.bl_idname, + utils.show_popup.ShowPopup.bl_idname, text=f"See {len(warnings)} warning{'s' if len(warnings) > 1 else ''}", icon='ERROR', ) diff --git a/features/aquatiq_paint_mask.py b/features/aquatiq_paint_mask.py index f76aa43..013cf0b 100644 --- a/features/aquatiq_paint_mask.py +++ b/features/aquatiq_paint_mask.py @@ -225,6 +225,7 @@ class AquatiqPaintMaskPanel(feature_utils.EngonFeaturePanelMixin, bpy.types.Pane bl_idname = "VIEW_3D_PT_engon_aquatiq_paint_mask" bl_parent_id = asset_pack_panels.AquatiqPanel.bl_idname bl_label = "Paint Mask" + bl_options = {'DEFAULT_CLOSED'} feature_name = "aquatiq_paint_mask" diff --git a/features/botaniq_adjustments.py b/features/botaniq_adjustments.py index 0af2566..92ea5bf 100644 --- a/features/botaniq_adjustments.py +++ b/features/botaniq_adjustments.py @@ -110,6 +110,7 @@ class BotaniqAdjustmentsPanel(feature_utils.PropertyAssetFeatureControlPanelMixi polib.custom_props_bpy.CustomPropertyNames.BQ_RANDOM_PER_LEAF, polib.custom_props_bpy.CustomPropertyNames.BQ_SEASON_OFFSET, } + bl_options = {'DEFAULT_CLOSED'} @classmethod def filter_adjustable_assets( @@ -182,11 +183,11 @@ def draw_multiedit( ) row = layout.row(align=True) - row.label(text="", icon='BRUSH_MIX') + row.label(text="", icon='FREEZE') row.prop( prefs, "season_offset", - icon='BRUSH_MIX', + icon='FREEZE', text=f"Season: {self.get_season_from_value(prefs.season_offset)}", slider=True, ) diff --git a/features/botaniq_animations/botaniq_animations.py b/features/botaniq_animations/botaniq_animations.py index 1d2f9b8..9d37662 100644 --- a/features/botaniq_animations/botaniq_animations.py +++ b/features/botaniq_animations/botaniq_animations.py @@ -185,6 +185,7 @@ class AnimationsPanel(feature_utils.EngonAssetFeatureControlPanelMixin, bpy.type bl_idname = "VIEW_3D_PT_engon_botaniq_animations" bl_parent_id = asset_pack_panels.BotaniqPanel.bl_idname bl_label = "Animations" + bl_options = {'DEFAULT_CLOSED'} feature_name = "botaniq_animations" diff --git a/features/colorize.py b/features/colorize.py index 26ac154..8c554de 100644 --- a/features/colorize.py +++ b/features/colorize.py @@ -113,6 +113,7 @@ class ColorizePanel(feature_utils.PropertyAssetFeatureControlPanelMixin, bpy.typ polib.custom_props_bpy.CustomPropertyNames.PQ_PRIMARY_COLOR_FACTOR, polib.custom_props_bpy.CustomPropertyNames.PQ_SECONDARY_COLOR_FACTOR, } + bl_options = {'DEFAULT_CLOSED'} @classmethod def filter_adjustable_assets( diff --git a/features/emergency_lights.py b/features/emergency_lights.py index 92adb6d..dc755f4 100644 --- a/features/emergency_lights.py +++ b/features/emergency_lights.py @@ -70,6 +70,7 @@ class EmergencyLightsPanel( bl_parent_id = asset_pack_panels.TraffiqPanel.bl_idname bl_label = "Emergency Lights" feature_name = "emergency_lights" + bl_options = {'DEFAULT_CLOSED'} template = polib.node_utils_bpy.NodeSocketsDrawTemplate( asset_helpers.TQ_EMERGENCY_LIGHTS_NODE_GROUP_NAME, filter_=lambda _: True diff --git a/features/feature_utils.py b/features/feature_utils.py index 8d56788..8f92891 100644 --- a/features/feature_utils.py +++ b/features/feature_utils.py @@ -22,7 +22,6 @@ import bpy import itertools import random -import sys from .. import polib from .. import asset_registry from .. import asset_helpers diff --git a/features/license_plates_generator.py b/features/license_plates_generator.py index 4b6a4da..25cd69c 100644 --- a/features/license_plates_generator.py +++ b/features/license_plates_generator.py @@ -24,8 +24,9 @@ import logging from . import feature_utils from . import asset_pack_panels -from .. import polib from .. import asset_helpers +from .. import polib +from .. import utils logger = logging.getLogger(f"polygoniq.{__name__}") @@ -37,6 +38,20 @@ BACK_PLATE_PARENT_NAME_SUFFIX = "_License-Plate_B" +def get_license_plate_modifier(obj: bpy.types.Object) -> typing.Optional[bpy.types.NodesModifier]: + mods = polib.geonodes_mod_utils_bpy.get_geometry_nodes_modifiers_by_node_group( + obj, + asset_helpers.TQ_LICENSE_PLATE_NODE_GROUP_NAME_PREFIX, + ) + if len(mods) == 0: + return None + elif len(mods) == 1: + return mods[0] + else: + logger.warning(f"Multiple license plate modifiers found on object '{obj.name}'!") + return mods[0] + + @feature_utils.register_feature class LicensePlatesGeneratorPanelMixin(feature_utils.GeonodesAssetFeatureControlPanelMixin): feature_name = "license_plates_generator" @@ -52,6 +67,7 @@ class LicensePlatesGeneratorPanel( bl_idname = "VIEW_3D_PT_engon_license_plates_generator" bl_parent_id = asset_pack_panels.TraffiqPanel.bl_idname bl_label = "License Plates" + bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context: bpy.types.Context) -> None: self.layout.label(text="", icon='EVENT_L') @@ -125,6 +141,33 @@ class FrontPlatePanel( obj.parent.name ).endswith(FRONT_PLATE_PARENT_NAME_SUFFIX) + def draw(self, context: bpy.types.Context) -> None: + if context.active_object is None: + return + + decomposed_car = polib.asset_pack_bpy.decompose_traffiq_vehicle(context.active_object) + if decomposed_car is None: + return + + front_plate = decomposed_car.front_plate + back_plate = decomposed_car.back_plate + assert front_plate is not None + if back_plate is not None: + op = self.layout.operator( + utils.copy_nodes_mod_values.CopyGeonodesModifierValues.bl_idname, + text="Copy To Back", + icon='PASTEDOWN', + ) + op.src_name = front_plate.name + op.dst_name = back_plate.name + # The license plates modifiers are always at index 0. This operator won't be drawn + # in the UI if there is not back plate and front plate. + op.src_mod_idx = 0 + op.dst_mod_idx = 0 + + self.layout.separator() + super().draw(context) + MODULE_CLASSES.append(FrontPlatePanel) @@ -140,6 +183,33 @@ class BackPlatePanel( obj.parent.name ).endswith(BACK_PLATE_PARENT_NAME_SUFFIX) + def draw(self, context: bpy.types.Context) -> None: + if context.active_object is None: + return + + decomposed_car = polib.asset_pack_bpy.decompose_traffiq_vehicle(context.active_object) + if decomposed_car is None: + return + + front_plate = decomposed_car.front_plate + back_plate = decomposed_car.back_plate + assert back_plate is not None + if front_plate is not None: + op = self.layout.operator( + utils.copy_nodes_mod_values.CopyGeonodesModifierValues.bl_idname, + text="Copy To Front", + icon='PASTEDOWN', + ) + op.src_name = back_plate.name + op.dst_name = front_plate.name + # The license plates modifiers are always at index 0. This operator won't be drawn + # in the UI if there is not back plate and front plate. + op.src_mod_idx = 0 + op.dst_mod_idx = 0 + + self.layout.separator() + super().draw(context) + MODULE_CLASSES.append(BackPlatePanel) diff --git a/features/light_adjustments.py b/features/light_adjustments.py index c6efb25..b32b6cb 100644 --- a/features/light_adjustments.py +++ b/features/light_adjustments.py @@ -133,6 +133,7 @@ class LightAdjustmentsPanel(feature_utils.PropertyAssetFeatureControlPanelMixin, polib.custom_props_bpy.CustomPropertyNames.PQ_LIGHT_RGB, polib.custom_props_bpy.CustomPropertyNames.PQ_LIGHT_STRENGTH, } + bl_options = {'DEFAULT_CLOSED'} @classmethod def filter_adjustable_assets( diff --git a/features/puddles.py b/features/puddles.py index 53e9c4a..7ae852f 100644 --- a/features/puddles.py +++ b/features/puddles.py @@ -266,6 +266,7 @@ class PuddlesPanel(feature_utils.EngonFeaturePanelMixin, bpy.types.Panel): bl_idname = "VIEW_3D_PT_engon_puddles" bl_parent_id = asset_pack_panels.AquatiqPanel.bl_idname bl_label = "Puddles" + bl_options = {'DEFAULT_CLOSED'} feature_name = "puddles" diff --git a/features/rain_generator.py b/features/rain_generator.py index a48d315..cfb59a9 100644 --- a/features/rain_generator.py +++ b/features/rain_generator.py @@ -40,6 +40,7 @@ class RainGeneratorPanel(RainGeneratorPanelMixin, bpy.types.Panel): bl_idname = "VIEW_3D_PT_aquatiq_rain_generator" bl_parent_id = asset_pack_panels.AquatiqPanel.bl_idname bl_label = "Rain Generator" + bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context: bpy.types.Context) -> None: self.layout.label(text="", icon='OUTLINER_DATA_LIGHTPROBE') diff --git a/features/river_generator.py b/features/river_generator.py index 17062dd..6696883 100644 --- a/features/river_generator.py +++ b/features/river_generator.py @@ -40,6 +40,7 @@ class RiverGeneratorPanel(RiverGeneratorPanelMixin, bpy.types.Panel): bl_idname = "VIEW_3D_PT_aquatiq_river_generator" bl_parent_id = asset_pack_panels.AquatiqPanel.bl_idname bl_label = "River Generator" + bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context: bpy.types.Context) -> None: self.layout.label(text="", icon='FORCE_FORCE') diff --git a/features/road_generator/build_roads_modal.py b/features/road_generator/build_roads_modal.py index 01fa580..f128b4e 100644 --- a/features/road_generator/build_roads_modal.py +++ b/features/road_generator/build_roads_modal.py @@ -23,7 +23,6 @@ import bpy import os -import traceback import typing import mathutils import bpy_extras.view3d_utils diff --git a/features/road_generator/panel.py b/features/road_generator/panel.py index cc79635..d9e5b35 100644 --- a/features/road_generator/panel.py +++ b/features/road_generator/panel.py @@ -245,6 +245,7 @@ class RoadGeneratorPanel(RoadGeneratorPanelMixin, bpy.types.Panel): bl_idname = "VIEW_3D_PT_engon_build_roads_modal" bl_label = "Road Generator (Beta)" bl_parent_id = asset_pack_panels.TraffiqPanel.bl_idname + bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context: bpy.types.Context): self.layout.label(text="", icon='MOD_SIMPLEDEFORM') diff --git a/features/traffiq_lights_settings.py b/features/traffiq_lights_settings.py index b6eafe3..63a6387 100644 --- a/features/traffiq_lights_settings.py +++ b/features/traffiq_lights_settings.py @@ -96,6 +96,7 @@ class TraffiqLightsSettingsPanel( bl_label = "Lights Settings" feature_name = "traffiq_lights_settings" related_custom_properties = {polib.custom_props_bpy.CustomPropertyNames.TQ_LIGHTS} + bl_options = {'DEFAULT_CLOSED'} @classmethod def find_unique_lights_containers_with_roots( diff --git a/features/traffiq_paint_adjustments.py b/features/traffiq_paint_adjustments.py index a3a8a59..aee44a2 100644 --- a/features/traffiq_paint_adjustments.py +++ b/features/traffiq_paint_adjustments.py @@ -162,6 +162,7 @@ class TraffiqPaintAdjustmentsPanel( polib.custom_props_bpy.CustomPropertyNames.TQ_FLAKES_AMOUNT, polib.custom_props_bpy.CustomPropertyNames.TQ_CLEARCOAT, } + bl_options = {'DEFAULT_CLOSED'} @classmethod def filter_adjustable_assets( diff --git a/features/traffiq_rigs.py b/features/traffiq_rigs.py index a6e826b..baa90c5 100644 --- a/features/traffiq_rigs.py +++ b/features/traffiq_rigs.py @@ -947,6 +947,7 @@ class TraffiqRigsPanel(bpy.types.Panel, feature_utils.PropertyAssetFeatureContro polib.custom_props_bpy.CustomPropertyNames.TQ_WHEELS_Y_ROLLING, polib.custom_props_bpy.CustomPropertyNames.TQ_CAR_RIG, } + bl_options = {'DEFAULT_CLOSED'} @classmethod def filter_adjustable_assets( diff --git a/features/traffiq_wear.py b/features/traffiq_wear.py index 005e302..f96957d 100644 --- a/features/traffiq_wear.py +++ b/features/traffiq_wear.py @@ -132,6 +132,7 @@ class TraffiqWearAdjustmentsPanel( polib.custom_props_bpy.CustomPropertyNames.TQ_SCRATCHES, polib.custom_props_bpy.CustomPropertyNames.TQ_BUMPS, } + bl_options = {'DEFAULT_CLOSED'} @classmethod def filter_adjustable_assets( diff --git a/features/vegetation_generator.py b/features/vegetation_generator.py index df83312..8a05ca0 100644 --- a/features/vegetation_generator.py +++ b/features/vegetation_generator.py @@ -47,6 +47,7 @@ class VegetationGeneratorPanel( bl_idname = "VIEW_3D_PT_engon_vegetation_generator" bl_parent_id = asset_pack_panels.BotaniqPanel.bl_idname bl_label = "Vegetation Generator" + bl_options = {'DEFAULT_CLOSED'} template = polib.node_utils_bpy.NodeSocketsDrawTemplate( asset_helpers.BQ_CURVES_GENERATOR_NODE_GROUP_NAME, diff --git a/features/vine_generator.py b/features/vine_generator.py index 5eab7b0..fda62f3 100644 --- a/features/vine_generator.py +++ b/features/vine_generator.py @@ -42,6 +42,7 @@ class VineGeneratorPanel( bl_idname = "VIEW_3D_PT_botaniq_vine_generator" bl_parent_id = asset_pack_panels.BotaniqPanel.bl_idname bl_label = "Vine Generator" + bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context: bpy.types.Context) -> None: self.layout.label(text="", icon="GRAPH") diff --git a/materialiq/displacement.py b/materialiq/displacement.py index 03114a9..fefe802 100644 --- a/materialiq/displacement.py +++ b/materialiq/displacement.py @@ -23,7 +23,6 @@ import logging from .. import polib from .. import hatchery -from .. import asset_helpers logger = logging.getLogger(f"polygoniq.{__name__}") diff --git a/materialiq/panel.py b/materialiq/panel.py index ccb337e..44cf6b5 100644 --- a/materialiq/panel.py +++ b/materialiq/panel.py @@ -184,6 +184,7 @@ class ToolsPanel(MaterialiqPanelMixin, bpy.types.Panel): bl_idname = "VIEW_3D_PT_engon_materialiq_tools" bl_parent_id = MaterialiqPanel.bl_idname bl_label = "Tools" + bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context: bpy.types.Context) -> None: self.layout.label(text="", icon='TOOL_SETTINGS') diff --git a/materialiq/textures.py b/materialiq/textures.py index 67400c2..1e2cfe1 100644 --- a/materialiq/textures.py +++ b/materialiq/textures.py @@ -23,8 +23,6 @@ import logging from .. import polib from .. import hatchery -from . import misc_ops -from .. import preferences from .. import asset_helpers logger = logging.getLogger(f"polygoniq.{__name__}") diff --git a/panel.py b/panel.py index 5c1f757..67155ba 100644 --- a/panel.py +++ b/panel.py @@ -25,11 +25,9 @@ import typing import logging import random -from . import mapr from . import polib from . import hatchery from . import browser -from . import asset_registry from . import preferences from . import blend_maintenance from . import convert_selection @@ -48,7 +46,7 @@ class EngonPanelMixin: @polib.log_helpers_bpy.logged_operator class SnapToGround(bpy.types.Operator): - bl_idname = "engon.snap_to_ground_bpy" + bl_idname = "engon.snap_to_ground" bl_label = "Snap to Ground" bl_description = "Put selected assets as close to the ground as possible" @@ -107,20 +105,18 @@ def execute(self, context): ): continue - root_object, body, lights, wheels, brakes = ( - polib.asset_pack_bpy.decompose_traffiq_vehicle(obj) - ) - if root_object is not None: # traffiq behavior + decomposed_car = polib.asset_pack_bpy.decompose_traffiq_vehicle(obj) + if decomposed_car is not None: # traffiq behavior logger.debug( f"Was able to decompose {obj.name} as if it was a traffiq vehicle. " f"Snapping to ground using the traffiq behavior." ) - if len(wheels) > 0: + if len(decomposed_car.wheels) > 0: logger.info( - f"Using {len(wheels)} separate wheels to determine final rotation..." + f"Using {len(decomposed_car.wheels)} separate wheels to determine final rotation..." ) is_snapped = polib.snap_to_ground_bpy.snap_to_ground_separate_wheels( - obj, root_object, wheels, ground_objects + obj, decomposed_car.root_object, decomposed_car.wheels, ground_objects ) else: logger.info( @@ -128,7 +124,7 @@ def execute(self, context): f"final rotation..." ) is_snapped = polib.snap_to_ground_bpy.snap_to_ground_adjust_rotation( - obj, root_object, ground_objects + obj, decomposed_car.root_object, ground_objects ) elif polib.asset_pack_bpy.is_polygoniq_object(obj, lambda x: x == "botaniq"): diff --git a/preferences/__init__.py b/preferences/__init__.py index 9180fa9..537c245 100644 --- a/preferences/__init__.py +++ b/preferences/__init__.py @@ -31,7 +31,7 @@ from . import browser_preferences from . import what_is_new_preferences from .. import keymaps -from .. import ui_utils +from .. import utils from .. import features from .. import polib from .. import __package__ as base_package @@ -47,7 +47,7 @@ class ShowReleaseNotes(bpy.types.Operator): bl_idname = "engon.show_release_notes" bl_label = "Show Release Notes" - bl_description = "Show the release notes for the latest version of blend1" + bl_description = "Show the release notes for the latest version of engon" bl_options = {'REGISTER'} release_tag: bpy.props.StringProperty( @@ -280,7 +280,7 @@ def draw_save_userpref_prompt(self, layout: bpy.types.UILayout): row.prop(self, "save_prefs") row = row.row() row.alignment = 'RIGHT' - op = row.operator(ui_utils.ShowPopup.bl_idname, text="", icon='INFO') + op = row.operator(utils.show_popup.ShowPopup.bl_idname, text="", icon='INFO') op.message = ( "Automatically saves preferences after running operators " "(e.g. Install Asset Pack) that change engon preferences. \n" diff --git a/preferences/what_is_new_preferences.py b/preferences/what_is_new_preferences.py index 061980d..1114a2f 100644 --- a/preferences/what_is_new_preferences.py +++ b/preferences/what_is_new_preferences.py @@ -2,8 +2,6 @@ import bpy import typing -from .. import ui_utils -from . import prefs_utils MODULE_CLASSES: typing.List[typing.Any] = [] diff --git a/python_deps/polib/asset_pack_bpy.py b/python_deps/polib/asset_pack_bpy.py index 21293e2..21d0ba0 100644 --- a/python_deps/polib/asset_pack_bpy.py +++ b/python_deps/polib/asset_pack_bpy.py @@ -5,6 +5,7 @@ import bpy.utils.previews import typing import collections +import dataclasses import enum import logging @@ -134,6 +135,7 @@ class TraffiqAssetPart(enum.Enum): Lights = 'Lights' Wheel = 'Wheel' Brake = 'Brake' + LicensePlate = 'License-Plate' def is_traffiq_asset_part(obj: bpy.types.Object, part: TraffiqAssetPart) -> bool: @@ -143,21 +145,21 @@ def is_traffiq_asset_part(obj: bpy.types.Object, part: TraffiqAssetPart) -> bool obj_name = utils_bpy.remove_object_duplicate_suffix(obj.name) if part in {TraffiqAssetPart.Body, TraffiqAssetPart.Lights}: - splitted_name = obj_name.rsplit("_", 1) - if len(splitted_name) != 2: + split_name = obj_name.rsplit("_", 1) + if len(split_name) != 2: return False - _, obj_part_name = splitted_name + _, obj_part_name = split_name if obj_part_name != part.name: return False return True elif part in {TraffiqAssetPart.Wheel, TraffiqAssetPart.Brake}: - splitted_name = obj_name.rsplit("_", 3) - if len(splitted_name) != 4: + split_name = obj_name.rsplit("_", 3) + if len(split_name) != 4: return False - _, obj_part_name, position, wheel_number = splitted_name + _, obj_part_name, position, wheel_number = split_name if obj_part_name != part.name: return False if position not in {"FL", "FR", "BL", "BR", "F", "B"}: @@ -165,17 +167,30 @@ def is_traffiq_asset_part(obj: bpy.types.Object, part: TraffiqAssetPart) -> bool if not wheel_number.isdigit(): return False return True + elif part == TraffiqAssetPart.LicensePlate: + split_name = obj_name.rsplit("_", 2) + if len(split_name) != 3: + return False + + _, obj_part_name, position = split_name + if obj_part_name != part.value: + return False + if position not in {"F", "B"}: + return False + return True return False -DecomposedCarType = typing.Tuple[ - bpy.types.Object, - bpy.types.Object, - bpy.types.Object, - typing.List[bpy.types.Object], - typing.List[bpy.types.Object], -] +@dataclasses.dataclass +class DecomposedCar: + root_object: bpy.types.Object + body: bpy.types.Object + lights: typing.Optional[bpy.types.Object] + wheels: typing.List[bpy.types.Object] + brakes: typing.List[bpy.types.Object] + front_plate: typing.Optional[bpy.types.Object] = None + back_plate: typing.Optional[bpy.types.Object] = None def get_root_object_of_asset(asset: bpy.types.Object) -> typing.Optional[bpy.types.Object]: @@ -232,15 +247,14 @@ def get_entire_object_hierarchy(obj: bpy.types.Object) -> typing.Iterable[bpy.ty yield obj -def decompose_traffiq_vehicle(obj: bpy.types.Object) -> DecomposedCarType: - if obj is None: - return None, None, None, [], [] - +def decompose_traffiq_vehicle(obj: bpy.types.Object) -> typing.Optional[DecomposedCar]: root_object = get_root_object_of_asset(obj) body = None lights = None wheels = [] brakes = [] + front_license_plate = None + back_license_plate = None hierarchy_objects = get_entire_object_hierarchy(obj) for hierarchy_obj in hierarchy_objects: @@ -256,8 +270,20 @@ def decompose_traffiq_vehicle(obj: bpy.types.Object) -> DecomposedCarType: wheels.append(hierarchy_obj) elif is_traffiq_asset_part(hierarchy_obj, TraffiqAssetPart.Brake): brakes.append(hierarchy_obj) + elif is_traffiq_asset_part(hierarchy_obj, TraffiqAssetPart.LicensePlate): + _, suffix = utils_bpy.remove_object_duplicate_suffix(hierarchy_obj.name).rsplit("_", 1) + if suffix == "F": + front_license_plate = hierarchy_obj.children[0] + elif suffix == "B": + back_license_plate = hierarchy_obj.children[0] + + # If no root object or body is found, this is not a valid traffiq vehicle + if root_object is None or body is None: + return None - return root_object, body, lights, wheels, brakes + return DecomposedCar( + root_object, body, lights, wheels, brakes, front_license_plate, back_license_plate + ) def find_traffiq_asset_parts( diff --git a/python_deps/polib/node_utils_bpy.py b/python_deps/polib/node_utils_bpy.py index 6ccf14d..e973558 100644 --- a/python_deps/polib/node_utils_bpy.py +++ b/python_deps/polib/node_utils_bpy.py @@ -7,7 +7,6 @@ import dataclasses from . import utils_bpy -from . import custom_props_bpy # Type that's compatible with both old and new node tree interfaces diff --git a/python_deps/polib/remove_duplicates_bpy.py b/python_deps/polib/remove_duplicates_bpy.py index 752a986..85cacae 100644 --- a/python_deps/polib/remove_duplicates_bpy.py +++ b/python_deps/polib/remove_duplicates_bpy.py @@ -3,7 +3,6 @@ import bpy -import re import os import typing from . import utils_bpy diff --git a/python_deps/polib/ui_bpy.py b/python_deps/polib/ui_bpy.py index e11ea23..db6e79d 100644 --- a/python_deps/polib/ui_bpy.py +++ b/python_deps/polib/ui_bpy.py @@ -327,7 +327,7 @@ def show_release_notes_popup( addon_name = mod_info["name"].split("_", 1)[0] release_info = utils_bpy.get_addon_release_info(addon_name, release_tag) - error_msg = f"Cannot retrieve release info for {addon_name}!" + error_msg = f"Release info cannot be retrieved for {addon_name} {release_tag}" if release_info is None: logger.error(error_msg) show_message_box(error_msg, "Error", icon='ERROR') diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..4c2029f --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,32 @@ +# copyright (c) 2018- polygoniq xyz s.r.o. + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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 2 +# 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, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +from . import copy_nodes_mod_values +from . import show_popup + + +def register(): + copy_nodes_mod_values.register() + show_popup.register() + + +def unregister(): + show_popup.unregister() + copy_nodes_mod_values.unregister() diff --git a/utils/copy_nodes_mod_values.py b/utils/copy_nodes_mod_values.py new file mode 100644 index 0000000..31c9b02 --- /dev/null +++ b/utils/copy_nodes_mod_values.py @@ -0,0 +1,135 @@ +# copyright (c) 2018- polygoniq xyz s.r.o. + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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 2 +# 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, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +import logging +import typing +from .. import polib + +logger = logging.getLogger(f"polygoniq.{__name__}") + + +MODULE_CLASSES: typing.List[typing.Type] = [] + + +@polib.log_helpers_bpy.logged_operator +class CopyGeonodesModifierValues(bpy.types.Operator): + bl_idname = "engon.copy_geonodes_modifier_values" + bl_label = "Copy Modifier Values" + bl_description = "Copies values of geometry nodes modifier from one object to another" + + src_name: bpy.props.StringProperty( + name="Source Object", + description="The source object to copy modifier values from", + ) + + src_mod_idx: bpy.props.IntProperty( + min=0, + name="Source Modifier Index", + description="Index of the modifier to copy from 'Source Object'", + ) + + dst_name: bpy.props.StringProperty( + name="Destination Object", + description="The destination object to copy modifier values to", + ) + + dst_mod_idx: bpy.props.IntProperty( + name="Destination Modifier Index", + description="Index of the modifier to copy to 'Destination Object'", + min=0, + ) + + def execute(self, context: bpy.types.Context): + src_object = bpy.data.objects.get(self.src_name) + dst_object = bpy.data.objects.get(self.dst_name) + if src_object is None or dst_object is None: + self.report({'ERROR'}, "Source or destination object not found.") + return {'CANCELLED'} + + src_mod = None + dst_mod = None + if self.src_mod_idx < len(src_object.modifiers): + src_mod = src_object.modifiers[self.src_mod_idx] + if self.dst_mod_idx < len(dst_object.modifiers): + dst_mod = dst_object.modifiers[self.dst_mod_idx] + + if src_mod is None or dst_mod is None: + self.report({'ERROR'}, "Source or destination modifier not found.") + return {'CANCELLED'} + + if src_mod.type != 'NODES' or dst_mod.type != 'NODES': + self.report( + {'ERROR'}, "Source or destination modifier is not a geometry nodes modifier." + ) + return {'CANCELLED'} + + src_input_map = polib.node_utils_bpy.get_node_tree_inputs_map(src_mod.node_group) + dst_input_map = polib.node_utils_bpy.get_node_tree_inputs_map(dst_mod.node_group) + if len(src_input_map) != len(dst_input_map): + self.report( + {'ERROR'}, + f"Different count of modifier inputs {len(src_input_map)} != {len(dst_input_map)}, cannot copy.", + ) + return {'CANCELLED'} + + any_input_incorrect = False + for src_identifier, src_input in src_input_map.items(): + dst_input = dst_input_map.get(src_identifier) + if dst_input is None: + any_input_incorrect = True + break + + # TODO: Check the type of the input whether it matches. Should we also check the name? + if polib.node_utils_bpy.get_socket_type( + src_input + ) != polib.node_utils_bpy.get_socket_type(dst_input): + logger.info(f"Input types don't match: {src_input} != {dst_input}") + any_input_incorrect = True + break + + if any_input_incorrect: + self.report({'ERROR'}, "Inputs of the two modifiers don't match, cannot copy!") + return {'CANCELLED'} + else: + # If all inputs match, we can copy the values + for input_ in src_mod.keys(): + dst_mod[input_] = src_mod[input_] + + dst_object.update_tag() + polib.ui_bpy.tag_areas_redraw(context, {'VIEW_3D'}) + self.report( + {'INFO'}, + f"Copied modifier values from '{src_object.name}[\"{src_mod.name}\"]' to '{dst_object.name}[\"{src_mod.name}\"]'.", + ) + return {'FINISHED'} + + +MODULE_CLASSES.append(CopyGeonodesModifierValues) + + +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) diff --git a/ui_utils.py b/utils/show_popup.py similarity index 98% rename from ui_utils.py rename to utils/show_popup.py index e408424..f5953cc 100644 --- a/ui_utils.py +++ b/utils/show_popup.py @@ -20,7 +20,7 @@ import bpy import typing -from . import polib +from .. import polib MODULE_CLASSES: typing.List[typing.Any] = []