Panel update + fix for remove function so it doesn't remove all instances
This commit is contained in:
1 parent
fbc7ac18e4
commit
3f87904cd1
5 files changed
+182
-159
No files matched your search
@@ -7,3 +7,6 @@ private
|
|||||||
.idea
|
.idea
|
||||||
# ignore mkdocs build
|
# ignore mkdocs build
|
||||||
site/
|
site/
|
||||||
|
|
||||||
|
# ignore dev utilities
|
||||||
|
dev/
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
# Melvile Tools: notes for Claude
|
|
||||||
|
|
||||||
Blender add-on (Blender 5.2) for the Melvile pipeline at Walking The Dog: Sanity Check,
|
|
||||||
Rename, Shot Manager and Scene Manager panels in the 3D View sidebar (WTD tab). `README.md`
|
|
||||||
is the user guide: keep it up to date with every user-visible change.
|
|
||||||
|
|
||||||
## Layout and conventions
|
|
||||||
|
|
||||||
- `config.py`: every production value (prefixes, formats, patterns, modes). Values are
|
|
||||||
read at run time (`config.X`, never copied at import) because a studio JSON file can
|
|
||||||
override them (add-on preferences "Config Override", or `WTD_SANITY_CONFIG`).
|
|
||||||
New tunable values go here, with a comment, and in the README's Configuration table.
|
|
||||||
- `ui/panel.py`: all panels. Operators live with their tool (`rename.py`,
|
|
||||||
`shot_viewer.py`, `scene_manager/`, `ui/operators.py` for the Sanity Check).
|
|
||||||
- `checks/`: sanity checks, auto-registered (see README > For developers).
|
|
||||||
- `__init__.py` deletes cached submodules on reload, so Reload Scripts picks up every file.
|
|
||||||
- Versions: `blender_manifest.toml` and `bl_info` in `__init__.py` (keep both in sync).
|
|
||||||
- Style: match the surrounding code; short docstrings, few comments, no heavy abstractions.
|
|
||||||
|
|
||||||
## Working with the user
|
|
||||||
|
|
||||||
- Commits: when asked, create a branch from `master`, commit there (end with the
|
|
||||||
Co-Authored-By line), and give the user the merge / push / branch-delete commands:
|
|
||||||
they run them themselves. Only commit straight on `master` when told to.
|
|
||||||
- Real Blender API facts beat memory: probe in headless Blender before relying on an API.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Blender binary: macOS `/Applications/Blender.app/Contents/MacOS/Blender`, Windows
|
|
||||||
`blender.exe` from the install folder. Import the add-on in tests by putting the folder
|
|
||||||
that contains this repo on `sys.path` and importing the repo folder name (see
|
|
||||||
`dev/test_scene_manager.py`).
|
|
||||||
|
|
||||||
- Headless: `blender -b --factory-startup --python-exit-code 1 --python <script> -- <args>`.
|
|
||||||
Timers do not run in background mode: pump background jobs by hand
|
|
||||||
(`scene_manager.ipc._poll()`).
|
|
||||||
- UI: run Blender with a window (`--factory-startup <file.blend> --python <script>`, a
|
|
||||||
.blend argument avoids the splash), drive it with `bpy.app.timers`, take screenshots with
|
|
||||||
`bpy.ops.screen.screenshot_area`, quit with `bpy.ops.wm.quit_blender()`. A sidebar tab
|
|
||||||
(`region.active_panel_category`) can only be set once the region has been drawn.
|
|
||||||
- Scene Manager without Libreflow (`dev/`):
|
|
||||||
1. `blender -b --factory-startup --python dev/make_test_assets.py -- <workdir>`
|
|
||||||
2. `python dev/fake_libreflow.py <workdir>` (keep running; restart for a clean state)
|
|
||||||
3. `blender -b --factory-startup --python-exit-code 1 --python dev/test_scene_manager.py -- <workdir>`
|
|
||||||
(71 checks, "All checks passed"). `/_test/publish` and `/_test/download` simulate a new
|
|
||||||
publish and a requested revision reaching this site.
|
|
||||||
The fake server follows the real `wtdcmds` replies; check them against the real
|
|
||||||
Libreflow when one is available.
|
|
||||||
|
|
||||||
## External pieces
|
|
||||||
|
|
||||||
- Libreflow `wtdcmds` extension (commands used by the Scene Manager), on the share:
|
|
||||||
`WG/_Dev/GIT/ndemaerschalk/_MELVILE/libreflow_dev_utils/dev/src/wtdcmds/`
|
|
||||||
(`src/libreflow/extensions/wtdcmds/wtd_cmds.py`, `BLENDER_ADDON_BRIEF.md`).
|
|
||||||
Server address/token: `~/.kabaret/ipc/server.json` (config `LIBREFLOW_SERVER_FILE`).
|
|
||||||
- Add Camera Rigs extension: `bpy.ops.object.build_camera_rig(mode=...)` builds the
|
|
||||||
Shot Manager cameras. It finds its rigs by the `rig_id` custom property, not by name.
|
|
||||||
- `tools_ops.py` "Get Oid" uses the separate kabaret Blender client (`bpy.ops.kabaret.cmds_call`).
|
|
||||||
|
|
||||||
## Design notes (found by testing)
|
|
||||||
|
|
||||||
- Shot Manager: shots are timeline markers bound to cameras, in one scene. Setting
|
|
||||||
`scene.frame_current` does not switch the camera (only `frame_set` does): set
|
|
||||||
`scene.camera` explicitly.
|
|
||||||
- Scene Manager: tags live on `bpy.types.Library` only. Library overrides lose custom
|
|
||||||
properties when they resync, so instances are traced back to their library (empty →
|
|
||||||
`instance_collection.library`, override → `override_library.reference.library`).
|
|
||||||
- `library.reload()` invalidates every Python reference to linked and override data:
|
|
||||||
look IDs up again afterwards. In Blender 5.2 it also replaces the Library itself (renamed
|
|
||||||
after its file, custom properties gone): `library.update()` finds it by path and restores
|
|
||||||
the name and tags.
|
|
||||||
- Removing a library leaves the libraries it linked indirectly (e.g. Blender's
|
|
||||||
`geometry_nodes_essentials.blend`): `remove_unused_libraries()` drops the ones whose data
|
|
||||||
is no longer used, never a managed library.
|
|
||||||
- LODs are child collections of the asset collection (`COL_{asset}_LOD{lod}`, any name,
|
|
||||||
ordered by `SCENE_MANAGER_LODS`, then numbers, then names; the first is the default). Overrides
|
|
||||||
switch by `hide_viewport`/`hide_render` on the LOD override collections: Blender stores
|
|
||||||
them as override properties (kept through save, reload and resync) and hidden LODs are
|
|
||||||
not evaluated. Collection instances instance the LOD collection itself. A second override's
|
|
||||||
collections are renamed (`.001`): match LODs on `override_library.reference.name`.
|
|
||||||
- "Shot Manager" is the user-facing name of what the code calls `shot_viewer`
|
|
||||||
(module, operators, `SHOT_VIEWER_*` config keys kept for existing override files).
|
|
||||||
- Background requests (`scene_manager.ipc.run`) hand results back through a timer;
|
|
||||||
in callbacks, find scenes by name (`bpy.context.scene` is not reliable in timers).
|
|
||||||
|
|
||||||
## Real Libreflow (first run, 2026-10-27)
|
|
||||||
|
|
||||||
- Kitsu asset types: `main_character`, `secondary_character`, `tertiary_character`,
|
|
||||||
`main_props`, `secondary_props`, `set_props`, `vehicles`, `bg_location`, `bg_set`,
|
|
||||||
`skies`, `setdress_bank`. The fake server uses the same names.
|
|
||||||
- Paths come back as Windows paths with backslashes; `comment` can be `null`.
|
|
||||||
- Set dressing publishes (`setdress_low`) hold a whole layout scene: the set is the
|
|
||||||
`environments` collection, next to `lights`, `cameras`, `characters`, `_unused`.
|
|
||||||
- Tested live: connection, context, casting, load (instance and override), version switch
|
|
||||||
and update, remove, assetize errors, one request (`car_paul` modeling.blend v001 →
|
|
||||||
`Requested`). Not tested: an actual assetize publish (it writes to production).
|
|
||||||
New wtdcmds commands need every Libreflow session restarted (a running one keeps the
|
|
||||||
old code and answers 404).
|
|
||||||
- Load Publish (2026-10-28) uses only existing commands (`wtd_list_assets`,
|
|
||||||
`wtd_get_asset_files`, `wtd_get_revisions`): 309 assets in ~0.1 s, live-tested on
|
|
||||||
`car_paul` (loaded twice: `car_paul_01`, `car_paul_02`).
|
|
||||||
- `/melvile/films/melvile` has no sequences or shots in Libreflow yet; `melvile_test` has 9.
|
|
||||||
- Deployed Libreflow (Overseer launcher): wtdcmds must be an "extension" entry, so the
|
|
||||||
launcher adds `libreflow.extensions.wtdcmds:install_extensions` to
|
|
||||||
`KABARET_FLOW_EXT_INSTALLERS`; the package name must stay `libreflow.extensions.wtdcmds`.
|
|
||||||
The launcher only rebuilds its env when the project config changes: "Force update".
|
|
||||||
|
|
||||||
## Open points
|
|
||||||
|
|
||||||
- Override vs instance per asset type (`SCENE_MANAGER_LINK_MODES`), and the file patterns
|
|
||||||
of sets (`setdress_low` seen on one asset only): confirm with production.
|
|
||||||
- The server picks the first file matching a pattern even when it is not on this site,
|
|
||||||
while another matching file is (e.g. `car_paul`: `modeling/modeling.blend` v001 not
|
|
||||||
available, `modeling/tes_modeling.blend` v002 available).
|
|
||||||
- Values marked TODO in `config.py` wait for production specs.
|
|
||||||
@@ -369,7 +369,7 @@ All of these can be changed in the [Configuration](#configuration).
|
|||||||
|
|
||||||
Lists everything the Scene Manager loaded, one line per asset, with its version:
|
Lists everything the Scene Manager loaded, one line per asset, with its version:
|
||||||
|
|
||||||
- **Load Publish...**: load any published file of the project, cast in the shot or not (see [Load any publish](#load-any-publish)).
|
- **Load...**: load any published file of the project, cast in the shot or not (see [Load any publish](#load-any-publish)).
|
||||||
|
|
||||||
- The version is **red** when it is not the latest publish. The line under it says either "v004 available" (click the update icon), or "v004 published, not available on this site yet" (click the globe to request it, then "v004 requested, waiting for the download").
|
- The version is **red** when it is not the latest publish. The line under it says either "v004 available" (click the update icon), or "v004 published, not available on this site yet" (click the globe to request it, then "v004 requested, waiting for the download").
|
||||||
- **Arrow**: shows the asset's instances, each with a button to select its objects and one to remove it. Under an instance with levels of detail, one button per LOD (`LOD high mid low`), the shown one pressed.
|
- **Arrow**: shows the asset's instances, each with a button to select its objects and one to remove it. Under an instance with levels of detail, one button per LOD (`LOD high mid low`), the shown one pressed.
|
||||||
@@ -384,7 +384,7 @@ Updating swaps the linked file and reloads it. Overrides keep their posing and a
|
|||||||
|
|
||||||
### Load any publish
|
### Load any publish
|
||||||
|
|
||||||
**Load Publish...** (top of the *Loaded* sub-panel) searches every asset of the project: type a few letters of its name, pick it. A dialog then shows:
|
**Load...** (top of the *Loaded* sub-panel) searches every asset of the project: type a few letters of its name, pick it. A dialog then shows:
|
||||||
|
|
||||||
| Setting | Use |
|
| Setting | Use |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|||||||
@@ -67,7 +67,12 @@ def instances(library):
|
|||||||
|
|
||||||
|
|
||||||
def instance_objects(instance):
|
def instance_objects(instance):
|
||||||
return [instance] if isinstance(instance, bpy.types.Object) else list(instance.all_objects)
|
"""The objects of one instance: the empty, or the override's own objects. Linked objects an
|
||||||
|
override keeps (bone widgets in a collection with nothing to override...) are shared by
|
||||||
|
every instance of the asset: they are left out, so selecting them picks no instance."""
|
||||||
|
if isinstance(instance, bpy.types.Object):
|
||||||
|
return [instance]
|
||||||
|
return [ob for ob in instance.all_objects if ob.library is None]
|
||||||
|
|
||||||
|
|
||||||
def selected_instances(view_layer):
|
def selected_instances(view_layer):
|
||||||
|
|||||||
+171
-41
@@ -3,11 +3,11 @@ import textwrap
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from .. import config, rename, shot_viewer, tools_ops
|
from .. import config, rename, shot_viewer, tools_ops
|
||||||
|
from ..core import CheckContext, Severity, checks_for_context, contexts, runner
|
||||||
|
from ..core.runner import Status
|
||||||
from ..scene_manager import ipc
|
from ..scene_manager import ipc
|
||||||
from ..scene_manager import library as sm_library
|
from ..scene_manager import library as sm_library
|
||||||
from ..scene_manager import operators as sm
|
from ..scene_manager import operators as sm
|
||||||
from ..core import CheckContext, Severity, checks_for_context, contexts, runner
|
|
||||||
from ..core.runner import Status
|
|
||||||
from .operators import EXPANDED
|
from .operators import EXPANDED
|
||||||
|
|
||||||
STATUS_ICONS = {
|
STATUS_ICONS = {
|
||||||
@@ -244,7 +244,18 @@ class _RenamePanel:
|
|||||||
class WTD_UL_shots(bpy.types.UIList):
|
class WTD_UL_shots(bpy.types.UIList):
|
||||||
"""Camera markers of the scene, in time order."""
|
"""Camera markers of the scene, in time order."""
|
||||||
|
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
|
def draw_item(
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
layout,
|
||||||
|
data,
|
||||||
|
item,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_property,
|
||||||
|
index=0,
|
||||||
|
flt_flag=0,
|
||||||
|
):
|
||||||
start, end = shot_viewer.shot_range(item)
|
start, end = shot_viewer.shot_range(item)
|
||||||
split = layout.split(factor=0.4)
|
split = layout.split(factor=0.4)
|
||||||
split.label(text=item.name, icon="MARKER_HLT")
|
split.label(text=item.name, icon="MARKER_HLT")
|
||||||
@@ -256,7 +267,10 @@ class WTD_UL_shots(bpy.types.UIList):
|
|||||||
markers = getattr(data, propname)
|
markers = getattr(data, propname)
|
||||||
ordered = [marker.as_pointer() for marker in shot_viewer.shots(data)]
|
ordered = [marker.as_pointer() for marker in shot_viewer.shots(data)]
|
||||||
position = {pointer: index for index, pointer in enumerate(ordered)}
|
position = {pointer: index for index, pointer in enumerate(ordered)}
|
||||||
flags = [self.bitflag_filter_item if marker.as_pointer() in position else 0 for marker in markers]
|
flags = [
|
||||||
|
self.bitflag_filter_item if marker.as_pointer() in position else 0
|
||||||
|
for marker in markers
|
||||||
|
]
|
||||||
if self.filter_name:
|
if self.filter_name:
|
||||||
by_name = bpy.types.UI_UL_list.filter_items_by_name(
|
by_name = bpy.types.UI_UL_list.filter_items_by_name(
|
||||||
self.filter_name, self.bitflag_filter_item, markers, "name"
|
self.filter_name, self.bitflag_filter_item, markers, "name"
|
||||||
@@ -294,13 +308,33 @@ class _ShotViewerPanel:
|
|||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.scale_y = 1.4
|
row.scale_y = 1.4
|
||||||
row.enabled = has_shots
|
row.enabled = has_shots
|
||||||
row.operator(shot_viewer.WTD_OT_shot_viewer_step.bl_idname, text="Previous", icon="TRIA_LEFT").step = -1
|
row.operator(
|
||||||
row.operator(shot_viewer.WTD_OT_shot_viewer_step.bl_idname, text="Next", icon="TRIA_RIGHT").step = 1
|
shot_viewer.WTD_OT_shot_viewer_step.bl_idname,
|
||||||
|
text="Previous",
|
||||||
|
icon="TRIA_LEFT",
|
||||||
|
).step = -1
|
||||||
|
row.operator(
|
||||||
|
shot_viewer.WTD_OT_shot_viewer_step.bl_idname,
|
||||||
|
text="Next",
|
||||||
|
icon="TRIA_RIGHT",
|
||||||
|
).step = 1
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.template_list("WTD_UL_shots", "", scene, "timeline_markers", settings, "shot_index", rows=5)
|
row.template_list(
|
||||||
|
"WTD_UL_shots",
|
||||||
|
"",
|
||||||
|
scene,
|
||||||
|
"timeline_markers",
|
||||||
|
settings,
|
||||||
|
"shot_index",
|
||||||
|
rows=5,
|
||||||
|
)
|
||||||
col = row.column(align=True)
|
col = row.column(align=True)
|
||||||
col.operator(shot_viewer.WTD_OT_shot_viewer_new_shot.bl_idname, text="", icon="ADD")
|
col.operator(
|
||||||
col.operator(shot_viewer.WTD_OT_shot_viewer_delete_shot.bl_idname, text="", icon="REMOVE")
|
shot_viewer.WTD_OT_shot_viewer_new_shot.bl_idname, text="", icon="ADD"
|
||||||
|
)
|
||||||
|
col.operator(
|
||||||
|
shot_viewer.WTD_OT_shot_viewer_delete_shot.bl_idname, text="", icon="REMOVE"
|
||||||
|
)
|
||||||
if not has_shots:
|
if not has_shots:
|
||||||
layout.label(text="No camera marker yet: add a shot with +", icon="INFO")
|
layout.label(text="No camera marker yet: add a shot with +", icon="INFO")
|
||||||
layout.prop(settings, "loop_shot")
|
layout.prop(settings, "loop_shot")
|
||||||
@@ -355,8 +389,14 @@ class _SceneManagerPanel:
|
|||||||
|
|
||||||
layout.prop(scene.wtd_scene_manager, "context_oid", text="", icon="SEQUENCE")
|
layout.prop(scene.wtd_scene_manager, "context_oid", text="", icon="SEQUENCE")
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator(sm.WTD_OT_scene_use_current_oid.bl_idname, text="Libreflow Oid", icon="IMPORT")
|
row.operator(
|
||||||
row.operator(sm.WTD_OT_scene_pick_shot.bl_idname, text="Pick Shot", icon="VIEWZOOM")
|
sm.WTD_OT_scene_use_current_oid.bl_idname,
|
||||||
|
text="Libreflow Oid",
|
||||||
|
icon="IMPORT",
|
||||||
|
)
|
||||||
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_pick_shot.bl_idname, text="Pick Shot", icon="VIEWZOOM"
|
||||||
|
)
|
||||||
text, valid = sm.context_label(sm.context_oid(scene))
|
text, valid = sm.context_label(sm.context_oid(scene))
|
||||||
if text:
|
if text:
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
@@ -386,11 +426,17 @@ class _SceneCastingPanel:
|
|||||||
row.scale_y = 1.3
|
row.scale_y = 1.3
|
||||||
row.operator(sm.WTD_OT_scene_load.bl_idname, text="Load Missing", icon="IMPORT")
|
row.operator(sm.WTD_OT_scene_load.bl_idname, text="Load Missing", icon="IMPORT")
|
||||||
if sm.missing_files(context.scene):
|
if sm.missing_files(context.scene):
|
||||||
row.operator(sm.WTD_OT_scene_request.bl_idname, text="Request Missing", icon="URL")
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_request.bl_idname, text="Request Missing", icon="URL"
|
||||||
|
)
|
||||||
row.operator(sm.WTD_OT_scene_refresh.bl_idname, text="", icon="FILE_REFRESH")
|
row.operator(sm.WTD_OT_scene_refresh.bl_idname, text="", icon="FILE_REFRESH")
|
||||||
casting = sm.STATE.casting.get(oid)
|
casting = sm.STATE.casting.get(oid)
|
||||||
if casting is None:
|
if casting is None:
|
||||||
text = "Not read yet: refresh" if sm.is_shot_context(oid) else "Set a shot as context"
|
text = (
|
||||||
|
"Not read yet: refresh"
|
||||||
|
if sm.is_shot_context(oid)
|
||||||
|
else "Set a shot as context"
|
||||||
|
)
|
||||||
layout.label(text=text, icon="INFO")
|
layout.label(text=text, icon="INFO")
|
||||||
return
|
return
|
||||||
if not casting["assets"]:
|
if not casting["assets"]:
|
||||||
@@ -399,36 +445,64 @@ class _SceneCastingPanel:
|
|||||||
found = sm_library.all_instances()
|
found = sm_library.all_instances()
|
||||||
col = layout.column(align=True)
|
col = layout.column(align=True)
|
||||||
for asset in casting["assets"]:
|
for asset in casting["assets"]:
|
||||||
library = sm_library.find_library(asset["file"]["file_oid"]) if asset.get("file") else None
|
library = (
|
||||||
|
sm_library.find_library(asset["file"]["file_oid"])
|
||||||
|
if asset.get("file")
|
||||||
|
else None
|
||||||
|
)
|
||||||
have = len(found.get(library.name, [])) if library else 0
|
have = len(found.get(library.name, [])) if library else 0
|
||||||
want = asset.get("nb_occurences") or 1
|
want = asset.get("nb_occurences") or 1
|
||||||
error = asset.get("error")
|
error = asset.get("error")
|
||||||
file = sm.file_data(asset["file"]["file_oid"], asset["file"]) if asset.get("file") else None
|
file = (
|
||||||
|
sm.file_data(asset["file"]["file_oid"], asset["file"])
|
||||||
|
if asset.get("file")
|
||||||
|
else None
|
||||||
|
)
|
||||||
latest = (file or {}).get("latest") or {}
|
latest = (file or {}).get("latest") or {}
|
||||||
requested = bool(error) and latest.get("sync_status") == "Requested"
|
requested = bool(error) and latest.get("sync_status") == "Requested"
|
||||||
if requested:
|
if requested:
|
||||||
error = f"{latest['name']} requested, waiting for the download"
|
error = f"{latest['name']} requested, waiting for the download"
|
||||||
icon = "SORTTIME" if requested else "ERROR" if error else "CHECKMARK" if have >= want else "IMPORT"
|
icon = (
|
||||||
|
"SORTTIME"
|
||||||
|
if requested
|
||||||
|
else "ERROR"
|
||||||
|
if error
|
||||||
|
else "CHECKMARK"
|
||||||
|
if have >= want
|
||||||
|
else "IMPORT"
|
||||||
|
)
|
||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
row.alert = bool(error) and not requested
|
row.alert = bool(error) and not requested
|
||||||
row.label(text=f"{asset['name']} ({asset.get('asset_type', '')})", icon=icon)
|
row.label(
|
||||||
|
text=f"{asset['name']} ({asset.get('asset_type', '')})", icon=icon
|
||||||
|
)
|
||||||
row.label(text=f"{have}/{want}")
|
row.label(text=f"{have}/{want}")
|
||||||
if not error and have < want:
|
if not error and have < want:
|
||||||
row.operator(sm.WTD_OT_scene_load.bl_idname, text="", icon="IMPORT").asset_oid = asset["oid"]
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_load.bl_idname, text="", icon="IMPORT"
|
||||||
|
).asset_oid = asset["oid"]
|
||||||
if sm.to_request(file):
|
if sm.to_request(file):
|
||||||
row.operator(sm.WTD_OT_scene_request.bl_idname, text="", icon="URL").file_oid = file["file_oid"]
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_request.bl_idname, text="", icon="URL"
|
||||||
|
).file_oid = file["file_oid"]
|
||||||
if error:
|
if error:
|
||||||
sub = col.row()
|
sub = col.row()
|
||||||
sub.alert = not requested
|
sub.alert = not requested
|
||||||
_wrapped_label(sub, context, error, icon="BLANK1")
|
_wrapped_label(sub, context, error, icon="BLANK1")
|
||||||
|
|
||||||
cast = {asset["oid"] for asset in casting["assets"]}
|
cast = {asset["oid"] for asset in casting["assets"]}
|
||||||
extra = [library for library in sm_library.managed_libraries() if library["wtd_asset_oid"] not in cast]
|
extra = [
|
||||||
|
library
|
||||||
|
for library in sm_library.managed_libraries()
|
||||||
|
if library["wtd_asset_oid"] not in cast
|
||||||
|
]
|
||||||
if extra:
|
if extra:
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="Loaded, no longer cast:", icon="ERROR")
|
box.label(text="Loaded, no longer cast:", icon="ERROR")
|
||||||
for library in extra:
|
for library in extra:
|
||||||
box.label(text=library.get("wtd_asset_name", library.name), icon="BLANK1")
|
box.label(
|
||||||
|
text=library.get("wtd_asset_name", library.name), icon="BLANK1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _SceneLoadedPanel:
|
class _SceneLoadedPanel:
|
||||||
@@ -439,13 +513,17 @@ class _SceneLoadedPanel:
|
|||||||
|
|
||||||
def draw(self, context) -> None:
|
def draw(self, context) -> None:
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.operator(sm.WTD_OT_scene_browse.bl_idname, text="Load Publish...", icon="VIEWZOOM")
|
layout.operator(
|
||||||
|
sm.WTD_OT_scene_browse.bl_idname, text="Load...", icon="VIEWZOOM"
|
||||||
|
)
|
||||||
libraries = sm_library.managed_libraries()
|
libraries = sm_library.managed_libraries()
|
||||||
if not libraries:
|
if not libraries:
|
||||||
layout.label(text="Nothing loaded by the Scene Manager", icon="INFO")
|
layout.label(text="Nothing loaded by the Scene Manager", icon="INFO")
|
||||||
return
|
return
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator(sm.WTD_OT_scene_update.bl_idname, text="Update All", icon="FILE_REFRESH")
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_update.bl_idname, text="Update All", icon="FILE_REFRESH"
|
||||||
|
)
|
||||||
row.operator(sm.WTD_OT_scene_refresh.bl_idname, text="", icon="VIEWZOOM")
|
row.operator(sm.WTD_OT_scene_refresh.bl_idname, text="", icon="VIEWZOOM")
|
||||||
|
|
||||||
found = sm_library.all_instances()
|
found = sm_library.all_instances()
|
||||||
@@ -460,16 +538,29 @@ class _SceneLoadedPanel:
|
|||||||
emboss=False,
|
emboss=False,
|
||||||
icon="DOWNARROW_HLT" if expanded else "RIGHTARROW",
|
icon="DOWNARROW_HLT" if expanded else "RIGHTARROW",
|
||||||
).library = library.name
|
).library = library.name
|
||||||
row.label(text=library.get("wtd_asset_name", library.name), icon="LIBRARY_DATA_DIRECT")
|
row.label(
|
||||||
|
text=library.get("wtd_asset_name", library.name),
|
||||||
|
icon="LIBRARY_DATA_DIRECT",
|
||||||
|
)
|
||||||
version = row.row(align=True)
|
version = row.row(align=True)
|
||||||
version.alert = outdated
|
version.alert = outdated
|
||||||
version.label(text=library.get("wtd_revision", "?"))
|
version.label(text=library.get("wtd_revision", "?"))
|
||||||
if state == "UPDATE":
|
if state == "UPDATE":
|
||||||
row.operator(sm.WTD_OT_scene_update.bl_idname, text="", icon="FILE_REFRESH").library = library.name
|
row.operator(
|
||||||
elif state == "PENDING" and sm.to_request(sm.file_data(library["wtd_file_oid"])):
|
sm.WTD_OT_scene_update.bl_idname, text="", icon="FILE_REFRESH"
|
||||||
row.operator(sm.WTD_OT_scene_request.bl_idname, text="", icon="URL").file_oid = library["wtd_file_oid"]
|
).library = library.name
|
||||||
row.operator(sm.WTD_OT_scene_pick_revision.bl_idname, text="", icon="COLLAPSEMENU").library = library.name
|
elif state == "PENDING" and sm.to_request(
|
||||||
row.operator(sm.WTD_OT_scene_remove.bl_idname, text="", icon="X").library = library.name
|
sm.file_data(library["wtd_file_oid"])
|
||||||
|
):
|
||||||
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_request.bl_idname, text="", icon="URL"
|
||||||
|
).file_oid = library["wtd_file_oid"]
|
||||||
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_pick_revision.bl_idname, text="", icon="COLLAPSEMENU"
|
||||||
|
).library = library.name
|
||||||
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_remove.bl_idname, text="", icon="X"
|
||||||
|
).library = library.name
|
||||||
if outdated:
|
if outdated:
|
||||||
sub = layout.row()
|
sub = layout.row()
|
||||||
sub.alert = True
|
sub.alert = True
|
||||||
@@ -480,23 +571,44 @@ class _SceneLoadedPanel:
|
|||||||
for instance in found.get(library.name, []):
|
for instance in found.get(library.name, []):
|
||||||
is_object = isinstance(instance, bpy.types.Object)
|
is_object = isinstance(instance, bpy.types.Object)
|
||||||
sub = col.row(align=True)
|
sub = col.row(align=True)
|
||||||
sub.label(text=instance.name, icon="OUTLINER_OB_EMPTY" if is_object else "OUTLINER_COLLECTION")
|
sub.label(
|
||||||
op = sub.operator(sm.WTD_OT_scene_select_instance.bl_idname, text="", icon="RESTRICT_SELECT_OFF")
|
text=instance.name,
|
||||||
|
icon="OUTLINER_OB_EMPTY" if is_object else "OUTLINER_COLLECTION",
|
||||||
|
)
|
||||||
|
op = sub.operator(
|
||||||
|
sm.WTD_OT_scene_select_instance.bl_idname,
|
||||||
|
text="",
|
||||||
|
icon="RESTRICT_SELECT_OFF",
|
||||||
|
)
|
||||||
op.library, op.instance = library.name, instance.name
|
op.library, op.instance = library.name, instance.name
|
||||||
sub.operator(sm.WTD_OT_scene_remove.bl_idname, text="", icon="X").instance = instance.name
|
sub.operator(
|
||||||
|
sm.WTD_OT_scene_remove.bl_idname, text="", icon="X"
|
||||||
|
).instance = instance.name
|
||||||
lods = sm_library.lods(library, instance)
|
lods = sm_library.lods(library, instance)
|
||||||
if lods:
|
if lods:
|
||||||
current = sm_library.current_lod(library, instance)
|
current = sm_library.current_lod(library, instance)
|
||||||
sub = col.row(align=True)
|
sub = col.row(align=True)
|
||||||
sub.label(text="LOD", icon="BLANK1")
|
sub.label(text="LOD", icon="BLANK1")
|
||||||
for name in lods:
|
for name in lods:
|
||||||
op = sub.operator(sm.WTD_OT_scene_set_lod.bl_idname, text=name, depress=name == current)
|
op = sub.operator(
|
||||||
op.lod, op.library, op.instance = name, library.name, instance.name
|
sm.WTD_OT_scene_set_lod.bl_idname,
|
||||||
|
text=name,
|
||||||
|
depress=name == current,
|
||||||
|
)
|
||||||
|
op.lod, op.library, op.instance = (
|
||||||
|
name,
|
||||||
|
library.name,
|
||||||
|
instance.name,
|
||||||
|
)
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator(sm.WTD_OT_scene_update.bl_idname, text="Update Selected").selected = True
|
row.operator(
|
||||||
row.operator(sm.WTD_OT_scene_remove.bl_idname, text="Remove Selected").selected = True
|
sm.WTD_OT_scene_update.bl_idname, text="Update Selected"
|
||||||
|
).selected = True
|
||||||
|
row.operator(
|
||||||
|
sm.WTD_OT_scene_remove.bl_idname, text="Remove Selected"
|
||||||
|
).selected = True
|
||||||
selected = [
|
selected = [
|
||||||
(library, instance)
|
(library, instance)
|
||||||
for library, instance in sm_library.selected_instances(context.view_layer)
|
for library, instance in sm_library.selected_instances(context.view_layer)
|
||||||
@@ -506,10 +618,21 @@ class _SceneLoadedPanel:
|
|||||||
col = layout.column(align=True)
|
col = layout.column(align=True)
|
||||||
col.label(text="LOD of Selected")
|
col.label(text="LOD of Selected")
|
||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
shown = {sm_library.current_lod(library, instance) for library, instance in selected}
|
shown = {
|
||||||
names = {name for library, instance in selected for name in sm_library.lods(library, instance)}
|
sm_library.current_lod(library, instance)
|
||||||
|
for library, instance in selected
|
||||||
|
}
|
||||||
|
names = {
|
||||||
|
name
|
||||||
|
for library, instance in selected
|
||||||
|
for name in sm_library.lods(library, instance)
|
||||||
|
}
|
||||||
for name in sorted(names, key=sm_library.lod_order):
|
for name in sorted(names, key=sm_library.lod_order):
|
||||||
op = row.operator(sm.WTD_OT_scene_set_lod.bl_idname, text=name, depress=shown == {name})
|
op = row.operator(
|
||||||
|
sm.WTD_OT_scene_set_lod.bl_idname,
|
||||||
|
text=name,
|
||||||
|
depress=shown == {name},
|
||||||
|
)
|
||||||
op.lod, op.selected = name, True
|
op.lod, op.selected = name, True
|
||||||
|
|
||||||
|
|
||||||
@@ -523,9 +646,16 @@ class _SceneAssetizePanel:
|
|||||||
def draw(self, context) -> None:
|
def draw(self, context) -> None:
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
_wrapped_label(
|
_wrapped_label(
|
||||||
layout, context, "Publish a local collection of this scene to Libreflow as an asset.", icon="INFO"
|
layout,
|
||||||
|
context,
|
||||||
|
"Publish a local collection of this scene to Libreflow as an asset.",
|
||||||
|
icon="INFO",
|
||||||
|
)
|
||||||
|
layout.operator(
|
||||||
|
sm.WTD_OT_scene_assetize.bl_idname,
|
||||||
|
text="Assetize Collection...",
|
||||||
|
icon="EXPORT",
|
||||||
)
|
)
|
||||||
layout.operator(sm.WTD_OT_scene_assetize.bl_idname, text="Assetize Collection...", icon="EXPORT")
|
|
||||||
|
|
||||||
|
|
||||||
class WTD_PT_sanity_view3d(_SanityPanel, bpy.types.Panel):
|
class WTD_PT_sanity_view3d(_SanityPanel, bpy.types.Panel):
|
||||||
|
|||||||
Reference in new issue
Block a user