update addons (for 5.2)
This commit is contained in:
@@ -2,6 +2,20 @@
|
||||
|
||||
All notable changes to **Maya Config Pro** are listed here. Versions match `blender_manifest.toml`.
|
||||
|
||||
## [1.8.3] — 2026-07-14
|
||||
|
||||
### Fixed
|
||||
- **Keymaps:** Fixed crash on blend file load from stale addon keymap RNA handles; deferred `load_post` re-sync, skip modal maps in the addon layer, use Blender’s keymap import for nested operator props.
|
||||
|
||||
### Changed
|
||||
- **Blender support:** Added **5.2 LTS**; minimum target is now **4.5 LTS** (4.2–4.4 dropped).
|
||||
- **Marking menu:** Loop/ring multi-select uses Blender 5.1+ operators; paint-mode submenu limited to mesh objects.
|
||||
|
||||
## [1.8.2] — 2026-07-08
|
||||
|
||||
### Fixed
|
||||
- **Quad Menu:** Space pie ortho views (Left/Right/Top/etc.) now affect only the active 3D viewport, not every viewport in multi-view layouts.
|
||||
|
||||
## [1.8.1] — 2026-06-16
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Maya Config Pro — Install the release ZIP (Blender 4.2+)
|
||||
Maya Config Pro — Install the release ZIP (Blender 4.5+)
|
||||
========================================================
|
||||
|
||||
This package is a Blender *extension* (blender_manifest.toml). Do not unzip
|
||||
|
||||
@@ -75,14 +75,14 @@ Maya Config Pro transforms Blender's interface and workflow to match Maya's fami
|
||||
This is the new modern way to install Maya Config Pro - no file copying required!
|
||||
|
||||
#### Requirements
|
||||
- Blender 4.2 LTS or newer
|
||||
- Blender 4.5 LTS or newer
|
||||
|
||||
#### Steps
|
||||
|
||||
1. **Download the Extension**
|
||||
- Download the MayaConfigPro ZIP file from Gumroad or your purchase location
|
||||
|
||||
2. **Install in Blender** (Blender 4.2+; extension with `blender_manifest.toml`)
|
||||
2. **Install in Blender** (Blender 4.5+; extension with `blender_manifest.toml`)
|
||||
- Open Blender
|
||||
- Go to **`Edit → Preferences → Get Extensions`**
|
||||
- Use the **top-right `⋯` menu → `Install from Disk`**, *or* **drag the ZIP onto the Preferences window**
|
||||
@@ -242,13 +242,13 @@ For older Blender versions (pre-4.2) or if you prefer the original mod approach:
|
||||
|
||||
| Blender Version | Support Level | Recommended Install |
|
||||
|-----------------|---------------|---------------------|
|
||||
| 4.2.x LTS | Full | Extension |
|
||||
| 4.3.x | Full | Extension |
|
||||
| 4.4.x | Full | Extension |
|
||||
| 4.5.x LTS | Full | Extension |
|
||||
| 5.0.x | Full | Extension |
|
||||
| 3.0.x - 4.1.x | Legacy Only | Mod Installation |
|
||||
| 2.83 - 2.93 | Legacy Only | Mod Installation |
|
||||
| 5.1.x | Full | Extension |
|
||||
| 5.2.x LTS | Full | Extension |
|
||||
| 4.2.x – 4.4.x | Unsupported | — |
|
||||
| 3.0.x – 4.1.x | Legacy Only | Mod Installation |
|
||||
| 2.83 – 2.93 | Legacy Only | Mod Installation |
|
||||
|
||||
---
|
||||
|
||||
@@ -262,7 +262,7 @@ For older Blender versions (pre-4.2) or if you prefer the original mod approach:
|
||||
|
||||
### Version 1.6 / Extension 1.7.0
|
||||
- **Extension Format:** Complete port to modern Blender Extension platform
|
||||
- **Multi-Version Support:** Compatible with Blender 4.2, 4.5 LTS, and 5.0+
|
||||
- **Multi-Version Support:** Compatible with Blender 4.5 LTS, 5.0, 5.1, and 5.2 LTS
|
||||
- **No File Copying:** Everything applies at runtime, no installation directory modifications
|
||||
- **Safe Registration:** Error handling for all version-specific API differences
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Maya Config Pro - Blender Extension
|
||||
A Maya-like configuration, shortcuts, and UI elements for Blender.
|
||||
|
||||
Version: 1.8.1
|
||||
Version: 1.8.3
|
||||
Author: Form Affinity (Jesse Doyle)
|
||||
"""
|
||||
|
||||
@@ -12,8 +12,8 @@ Author: Form Affinity (Jesse Doyle)
|
||||
bl_info = {
|
||||
"name": "Maya Config Pro",
|
||||
"author": "Form Affinity (Jesse Doyle)",
|
||||
"version": (1, 8, 1),
|
||||
"blender": (4, 2, 0),
|
||||
"version": (1, 8, 3),
|
||||
"blender": (4, 5, 0),
|
||||
"location": "View3D > Sidebar > Maya Config",
|
||||
"description": "Maya-like configuration, shortcuts, UI panels, and keymap presets",
|
||||
"warning": "",
|
||||
|
||||
@@ -3,12 +3,12 @@ schema_version = "1.0.0"
|
||||
id = "form_affinity_maya_config_pro"
|
||||
name = "Maya Config Pro"
|
||||
tagline = "Maya-like configuration, shortcuts, and UI elements for Blender"
|
||||
version = "1.8.1"
|
||||
version = "1.8.3"
|
||||
type = "add-on"
|
||||
|
||||
maintainer = "Form Affinity"
|
||||
license = ["GPL-3.0-or-later"]
|
||||
blender_version_min = "4.2.0"
|
||||
blender_version_min = "4.5.0"
|
||||
|
||||
website = "https://github.com/FormAffinity/MayaConfigPro"
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ that layer without restarting.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bl_keymap_utils.io import keymap_init_from_data
|
||||
from ..utils import compat
|
||||
from ..utils import keymap_fix
|
||||
|
||||
# Import the keyconfig data
|
||||
from . import fa_hotkeys_data
|
||||
|
||||
# Addon keymaps: (keymap, keymap_item) and created keymaps to remove for clean sync
|
||||
addon_keymaps: list = []
|
||||
# Addon keymaps we created (for unregister while handles are still valid)
|
||||
addon_keymap_objects: list = []
|
||||
|
||||
# Set from __init__.register to match this add-on's module id
|
||||
@@ -24,6 +24,41 @@ _addon_id: str = "form_affinity_maya_config_pro"
|
||||
|
||||
# Message bus: react to keyconfig changes from Preferences
|
||||
_msgbus_owner = object()
|
||||
_load_post_timer = None
|
||||
|
||||
|
||||
def _clear_keymap_tracking() -> None:
|
||||
addon_keymap_objects.clear()
|
||||
|
||||
|
||||
def _keymap_signature(km_name: str, km_args: dict) -> tuple:
|
||||
return (
|
||||
km_name,
|
||||
km_args.get("space_type", "EMPTY"),
|
||||
km_args.get("region_type", "WINDOW"),
|
||||
bool(km_args.get("modal", False)),
|
||||
)
|
||||
|
||||
|
||||
def _purge_tracked_keymaps(kc) -> None:
|
||||
"""Remove MCP addon keymaps without stale RNA handles (safe after file load)."""
|
||||
if not kc or not hasattr(fa_hotkeys_data, "keyconfig_data"):
|
||||
_clear_keymap_tracking()
|
||||
return
|
||||
|
||||
signatures = {
|
||||
_keymap_signature(name, args)
|
||||
for name, args, _ in fa_hotkeys_data.keyconfig_data
|
||||
if not args.get("modal", False)
|
||||
}
|
||||
for km in list(kc.keymaps):
|
||||
sig = (km.name, km.space_type, km.region_type, km.is_modal)
|
||||
if sig in signatures:
|
||||
try:
|
||||
kc.keymaps.remove(km)
|
||||
except Exception:
|
||||
pass
|
||||
_clear_keymap_tracking()
|
||||
|
||||
|
||||
def set_addon_id(addon_id: str) -> None:
|
||||
@@ -62,97 +97,88 @@ def register_keymaps():
|
||||
if not kc:
|
||||
return
|
||||
|
||||
try:
|
||||
if hasattr(fa_hotkeys_data, "keyconfig_data"):
|
||||
for km_name, km_args, km_content in fa_hotkeys_data.keyconfig_data:
|
||||
km = kc.keymaps.new(name=km_name, **km_args)
|
||||
addon_keymap_objects.append(km)
|
||||
if not hasattr(fa_hotkeys_data, "keyconfig_data"):
|
||||
return
|
||||
|
||||
if "items" in km_content:
|
||||
for item in km_content["items"]:
|
||||
if len(item) >= 2:
|
||||
idname = item[0]
|
||||
keymap_item = item[1]
|
||||
props = item[2] if len(item) > 2 else None
|
||||
|
||||
kmi = km.keymap_items.new(
|
||||
idname,
|
||||
type=keymap_item.get("type", "A"),
|
||||
value=keymap_item.get("value", "PRESS"),
|
||||
any=keymap_item.get("any", False),
|
||||
shift=keymap_item.get("shift", 0),
|
||||
ctrl=keymap_item.get("ctrl", 0),
|
||||
alt=keymap_item.get("alt", 0),
|
||||
oskey=keymap_item.get("oskey", False),
|
||||
key_modifier=keymap_item.get("key_modifier", "NONE"),
|
||||
repeat=keymap_item.get("repeat", False),
|
||||
head=keymap_item.get("head", False),
|
||||
)
|
||||
|
||||
if props and "properties" in props:
|
||||
for prop_name, prop_value in props["properties"]:
|
||||
if hasattr(kmi.properties, prop_name):
|
||||
setattr(kmi.properties, prop_name, prop_value)
|
||||
|
||||
if props and "active" in props:
|
||||
kmi.active = props["active"]
|
||||
|
||||
addon_keymaps.append((km, kmi))
|
||||
except Exception as e:
|
||||
print(f"Maya Config Pro: Could not register keymaps: {e}")
|
||||
for km_name, km_args, km_content in fa_hotkeys_data.keyconfig_data:
|
||||
if km_args.get("modal", False):
|
||||
continue
|
||||
try:
|
||||
km = kc.keymaps.new(name=km_name, **km_args)
|
||||
addon_keymap_objects.append(km)
|
||||
keymap_init_from_data(km, km_content["items"], is_modal=False)
|
||||
except Exception as e:
|
||||
print(f"Maya Config Pro: Could not register keymap '{km_name}': {e}")
|
||||
|
||||
|
||||
def unregister_keymaps():
|
||||
def unregister_keymaps(*, drop_stale: bool = False) -> None:
|
||||
"""Remove all MCP addon keymap items and the keymap objects we created."""
|
||||
global addon_keymap_objects
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
if not kc:
|
||||
_clear_keymap_tracking()
|
||||
return
|
||||
|
||||
for km, kmi in list(addon_keymaps):
|
||||
try:
|
||||
km.keymap_items.remove(kmi)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
addon_keymaps.clear()
|
||||
if drop_stale or not addon_keymap_objects:
|
||||
_purge_tracked_keymaps(kc)
|
||||
return
|
||||
|
||||
removed_any = False
|
||||
for km in reversed(addon_keymap_objects):
|
||||
try:
|
||||
kc.keymaps.remove(km)
|
||||
removed_any = True
|
||||
except Exception:
|
||||
pass
|
||||
addon_keymap_objects.clear()
|
||||
|
||||
if not removed_any:
|
||||
_purge_tracked_keymaps(kc)
|
||||
else:
|
||||
_clear_keymap_tracking()
|
||||
|
||||
|
||||
def sync_addon_keymaps() -> None:
|
||||
def sync_addon_keymaps(*, after_file_load: bool = False) -> None:
|
||||
"""
|
||||
If "Auto-load FA Keymap" is on and the active preset is `fa_hotkeys`, register
|
||||
the addon keymap layer; otherwise remove it. Safe to call often (switcher, prefs, msgbus).
|
||||
"""
|
||||
prefs = _get_prefs()
|
||||
if not prefs or not getattr(prefs, "auto_load_keymap", False):
|
||||
unregister_keymaps()
|
||||
unregister_keymaps(drop_stale=after_file_load)
|
||||
return
|
||||
if is_fa_keyconfig_active():
|
||||
unregister_keymaps()
|
||||
unregister_keymaps(drop_stale=after_file_load)
|
||||
register_keymaps()
|
||||
try:
|
||||
keymap_fix.patch_node_editor_alt_rmb("FA_HOTKEYS")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
unregister_keymaps()
|
||||
unregister_keymaps(drop_stale=after_file_load)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def _mcp_load_post_sync(_a, _b) -> None:
|
||||
try:
|
||||
sync_addon_keymaps()
|
||||
except Exception as e:
|
||||
print(f"Warning: Maya Config Pro: keymap sync after load: {e}")
|
||||
global _load_post_timer
|
||||
# File load invalidates stored keymap RNA handles — never touch them here.
|
||||
_clear_keymap_tracking()
|
||||
if _load_post_timer is not None:
|
||||
try:
|
||||
bpy.app.timers.unregister(_load_post_timer)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _sync_after_load():
|
||||
global _load_post_timer
|
||||
_load_post_timer = None
|
||||
try:
|
||||
sync_addon_keymaps(after_file_load=True)
|
||||
except Exception as e:
|
||||
print(f"Warning: Maya Config Pro: keymap sync after load: {e}")
|
||||
return None
|
||||
|
||||
_load_post_timer = bpy.app.timers.register(_sync_after_load, first_interval=0.0)
|
||||
|
||||
|
||||
def _msgbus_keyconfig(_):
|
||||
@@ -179,6 +205,13 @@ def register_keymap_listeners() -> None:
|
||||
|
||||
|
||||
def unregister_keymap_listeners() -> None:
|
||||
global _load_post_timer
|
||||
if _load_post_timer is not None:
|
||||
try:
|
||||
bpy.app.timers.unregister(_load_post_timer)
|
||||
except Exception:
|
||||
pass
|
||||
_load_post_timer = None
|
||||
try:
|
||||
bpy.msgbus.clear_by_owner(_msgbus_owner)
|
||||
except Exception:
|
||||
|
||||
@@ -8,6 +8,13 @@ from ..utils import compat
|
||||
from bpy.types import Menu
|
||||
|
||||
|
||||
def _draw_multi_loop_select(layout, *, ring: bool, icon: str) -> None:
|
||||
op_id = compat.mesh_multi_loop_select_op(ring=ring)
|
||||
op = layout.operator(op_id, text="", icon=icon)
|
||||
if op and op_id == "mesh.loop_multi_select":
|
||||
op.ring = ring
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Sub Menus
|
||||
# ------------------------------------------------------------------------
|
||||
@@ -46,10 +53,14 @@ class MCP_MT_SubMode(Menu):
|
||||
layout = self.layout
|
||||
layout.label(text="Mode:")
|
||||
layout.separator()
|
||||
layout.operator("object.mode_set", text="Sculpt", icon="SCULPTMODE_HLT").mode='SCULPT'
|
||||
layout.operator("object.mode_set", text="Vertex Paint", icon="VPAINT_HLT").mode='VERTEX_PAINT'
|
||||
layout.operator("object.mode_set", text="Weight Paint", icon="WPAINT_HLT").mode='WEIGHT_PAINT'
|
||||
layout.operator("object.mode_set", text="Texture Paint", icon="TPAINT_HLT").mode='TEXTURE_PAINT'
|
||||
obj = context.object
|
||||
if obj is None or obj.type != "MESH":
|
||||
layout.label(text="Requires a mesh object")
|
||||
return
|
||||
layout.operator("object.mode_set", text="Sculpt", icon="SCULPTMODE_HLT").mode = "SCULPT"
|
||||
layout.operator("object.mode_set", text="Vertex Paint", icon="VPAINT_HLT").mode = "VERTEX_PAINT"
|
||||
layout.operator("object.mode_set", text="Weight Paint", icon="WPAINT_HLT").mode = "WEIGHT_PAINT"
|
||||
layout.operator("object.mode_set", text="Texture Paint", icon="TPAINT_HLT").mode = "TEXTURE_PAINT"
|
||||
|
||||
|
||||
class MCP_MT_SubFreeze(Menu):
|
||||
@@ -130,12 +141,8 @@ class MCP_MT_MarkingMenu(Menu):
|
||||
op = other_menu.operator("mesh.select_all", text="", icon="SELECT_SUBTRACT")
|
||||
if op:
|
||||
op.action = "DESELECT"
|
||||
op = other_menu.operator("mesh.loop_multi_select", text="", icon="ANCHOR_LEFT")
|
||||
if op:
|
||||
op.ring = False
|
||||
op = other_menu.operator("mesh.loop_multi_select", text="", icon="ANCHOR_TOP")
|
||||
if op:
|
||||
op.ring = True
|
||||
_draw_multi_loop_select(other_menu, ring=False, icon="ANCHOR_LEFT")
|
||||
_draw_multi_loop_select(other_menu, ring=True, icon="ANCHOR_TOP")
|
||||
other_menu.operator("mesh.select_more", text="", icon="ADD")
|
||||
other_menu.operator("mesh.select_less", text="", icon="REMOVE")
|
||||
|
||||
|
||||
@@ -8,6 +8,37 @@ from ..utils import compat
|
||||
from bpy.types import Menu
|
||||
|
||||
|
||||
def _set_active_viewport_ortho_axis(context, axis: str):
|
||||
"""Apply an axis view in orthographic mode to the invoking 3D viewport only."""
|
||||
area = context.area
|
||||
if area is None or area.type != "VIEW_3D":
|
||||
return {"CANCELLED"}
|
||||
|
||||
space = context.space_data
|
||||
if space is None or space.type != "VIEW_3D":
|
||||
space = area.spaces.active
|
||||
if space is None:
|
||||
return {"CANCELLED"}
|
||||
|
||||
region = context.region
|
||||
if region is None:
|
||||
region = next((r for r in area.regions if r.type == "WINDOW"), None)
|
||||
if region is None:
|
||||
return {"CANCELLED"}
|
||||
|
||||
with context.temp_override(
|
||||
window=context.window,
|
||||
screen=context.screen,
|
||||
area=area,
|
||||
region=region,
|
||||
space_data=space,
|
||||
):
|
||||
bpy.ops.view3d.view_axis(type=axis)
|
||||
|
||||
space.region_3d.view_perspective = "ORTHO"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Main Quad Menu
|
||||
# ------------------------------------------------------------------------
|
||||
@@ -56,13 +87,7 @@ class MCP_OT_LeftOrtho(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.view_axis(type='LEFT')
|
||||
C = bpy.context
|
||||
for i, a in enumerate(C.screen.areas):
|
||||
if a.type == "VIEW_3D":
|
||||
space = a.spaces.active
|
||||
space.region_3d.view_perspective = 'ORTHO'
|
||||
return {'FINISHED'}
|
||||
return _set_active_viewport_ortho_axis(context, "LEFT")
|
||||
|
||||
|
||||
class MCP_OT_RightOrtho(bpy.types.Operator):
|
||||
@@ -72,13 +97,7 @@ class MCP_OT_RightOrtho(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.view_axis(type='RIGHT')
|
||||
C = bpy.context
|
||||
for i, a in enumerate(C.screen.areas):
|
||||
if a.type == "VIEW_3D":
|
||||
space = a.spaces.active
|
||||
space.region_3d.view_perspective = 'ORTHO'
|
||||
return {'FINISHED'}
|
||||
return _set_active_viewport_ortho_axis(context, "RIGHT")
|
||||
|
||||
|
||||
class MCP_OT_BottomOrtho(bpy.types.Operator):
|
||||
@@ -88,13 +107,7 @@ class MCP_OT_BottomOrtho(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.view_axis(type='BOTTOM')
|
||||
C = bpy.context
|
||||
for i, a in enumerate(C.screen.areas):
|
||||
if a.type == "VIEW_3D":
|
||||
space = a.spaces.active
|
||||
space.region_3d.view_perspective = 'ORTHO'
|
||||
return {'FINISHED'}
|
||||
return _set_active_viewport_ortho_axis(context, "BOTTOM")
|
||||
|
||||
|
||||
class MCP_OT_TopOrtho(bpy.types.Operator):
|
||||
@@ -104,13 +117,7 @@ class MCP_OT_TopOrtho(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.view_axis(type='TOP')
|
||||
C = bpy.context
|
||||
for i, a in enumerate(C.screen.areas):
|
||||
if a.type == "VIEW_3D":
|
||||
space = a.spaces.active
|
||||
space.region_3d.view_perspective = 'ORTHO'
|
||||
return {'FINISHED'}
|
||||
return _set_active_viewport_ortho_axis(context, "TOP")
|
||||
|
||||
|
||||
class MCP_OT_FrontOrtho(bpy.types.Operator):
|
||||
@@ -120,13 +127,7 @@ class MCP_OT_FrontOrtho(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.view_axis(type='FRONT')
|
||||
C = bpy.context
|
||||
for i, a in enumerate(C.screen.areas):
|
||||
if a.type == "VIEW_3D":
|
||||
space = a.spaces.active
|
||||
space.region_3d.view_perspective = 'ORTHO'
|
||||
return {'FINISHED'}
|
||||
return _set_active_viewport_ortho_axis(context, "FRONT")
|
||||
|
||||
|
||||
class MCP_OT_BackOrtho(bpy.types.Operator):
|
||||
@@ -136,13 +137,7 @@ class MCP_OT_BackOrtho(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.view_axis(type='BACK')
|
||||
C = bpy.context
|
||||
for i, a in enumerate(C.screen.areas):
|
||||
if a.type == "VIEW_3D":
|
||||
space = a.spaces.active
|
||||
space.region_3d.view_perspective = 'ORTHO'
|
||||
return {'FINISHED'}
|
||||
return _set_active_viewport_ortho_axis(context, "BACK")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
This module provides API compatibility functions for handling differences
|
||||
between Blender 4.2, 4.4, 4.5, and 5.0.
|
||||
between supported Blender versions (4.5 LTS through 5.2 LTS).
|
||||
"""
|
||||
|
||||
import bpy
|
||||
@@ -50,9 +50,9 @@ def get_tools_region_type():
|
||||
In Blender 2.8+, 'TOOLS' was replaced by 'UI'.
|
||||
|
||||
Returns:
|
||||
str: 'UI' (for all supported versions 4.2+)
|
||||
str: 'UI' (for all supported versions 4.5+)
|
||||
"""
|
||||
# For Blender 4.2 and later, always use 'UI'
|
||||
# For Blender 4.5 and later, always use 'UI'
|
||||
return 'UI'
|
||||
|
||||
|
||||
@@ -72,8 +72,16 @@ def temp_override(context, **kwargs):
|
||||
# Blender 3.2+ uses temp_override
|
||||
return context.temp_override(**kwargs)
|
||||
else:
|
||||
# Older versions - this shouldn't happen for 4.2+ minimum
|
||||
# Older versions - this shouldn't happen for 4.5+ minimum
|
||||
# but keeping for safety
|
||||
override = context.copy()
|
||||
override.update(kwargs)
|
||||
return override
|
||||
|
||||
|
||||
def mesh_multi_loop_select_op(*, ring: bool) -> str:
|
||||
"""Operator id for loop (ring=False) or ring (ring=True) multi-select."""
|
||||
if version.is_version_at_least(5, 1, 0):
|
||||
return "mesh.select_edge_ring_multi" if ring else "mesh.select_edge_loop_multi"
|
||||
return "mesh.loop_multi_select"
|
||||
|
||||
|
||||
@@ -1,53 +1,32 @@
|
||||
"""
|
||||
This module provides version detection and comparison utilities for
|
||||
multi-version Blender support (4.2, 4.4, 4.5, and 5.0).
|
||||
Version detection and comparison utilities for Blender 4.5 LTS through 5.2 LTS.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
# Version constants
|
||||
VERSION_4_2 = (4, 2, 0)
|
||||
VERSION_4_4 = (4, 4, 0)
|
||||
VERSION_4_5 = (4, 5, 0)
|
||||
VERSION_5_0 = (5, 0, 0)
|
||||
VERSION_5_1 = (5, 1, 0)
|
||||
VERSION_5_2 = (5, 2, 0)
|
||||
|
||||
|
||||
def get_blender_version():
|
||||
"""
|
||||
Returns the current Blender version as a tuple (major, minor, patch).
|
||||
|
||||
Returns:
|
||||
tuple: (major, minor, patch) version numbers
|
||||
"""
|
||||
"""Return the current Blender version as (major, minor, patch)."""
|
||||
return bpy.app.version
|
||||
|
||||
|
||||
def get_version_string():
|
||||
"""
|
||||
Returns the current Blender version as a string (e.g., "4.2.0").
|
||||
|
||||
Returns:
|
||||
str: Version string in format "major.minor.patch"
|
||||
"""
|
||||
"""Return the current Blender version as 'major.minor.patch'."""
|
||||
version = get_blender_version()
|
||||
return f"{version[0]}.{version[1]}.{version[2]}"
|
||||
|
||||
|
||||
def is_version_at_least(major, minor=0, patch=0):
|
||||
"""
|
||||
Check if the current Blender version is at least the specified version.
|
||||
|
||||
Args:
|
||||
major (int): Major version number
|
||||
minor (int): Minor version number (default: 0)
|
||||
patch (int): Patch version number (default: 0)
|
||||
|
||||
Returns:
|
||||
bool: True if current version >= specified version
|
||||
"""
|
||||
"""True when the running Blender version is >= major.minor.patch."""
|
||||
current = get_blender_version()
|
||||
target = (major, minor, patch)
|
||||
|
||||
|
||||
if current[0] != target[0]:
|
||||
return current[0] > target[0]
|
||||
if current[1] != target[1]:
|
||||
@@ -56,61 +35,55 @@ def is_version_at_least(major, minor=0, patch=0):
|
||||
|
||||
|
||||
def is_version_less_than(major, minor=0, patch=0):
|
||||
"""
|
||||
Check if the current Blender version is less than the specified version.
|
||||
|
||||
Args:
|
||||
major (int): Major version number
|
||||
minor (int): Minor version number (default: 0)
|
||||
patch (int): Patch version number (default: 0)
|
||||
|
||||
Returns:
|
||||
bool: True if current version < specified version
|
||||
"""
|
||||
"""True when the running Blender version is < major.minor.patch."""
|
||||
return not is_version_at_least(major, minor, patch)
|
||||
|
||||
|
||||
def get_version_category():
|
||||
"""
|
||||
Returns the version category string for the current Blender version.
|
||||
|
||||
Returns:
|
||||
str: '4.2', '4.4', '4.5', or '5.0' based on the current version
|
||||
Return a coarse support bucket for the running Blender version.
|
||||
|
||||
Supported targets: 4.5, 5.0, 5.1, 5.2.
|
||||
"""
|
||||
version = get_blender_version()
|
||||
major, minor = version[0], version[1]
|
||||
|
||||
|
||||
if major == 4:
|
||||
if minor < 4:
|
||||
return '4.2'
|
||||
elif minor < 5:
|
||||
return '4.4'
|
||||
else:
|
||||
return '4.5'
|
||||
elif major >= 5:
|
||||
return '5.0'
|
||||
else:
|
||||
# Fallback for older versions
|
||||
return f"{major}.{minor}"
|
||||
|
||||
|
||||
def is_version_4_2():
|
||||
"""Check if running Blender 4.2 (4.2.x only, not 4.3 or 4.4)."""
|
||||
version = get_blender_version()
|
||||
return version[0] == 4 and version[1] == 2
|
||||
if minor < 5:
|
||||
return "4.4"
|
||||
return "4.5"
|
||||
if major >= 5:
|
||||
if minor < 1:
|
||||
return "5.0"
|
||||
if minor < 2:
|
||||
return "5.1"
|
||||
return "5.2"
|
||||
return f"{major}.{minor}"
|
||||
|
||||
|
||||
def is_version_4_4():
|
||||
"""Check if running Blender 4.4 (4.4.x only, not 4.5)."""
|
||||
"""True on Blender 4.4.x."""
|
||||
version = get_blender_version()
|
||||
return version[0] == 4 and version[1] == 4
|
||||
|
||||
|
||||
def is_version_4_5():
|
||||
"""Check if running Blender 4.5 LTS."""
|
||||
"""True on Blender 4.5 LTS."""
|
||||
return is_version_at_least(4, 5, 0) and is_version_less_than(5, 0, 0)
|
||||
|
||||
|
||||
def is_version_5_0():
|
||||
"""Check if running Blender 5.0 or later."""
|
||||
return is_version_at_least(5, 0, 0)
|
||||
"""True on Blender 5.0.x."""
|
||||
version = get_blender_version()
|
||||
return version[0] == 5 and version[1] == 0
|
||||
|
||||
|
||||
def is_version_5_1():
|
||||
"""True on Blender 5.1.x."""
|
||||
version = get_blender_version()
|
||||
return version[0] == 5 and version[1] == 1
|
||||
|
||||
|
||||
def is_version_5_2():
|
||||
"""True on Blender 5.2 LTS and newer 5.x."""
|
||||
return is_version_at_least(5, 2, 0)
|
||||
|
||||
Reference in New Issue
Block a user