Skip to content

Commit

Permalink
Updated engon from monorepo commit a113594a3b418b1a1d5ad3f80db54297dc…
Browse files Browse the repository at this point in the history
…81ef08
  • Loading branch information
Griperis committed Jun 11, 2024
1 parent fa032c4 commit 6799c74
Show file tree
Hide file tree
Showing 18 changed files with 637 additions and 220 deletions.
9 changes: 6 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
from . import pack_info_search_paths
from . import asset_helpers
from . import preferences
from . import convert_selection
from . import panel
from . import browser
from . import blend_maintenance
Expand All @@ -104,12 +105,12 @@
bl_info = {
"name": "engon",
"author": "polygoniq xyz s.r.o.",
"version": (1, 1, 0), # bump doc_url as well!
"version": (1, 2, 0), # bump doc_url as well!
"blender": (3, 3, 0),
"location": "polygoniq tab in the sidebar of the 3D View window",
"description": "",
"category": "Object",
"doc_url": "https://docs.polygoniq.com/engon/1.1.0/",
"doc_url": "https://docs.polygoniq.com/engon/1.2.0/",
"tracker_url": "https://polygoniq.com/discord/"
}

Expand All @@ -124,6 +125,7 @@ def register():
ui_utils.register()
pack_info_search_paths.register()
preferences.register()
convert_selection.register()
panel.register()
scatter.register()
blend_maintenance.register()
Expand Down Expand Up @@ -163,14 +165,15 @@ def unregister():
blend_maintenance.unregister()
scatter.unregister()
panel.unregister()
convert_selection.unregister()
preferences.unregister()
pack_info_search_paths.unregister()
ui_utils.unregister()

# Remove all nested modules from module cache, more reliable than importlib.reload(..)
# Idea by BD3D / Jacques Lucke
for module_name in list(sys.modules.keys()):
if module_name.startswith(__name__):
if module_name.startswith(__package__):
del sys.modules[module_name]

addon_updater_ops.unregister()
Expand Down
5 changes: 5 additions & 0 deletions asset_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def from_json_dict(pack_info_path: str, json_dict: typing.Dict[typing.Any, typin
f"Given json dict contains vendor but its type is '{type(vendor)}' "
f"instead of the expected 'str'!")
engon_features = json_dict.get("engon_features", [])
# min_engon_version default is the version, when the field was introduced - 1.2.0
min_engon_version = json_dict.get("min_engon_version", [1, 2, 0])
pack_info_path = os.path.realpath(os.path.abspath(pack_info_path))
pack_info_parent_path = os.path.dirname(pack_info_path)
install_path = os.path.realpath(os.path.abspath(pack_info_parent_path))
Expand Down Expand Up @@ -103,6 +105,7 @@ def from_json_dict(pack_info_path: str, json_dict: typing.Dict[typing.Any, typin
typing.cast(typing.Tuple[int, int, int], tuple(version)),
vendor,
engon_features,
tuple(min_engon_version),
install_path,
pack_info_path,
index_paths,
Expand Down Expand Up @@ -130,6 +133,7 @@ def __init__(
version: typing.Tuple[int, int, int],
vendor: str,
engon_features: typing.List[str],
min_engon_version: typing.Tuple[int, int, int],
install_path: str,
pack_info_path: str,
index_paths: typing.List[str],
Expand All @@ -156,6 +160,7 @@ def __init__(
if len(engon_features) == 0:
raise NotImplementedError("At least one engon feature required in each asset pack!")
self.engon_feature = engon_features[0]
self.min_engon_version = min_engon_version
self.install_path = install_path
self.pack_info_path = pack_info_path
self.index_paths = index_paths
Expand Down
5 changes: 4 additions & 1 deletion blend_maintenance/asset_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ class AssetPackMigrations(typing.NamedTuple):
"collections": [RegexMapping(re.compile("^(AM154-)(.*)"), r"am154_\2")],
"meshes": [RegexMapping(re.compile("^(AM154-)(.*)"), r"am154_\2")],
"objects": [RegexMapping(re.compile("^(AM154-)(.*)"), r"am154_\2")],
"materials": [RegexMapping(re.compile("^(bq_)(.*)"), r"am154_\2")],
"materials": [
RegexMapping(re.compile("^(bq_)(.*)"), r"am154_\2"),
RegexMapping(re.compile("(.*)(_bqm)$"), r"am154_\1")
],
"node_groups": [RegexMapping(re.compile("^(bq_)(.*)"), r"am154_\2")],
}
)
Expand Down
2 changes: 1 addition & 1 deletion blender_manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ schema_version = "1.0.0"
# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "engon"
version = "1.0.3"
version = "1.2.0"
name = "engon"
tagline = "Browse assets, filter and sort them, scatter, animate, manipulate rigs"
maintainer = "polygoniq <[email protected]>"
Expand Down
4 changes: 4 additions & 0 deletions browser/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ def prefs_navbar_draw_override(self, context: bpy.types.Context) -> None:
else:
categories.draw_tree_category_navigation(context, layout)

layout.separator()
row = layout.row(align=True)
row.operator(spawn.MAPR_BrowserSpawnAllDisplayed.bl_idname, icon='IMGDISPLAY')
layout.separator()
filters.draw(context, layout)


Expand Down
7 changes: 4 additions & 3 deletions browser/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import os
import typing
import mapr
import polib
import logging
from . import filters
from . import previews
Expand All @@ -32,8 +33,8 @@
MODULE_CLASSES: typing.List[typing.Any] = []

# Top secret path to the dev location
EXPECTED_DEV_PATH = os.path.expanduser("~/polygoniq/")
IS_DEV = os.path.exists(os.path.realpath(os.path.abspath(os.path.join(EXPECTED_DEV_PATH, ".git"))))
EXPECTED_DEV_PATH = os.path.realpath(os.path.expanduser("~/polygoniq/"))
IS_DEV = os.path.exists(os.path.join(EXPECTED_DEV_PATH, ".git"))


class MAPR_BrowserDeleteCache(bpy.types.Operator):
Expand Down Expand Up @@ -93,7 +94,7 @@ def execute(self, context: bpy.types.Context):
if getattr(self, "asset_path", None) is None:
raise RuntimeError("asset_path is initialized in invoke, use INVOKE_DEFAULT!")

bpy.ops.wm.open_mainfile(filepath=self.asset_path, load_ui=False)
polib.utils_bpy.fork_running_blender(self.asset_path)
return {'FINISHED'}

def invoke(self, context: bpy.types.Context, event: bpy.types.Event):
Expand Down
39 changes: 39 additions & 0 deletions browser/spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
import math
import mathutils
import hatchery
from . import filters
from .. import preferences
from .. import asset_registry
from .. import asset_helpers
logger = logging.getLogger(f"polygoniq.{__name__}")

SPAWN_ALL_DISPLAYED_ASSETS_WARNING_LIMIT = 30


MODULE_CLASSES: typing.List[typing.Any] = []

Expand Down Expand Up @@ -121,6 +124,12 @@ def execute(self, context: bpy.types.Context):
self.report({'ERROR'}, f"Asset with id {self.asset_id} not found")
return {'CANCELLED'}

# If no object is selected we will spawn a sphere and assign material on it
if asset.type_ == mapr.asset_data.AssetDataType.blender_material and len(context.selected_objects) == 0:
bpy.ops.mesh.primitive_uv_sphere_add()
bpy.ops.object.shade_smooth()
bpy.ops.object.material_slot_add()

self._spawn(context, asset, prefs.spawn_options.get_spawn_options(asset, context))
# Make editable and remove duplicates is currently out of hatchery and works based on
# assumption of correct context, which is suboptimal, but at current time the functions
Expand Down Expand Up @@ -152,6 +161,36 @@ def execute(self, context: bpy.types.Context):
MODULE_CLASSES.append(MAPR_BrowserSpawnAsset)


@polib.log_helpers_bpy.logged_operator
class MAPR_BrowserSpawnAllDisplayed(bpy.types.Operator):
bl_idname = "engon.browser_spawn_all_displayed"
bl_label = "Spawn All Displayed"
bl_description = "Spawn all currently displayed assets"

def invoke(self, context: bpy.types.Context, event: bpy.types.Event):
if len(filters.asset_repository.current_assets) > SPAWN_ALL_DISPLAYED_ASSETS_WARNING_LIMIT:
return context.window_manager.invoke_props_dialog(self)
else:
return self.execute(context)

def draw(self, context: bpy.types.Context) -> None:
layout = self.layout
layout.label(
text=f"This operation will spawn {len(filters.asset_repository.current_assets)} assets, continue?")

@polib.utils_bpy.blender_cursor('WAIT')
def execute(self, context: bpy.types.Context):
prefs = preferences.prefs_utils.get_preferences(context).mapr_preferences
assets = filters.asset_repository.current_assets
for asset in assets:
MAPR_SpawnAssetBase._spawn(
self, context, asset, prefs.spawn_options.get_spawn_options(asset, context))
return {'FINISHED'}


MODULE_CLASSES.append(MAPR_BrowserSpawnAllDisplayed)


@polib.log_helpers_bpy.logged_operator
class MAPR_BrowserDrawGeometryNodesAsset(MAPR_SpawnAssetBase):
"""Specialized spawn operator to add geometry nodes asset and start draw mode."""
Expand Down
187 changes: 187 additions & 0 deletions convert_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# copyright (c) 2018- polygoniq xyz s.r.o.

import bpy
import polib
import mapr
import hatchery
import typing
import logging
from . import asset_registry
from . import preferences
logger = logging.getLogger(f"polygoniq.{__name__}")


MODULE_CLASSES: typing.List[typing.Any] = []

# The 'make_selection_linked' implementation is here because we need to access the 'asset_registry'
# instance. The 'make_selection_editable' is in the 'polib.asset_pack_bpy' module, where it can
# be used without dependency on the 'mapr' module.
# If we ever need 'make_selection_linked' in other places, let's move it to 'polib' and add the
# dependency to 'mapr'.


def make_selection_linked(
context: bpy.types.Context,
asset_provider: mapr.asset_provider.AssetProvider,
file_provider: mapr.file_provider.FileProvider
) -> typing.List[bpy.types.Object]:
previous_active_obj_name = context.active_object.name if context.active_object else None
converted_objects = []

spawner = mapr.blender_asset_spawner.AssetSpawner(
asset_provider, file_provider)

for obj in polib.asset_pack_bpy.find_polygoniq_root_objects(context.selected_objects):
if obj.instance_type == 'COLLECTION':
continue

id_from_object = obj.get(mapr.blender_asset_spawner.ASSET_ID_PROP_NAME, None)
if id_from_object is None:
# Object can have missing id if it comes from pre-engon asset pack
logger.error(
f"Object '{obj.name}' has no asset id, cannot convert to linked.")
continue

asset = asset_provider.get_asset(id_from_object)
if asset is None:
# This can happen if the asset id of the object present in scene is not known
# to engon - e.g. if corresponding asset pack is not loaded.
logger.error(
f"Asset with id '{id_from_object}' not found in any installed or registered "
"Asset Pack, cannot convert to linked."
)
continue

if asset.type_ != mapr.asset_data.AssetDataType.blender_model:
continue

old_model_matrix = obj.matrix_world.copy()
old_collections = list(obj.users_collection)
old_color = tuple(obj.color)
old_parent = obj.parent

# This way old object names won't interfere with the new ones
hierarchy_objects = polib.asset_pack_bpy.get_hierarchy(obj)
for hierarchy_obj in hierarchy_objects:
hierarchy_obj.name = polib.utils_bpy.generate_unique_name(
f"del_{hierarchy_obj.name}", bpy.data.objects)

# Spawn the asset if its mapr id is found
spawned_data = spawner.spawn(context, asset, hatchery.spawn.ModelSpawnOptions(
parent_collection=None,
select_spawned=False
))
if spawned_data is None:
logger.error(f"Failed to spawn asset {asset.id_}")
continue

assert isinstance(spawned_data, hatchery.spawn.ModelSpawnedData)

instance_root = spawned_data.instancer
instance_root.matrix_world = old_model_matrix
instance_root.parent = old_parent
instance_root.color = old_color

for coll in old_collections:
if instance_root.name not in coll.objects:
coll.objects.link(instance_root)

converted_objects.append(instance_root)

bpy.data.batch_remove(hierarchy_objects)

# Force Blender to evaluate view_layer data after programmatically removing/linking objects.
# https://docs.blender.org/api/current/info_gotcha.html#no-updates-after-setting-values
context.view_layer.update()

# Select root instances of the newly created objects, user had to have them selected before,
# otherwise they wouldn't be converted at all.
for obj in converted_objects:
obj.select_set(True)

if previous_active_obj_name is not None and \
previous_active_obj_name in context.view_layer.objects:
context.view_layer.objects.active = bpy.data.objects[previous_active_obj_name]

return converted_objects


@polib.log_helpers_bpy.logged_operator
class MakeSelectionEditable(bpy.types.Operator):
bl_idname = "engon.make_selection_editable"
bl_label = "Convert to Editable"
bl_description = "Converts Collections into Mesh Data with Editable Materials"

bl_options = {'REGISTER', 'UNDO'}

@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
return context.mode == 'OBJECT' and len(context.selected_objects) > 0

@polib.utils_bpy.blender_cursor('WAIT')
def execute(self, context: bpy.types.Context):
selected_objects_and_parents_names = polib.asset_pack_bpy.make_selection_editable(
context, True, keep_selection=True, keep_active=True)
pack_paths = asset_registry.instance.get_packs_paths()

logger.info(
f"Resulting objects and parents: {selected_objects_and_parents_names}")

prefs = preferences.prefs_utils.get_preferences(context).mapr_preferences
if prefs.spawn_options.remove_duplicates:
filters = [polib.remove_duplicates_bpy.polygoniq_duplicate_data_filter]
polib.remove_duplicates_bpy.remove_duplicate_datablocks(
bpy.data.materials, filters, pack_paths)
polib.remove_duplicates_bpy.remove_duplicate_datablocks(
bpy.data.images, filters, pack_paths)
polib.remove_duplicates_bpy.remove_duplicate_datablocks(
bpy.data.node_groups, filters, pack_paths)

return {'FINISHED'}


MODULE_CLASSES.append(MakeSelectionEditable)


@polib.log_helpers_bpy.logged_operator
class MakeSelectionLinked(bpy.types.Operator):
bl_idname = "engon.make_selection_linked"
bl_label = "Convert to Linked"
bl_description = "Converts selected objects to their linked variants from " \
"engon asset packs. WARNING: This operation removes " \
"all local changes. Doesn't work on particle systems, " \
"only polygoniq assets are supported by this operator"
bl_options = {'REGISTER', 'UNDO'}

@classmethod
def poll(cls, context: bpy.types.Context):
return context.mode == 'OBJECT' and next(
polib.asset_pack_bpy.get_polygoniq_objects(
context.selected_objects, include_linked=False),
None
) is not None

@polib.utils_bpy.blender_cursor('WAIT')
def execute(self, context: bpy.types.Context):
converted_objects = make_selection_linked(
context,
asset_registry.instance.master_asset_provider,
asset_registry.instance.master_file_provider
)

self.report({'INFO'}, f"Converted {len(converted_objects)} object(s) to linked")

return {'FINISHED'}


MODULE_CLASSES.append(MakeSelectionLinked)


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)
Loading

0 comments on commit 6799c74

Please sign in to comment.