BCON 2026 Austin
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
Maya Config Pro — Install the release ZIP (Blender 4.2+)
|
||||
========================================================
|
||||
|
||||
This package is a Blender *extension* (blender_manifest.toml). Do not unzip
|
||||
into a scripts folder by hand unless you know the layout.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
1. Blender: Edit → Preferences
|
||||
2. Open "Get Extensions" (left sidebar)
|
||||
3. Either:
|
||||
- Top-right "..." menu → "Install from Disk" → choose this ZIP, or
|
||||
- Drag and drop the ZIP file onto the Preferences window
|
||||
4. In the extensions list, search: Maya Config Pro (id: form_affinity_maya_config_pro)
|
||||
5. Enable the add-on with the checkbox
|
||||
|
||||
If you do not see it, click "Refresh Local" in Get Extensions (or Add-ons),
|
||||
or restart Blender after install.
|
||||
|
||||
Note: In Blender 5.x, drag-and-drop or "Install from Disk" may extract the
|
||||
package into scripts/addons. The add-on must define bl_info in __init__.py
|
||||
for that path — otherwise Blender prints "Modules Installed ()" and the
|
||||
entry does not show up. Enable it under Preferences → Add-ons (search:
|
||||
"Maya Config Pro").
|
||||
@@ -0,0 +1,388 @@
|
||||
# Maya Config Pro
|
||||
|
||||
A Maya-like configuration, shortcuts, and UI elements for Blender.
|
||||
|
||||
**Version:** 1.7.0 (Extension Port)
|
||||
**Original Author:** Jesse Doyle | Form Affinity
|
||||
**License:** GPL-3.0-or-later
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [As Blender Extension (Recommended)](#as-blender-extension-recommended)
|
||||
- [Legacy Mod Installation](#legacy-mod-installation)
|
||||
- [Quick Start](#quick-start)
|
||||
- [What's Included](#whats-included)
|
||||
- [Version Compatibility](#version-compatibility)
|
||||
- [Update History](#update-history)
|
||||
- [FAQ](#faq)
|
||||
- [Support & Contact](#support--contact)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Maya Config Pro transforms Blender's interface and workflow to match Maya's familiar paradigm. It includes custom layouts, marking menus, side panels, shelves, hotkeys, and themes designed to make Blender feel like home for Maya users.
|
||||
|
||||
> **Note:** This extension port maintains all original functionality while using the modern Blender Extension format. No file copying to Blender directories is required - everything applies at runtime.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Interface Components
|
||||
|
||||
| Feature | Description | Access |
|
||||
|---------|-------------|--------|
|
||||
| **ProAni 1.7 Panel** | Main configuration panel with theme switching, subdivision controls, gizmo settings | N-Panel > Maya Config |
|
||||
| **Sidebar Shelf** | N-panel modeling toolkit with extrude, bevel, bridge, loop tools | N-Panel > Shelf |
|
||||
| **Dynamic Shelf** | Top shelf with mode switching (Modeling, Edit Poly, Nurbs, Rendering, FX, Animation) | Properties Editor |
|
||||
| **Animation Shelf** | Rigging and animation tools | Properties Editor |
|
||||
|
||||
### Pie Menus (Marking Menus)
|
||||
|
||||
| Menu | Hotkey | Description |
|
||||
|------|--------|-------------|
|
||||
| **Marking Menu** | Right-Click | Maya-style marking menu with create, mode switch, freeze transforms |
|
||||
| **Quad Menu** | Spacebar | Orthographic views and camera controls |
|
||||
| **Tab Switcher** | Alt + Right-Click | Workspace switching (Camera, UV, Sculpting, Animation, etc.) |
|
||||
| **Animation Menu** | Shift + Right-Click (Animation mode) | Animation workflow menu |
|
||||
| **Special Tools** | Edit Mode context | Edit mode vertex/edge/face tools |
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Maya-Style Navigation:** Alt + LMB/MMB/RMB for tumble, track, dolly
|
||||
- **Component Selection:** Q=box select, W=move, E=rotate, R=scale
|
||||
- **Mesh Smooth Preview:** Quick subdivision surface preview with 1-3 iterations
|
||||
- **Live Symmetry:** Mirror modeling with X/Z axis support
|
||||
- **Camera Tools 2.0:** Lock camera to view with visual toggle feedback
|
||||
- **Theme Switching:** Quick theme switching (Maya Blue, Maya Gradient, Modo, Blender Dark, etc.)
|
||||
- **Object-to-Object Select:** Select objects while remaining in edit mode
|
||||
- **Extrude Along Axis:** Ctrl + Double Click for fast axis extrusion
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### As Blender Extension (Recommended)
|
||||
|
||||
This is the new modern way to install Maya Config Pro - no file copying required!
|
||||
|
||||
#### Requirements
|
||||
- Blender 4.2 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`)
|
||||
- Open Blender
|
||||
- Go to **`Edit → Preferences → Get Extensions`**
|
||||
- Use the **top-right `⋯` menu → `Install from Disk`**, *or* **drag the ZIP onto the Preferences window**
|
||||
- Select the downloaded ZIP (do not extract it first)
|
||||
- If the list does not update, click **Refresh Local** or restart Blender
|
||||
|
||||
3. **Enable the Extension**
|
||||
- In **Get Extensions**, search for **Maya Config Pro** (package id: `form_affinity_maya_config_pro`)
|
||||
- Enable it with the checkbox (same entry may also appear under **Add-ons** after install)
|
||||
- The ProAni 1.7 panel will appear in the 3D Viewport sidebar
|
||||
|
||||
**If “Install from Disk” under Add-ons seems to do nothing:** use **Get Extensions** as above — that is the path Blender documents for local extension packages. See also `INSTALL.txt` in the release ZIP.
|
||||
|
||||
4. **Configure Preferences** (optional)
|
||||
- In the add-on preferences, you can enable auto-load for FA keymaps
|
||||
- Or manually activate the keymap from the keymap preferences
|
||||
|
||||
#### Verification
|
||||
|
||||
After installation, verify these three things are working:
|
||||
|
||||
1. The "ProAni 1.7" panel shows on the left side (N-Panel > Maya Config)
|
||||
2. Right-click activates the marking menu in the 3D viewport
|
||||
3. Spacebar activates the Quad View pie menu
|
||||
|
||||
### Legacy Mod Installation
|
||||
|
||||
For older Blender versions (pre-4.2) or if you prefer the original mod approach:
|
||||
|
||||
#### Video Instructions
|
||||
- **Blender 4.3+ Builds:** https://youtu.be/i7a8HjV9-kk
|
||||
- **Blender 2.80+ Full Version:** https://youtu.be/eJGliDhBCq8
|
||||
- **Mac Catalina:** https://youtu.be/3rphJdNuwJA
|
||||
|
||||
#### Manual Installation Steps
|
||||
|
||||
1. **Locate Blender Directory**
|
||||
- Windows: `C:\Program Files\Blender Foundation\Blender X.X\X.X\scripts\startup`
|
||||
- Mac: `/Applications/Blender.app/Contents/Resources/X.X/scripts/startup`
|
||||
|
||||
2. **Copy Scripts**
|
||||
Copy these files to the `scripts/startup` folder:
|
||||
- `delete_withoutConfirm.py`
|
||||
- `fa_hotkeys.py`
|
||||
- `fa_marking_menu.py`
|
||||
- `fa_panel_pro.py`
|
||||
- `fa_panel_sidebar.py`
|
||||
- `fa_panel_panel.py`
|
||||
- `fa_quadview.py`
|
||||
- `fa_special_tools.py`
|
||||
- `fa_tab_switcher.py`
|
||||
- `msm_from_object.py`
|
||||
|
||||
3. **Install Themes**
|
||||
Copy theme files to `scripts/presets/interface_theme/`:
|
||||
- `maya_blue_theme.xml`
|
||||
- `maya_theme_gradient.xml`
|
||||
- `modo_theme.xml`
|
||||
- `blender_dark.xml`
|
||||
- `white_bg_theme.xml`
|
||||
- `sketchup.xml`
|
||||
|
||||
4. **Copy Config**
|
||||
Copy the `config` folder (containing `startup.blend` and `userpref.blend`) to the Blender version folder.
|
||||
|
||||
5. **Run as Administrator** (Windows Full Version only)
|
||||
- Right-click `blender.exe` and select "Run as Administrator" for first startup
|
||||
|
||||
6. **Import Keymap**
|
||||
- In Blender: `Edit > Preferences > Keymap`
|
||||
- Import `fa_hotkeys.py`
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Essential Hotkeys
|
||||
|
||||
| Blender Default | Maya Config Pro | Action |
|
||||
|-----------------|-------------------|--------|
|
||||
| Left Click | Right Click | Select |
|
||||
| Right Click | Right Click | Marking Menu (in viewport) |
|
||||
| Spacebar | Spacebar | Quad Menu |
|
||||
| T | T | Left Panel Toggle |
|
||||
| N | N | Right Panel Toggle |
|
||||
| Ctrl + Tab | Q | Box Select |
|
||||
| G | W | Move/Translate |
|
||||
| R | E | Rotate |
|
||||
| S | R | Scale |
|
||||
| Alt + Left | Alt + Left | Tumble/Orbit |
|
||||
| Shift + Middle | Alt + Middle | Track/Pan |
|
||||
| Ctrl + Middle | Alt + Right | Dolly/Zoom |
|
||||
| / (numpad) | Alt + E | Isolate Selection |
|
||||
| . (numpad) | F | Frame Selected |
|
||||
|
||||
### Navigation
|
||||
|
||||
- **Tumble/Orbit:** Alt + Left Mouse Button
|
||||
- **Track/Pan:** Alt + Middle Mouse Button
|
||||
- **Dolly/Zoom:** Alt + Right Mouse Button
|
||||
- **Frame Selected:** F
|
||||
- **Isolate Selection:** Alt + E
|
||||
|
||||
### Modeling
|
||||
|
||||
- **Box Select:** Q
|
||||
- **Move:** W
|
||||
- **Rotate:** E
|
||||
- **Scale:** R
|
||||
- **Multi Component:** Shift + Right Click (Marking Menu)
|
||||
- **Vertex Mode:** Marking Menu > Vertex
|
||||
- **Edge Mode:** Marking Menu > Edge
|
||||
- **Face Mode:** Marking Menu > Face
|
||||
|
||||
### Workflow Tips
|
||||
|
||||
1. **Quick Theme Switching:** Use the color buttons in the ProAni panel or Alt + B to cycle themes
|
||||
2. **Snap to Vertex:** Press V while in pivot adjust mode (D)
|
||||
3. **Camera Lock:** Use CamTools 2.0 panel to visually toggle camera lock state
|
||||
4. **Symmetry:** Select object, use X or Z buttons in Modeling Toolkit, then Apply
|
||||
|
||||
---
|
||||
|
||||
## What's Included
|
||||
|
||||
### Python Scripts
|
||||
- 15+ operator and panel scripts providing Maya-like functionality
|
||||
- Mesh select mode switching
|
||||
- Delete without confirmation
|
||||
- Object-to-object raycast selection
|
||||
- Animation timeline tools
|
||||
|
||||
### Themes
|
||||
- Maya Blue Theme
|
||||
- Maya Gradient Theme
|
||||
- Modo Theme
|
||||
- Blender Dark Theme
|
||||
- White Background Theme
|
||||
- Sketchup Theme
|
||||
|
||||
### Keymaps
|
||||
- Complete Maya-like keymap configuration (153KB of keymap data)
|
||||
- Industry Compatible keymap option
|
||||
- Standard Blender keymap option
|
||||
|
||||
### Layouts
|
||||
- HyperShade workspace
|
||||
- Camera workspace
|
||||
- UV Editing workspace
|
||||
- Modeling workspace
|
||||
- Animation workspace
|
||||
- Rendering workspace
|
||||
|
||||
---
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## Update History
|
||||
|
||||
### 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+
|
||||
- **No File Copying:** Everything applies at runtime, no installation directory modifications
|
||||
- **Safe Registration:** Error handling for all version-specific API differences
|
||||
|
||||
### Version 1.6 (Blender 4.3)
|
||||
- Ctrl + G to group objects as collections
|
||||
- Fixed Outliner delete with X/Delete key
|
||||
- Added cage projection to Mesh Smooth Preview (2 iterations)
|
||||
- Fixed UI loading via portable folder structure
|
||||
- Fixed Blender/Industry Standard keymap buttons
|
||||
- Fixed theme loading from portable folder
|
||||
- Fixed Pivot Snap (D, D+V, D+G, D+C)
|
||||
|
||||
### Version 2.1
|
||||
- Object-to-object select in Edit Mode (no need to exit Edit Mode)
|
||||
- QuickFavorites moved to Shift + Q
|
||||
- Alt + E now works in Pose Mode for Isolate Selection
|
||||
- "Relax Pose to Breakdown" changed to Alt + Shift + E
|
||||
|
||||
### Version 2.0
|
||||
- Fixed Nurb Sphere float/int error
|
||||
- Fixed F2 and Ctrl + F2 for Rename/Batch Rename
|
||||
- Added Active mesh area to Outliner
|
||||
- Added Asset Browser integration
|
||||
- Added Merge by Distance to Marking Menu
|
||||
- CamTools adjustments
|
||||
|
||||
### Version 1.9
|
||||
- New "Edit Poly" shelf with Delta Transfer, Join, Separate
|
||||
- Quad Menu stays on Orthographic when switching views
|
||||
- Blender 3.0 compatibility updates
|
||||
- Alt MMB/RMB for UV editor Pan/Zoom
|
||||
- Alt + F for Frame Selected (single region)
|
||||
- Middle Mouse for Move
|
||||
|
||||
### Version 1.8
|
||||
- Blender 2.93 compatibility
|
||||
- Auto-switch to Orthographic from Quad View
|
||||
- Shift select in Outliner
|
||||
|
||||
### Version 1.7
|
||||
- Dynamic stretchy top shelf with dropdown
|
||||
- Pro Panel widget default button and theme buttons
|
||||
- CamTools 2.0 with visual toggle feedback
|
||||
- New pivot features (V for vertex snap in pivot mode)
|
||||
- Alt + LMB works in Greasepencil Draw mode
|
||||
- Alt key can be held continuously
|
||||
|
||||
### Version 1.6
|
||||
- CamTools 1.0 with Camera View Lock
|
||||
- Extrude Along Axis (Ctrl + Double Click)
|
||||
- Modeling Toolkit section in right shelf
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
### Installation Issues
|
||||
|
||||
**Q: fa_hotkeys.py won't import through the keymap preferences**
|
||||
**A:** Run Blender as Administrator (right-click blender.exe > Run as Administrator). This is only needed for the first startup on Windows Full Version.
|
||||
|
||||
**Q: Addons won't install through Preferences - they don't show up**
|
||||
**A:** For ZIP format addons, manual installation may be required. Quit Blender and unpack the addon to:
|
||||
`C:\Program Files\Blender Foundation\Blender X.X\X.X\scripts\addons`
|
||||
|
||||
### Feature Issues
|
||||
|
||||
**Q: Alt+RMB zoom uses vertical drag (Blender) instead of horizontal (Maya)**
|
||||
**A:** Activating the **FA Hotkeys** keymap from the Pro panel sets Blender’s zoom drag axis to **horizontal** (Maya-style); activating **Blender** or **Industry Compatible** sets it back to **vertical**. You can still change **Edit > Preferences > Navigation > Zoom > Zoom Axis** yourself if needed.
|
||||
|
||||
**Q: Mesh Smooth Preview gives an error**
|
||||
**A:** Make sure an object is selected first.
|
||||
|
||||
**Q: Live Symmetry gives strange results**
|
||||
**A:** Ensure your object faces the Y direction (towards default camera) and all transforms are at zero.
|
||||
|
||||
**Q: Shortcut Equivalence isn't returning hotkey requests**
|
||||
**A:** Enter shortcuts as single letters (e.g., `r`) or with one space (e.g., `ctrl r`). Don't use CAPS.
|
||||
|
||||
### General Questions
|
||||
|
||||
**Q: How do I access the Discord channel?**
|
||||
**A:** Join via https://discord.gg/3XsJ8ak (Discord account with verified email required)
|
||||
|
||||
**Q: How do I request a refund?**
|
||||
**A:** Contact FormAffinity via the email below with your purchase email address.
|
||||
|
||||
---
|
||||
|
||||
## Support & Contact
|
||||
|
||||
### Community Support
|
||||
- **Discord:** https://discord.gg/3XsJ8ak
|
||||
- Post questions, comments, suggestions, bug reports, and recommendations
|
||||
|
||||
### Direct Contact
|
||||
- **Email:** contactformaffinity@gmail.com
|
||||
- **Business Inquiries:** contactformaffinity@gmail.com
|
||||
- **Product Refunds:** Include purchase email address in your request
|
||||
|
||||
### Video Resources
|
||||
- **Latest Install Video:** https://youtu.be/i7a8HjV9-kk
|
||||
- **Blender 2.80+ Install:** https://youtu.be/eJGliDhBCq8
|
||||
- **Mac Catalina Install:** https://youtu.be/3rphJdNuwJA
|
||||
- **Youtube Channel:** http://gumroad.com/formaffinity
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
**Created by:** Jesse Doyle | Form Affinity
|
||||
**Copyright:** 2024 Jesse Doyle
|
||||
|
||||
**Special Thanks:**
|
||||
- Sven for content support
|
||||
- ReachMatthews, Nittorious, Way2Close, Natetronn for testing and coding help
|
||||
- All users for their support and feedback!
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under GPL-3.0-or-later as a Blender Extension.
|
||||
|
||||
Original Maya Config Pro is a commercial product by Form Affinity. This extension port maintains compatibility with the original while using the open-source Blender Extension format.
|
||||
|
||||
---
|
||||
|
||||
*Thank you for using Form Affinity content!*
|
||||
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Maya Config Pro - Blender Extension
|
||||
A Maya-like configuration, shortcuts, and UI elements for Blender.
|
||||
|
||||
Version: 1.7.0
|
||||
Author: Form Affinity (Jesse Doyle)
|
||||
"""
|
||||
|
||||
# Required when Blender installs this folder under scripts/addons (Install from Disk /
|
||||
# drag-drop). blender_manifest.toml alone is not enough for that legacy path to list/enable
|
||||
# the add-on; without bl_info the UI shows "Modules Installed ()" and nothing usable registers.
|
||||
bl_info = {
|
||||
"name": "Maya Config Pro",
|
||||
"author": "Form Affinity (Jesse Doyle)",
|
||||
"version": (1, 7, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Maya Config",
|
||||
"description": "Maya-like configuration, shortcuts, UI panels, and keymap presets",
|
||||
"warning": "",
|
||||
"doc_url": "https://github.com/FormAffinity/MayaConfigPro",
|
||||
"category": "3D View",
|
||||
}
|
||||
|
||||
import bpy
|
||||
from .utils import compat
|
||||
|
||||
# Must match this add-on's Python package name (e.g. bl_ext.vscode_development.MayaConfigPro),
|
||||
# not only blender_manifest.toml id — otherwise AddonPreferences.draw never appears.
|
||||
_ADDON_MODULE = __package__ or "form_affinity_maya_config_pro"
|
||||
|
||||
# Import all submodules
|
||||
from .panels import (
|
||||
panel_pro,
|
||||
panel_sidebar,
|
||||
panel_shelf,
|
||||
panel_animation,
|
||||
)
|
||||
|
||||
from .ops import (
|
||||
marking_menu,
|
||||
quadview,
|
||||
tab_switcher,
|
||||
animation_timeline,
|
||||
animation_menu,
|
||||
special_tools,
|
||||
object_select,
|
||||
mesh_select_mode,
|
||||
delete_ops,
|
||||
)
|
||||
|
||||
from .keyconfigs import fa_hotkeys
|
||||
from .utils import deploy as deploy_util
|
||||
from .ops import deploy_ops
|
||||
|
||||
# Addon preferences
|
||||
class MCP_AddonPreferences(bpy.types.AddonPreferences):
|
||||
"""Maya Config Pro preferences"""
|
||||
bl_idname = _ADDON_MODULE
|
||||
|
||||
auto_load_keymap: bpy.props.BoolProperty(
|
||||
name="Auto-load FA Keymap",
|
||||
description="Automatically load the Maya-like keymap when Blender starts",
|
||||
default=True
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
box = layout.box()
|
||||
box.label(text="Deploy to user config", icon="EXPORT")
|
||||
col = box.column(align=False)
|
||||
|
||||
row = col.split(factor=0.55, align=True)
|
||||
sub = row.row(align=True)
|
||||
sub.operator(
|
||||
deploy_ops.MCP_OT_DeployKeymapPresets.bl_idname,
|
||||
text="Deploy keymap presets",
|
||||
icon="IMPORT",
|
||||
)
|
||||
sub = row.row(align=True)
|
||||
if deploy_util.is_keyconfig_deployed():
|
||||
sub.label(text="Ready", icon="CHECKMARK")
|
||||
else:
|
||||
sub.label(text="Not deployed", icon="ERROR")
|
||||
|
||||
row = col.split(factor=0.55, align=True)
|
||||
sub = row.row(align=True)
|
||||
sub.operator(
|
||||
deploy_ops.MCP_OT_DeployStartupBlend.bl_idname,
|
||||
text="Deploy startup.blend",
|
||||
icon="FILE_BLEND",
|
||||
)
|
||||
sub = row.row(align=True)
|
||||
if deploy_util.is_startup_bundle_available():
|
||||
sub.label(text="Bundled file present", icon="INFO")
|
||||
else:
|
||||
sub.label(text="None bundled", icon="INFO")
|
||||
|
||||
col.separator()
|
||||
col.label(
|
||||
text="Copies Blender's keymaps (and MCP fa_hotkeys) into your Scripts folder, "
|
||||
"and optional startup.blend into your config. Needed for Pro panel keymap buttons.",
|
||||
icon="INFO",
|
||||
)
|
||||
|
||||
layout.separator()
|
||||
layout.prop(self, "auto_load_keymap")
|
||||
|
||||
|
||||
# List of all classes in this module
|
||||
classes = (
|
||||
MCP_AddonPreferences,
|
||||
)
|
||||
|
||||
# List of all submodule register functions
|
||||
submodules = [
|
||||
deploy_ops,
|
||||
panel_pro,
|
||||
panel_sidebar,
|
||||
panel_shelf,
|
||||
panel_animation,
|
||||
marking_menu,
|
||||
quadview,
|
||||
tab_switcher,
|
||||
animation_timeline,
|
||||
animation_menu,
|
||||
special_tools,
|
||||
object_select,
|
||||
mesh_select_mode,
|
||||
delete_ops,
|
||||
fa_hotkeys,
|
||||
]
|
||||
|
||||
def register():
|
||||
"""Register all classes and submodules"""
|
||||
# Register main classes
|
||||
for cls in classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
# Register all submodules
|
||||
for module in submodules:
|
||||
try:
|
||||
module.register()
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to register {module.__name__}: {e}")
|
||||
|
||||
# Register keymaps if auto-load is enabled
|
||||
prefs = bpy.context.preferences.addons.get(_ADDON_MODULE)
|
||||
if prefs and prefs.preferences.auto_load_keymap:
|
||||
try:
|
||||
fa_hotkeys.register_keymaps()
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to register keymaps: {e}")
|
||||
|
||||
|
||||
def unregister():
|
||||
"""Unregister all classes and submodules"""
|
||||
# Unregister keymaps first
|
||||
try:
|
||||
fa_hotkeys.unregister_keymaps()
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to unregister keymaps: {e}")
|
||||
|
||||
# Unregister all submodules (in reverse order)
|
||||
for module in reversed(submodules):
|
||||
try:
|
||||
module.unregister()
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to unregister {module.__name__}: {e}")
|
||||
|
||||
# Unregister main classes
|
||||
for cls in reversed(classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,30 @@
|
||||
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.7.0"
|
||||
type = "add-on"
|
||||
|
||||
maintainer = "Form Affinity"
|
||||
license = ["GPL-3.0-or-later"]
|
||||
blender_version_min = "4.2.0"
|
||||
|
||||
website = "https://github.com/FormAffinity/MayaConfigPro"
|
||||
|
||||
tags = ["3D View", "User Interface", "Animation", "Keymap", "Presets"]
|
||||
|
||||
[permissions]
|
||||
files = "Read and write configuration files for theme and keymap presets"
|
||||
|
||||
[build]
|
||||
paths_exclude_pattern = [
|
||||
"__pycache__/",
|
||||
"*.pyc",
|
||||
".git/",
|
||||
".github/",
|
||||
"docs/",
|
||||
"tests/",
|
||||
"*.blend1",
|
||||
"*.blend2",
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
MAYA CONFIG PRO V 1.9 - HOTKEY LIST
|
||||
---------------------------
|
||||
NAVIGATION
|
||||
---------------------------
|
||||
Q = Box Selection Tool
|
||||
W = Move (translate) Tool
|
||||
E = Rotation Tool
|
||||
R = Scale Tool
|
||||
|
||||
Alt LMB = Rotate View
|
||||
Alt MMB = Pan View
|
||||
Alt RMB = Zoom View
|
||||
|
||||
---------------------------
|
||||
MENUS (pies, etc)
|
||||
---------------------------
|
||||
RMB = Marking Menu
|
||||
Space Bar = Quad Menu
|
||||
Shift RMB = Special Tools Menu
|
||||
Alt T = Workspace Tab Switcher Menu
|
||||
|
||||
---------------------------
|
||||
ARMATURE/POSE MODE
|
||||
---------------------------
|
||||
Ctrl E = Extrude
|
||||
|
||||
---------------------------
|
||||
MESH SMOOTH PREVIEW
|
||||
---------------------------
|
||||
1 = Off
|
||||
2 = Subdivisions set to 2
|
||||
3 = Subdivisions set to 3
|
||||
|
||||
---------------------------
|
||||
MODELING
|
||||
---------------------------
|
||||
Ctrl E = Extrude (Along Normals)
|
||||
G = Create Edge/Face
|
||||
Alt E = Isolate Select (Local View)
|
||||
F = Frame Selected
|
||||
Alt F = Frame Selected (Single Region instead of All Regions, use in Quadview)
|
||||
Shift .(>) = Select More
|
||||
Shift .(<) = Select Less
|
||||
|
||||
Hold Down Ctrl while Double LMB Click = Extrude Along Axis Tool
|
||||
|
||||
---------------------------
|
||||
ADDITIONAL
|
||||
---------------------------
|
||||
Shift Ctrl RMB = Set 3D Cursor
|
||||
Ctrl D = Show/Hide 3D Cursor
|
||||
D = Show/Hide Origin
|
||||
D then V = Snap Origin to Vertices
|
||||
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
Maya Config Pro - Keyconfigs Package
|
||||
Keymap configuration for Maya-like hotkeys.
|
||||
"""
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Maya Config Pro - Keymap Configuration
|
||||
Maya-like keymap preset for Blender.
|
||||
|
||||
This module loads and registers the FA Hotkeys keymap configuration.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
|
||||
# Import the keyconfig data
|
||||
from . import fa_hotkeys_data
|
||||
|
||||
# Addon keymaps
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
def register_keymaps():
|
||||
"""Register FA Hotkeys as addon keymaps"""
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
if not kc:
|
||||
return
|
||||
|
||||
# Try to load the keyconfig data
|
||||
try:
|
||||
# The keyconfig_data is defined in fa_hotkeys_data module
|
||||
if hasattr(fa_hotkeys_data, 'keyconfig_data'):
|
||||
for km_name, km_args, km_content in fa_hotkeys_data.keyconfig_data:
|
||||
# Create or get keymap
|
||||
km = kc.keymaps.new(name=km_name, **km_args)
|
||||
|
||||
# Add keymap items
|
||||
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
|
||||
|
||||
# Create keymap item
|
||||
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)
|
||||
)
|
||||
|
||||
# Set properties if provided
|
||||
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)
|
||||
|
||||
# Handle active state
|
||||
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}")
|
||||
|
||||
|
||||
def unregister_keymaps():
|
||||
"""Unregister FA Hotkeys addon keymaps"""
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
if not kc:
|
||||
return
|
||||
|
||||
# Remove all registered keymap items
|
||||
for km, kmi in addon_keymaps:
|
||||
try:
|
||||
km.keymap_items.remove(kmi)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
addon_keymaps.clear()
|
||||
|
||||
|
||||
# Classes to register (none needed for keyconfig)
|
||||
_classes = []
|
||||
|
||||
|
||||
def register():
|
||||
"""Register keymap module"""
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
# Don't auto-register keymaps here - let preferences control this
|
||||
# register_keymaps()
|
||||
|
||||
|
||||
def unregister():
|
||||
"""Unregister keymap module"""
|
||||
unregister_keymaps()
|
||||
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
Maya Config Pro - Operators Package
|
||||
Operators and menus for the extension.
|
||||
"""
|
||||
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
Maya Config Pro - Animation Menu
|
||||
Animation pie menu for rigging and animation workflows.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
from bpy.types import Menu
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Animation Submenus
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_AniSubEmpty(Menu):
|
||||
"""Create Empty/Null Submenu"""
|
||||
bl_label = 'MCP_MT_AniSubEmpty'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Create Empty/Null:")
|
||||
layout.separator()
|
||||
layout.operator("object.empty_add", text="Plain Axis", icon="EMPTY_AXIS").type='PLAIN_AXES'
|
||||
layout.operator("object.empty_add", text="Arrows", icon="EMPTY_ARROWS").type='ARROWS'
|
||||
layout.operator("object.empty_add", text="Single Arrow", icon="EMPTY_SINGLE_ARROW").type='SINGLE_ARROW'
|
||||
layout.operator("object.empty_add", text="Circle", icon="MESH_CIRCLE").type='CIRCLE'
|
||||
layout.operator("object.empty_add", text="Cube", icon="CUBE").type='CUBE'
|
||||
layout.operator("object.empty_add", text="Sphere", icon="SPHERE").type='SPHERE'
|
||||
layout.operator("object.empty_add", text="Cone", icon="CONE").type='CONE'
|
||||
layout.operator("object.empty_add", text="Image", icon="IMAGE_DATA").type='IMAGE'
|
||||
|
||||
|
||||
class MCP_MT_AniSubEditor(Menu):
|
||||
"""Editor Submenu"""
|
||||
bl_label = 'MCP_MT_AniSubEditor'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Editor:")
|
||||
layout.separator()
|
||||
layout.operator("mcp.split_area_timeline_ani", text="Timeline", icon="TIME")
|
||||
layout.operator("mcp.dope_sheet_ani_menu", text="Dope Sheet", icon="ACTION")
|
||||
|
||||
|
||||
class MCP_MT_AniSubFreeze(Menu):
|
||||
"""Freeze Transforms Submenu"""
|
||||
bl_label = 'MCP_MT_AniSubFreeze'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Freeze Transforms", icon="FREEZE")
|
||||
layout.separator()
|
||||
layout.operator("object.transforms_to_deltas", text="Clear All").mode='ALL'
|
||||
layout.operator("object.transforms_to_deltas", text="Location").mode="LOC"
|
||||
layout.operator("object.transforms_to_deltas", text="Rotation").mode="ROT"
|
||||
layout.operator("object.transforms_to_deltas", text="Scale").mode="SCALE"
|
||||
layout.separator()
|
||||
layout.label(text="Reset Transforms", icon="LOOP_BACK")
|
||||
layout.separator()
|
||||
layout.operator("object.location_clear", text="Location").clear_delta=False
|
||||
layout.operator("object.rotation_clear", text="Rotation").clear_delta=False
|
||||
layout.operator("object.scale_clear", text="Scale").clear_delta=False
|
||||
layout.operator("object.origin_clear", text="Origin")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Main Animation Menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_AnimationMenu(Menu):
|
||||
"""FA Animation Menu"""
|
||||
bl_label = "FA Animation Menu"
|
||||
bl_idname = "MCP_MT_AnimationMenu"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
|
||||
pie.operator('wm.link', text="Link", icon="LINKED")
|
||||
pie.operator("mcp.pose_mode_ani_menu", text="Pose Mode", icon="POSE_HLT")
|
||||
pie.operator('armature.extrude_move', text="Extrude Bone", icon="AXIS_TOP")
|
||||
pie.operator('mcp.split_area_timeline_ani', text="", icon="TIME")
|
||||
pie.operator("view3d.localview", text="Isolate", icon="BORDERMOVE")
|
||||
pie.operator("object.mode_set", text="Object", icon="OBJECT_DATA")
|
||||
pie.operator('wm.append', text="Append", icon="APPEND_BLEND")
|
||||
pie.operator('object.mode_set', text="Edit Mode", icon="EDITMODE_HLT").mode='EDIT'
|
||||
pie.separator()
|
||||
pie.separator()
|
||||
|
||||
other = pie.column()
|
||||
gap = other.column()
|
||||
gap.separator()
|
||||
gap.scale_y = 6
|
||||
other_menu = other.box().column().split()
|
||||
|
||||
other_menu.scale_y = 1.1
|
||||
other_menu.operator('object.armature_add', text="+ Armature", icon="OUTLINER_OB_ARMATURE")
|
||||
other_menu.operator('mcp.graph_editor_ani_menu', text="", icon="GRAPH")
|
||||
other_menu.operator('mcp.dope_sheet_ani_menu', text="", icon="ACTION")
|
||||
other_menu.operator('mcp.drivers_ani_menu', text="", icon="DRIVER")
|
||||
split = layout.split()
|
||||
col = split.column()
|
||||
other_menu.menu('MCP_MT_AniSubEditor', icon='STATUSBAR', text='')
|
||||
other_menu = other.box().column().split()
|
||||
other_menu.operator('mcp.drop_to_floor_ani_menu', text="", icon='TRIA_DOWN')
|
||||
other_menu.operator('anim.keyframe_insert_menu', text="", icon="DECORATE_ANIMATE")
|
||||
other_menu.menu('MCP_MT_AniSubEmpty', icon='EMPTY_AXIS', text='')
|
||||
other_menu = other.box().column().split()
|
||||
other_menu.operator("screen.screen_full_area", text="Max Screen", icon="FULLSCREEN_ENTER")
|
||||
other_menu.menu('MCP_MT_AniSubFreeze', icon='FREEZE', text='')
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Animation Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_DropToFloorAniMenu(bpy.types.Operator):
|
||||
"""Drop/Raise Object to Floor"""
|
||||
bl_label = "Drop to Floor"
|
||||
bl_idname = "mcp.drop_to_floor_ani_menu"
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
mx = obj.matrix_world
|
||||
minz = min((mx @ v.co).z for v in obj.data.vertices)
|
||||
mx.translation.z -= minz
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_SplitAreaTimelineAniMenu(bpy.types.Operator):
|
||||
"""Timeline"""
|
||||
bl_label = "Split Area"
|
||||
bl_idname = 'mcp.split_area_timeline_ani'
|
||||
|
||||
def execute(self, context):
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
with bpy.context.temp_override(area=area):
|
||||
bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.20)
|
||||
bpy.context.screen.areas[-1].ui_type = 'DOPESHEET'
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_DopeSheetAniMenu(bpy.types.Operator):
|
||||
"""Dope Sheet"""
|
||||
bl_label = "Dope Sheet"
|
||||
bl_idname = 'mcp.dope_sheet_ani_menu'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "DOPESHEET_EDITOR"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_PoseModeAniMenu(bpy.types.Operator):
|
||||
"""Pose Mode"""
|
||||
bl_label = "Pose Mode"
|
||||
bl_idname = 'mcp.pose_mode_ani_menu'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = context.active_object
|
||||
return active_object is not None and active_object.type == 'ARMATURE' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_SplitAreaAniMenu(bpy.types.Operator):
|
||||
"""Timeline"""
|
||||
bl_label = "Split Area"
|
||||
bl_idname = 'mcp.split_area_ani_menu'
|
||||
|
||||
def execute(self, context):
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
with bpy.context.temp_override(area=area):
|
||||
bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.25)
|
||||
bpy.context.screen.areas[-1].type = 'DOPESHEET_EDITOR'
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_SplitArea2AniMenu(bpy.types.Operator):
|
||||
"""Dope Sheet"""
|
||||
bl_label = "Split Area"
|
||||
bl_idname = 'mcp.split_area2_ani_menu'
|
||||
|
||||
def execute(self, context):
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
with bpy.context.temp_override(area=area):
|
||||
bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.25)
|
||||
bpy.context.screen.areas[-1].type = 'DOPESHEET_EDITOR'
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_GraphEditorAniMenu(bpy.types.Operator):
|
||||
"""Graph Editor"""
|
||||
bl_label = "Graph Editor"
|
||||
bl_idname = 'mcp.graph_editor_ani_menu'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "GRAPH_EDITOR"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_DriversAniMenu(bpy.types.Operator):
|
||||
"""Drivers Editor"""
|
||||
bl_label = "Drivers Editor"
|
||||
bl_idname = 'mcp.drivers_ani_menu'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.drivers_editor_show()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Menus
|
||||
MCP_MT_AniSubEmpty,
|
||||
MCP_MT_AniSubEditor,
|
||||
MCP_MT_AniSubFreeze,
|
||||
MCP_MT_AnimationMenu,
|
||||
# Operators
|
||||
MCP_OT_DropToFloorAniMenu,
|
||||
MCP_OT_SplitAreaTimelineAniMenu,
|
||||
MCP_OT_DopeSheetAniMenu,
|
||||
MCP_OT_PoseModeAniMenu,
|
||||
MCP_OT_SplitAreaAniMenu,
|
||||
MCP_OT_SplitArea2AniMenu,
|
||||
MCP_OT_GraphEditorAniMenu,
|
||||
MCP_OT_DriversAniMenu,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Maya Config Pro - Animation Timeline
|
||||
Timeline key manipulation operators.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Key Move Operator
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_KeyMove(bpy.types.Operator):
|
||||
"""Copy/Transform Keys in Timeline"""
|
||||
bl_idname = "mcp.key_move"
|
||||
bl_label = "FA Transform Keys"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.action.duplicate_move()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
MCP_OT_KeyMove,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Maya Config Pro - Delete Without Confirm
|
||||
Deletes element based on mesh select mode without confirmation.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
|
||||
|
||||
class MCP_OT_DeleteWithoutConfirm(bpy.types.Operator):
|
||||
"""Deletes element from msm without confirmation"""
|
||||
bl_idname = "mcp.delete_without_confirm"
|
||||
bl_label = "Delete Without Confirm"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
# get msm
|
||||
msm = bpy.context.tool_settings.mesh_select_mode
|
||||
|
||||
# cancel if no active
|
||||
if not bpy.context.active_object:
|
||||
return {'FINISHED'}
|
||||
|
||||
if bpy.context.active_object.mode == "EDIT":
|
||||
if msm[0]:
|
||||
# delete verts
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
elif msm[1]:
|
||||
# delete edges
|
||||
bpy.ops.mesh.delete(type='EDGE')
|
||||
elif msm[2]:
|
||||
# delete faces
|
||||
bpy.ops.mesh.delete(type='FACE')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
MCP_OT_DeleteWithoutConfirm,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Operators: deploy bundled assets and activate user keymap presets safely.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import bpy
|
||||
|
||||
from ..utils import compat
|
||||
from ..utils import deploy as deploy_util
|
||||
|
||||
|
||||
class MCP_OT_DeployKeymapPresets(bpy.types.Operator):
|
||||
bl_idname = "mcp.deploy_keymap_presets"
|
||||
bl_label = "Deploy Keymap Presets"
|
||||
bl_description = (
|
||||
"Copy Blender default keymaps (and MCP fa_hotkeys) into your user scripts folder "
|
||||
"so keymap switching buttons work"
|
||||
)
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
ok, msg = deploy_util.deploy_keymap_presets()
|
||||
if ok:
|
||||
self.report({"INFO"}, msg.replace("\n", " - "))
|
||||
else:
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"FINISHED"} if ok else {"CANCELLED"}
|
||||
|
||||
|
||||
class MCP_OT_DeployStartupBlend(bpy.types.Operator):
|
||||
bl_idname = "mcp.deploy_startup_blend"
|
||||
bl_label = "Deploy startup.blend"
|
||||
bl_description = "Copy bundled startup.blend into your user config folder"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return deploy_util.is_startup_bundle_available()
|
||||
|
||||
def execute(self, context):
|
||||
ok, msg = deploy_util.deploy_startup_blend()
|
||||
if ok:
|
||||
self.report({"INFO"}, msg.replace("\n", " - "))
|
||||
else:
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"FINISHED"} if ok else {"CANCELLED"}
|
||||
|
||||
|
||||
class MCP_OT_ActivateKeymapPreset(bpy.types.Operator):
|
||||
bl_idname = "mcp.activate_keymap_preset"
|
||||
bl_label = "Activate Keymap"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
preset: bpy.props.EnumProperty(
|
||||
name="Preset",
|
||||
items=(
|
||||
("BLENDER", "Blender", "Blender default keymap"),
|
||||
("FA_HOTKEYS", "FA Hotkeys", "Maya Config Pro FA hotkeys"),
|
||||
("INDUSTRY", "Industry Compatible", "Industry Compatible keymap"),
|
||||
),
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if not deploy_util.is_keyconfig_deployed():
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"Keymap presets are not deployed. "
|
||||
"Edit → Preferences → Add-ons → Maya Config Pro → Deploy keymap presets.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
names = {
|
||||
"BLENDER": "Blender.py",
|
||||
"FA_HOTKEYS": "fa_hotkeys.py",
|
||||
"INDUSTRY": "Industry_Compatible.py",
|
||||
}
|
||||
filename = names[self.preset]
|
||||
filepath = os.path.join(deploy_util.user_keyconfig_dir(), filename)
|
||||
if not os.path.isfile(filepath):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Missing {filename}. Open Preferences and run Deploy keymap presets again.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
bpy.ops.preferences.keyconfig_activate(filepath=filepath)
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Could not activate keymap: {e}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Maya-style Alt+RMB zoom uses horizontal mouse drag; Blender default uses vertical.
|
||||
inputs = context.preferences.inputs
|
||||
if self.preset == "FA_HOTKEYS":
|
||||
inputs.view_zoom_axis = "HORIZONTAL"
|
||||
elif self.preset in {"BLENDER", "INDUSTRY"}:
|
||||
inputs.view_zoom_axis = "VERTICAL"
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
_classes = (
|
||||
MCP_OT_DeployKeymapPresets,
|
||||
MCP_OT_DeployStartupBlend,
|
||||
MCP_OT_ActivateKeymapPreset,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,333 @@
|
||||
"""
|
||||
Maya Config Pro - Marking Menu
|
||||
Maya-style marking menu (pie menu) with create, mode switch, freeze transforms, and snap options.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
from bpy.types import Menu
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Sub Menus
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_SubCreate(Menu):
|
||||
"""Create Objects Submenu"""
|
||||
bl_label = 'MCP_MT_SubCreate'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Create:")
|
||||
layout.separator()
|
||||
layout.operator("mesh.primitive_plane_add", text="Plane", icon="MESH_PLANE")
|
||||
layout.operator("mesh.primitive_cube_add", text="Cube", icon="MESH_CUBE")
|
||||
layout.operator("mesh.primitive_circle_add", text="Circle", icon="MESH_CIRCLE")
|
||||
layout.operator("mesh.primitive_uv_sphere_add", text="UV Sphere", icon="MESH_UVSPHERE")
|
||||
layout.operator("mesh.primitive_ico_sphere_add", text="Ico Sphere", icon="MESH_ICOSPHERE")
|
||||
layout.operator("mesh.primitive_cylinder_add", text="Cylinder", icon="MESH_CYLINDER")
|
||||
layout.operator("mesh.primitive_cone_add", text="Cone", icon="MESH_CONE")
|
||||
layout.operator("mesh.primitive_torus_add", text="Torus", icon="MESH_TORUS")
|
||||
layout.operator("object.empty_add", text="Null", icon="EMPTY_AXIS").type='PLAIN_AXES'
|
||||
layout.separator()
|
||||
layout.operator("object.light_add", text="Point", icon="LIGHT_POINT").type="POINT"
|
||||
layout.operator("object.light_add", text="Sun", icon="LIGHT_SUN").type="SUN"
|
||||
layout.operator("object.light_add", text="Spot", icon="LIGHT_SPOT").type="SPOT"
|
||||
layout.operator("object.light_add", text="Area", icon="LIGHT_AREA").type="AREA"
|
||||
layout.separator()
|
||||
layout.operator("object.camera_add", text="Camera", icon="OUTLINER_DATA_CAMERA")
|
||||
|
||||
|
||||
class MCP_MT_SubMode(Menu):
|
||||
"""Mode Switch Submenu"""
|
||||
bl_label = 'MCP_MT_SubMode'
|
||||
|
||||
def draw(self, context):
|
||||
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'
|
||||
|
||||
|
||||
class MCP_MT_SubFreeze(Menu):
|
||||
"""Freeze/Reset Transforms Submenu"""
|
||||
bl_label = 'MCP_MT_SubFreeze'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Freeze Transforms", icon="FREEZE")
|
||||
layout.separator()
|
||||
layout.operator("object.transforms_to_deltas", text="Clear All").mode='ALL'
|
||||
layout.operator("object.transforms_to_deltas", text="Location").mode="LOC"
|
||||
layout.operator("object.transforms_to_deltas", text="Rotation").mode="ROT"
|
||||
layout.operator("object.transforms_to_deltas", text="Scale").mode="SCALE"
|
||||
layout.separator()
|
||||
layout.label(text="Reset Transforms", icon="LOOP_BACK")
|
||||
layout.separator()
|
||||
layout.operator("object.location_clear", text="Location").clear_delta=False
|
||||
layout.operator("object.rotation_clear", text="Rotation").clear_delta=False
|
||||
layout.operator("object.scale_clear", text="Scale").clear_delta=False
|
||||
layout.operator("object.origin_clear", text="Origin")
|
||||
|
||||
|
||||
class MCP_MT_SubSnap(Menu):
|
||||
"""Snap Options Submenu"""
|
||||
bl_label = 'MCP_MT_SubSnap'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(icon="SNAP_ON")
|
||||
layout.separator()
|
||||
layout.operator("mcp.vertex_snap", text="Vertex", icon='SNAP_ON')
|
||||
layout.operator("mcp.grid_snap", text="Grid", icon='SNAP_ON')
|
||||
layout.operator("mcp.edge_snap", text="Curve", icon='SNAP_ON')
|
||||
layout.operator("mcp.face_snap", text="Face", icon='SNAP_ON')
|
||||
layout.operator("mcp.off_snap", text="SnapOff", icon='SNAP_OFF')
|
||||
layout.operator("mcp.show_origin", text="Origin", icon='OBJECT_ORIGIN')
|
||||
layout.operator("mcp.hide_origin", text="Origin Hide", icon='TRANSFORM_ORIGINS')
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Main Marking Menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_MarkingMenu(Menu):
|
||||
"""FA Marking Menu"""
|
||||
bl_label = "FA Marking Menu"
|
||||
bl_idname = "MCP_MT_MarkingMenu"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
|
||||
pie.operator("mcp.msm_from_object", text="Vertex", icon='VERTEXSEL').mode = 'vert'
|
||||
pie.operator("mcp.multi", text="Multi", icon='EDITMODE_HLT')
|
||||
pie.operator("object.shade_smooth", text="Smooth", icon="MOD_SMOOTH")
|
||||
pie.operator("mcp.msm_from_object", text="Edge", icon='EDGESEL').mode = 'edge'
|
||||
pie.operator("view3d.localview", text="Isolate", icon="BORDERMOVE")
|
||||
pie.operator("object.mode_set", text="Object", icon="OBJECT_DATA")
|
||||
pie.operator("object.shade_flat", text="Shade Flat", icon="MOD_WIREFRAME")
|
||||
pie.operator("mcp.msm_from_object", text="Face", icon='FACESEL').mode = 'face'
|
||||
pie.separator()
|
||||
pie.separator()
|
||||
|
||||
other = pie.column()
|
||||
gap = other.column()
|
||||
gap.separator()
|
||||
gap.scale_y = 6
|
||||
other_menu = other.box().column().split()
|
||||
|
||||
other_menu.scale_y = 1.1
|
||||
op = other_menu.operator("mesh.select_all", text="", icon="UV_SYNC_SELECT")
|
||||
if op:
|
||||
op.action = "INVERT"
|
||||
op = other_menu.operator("mesh.select_all", text="", icon="SELECT_EXTEND")
|
||||
if op:
|
||||
op.action = "SELECT"
|
||||
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
|
||||
other_menu.operator("mesh.select_more", text="", icon="ADD")
|
||||
other_menu.operator("mesh.select_less", text="", icon="REMOVE")
|
||||
|
||||
split = layout.split()
|
||||
col = split.column()
|
||||
other_menu.menu('MCP_MT_SubCreate', icon='RIGHTARROW_THIN', text='Add')
|
||||
other_menu = other.box().column().split()
|
||||
other_menu.operator("screen.screen_full_area", text='', icon="FULLSCREEN_ENTER")
|
||||
other_menu.operator("mcp.subdivide", text="", icon="MOD_MULTIRES")
|
||||
other_menu.operator("mcp.unsubdivide", text="", icon="MESH_PLANE")
|
||||
other_menu.operator("mesh.flip_normals", text="", icon="NORMALS_FACE")
|
||||
other_menu.operator("mesh.merge", text="", icon="AUTOMERGE_ON")
|
||||
other_menu.operator("mesh.remove_doubles", text="", icon="AUTOMERGE_OFF")
|
||||
|
||||
other_menu.menu('MCP_MT_SubFreeze', icon='FREEZE', text='')
|
||||
other_menu = other.box().column().split()
|
||||
other_menu.prop(bpy.context.space_data.overlay, 'show_wireframes', text='', icon='MESH_UVSPHERE', toggle=True)
|
||||
obj = context.object
|
||||
if obj is not None:
|
||||
other_menu.prop(obj, 'show_wire', text='', icon='SHADING_WIRE', toggle=True)
|
||||
other_menu.menu('MCP_MT_SubMode', icon='SCULPTMODE_HLT', text='')
|
||||
other_menu.menu('MCP_MT_SubSnap', text='', icon='SNAP_ON')
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Utility Operators for Marking Menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_Multi(bpy.types.Operator):
|
||||
"""Multi Sub-Component Mode"""
|
||||
bl_label = "Multi"
|
||||
bl_idname = "mcp.multi"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, True, True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_Subdivide(bpy.types.Operator):
|
||||
"""Subdivide Object"""
|
||||
bl_label = "Subdivide"
|
||||
bl_idname = "mcp.subdivide_mm"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, True, False)
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.subdivide()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_Unsubdivide(bpy.types.Operator):
|
||||
"""Un-Subdivide Object"""
|
||||
bl_label = "Un-Subdivide"
|
||||
bl_idname = "mcp.unsubdivide_mm"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, True, False)
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.unsubdivide()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Origin Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ShowOrigin(bpy.types.Operator):
|
||||
"""Show Origin"""
|
||||
bl_idname = "mcp.show_origin"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.tool_settings.use_transform_data_origin = True
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_HideOrigin(bpy.types.Operator):
|
||||
"""Hide Origin"""
|
||||
bl_idname = "mcp.hide_origin"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.tool_settings.use_transform_data_origin = False
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Snap Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_VertexSnap(bpy.types.Operator):
|
||||
"""Vertex Snapping"""
|
||||
bl_idname = "mcp.vertex_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'VERTEX'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_GridSnap(bpy.types.Operator):
|
||||
"""Grid Snapping"""
|
||||
bl_idname = "mcp.grid_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'INCREMENT'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_EdgeSnap(bpy.types.Operator):
|
||||
"""Edge Snapping"""
|
||||
bl_idname = "mcp.edge_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'EDGE'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_FaceSnap(bpy.types.Operator):
|
||||
"""Face Snapping"""
|
||||
bl_idname = "mcp.face_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'FACE'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_OffSnap(bpy.types.Operator):
|
||||
"""Turn Off Snapping"""
|
||||
bl_idname = "mcp.off_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.context.scene.tool_settings.use_snap = False
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'INCREMENT'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Menus
|
||||
MCP_MT_SubCreate,
|
||||
MCP_MT_SubMode,
|
||||
MCP_MT_SubFreeze,
|
||||
MCP_MT_SubSnap,
|
||||
MCP_MT_MarkingMenu,
|
||||
# Operators
|
||||
MCP_OT_Multi,
|
||||
MCP_OT_Subdivide,
|
||||
MCP_OT_Unsubdivide,
|
||||
MCP_OT_ShowOrigin,
|
||||
MCP_OT_HideOrigin,
|
||||
MCP_OT_VertexSnap,
|
||||
MCP_OT_GridSnap,
|
||||
MCP_OT_EdgeSnap,
|
||||
MCP_OT_FaceSnap,
|
||||
MCP_OT_OffSnap,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Maya Config Pro - Mesh Select Mode
|
||||
Switches to object mode and changes mesh select mode.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
|
||||
|
||||
class MCP_OT_MsmFromObject(bpy.types.Operator):
|
||||
"""Switches to object mode and changes mesh select mode"""
|
||||
bl_idname = "mcp.msm_from_object"
|
||||
bl_label = "Mesh Select Mode"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
mode: bpy.props.StringProperty(
|
||||
name="mode",
|
||||
default="vert"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if len(bpy.context.selected_objects) < 1:
|
||||
self.report({'WARNING'}, 'No object selected')
|
||||
return {'FINISHED'}
|
||||
|
||||
if bpy.context.object.mode == 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
|
||||
if self.mode == 'vert':
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
elif self.mode == 'edge':
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, True, False)
|
||||
elif self.mode == 'face':
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, False, True)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
MCP_OT_MsmFromObject,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
Maya Config Pro - Object to Object Select
|
||||
Raycast object selection while in edit mode.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
def select_obj(context, event, deselect_first=True):
|
||||
"""Select object under cursor while in edit mode"""
|
||||
if deselect_first:
|
||||
obj = context.object
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
ray_cast(context, event)
|
||||
if deselect_first:
|
||||
obj.select_set(False)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
|
||||
def ray_cast(context, event):
|
||||
"""Run this function on left mouse, execute the ray cast"""
|
||||
# get the context arguments
|
||||
scene = context.scene
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
coord = event.mouse_region_x, event.mouse_region_y
|
||||
|
||||
# get the ray from the viewport and mouse
|
||||
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
|
||||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
|
||||
ray_target = ray_origin + view_vector
|
||||
|
||||
def visible_objects_and_duplis():
|
||||
"""Loop over (object, matrix) pairs (mesh only)"""
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
for dup in depsgraph.object_instances:
|
||||
if dup.is_instance: # Real dupli instance
|
||||
obj = dup.instance_object
|
||||
yield (obj, dup.matrix_world.copy())
|
||||
else: # Usual object
|
||||
obj = dup.object
|
||||
yield (obj, obj.matrix_world.copy())
|
||||
|
||||
def obj_ray_cast(obj, matrix):
|
||||
"""Wrapper for ray casting that moves the ray into object space"""
|
||||
# get the ray relative to the object
|
||||
matrix_inv = matrix.inverted()
|
||||
ray_origin_obj = matrix_inv @ ray_origin
|
||||
ray_target_obj = matrix_inv @ ray_target
|
||||
ray_direction_obj = ray_target_obj - ray_origin_obj
|
||||
|
||||
# cast the ray
|
||||
success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
|
||||
|
||||
if success:
|
||||
return location, normal, face_index
|
||||
else:
|
||||
return None, None, None
|
||||
|
||||
# cast rays and find the closest object
|
||||
best_length_squared = -1.0
|
||||
best_obj = None
|
||||
|
||||
for obj, matrix in visible_objects_and_duplis():
|
||||
if obj.type == 'MESH':
|
||||
hit, normal, face_index = obj_ray_cast(obj, matrix)
|
||||
if hit is not None:
|
||||
hit_world = matrix @ hit
|
||||
scene.cursor.location = hit_world
|
||||
length_squared = (hit_world - ray_origin).length_squared
|
||||
if best_obj is None or length_squared < best_length_squared:
|
||||
best_length_squared = length_squared
|
||||
best_obj = obj
|
||||
bpy.ops.view3d.snap_cursor_to_center()
|
||||
|
||||
# now we have the object under the mouse cursor,
|
||||
# we could do lots of stuff but for the example just select.
|
||||
if best_obj is not None:
|
||||
# for selection etc. we need the original object,
|
||||
# evaluated objects are not in viewlayer
|
||||
best_original = best_obj.original
|
||||
best_original.select_set(True)
|
||||
context.view_layer.objects.active = best_original
|
||||
else:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
class MCP_OT_ViewOperatorRayCast(bpy.types.Operator):
|
||||
"""Modal object selection with a ray cast"""
|
||||
bl_idname = "mcp.modal_operator_raycast"
|
||||
bl_label = "FA Object 2 Object"
|
||||
|
||||
def invoke(self, context, event):
|
||||
print('invoke')
|
||||
bpy.ops.view3d.select(extend=False, deselect=False, toggle=False, deselect_all=True, select_passthrough=False, center=False, enumerate=False, object=False, location=(0, 0))
|
||||
|
||||
if context.space_data.type == 'VIEW_3D':
|
||||
if bpy.context.mode == 'EDIT_MESH':
|
||||
select_obj(context, event)
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
self.report({'WARNING'}, "Edit Mode only")
|
||||
return {'PASS_THROUGH'}
|
||||
else:
|
||||
self.report({'WARNING'}, "Active space must be a View3d")
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
MCP_OT_ViewOperatorRayCast,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,290 @@
|
||||
"""
|
||||
Maya Config Pro - Quad View Menu
|
||||
Quad view menu with orthographic views and camera controls.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
from bpy.types import Menu
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Main Quad Menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_QuadMenu(Menu):
|
||||
"""FA Quad Menu"""
|
||||
bl_label = "FA Quad Menu"
|
||||
bl_idname = "MCP_MT_QuadMenu"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
pie.operator("mcp.left_ortho", text="Left", icon='VIEW_ORTHO')
|
||||
pie.operator("mcp.right_ortho", text="Right", icon='VIEW_ORTHO')
|
||||
pie.operator("mcp.bottom_ortho", text="Bottom", icon='VIEW_ORTHO')
|
||||
pie.operator("mcp.top_ortho", text="Top", icon='VIEW_ORTHO')
|
||||
pie.operator("mcp.front_ortho", text="Front", icon='VIEW_ORTHO')
|
||||
pie.operator("mcp.back_ortho", text="Back", icon='VIEW_ORTHO')
|
||||
pie.operator("view3d.view_persportho", text="Persp/Ortho", icon="ARROW_LEFTRIGHT")
|
||||
pie.operator("screen.region_quadview", text="QuadView", icon='VIEW_PERSPECTIVE')
|
||||
pie.separator()
|
||||
pie.separator()
|
||||
|
||||
other = pie.column()
|
||||
gap = other.column()
|
||||
gap.separator()
|
||||
gap.scale_y = 7
|
||||
other_menu = other.box().column()
|
||||
other_menu.scale_y = 1.3
|
||||
other_menu.operator("mcp.split_view", text="Vertical Split", icon='SPREADSHEET')
|
||||
other_menu.operator("mcp.split_view_h", text="Horizontal Split", icon='SPREADSHEET')
|
||||
other_menu.operator("view3d.view_camera", text="Active Cam", icon='CAMERA_DATA')
|
||||
other_menu.operator('mcp.cam_view_lock', text="View Lock", icon='CON_CAMERASOLVER')
|
||||
other_menu.operator("view3d.camera_to_view", text="Cam To View", icon="RESTRICT_VIEW_ON")
|
||||
other_menu.operator("screen.screen_full_area", text="Max Screen", icon="FULLSCREEN_ENTER")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Orthographic View Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_LeftOrtho(bpy.types.Operator):
|
||||
"""Orthographic View Left"""
|
||||
bl_idname = "mcp.left_ortho"
|
||||
bl_label = ""
|
||||
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'}
|
||||
|
||||
|
||||
class MCP_OT_RightOrtho(bpy.types.Operator):
|
||||
"""Orthographic View Right"""
|
||||
bl_idname = "mcp.right_ortho"
|
||||
bl_label = ""
|
||||
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'}
|
||||
|
||||
|
||||
class MCP_OT_BottomOrtho(bpy.types.Operator):
|
||||
"""Orthographic View Bottom"""
|
||||
bl_idname = "mcp.bottom_ortho"
|
||||
bl_label = ""
|
||||
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'}
|
||||
|
||||
|
||||
class MCP_OT_TopOrtho(bpy.types.Operator):
|
||||
"""Orthographic View Top"""
|
||||
bl_idname = "mcp.top_ortho"
|
||||
bl_label = ""
|
||||
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'}
|
||||
|
||||
|
||||
class MCP_OT_FrontOrtho(bpy.types.Operator):
|
||||
"""Orthographic View Front"""
|
||||
bl_idname = "mcp.front_ortho"
|
||||
bl_label = ""
|
||||
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'}
|
||||
|
||||
|
||||
class MCP_OT_BackOrtho(bpy.types.Operator):
|
||||
"""Orthographic View Back"""
|
||||
bl_idname = "mcp.back_ortho"
|
||||
bl_label = ""
|
||||
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'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# View Split Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_SplitView(bpy.types.Operator):
|
||||
"""Split View Vertically"""
|
||||
bl_idname = "mcp.split_view"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.area_split(direction='VERTICAL')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_SplitViewH(bpy.types.Operator):
|
||||
"""Split View Horizontally"""
|
||||
bl_idname = "mcp.split_view_h"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.area_split()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Camera View Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_CamViewLock(bpy.types.Operator):
|
||||
"""Camera - View Lock"""
|
||||
bl_idname = "mcp.cam_view_lock"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.space_data.lock_camera = not bpy.context.space_data.lock_camera
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_CameraSafeArea(bpy.types.Operator):
|
||||
"""Camera - safe areas"""
|
||||
bl_idname = "mcp.camera_safe_area"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.object.data.show_safe_areas = not bpy.context.object.data.show_safe_areas
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Key Grid Snap
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_KeyGridSnap(bpy.types.Operator):
|
||||
"""Snap to Grid Hotkey"""
|
||||
bl_idname = "mcp.key_grid_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'INCREMENT'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_KeyVertexSnap(bpy.types.Operator):
|
||||
"""Snap to Vertex Hotkey"""
|
||||
bl_idname = "mcp.key_vertex_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'VERTEX'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_KeyCurveSnap(bpy.types.Operator):
|
||||
"""Snap to Curve Hotkey"""
|
||||
bl_idname = "mcp.key_curve_snap"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
bpy.ops.mcp.show_origin()
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.snap_elements_base = {'EDGE'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_OriginHide(bpy.types.Operator):
|
||||
"""Hide Origin"""
|
||||
bl_idname = "mcp.origin_hide_qv"
|
||||
bl_label = "FA Hide Origin"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.tool_settings.use_snap = False
|
||||
bpy.context.scene.tool_settings.use_transform_data_origin = False
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Menu
|
||||
MCP_MT_QuadMenu,
|
||||
# Ortho Views
|
||||
MCP_OT_LeftOrtho,
|
||||
MCP_OT_RightOrtho,
|
||||
MCP_OT_BottomOrtho,
|
||||
MCP_OT_TopOrtho,
|
||||
MCP_OT_FrontOrtho,
|
||||
MCP_OT_BackOrtho,
|
||||
# Split
|
||||
MCP_OT_SplitView,
|
||||
MCP_OT_SplitViewH,
|
||||
# Camera
|
||||
MCP_OT_CamViewLock,
|
||||
MCP_OT_CameraSafeArea,
|
||||
# Snap
|
||||
MCP_OT_KeyGridSnap,
|
||||
MCP_OT_KeyVertexSnap,
|
||||
MCP_OT_KeyCurveSnap,
|
||||
MCP_OT_OriginHide,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
Maya Config Pro - Special Tools Menu
|
||||
Special tools menu for edit modes.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
from bpy.types import Menu, Operator
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Object Mode: D key — toggle Affect Only Origins on scene "Scene"
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
|
||||
class MCP_OT_ToggleAffectOriginsScene(Operator):
|
||||
"""Toggle Affect Only Origins on the data-block named Scene."""
|
||||
|
||||
bl_idname = "mcp.toggle_affect_origins_scene"
|
||||
bl_label = "Toggle Affect Only Origins (Scene)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
scene = bpy.data.scenes.get("Scene")
|
||||
if scene is None:
|
||||
self.report({"WARNING"}, 'No data block named "Scene"')
|
||||
return {"CANCELLED"}
|
||||
ts = scene.tool_settings
|
||||
ts.use_transform_data_origin = not ts.use_transform_data_origin
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Special Tools Menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_SpecialTools(Menu):
|
||||
"""FA Special Tools"""
|
||||
bl_label = "FA Special Tools"
|
||||
bl_idname = "MCP_MT_SpecialTools"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if context.space_data.type == 'VIEW_3D':
|
||||
if context.mode == 'EDIT_MESH':
|
||||
layout.separator()
|
||||
|
||||
# if vertex mode
|
||||
if context.tool_settings.mesh_select_mode[:] == (True, False, False):
|
||||
layout.menu("VIEW3D_MT_edit_mesh_vertices", text="Vertex Special", icon='VERTEXSEL')
|
||||
layout.separator()
|
||||
layout.label(text="Mode Switch", icon='DOWNARROW_HLT')
|
||||
layout.operator("mcp.msm_from_object", text="Edges", icon='EDGESEL').mode = 'edge'
|
||||
layout.operator("mcp.msm_from_object", text="Faces", icon='FACESEL').mode = 'face'
|
||||
layout.operator("object.mode_set", text="Object Mode", icon="OBJECT_DATA")
|
||||
|
||||
# if edge mode
|
||||
if context.tool_settings.mesh_select_mode[:] == (False, True, False):
|
||||
layout.menu("VIEW3D_MT_edit_mesh_edges", text="Edge Special", icon='EDGESEL')
|
||||
layout.separator()
|
||||
layout.label(text="Mode Switch", icon='DOWNARROW_HLT')
|
||||
layout.operator("mcp.msm_from_object", text="Vertices", icon='VERTEXSEL').mode = 'vert'
|
||||
layout.operator("mcp.msm_from_object", text="Faces", icon='FACESEL').mode = 'face'
|
||||
layout.operator("object.mode_set", text="Object Mode", icon="OBJECT_DATA")
|
||||
|
||||
# if face mode
|
||||
if bpy.context.tool_settings.mesh_select_mode[:] == (False, False, True):
|
||||
layout.menu("VIEW3D_MT_edit_mesh_faces", text="Face Special", icon='FACESEL')
|
||||
layout.separator()
|
||||
layout.label(text="Mode Switch", icon='DOWNARROW_HLT')
|
||||
layout.operator("mcp.msm_from_object", text="Vertices", icon='VERTEXSEL').mode = 'vert'
|
||||
layout.operator("mcp.msm_from_object", text="Edges", icon='EDGESEL').mode = 'edge'
|
||||
layout.operator("object.mode_set", text="Object Mode", icon="OBJECT_DATA")
|
||||
|
||||
elif context.mode == 'OBJECT':
|
||||
layout.label(text="object mode")
|
||||
elif context.space_data.type == 'IMAGE_EDITOR':
|
||||
layout.label(text="No Context! image editor")
|
||||
else:
|
||||
layout.label(text="No Context!")
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
MCP_OT_ToggleAffectOriginsScene,
|
||||
MCP_MT_SpecialTools,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Maya Config Pro - Tab Switcher
|
||||
Workspace tab switcher pie menu.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
from bpy.types import Menu
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Tab Switcher Menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_MT_TabSwitcher(Menu):
|
||||
"""FA Tab Switcher"""
|
||||
bl_idname = "MCP_MT_TabSwitcher"
|
||||
bl_label = "FA Tab Switcher"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
|
||||
pie.operator("mcp.workspace_camera", text="Camera", icon='CAMERA_DATA')
|
||||
pie.operator("mcp.workspace_uvs", text="UV Editing", icon='UV_DATA')
|
||||
pie.operator("mcp.workspace_sculpting", text="Sculpting", icon='SCULPTMODE_HLT')
|
||||
pie.operator("mcp.workspace_animation", text="Animation", icon='RENDER_ANIMATION')
|
||||
pie.operator("mcp.workspace_rendering", text="Rendering", icon='SHADING_RENDERED')
|
||||
pie.operator("mcp.workspace_texturepaint", text="Texture Paint", icon='BRUSH_DATA')
|
||||
pie.operator("mcp.workspace_modeling", text="Modeling", icon='EDITMODE_HLT')
|
||||
pie.operator("mcp.workspace_hypershade", text="HyperShade", icon='MATERIAL_DATA')
|
||||
pie.separator()
|
||||
pie.separator()
|
||||
|
||||
other = pie.column()
|
||||
gap = other.column()
|
||||
gap.separator()
|
||||
gap.scale_y = 7
|
||||
other_menu = other.box().column()
|
||||
other_menu.scale_y = 1.3
|
||||
other_menu.operator("mcp.workspace_compositing", text="Compositing", icon="NODE_COMPOSITING")
|
||||
other_menu.operator("mcp.workspace_scripting", text="Scripting", icon="FILE_SCRIPT")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Workspace Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_WorkspaceCamera(bpy.types.Operator):
|
||||
"""Switch to Camera workspace"""
|
||||
bl_idname = "mcp.workspace_camera"
|
||||
bl_label = "Camera"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Camera']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceModeling(bpy.types.Operator):
|
||||
"""Switch to Modeling workspace"""
|
||||
bl_idname = "mcp.workspace_modeling"
|
||||
bl_label = "Modeling"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Modeling']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceSculpting(bpy.types.Operator):
|
||||
"""Switch to Sculpting workspace"""
|
||||
bl_idname = "mcp.workspace_sculpting"
|
||||
bl_label = "Sculpting"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Sculpting']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceHypershade(bpy.types.Operator):
|
||||
"""Switch to HyperShade workspace"""
|
||||
bl_idname = "mcp.workspace_hypershade"
|
||||
bl_label = "HyperShade"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['HyperShade']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceUVs(bpy.types.Operator):
|
||||
"""Switch to UV Editing workspace"""
|
||||
bl_idname = "mcp.workspace_uvs"
|
||||
bl_label = "UV Editing"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['UV Editing']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceTexturePaint(bpy.types.Operator):
|
||||
"""Switch to Texture Paint workspace"""
|
||||
bl_idname = "mcp.workspace_texturepaint"
|
||||
bl_label = "Texture Paint"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Texture Paint']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceAnimation(bpy.types.Operator):
|
||||
"""Switch to Animation workspace"""
|
||||
bl_idname = "mcp.workspace_animation"
|
||||
bl_label = "Animation"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Animation']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceRendering(bpy.types.Operator):
|
||||
"""Switch to Rendering workspace"""
|
||||
bl_idname = "mcp.workspace_rendering"
|
||||
bl_label = "Rendering"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Rendering']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceCompositing(bpy.types.Operator):
|
||||
"""Switch to Compositing workspace"""
|
||||
bl_idname = "mcp.workspace_compositing"
|
||||
bl_label = "Compositing"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Compositing']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_WorkspaceScripting(bpy.types.Operator):
|
||||
"""Switch to Scripting workspace"""
|
||||
bl_idname = "mcp.workspace_scripting"
|
||||
bl_label = "Scripting"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Scripting']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Menu
|
||||
MCP_MT_TabSwitcher,
|
||||
# Workspace Operators
|
||||
MCP_OT_WorkspaceCamera,
|
||||
MCP_OT_WorkspaceModeling,
|
||||
MCP_OT_WorkspaceSculpting,
|
||||
MCP_OT_WorkspaceHypershade,
|
||||
MCP_OT_WorkspaceUVs,
|
||||
MCP_OT_WorkspaceTexturePaint,
|
||||
MCP_OT_WorkspaceAnimation,
|
||||
MCP_OT_WorkspaceRendering,
|
||||
MCP_OT_WorkspaceCompositing,
|
||||
MCP_OT_WorkspaceScripting,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Maya Config Pro - Panels Package
|
||||
UI panels for the extension.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import importlib.util
|
||||
import os
|
||||
|
||||
# Module cache
|
||||
_module_cache = {}
|
||||
|
||||
|
||||
def _load_module(name):
|
||||
"""Load a module by executing its file"""
|
||||
if name in _module_cache:
|
||||
return _module_cache[name]
|
||||
|
||||
# Get the file path
|
||||
current_dir = os.path.dirname(__file__)
|
||||
file_path = os.path.join(current_dir, f"{name}.py")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
raise ImportError(f"Module file not found: {file_path}")
|
||||
|
||||
# Load the module
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
f"{__name__}.{name}",
|
||||
file_path
|
||||
)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
|
||||
# Add to sys.modules temporarily
|
||||
sys.modules[f"{__name__}.{name}"] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
_module_cache[name] = module
|
||||
return module
|
||||
|
||||
|
||||
def register():
|
||||
"""Register all panels"""
|
||||
# Register shelf header first (replaces PROPERTIES panels)
|
||||
try:
|
||||
shelf_header = _load_module("shelf_header")
|
||||
shelf_header.register()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to register shelf header: {e}")
|
||||
|
||||
# Register other panels
|
||||
try:
|
||||
panel_pro = _load_module("panel_pro")
|
||||
panel_pro.register()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to register panel_pro: {e}")
|
||||
|
||||
try:
|
||||
panel_sidebar = _load_module("panel_sidebar")
|
||||
panel_sidebar.register()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to register panel_sidebar: {e}")
|
||||
|
||||
try:
|
||||
panel_shelf = _load_module("panel_shelf")
|
||||
panel_shelf.register()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to register panel_shelf (operators only): {e}")
|
||||
|
||||
try:
|
||||
panel_animation = _load_module("panel_animation")
|
||||
panel_animation.register()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to register panel_animation (operators only): {e}")
|
||||
|
||||
try:
|
||||
viewport_shelf = _load_module("viewport_shelf")
|
||||
viewport_shelf.register()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to register viewport_shelf: {e}")
|
||||
|
||||
|
||||
def unregister():
|
||||
"""Unregister all panels"""
|
||||
# Unregister in reverse order
|
||||
for name in ["viewport_shelf", "panel_animation", "panel_shelf", "panel_sidebar", "panel_pro", "shelf_header"]:
|
||||
try:
|
||||
if name in _module_cache:
|
||||
_module_cache[name].unregister()
|
||||
except Exception as e:
|
||||
print(f"MCP: Failed to unregister {name}: {e}")
|
||||
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
Maya Config Pro - Animation Panel
|
||||
Animation shelf with rigging and animation tools.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from ..utils import compat
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Animation Shelf Panel
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_PT_AnimationShelf(bpy.types.Panel):
|
||||
"""Maya Animation Shelf"""
|
||||
bl_idname = "MCP_PT_AnimationShelf"
|
||||
bl_label = "Animation Shelf"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
draw_type = "animation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 1
|
||||
row.scale_x = 2
|
||||
|
||||
row.operator('wm.link', text="", icon="LINKED")
|
||||
row.operator('wm.append', text="", icon="APPEND_BLEND")
|
||||
row.operator('mcp.pose_mode_ani', text="", icon="POSE_HLT")
|
||||
row.operator('object.armature_add', text="", icon="OUTLINER_OB_ARMATURE")
|
||||
row.operator('mcp.drop_to_floor_ani', text="", icon='TRIA_DOWN')
|
||||
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('mcp.graph_editor_ani', text="", icon="GRAPH")
|
||||
row.operator('mcp.dope_sheet_ani', text="", icon="ACTION")
|
||||
row.operator('mcp.drivers_ani', text="", icon="DRIVER")
|
||||
row.operator('anim.keyframe_insert_menu', text="", icon="DECORATE_ANIMATE")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Properties Editor
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PropertiesEditorAni(bpy.types.Operator):
|
||||
"""Properties Editor"""
|
||||
bl_label = "Properties Editor"
|
||||
bl_idname = 'mcp.properties_editor_ani'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "PROPERTIES"
|
||||
|
||||
bpy.ops.pose.constraint_add(type='ACTION')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Link Rig
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_LinkRigAni(bpy.types.Operator):
|
||||
"""Link Rig"""
|
||||
bl_label = "Import Rig as Linked"
|
||||
bl_idname = 'mcp.link_rig_ani'
|
||||
|
||||
def execute(self, context):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Pose Mode
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PoseModeAni(bpy.types.Operator):
|
||||
"""Pose Mode"""
|
||||
bl_label = "Pose Mode"
|
||||
bl_idname = 'mcp.pose_mode_ani'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = context.active_object
|
||||
return active_object is not None and active_object.type == 'ARMATURE' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Graph Editor
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_GraphEditorAni(bpy.types.Operator):
|
||||
"""Graph Editor"""
|
||||
bl_label = "Graph Editor"
|
||||
bl_idname = 'mcp.graph_editor_ani'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "GRAPH_EDITOR"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Dope Sheet
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_DopeSheetAni(bpy.types.Operator):
|
||||
"""Dope Sheet"""
|
||||
bl_label = "Dope Sheet"
|
||||
bl_idname = 'mcp.dope_sheet_ani'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "DOPESHEET_EDITOR"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Drivers Editor
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_DriversAni(bpy.types.Operator):
|
||||
"""Drivers Editor"""
|
||||
bl_label = "Drivers Editor"
|
||||
bl_idname = 'mcp.drivers_ani'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.drivers_editor_show()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Drop To Floor
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_DropToFloorAni(bpy.types.Operator):
|
||||
"""Drop/Raise Object to Floor"""
|
||||
bl_label = "Drop to Floor"
|
||||
bl_idname = "mcp.drop_to_floor_ani"
|
||||
|
||||
def execute(self, context):
|
||||
context = bpy.context
|
||||
for obj in context.selected_objects:
|
||||
mx = obj.matrix_world
|
||||
minz = min((mx @ v.co).z for v in obj.data.vertices)
|
||||
mx.translation.z -= minz
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Panel
|
||||
MCP_PT_AnimationShelf,
|
||||
# Animation Operators
|
||||
MCP_OT_PropertiesEditorAni,
|
||||
MCP_OT_LinkRigAni,
|
||||
MCP_OT_PoseModeAni,
|
||||
MCP_OT_GraphEditorAni,
|
||||
MCP_OT_DopeSheetAni,
|
||||
MCP_OT_DriversAni,
|
||||
MCP_OT_DropToFloorAni,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,832 @@
|
||||
"""
|
||||
Maya Config Pro - Main Panel (ProAni 1.7)
|
||||
Left side panel with theme switching, subdivision controls, and gizmo settings.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import os
|
||||
from ..utils import compat
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Main Panel
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_PT_ProPanel(bpy.types.Panel):
|
||||
"""Pro Ani 1.7 coincides with Blender 5.0. A Custom Panel in the Properties Toolbar"""
|
||||
bl_label = "ProAni 1.7"
|
||||
bl_idname = "MCP_PT_ProPanel"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'TOOLS'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = .75
|
||||
row.scale_x = 1
|
||||
row.operator("mcp.subsurf_remove", text="·")
|
||||
row.operator("mcp.subsurf_2", text="··")
|
||||
row.operator("mcp.subsurf_3", text="···")
|
||||
|
||||
row = layout.row()
|
||||
row.operator('mcp.gizmo_default', text='', icon='GIZMO')
|
||||
row.prop(context.preferences.view, 'gizmo_size', slider=True)
|
||||
|
||||
row = layout.row()
|
||||
row.prop(bpy.context.space_data.overlay, 'show_stats', text='', icon='SORTSIZE', toggle=True)
|
||||
row.prop(bpy.context.space_data.overlay, 'show_cursor', text='', icon='PIVOT_CURSOR', toggle=True)
|
||||
row.operator('view3d.snap_cursor_to_center', text='', icon='ORIENTATION_CURSOR')
|
||||
row = layout.row()
|
||||
row.operator('mesh.select_mirror', text='Symmetry', icon='MOD_MIRROR')
|
||||
|
||||
row = layout.row()
|
||||
row.operator("render.render", text='', icon='RENDER_STILL')
|
||||
row.operator("mcp.popup_hypershade", text='', icon='SHADING_TEXTURE')
|
||||
row.operator("mcp.popup_grapheditor", text='', icon='GRAPH')
|
||||
|
||||
row = layout.row()
|
||||
row.operator("render.view_show", text='', icon='RENDER_RESULT')
|
||||
row.operator("mcp.popup_assetbrowser", text='', icon='ASSET_MANAGER')
|
||||
row.operator("mcp.asset_add", text='', icon='PLUS')
|
||||
|
||||
row = layout.row()
|
||||
row.operator("screen.userpref_show", text="", icon='PREFERENCES')
|
||||
row.operator("mcp.popup_keymaptext", text="", icon='KEYINGSET')
|
||||
row.operator("mcp.shortcut_eq", text="", icon='ARROW_LEFTRIGHT')
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("mcp.activate_keymap_preset", text="", icon="BLENDER")
|
||||
op.preset = "BLENDER"
|
||||
op = row.operator("mcp.activate_keymap_preset", text="", icon="EVENT_M")
|
||||
op.preset = "FA_HOTKEYS"
|
||||
op = row.operator("mcp.activate_keymap_preset", text="", icon="EVENT_I")
|
||||
op.preset = "INDUSTRY"
|
||||
|
||||
row = layout.row()
|
||||
row.operator("wm.url_open", text="", icon="HEART").url = "http://gumroad.com/formaffinity"
|
||||
row.operator("wm.url_open", text="", icon="URL").url = "https://www.youtube.com/channel/UCcCo0cPbrsWeoImgs6L7OOA"
|
||||
row.operator("mcp.config_info", text="", icon="INFO")
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = .5
|
||||
row.scale_x = .6
|
||||
row.operator("mcp.theme_maya_gradient", text="", emboss=False, icon='CLIPUV_HLT')
|
||||
row.operator("mcp.theme_maya_blue", text="", emboss=False, icon='CLIPUV_HLT')
|
||||
row.operator("mcp.theme_modo", text="", emboss=False, icon='CLIPUV_HLT')
|
||||
row.operator("mcp.theme_blender_dark", text="", emboss=False, icon='CLIPUV_HLT')
|
||||
row.operator("mcp.theme_white_bg", text="", emboss=False, icon='CLIPUV_HLT')
|
||||
row.operator("mcp.theme_sketchup", text="", emboss=False, icon='CLIPUV_HLT')
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Theme Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ThemeMayaBlue(bpy.types.Operator):
|
||||
"""Maya Blue Theme"""
|
||||
bl_idname = "mcp.theme_maya_blue"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', 'maya_blue_theme.xml')
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, "Maya Blue Theme")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_ThemeMayaGradient(bpy.types.Operator):
|
||||
"""Maya Gradient Theme"""
|
||||
bl_idname = "mcp.theme_maya_gradient"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', 'maya_theme_gradient.xml')
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, "Maya Gradient Theme")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_ThemeModo(bpy.types.Operator):
|
||||
"""Modo Theme"""
|
||||
bl_idname = "mcp.theme_modo"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', 'modo_theme.xml')
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, "Modo Theme")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_ThemeBlenderDark(bpy.types.Operator):
|
||||
"""Blender Dark Theme"""
|
||||
bl_idname = "mcp.theme_blender_dark"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', 'blender_dark.xml')
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, "Blender Dark Theme")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_ThemeWhiteBG(bpy.types.Operator):
|
||||
"""White Background Theme"""
|
||||
bl_idname = "mcp.theme_white_bg"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', 'white_bg_theme.xml')
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, "White BG Theme")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_ThemeSketchup(bpy.types.Operator):
|
||||
"""Sketchup Theme"""
|
||||
bl_idname = "mcp.theme_sketchup"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', 'sketchup.xml')
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, "Sketchup Theme")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_ThemeDoubleHotkey(bpy.types.Operator):
|
||||
"""Allow alt b to be pressed multiple times to cycle through interface themes"""
|
||||
bl_idname = "mcp.theme_double"
|
||||
bl_label = "Double Hotkey"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
count = 0
|
||||
|
||||
def setTheme(self, interface_theme, theme_name):
|
||||
root_path = bpy.utils.resource_path('USER')
|
||||
theme_filepath = os.path.join(root_path, 'scripts', 'presets', 'interface_theme', interface_theme)
|
||||
bpy.ops.script.execute_preset(filepath=theme_filepath, menu_idname='USERPREF_MT_interface_theme_presets')
|
||||
self.report({'INFO'}, theme_name)
|
||||
|
||||
def execute(self, context):
|
||||
MCP_OT_ThemeDoubleHotkey.count += 1
|
||||
num_options = 6
|
||||
current_value = MCP_OT_ThemeDoubleHotkey.count % num_options
|
||||
|
||||
if current_value == 0:
|
||||
self.setTheme(interface_theme='maya_theme_gradient.xml', theme_name='Maya Gradient Theme')
|
||||
elif current_value == 1:
|
||||
self.setTheme(interface_theme='maya_blue_theme.xml', theme_name='Maya Blue Theme')
|
||||
elif current_value == 2:
|
||||
self.setTheme(interface_theme='modo_theme.xml', theme_name='Modo Theme')
|
||||
elif current_value == 3:
|
||||
self.setTheme(interface_theme='blender_dark.xml', theme_name='Blender Dark Theme')
|
||||
elif current_value == 4:
|
||||
self.setTheme(interface_theme='white_bg_theme.xml', theme_name='White Background Theme')
|
||||
elif current_value == 5:
|
||||
self.setTheme(interface_theme='sketchup.xml', theme_name='Sketchup Theme')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Gizmo Size Default 75
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_GizmoDefault(bpy.types.Operator):
|
||||
"""Reset Gizmo Size to Default"""
|
||||
bl_idname = "mcp.gizmo_default"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.preferences.view.gizmo_size = 75
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Pivot Lock
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PivotLock(bpy.types.Operator):
|
||||
"""Lock/Unlock Pivot"""
|
||||
bl_idname = "mcp.pivot_lock"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.tool_settings.use_snap = not bpy.context.scene.tool_settings.use_snap
|
||||
bpy.context.scene.tool_settings.snap_elements = {'VERTEX'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Subdivision Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_SubsurfRemove(bpy.types.Operator):
|
||||
"""Turn off Subdivision Surface Preview"""
|
||||
bl_idname = "mcp.subsurf_remove"
|
||||
bl_label = "·"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
active_object = bpy.context.active_object
|
||||
if not active_object:
|
||||
return {'FINISHED'}
|
||||
|
||||
active_name = active_object.name
|
||||
active_mode = active_object.mode
|
||||
active_type = active_object.type
|
||||
|
||||
object_types = ['MESH', 'CURVE']
|
||||
mods = bpy.context.object.modifiers
|
||||
mod_type = 'SUBSURF'
|
||||
mod_name = None
|
||||
|
||||
if active_type in object_types:
|
||||
for mod in mods:
|
||||
if mod.type == mod_type:
|
||||
mod_name = mod.name
|
||||
break
|
||||
|
||||
if mod_name is not None:
|
||||
bpy.ops.object.modifier_remove(modifier=mod_name)
|
||||
self.report({'INFO'}, "Smooth Mesh Preview Removed!")
|
||||
|
||||
if active_mode == 'EDIT':
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.faces_shade_flat()
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
elif active_mode == 'OBJECT':
|
||||
bpy.data.objects[active_name].select_set(True)
|
||||
bpy.ops.object.shade_flat()
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_Subsurf2(bpy.types.Operator):
|
||||
"""Set Subdivision Surface Preview to 2 iterations"""
|
||||
bl_idname = "mcp.subsurf_2"
|
||||
bl_label = "··"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
active_object = bpy.context.active_object
|
||||
if not active_object:
|
||||
return {'FINISHED'}
|
||||
|
||||
active_name = active_object.name
|
||||
active_mode = active_object.mode
|
||||
active_type = active_object.type
|
||||
|
||||
object_types = ['MESH', 'CURVE']
|
||||
mods = bpy.context.object.modifiers
|
||||
mod_type = 'SUBSURF'
|
||||
mod_name = None
|
||||
|
||||
if active_type in object_types:
|
||||
for mod in mods:
|
||||
if mod.type == mod_type:
|
||||
mod_name = mod.name
|
||||
break
|
||||
|
||||
if mod_name is not None:
|
||||
bpy.ops.object.modifier_remove(modifier=mod_name)
|
||||
self.report({'INFO'}, "Smooth Mesh Preview Removed!")
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.data.objects[active_name].select_set(True)
|
||||
bpy.ops.object.shade_flat()
|
||||
else:
|
||||
bpy.ops.object.modifier_add(type=mod_type)
|
||||
self.report({'INFO'}, "Smooth Mesh Preview Applied!")
|
||||
|
||||
if active_mode == 'EDIT':
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.faces_shade_smooth()
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
elif active_mode == 'OBJECT':
|
||||
bpy.data.objects[active_name].select_set(True)
|
||||
bpy.ops.object.shade_smooth()
|
||||
|
||||
bpy.data.objects[active_name].modifiers["Subdivision"].levels = 2
|
||||
bpy.data.objects[active_name].modifiers["Subdivision"].render_levels = 2
|
||||
|
||||
if bpy.context.active_object:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_Subsurf3(bpy.types.Operator):
|
||||
"""Set Subdivision Surface Preview to 3 iterations"""
|
||||
bl_idname = "mcp.subsurf_3"
|
||||
bl_label = "···"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
active_object = bpy.context.active_object
|
||||
if not active_object:
|
||||
return {'FINISHED'}
|
||||
|
||||
active_name = active_object.name
|
||||
active_mode = active_object.mode
|
||||
active_type = active_object.type
|
||||
|
||||
object_types = ['MESH', 'CURVE']
|
||||
mods = bpy.context.object.modifiers
|
||||
mod_type = 'SUBSURF'
|
||||
mod_name = None
|
||||
|
||||
if active_type in object_types:
|
||||
for mod in mods:
|
||||
if mod.type == mod_type:
|
||||
mod_name = mod.name
|
||||
break
|
||||
|
||||
if mod_name is not None:
|
||||
bpy.ops.object.modifier_remove(modifier=mod_name)
|
||||
self.report({'INFO'}, "Smooth Mesh Preview Removed!")
|
||||
|
||||
if active_mode == 'EDIT':
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.faces_shade_flat()
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
elif active_mode == 'OBJECT':
|
||||
bpy.data.objects[active_name].select_set(True)
|
||||
bpy.ops.object.shade_flat()
|
||||
else:
|
||||
bpy.ops.object.modifier_add(type=mod_type)
|
||||
self.report({'INFO'}, "Smooth Mesh Preview Applied!")
|
||||
|
||||
if active_mode == 'EDIT':
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.faces_shade_smooth()
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
elif active_mode == 'OBJECT':
|
||||
bpy.data.objects[active_name].select_set(True)
|
||||
bpy.ops.object.shade_smooth()
|
||||
|
||||
bpy.data.objects[active_name].modifiers["Subdivision"].levels = 3
|
||||
bpy.data.objects[active_name].modifiers["Subdivision"].render_levels = 3
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Graph Editor Pop Up
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
my_timeline = None
|
||||
|
||||
class MCP_OT_PopupGraphEditor(bpy.types.Operator):
|
||||
"""Graph Editor"""
|
||||
bl_label = "Graph Editor"
|
||||
bl_idname = 'mcp.popup_grapheditor'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
|
||||
# Change area type
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "VIEW_3D"
|
||||
bpy.context.area.ui_type = 'FCURVES'
|
||||
|
||||
global my_timeline
|
||||
if my_timeline:
|
||||
bpy.ops.screen.area_close({"area": my_timeline})
|
||||
my_timeline = None
|
||||
else:
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'FCURVES':
|
||||
with bpy.context.temp_override(area=area):
|
||||
bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.2)
|
||||
bpy.context.screen.areas[-1].ui_type = 'DOPESHEET'
|
||||
my_timeline = bpy.context.screen.areas[-1]
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Hypershade
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
my_timeline_hs = None
|
||||
|
||||
class MCP_OT_PopupHypershade(bpy.types.Operator):
|
||||
"""Hypershade"""
|
||||
bl_label = "Hypershade"
|
||||
bl_idname = 'mcp.popup_hypershade'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
|
||||
# Change area type
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "VIEW_3D"
|
||||
bpy.context.area.ui_type = 'ShaderNodeTree'
|
||||
|
||||
global my_timeline_hs
|
||||
if my_timeline_hs:
|
||||
bpy.ops.screen.area_close({"area": my_timeline_hs})
|
||||
my_timeline_hs = None
|
||||
else:
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'ShaderNodeTree':
|
||||
with bpy.context.temp_override(area=area):
|
||||
bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.20)
|
||||
bpy.context.screen.areas[-1].ui_type = 'DOPESHEET'
|
||||
my_timeline_hs = bpy.context.screen.areas[-1]
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Asset Browser and Asset Add
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PopupAssetBrowser(bpy.types.Operator):
|
||||
"""Display Asset Browser"""
|
||||
bl_label = "Asset Browser"
|
||||
bl_idname = 'mcp.popup_assetbrowser'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
|
||||
# Change area type
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "VIEW_3D"
|
||||
bpy.context.area.ui_type = 'ASSETS'
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_AssetAdd(bpy.types.Operator):
|
||||
"""Add Asset"""
|
||||
bl_idname = "mcp.asset_add"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.asset.mark()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Keymap Text
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PopupKeymapText(bpy.types.Operator):
|
||||
"""Display Maya Config Pro Hotkeys"""
|
||||
bl_label = "Hotkey List"
|
||||
bl_idname = 'mcp.popup_keymaptext'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
|
||||
# Change area type
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "TEXT_EDITOR"
|
||||
|
||||
# Try to load the text file
|
||||
try:
|
||||
text = bpy.data.texts['fa_hotkeys_text.txt']
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'TEXT_EDITOR':
|
||||
area.spaces[0].text = text
|
||||
break
|
||||
except KeyError:
|
||||
self.report({'WARNING'}, "fa_hotkeys_text.txt not found")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Shortcut Equivalence Popup
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ShortcutEq(bpy.types.Operator):
|
||||
"""Shortcut Equivalence - Version 1.0"""
|
||||
bl_label = "To check a Maya Equivalent (See Info Log)"
|
||||
bl_idname = "mcp.shortcut_eq"
|
||||
|
||||
text: bpy.props.StringProperty(name="Enter Blender Hotkey", default="")
|
||||
|
||||
def execute(self, context):
|
||||
text = self.text
|
||||
|
||||
bpy.ops.object.empty_add(type='PLAIN_AXES')
|
||||
obj = bpy.context.object
|
||||
obj.name = text
|
||||
|
||||
if text == 'w':
|
||||
self.report({'INFO'}, "Maya Config = Q Hotkey! (Box Select)")
|
||||
elif text == 'g':
|
||||
self.report({'INFO'}, "Maya = W Hotkey! (Move)")
|
||||
elif text == 'r':
|
||||
self.report({'INFO'}, "Maya = E Hotkey! (Rotate)")
|
||||
elif text == 's':
|
||||
self.report({'INFO'}, "Maya = R Hotkey! (Scale)")
|
||||
elif text == 'mmb':
|
||||
self.report({'INFO'}, "Maya = Alt + LMB Hotkey/Mouse! (Rotate View)")
|
||||
elif text == 'shift mmb':
|
||||
self.report({'INFO'}, "Maya = Alt + MMB Hotkey/Mouse! (Pan View)")
|
||||
elif text == 'ctrl mmb':
|
||||
self.report({'INFO'}, "Maya = Alt + RMB Hotkey/Mouse! (Zoom View)")
|
||||
elif text == 'rmb':
|
||||
self.report({'INFO'}, "Maya = Marking Menu! (In place of Blenders Object Context Menu)")
|
||||
elif text == 'space bar':
|
||||
self.report({'INFO'}, "Maya = Quad Menu! (In Place of Blenders Play Animation)")
|
||||
elif text == 'edit mode shift rmb':
|
||||
self.report({'INFO'}, "Maya = Special Tools Menu! (In Place of Blenders Position 3D Cursor)")
|
||||
elif text == 'e':
|
||||
self.report({'INFO'}, "Maya = Ctrl E! (Extrude)")
|
||||
elif text == 'f':
|
||||
self.report({'INFO'}, "Maya = G! (Create Face)")
|
||||
elif text == '/':
|
||||
self.report({'INFO'}, "Maya = Alt E! (Isolate Select (Local View))")
|
||||
elif text == 'numbad .':
|
||||
self.report({'INFO'}, "Maya = F! (Frame Selected)")
|
||||
elif text == 'ctrl numpad +':
|
||||
self.report({'INFO'}, "Maya = Shift . (>)! (Select More)")
|
||||
elif text == 'ctrl numpad -':
|
||||
self.report({'INFO'}, "Maya = Shift , (<)! (Select Less)")
|
||||
elif text == 'shift rmb':
|
||||
self.report({'INFO'}, "Maya = Shift Ctrl RMB! (Set 3D Cursor)")
|
||||
elif text == 'ctrl d':
|
||||
self.report({'INFO'}, "Maya = Ctrl D! (Show/Hide 3D Cursor)")
|
||||
elif text == 'ctrl .':
|
||||
self.report({'INFO'}, "Maya = D! (Show/Hide Origin)")
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class MCP_OT_ConfigInfo(bpy.types.Operator):
|
||||
"""Thank you for using it!"""
|
||||
bl_label = "Maya Config Pro - Version 1.7, by FormAffinity"
|
||||
bl_idname = "mcp.config_info"
|
||||
|
||||
text: bpy.props.StringProperty(name="Support: contactformaffinity@gmail.com", default="")
|
||||
|
||||
def execute(self, context):
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Cam Tools Panel
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_PT_CamTools(bpy.types.Panel):
|
||||
"""Camera Tools Panel"""
|
||||
bl_label = "CTs 2.0"
|
||||
bl_idname = "MCP_PT_CamTools"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'TOOLS'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
row = layout.row()
|
||||
row.operator('view3d.camera_to_view', text="", icon="RESTRICT_VIEW_ON")
|
||||
row.operator("view3d.view_camera", text="", icon='CAMERA_DATA')
|
||||
row.prop(bpy.context.space_data, 'lock_camera', text="", icon='CON_CAMERASOLVER', toggle=True)
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 1
|
||||
row.scale_x = 2
|
||||
row.operator('mcp.cam_lock_all', text="CAM", emboss=True, icon="LOCKED")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("screen.region_quadview", text="", icon='VIEW_PERSPECTIVE')
|
||||
row.operator("view3d.view_persportho", text="", icon="VIEW_ORTHO")
|
||||
row.operator("screen.screen_full_area", text="", icon="FULLSCREEN_ENTER")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Cam Tools Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_CameraGuides(bpy.types.Operator):
|
||||
"""Show Safe Guides"""
|
||||
bl_idname = "mcp.camera_guides"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
def execute(self, context):
|
||||
bpy.data.objects["Camera"].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Camera']
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.context.object.data.show_safe_areas = not bpy.context.object.data.show_safe_areas
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_CamLockLocation(bpy.types.Operator):
|
||||
"""Lock Camera Location"""
|
||||
bl_idname = "mcp.cam_lock_location"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
def execute(self, context):
|
||||
object_types = ['CAMERA']
|
||||
active_object = bpy.context.active_object
|
||||
active_type = active_object.type
|
||||
if active_type == 'MESH':
|
||||
self.report({'INFO'}, 'Select Camera First!')
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Camera']
|
||||
bpy.data.objects["Camera"].select_set(True)
|
||||
bpy.context.window.scene.objects['Camera']
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.context.object.lock_location[0] = not bpy.context.object.lock_location[0]
|
||||
bpy.context.object.lock_location[1] = not bpy.context.object.lock_location[1]
|
||||
bpy.context.object.lock_location[2] = not bpy.context.object.lock_location[2]
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_CamLockRotation(bpy.types.Operator):
|
||||
"""Lock Camera Rotation"""
|
||||
bl_idname = "mcp.cam_lock_rotation"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
active_object = bpy.context.active_object
|
||||
active_type = active_object.type
|
||||
if active_type == 'MESH':
|
||||
self.report({'INFO'}, 'Select Camera First!')
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Camera']
|
||||
bpy.data.objects["Camera"].select_set(True)
|
||||
bpy.context.window.scene.objects['Camera']
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.context.object.lock_rotation[0] = not bpy.context.object.lock_rotation[0]
|
||||
bpy.context.object.lock_rotation[1] = not bpy.context.object.lock_rotation[1]
|
||||
bpy.context.object.lock_rotation[2] = not bpy.context.object.lock_rotation[2]
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_CamLockScale(bpy.types.Operator):
|
||||
"""Lock Camera Scale"""
|
||||
bl_idname = "mcp.cam_lock_scale"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
active_object = bpy.context.active_object
|
||||
active_type = active_object.type
|
||||
if active_type == 'MESH':
|
||||
self.report({'INFO'}, 'Select Camera First!')
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Camera']
|
||||
bpy.data.objects["Camera"].select_set(True)
|
||||
bpy.context.window.scene.objects['Camera']
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.context.object.lock_scale[0] = not bpy.context.object.lock_scale[0]
|
||||
bpy.context.object.lock_scale[1] = not bpy.context.object.lock_scale[1]
|
||||
bpy.context.object.lock_scale[2] = not bpy.context.object.lock_scale[2]
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_CamLockAll(bpy.types.Operator):
|
||||
"""Lock Camera Transforms (LRS)"""
|
||||
bl_idname = "mcp.cam_lock_all"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
active_object = bpy.context.active_object
|
||||
active_type = active_object.type
|
||||
if active_type == 'MESH':
|
||||
self.report({'INFO'}, 'Select Camera First!')
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Camera']
|
||||
bpy.data.objects["Camera"].select_set(True)
|
||||
bpy.context.window.scene.objects['Camera']
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.context.object.lock_location[0] = not bpy.context.object.lock_location[0]
|
||||
bpy.context.object.lock_location[1] = not bpy.context.object.lock_location[1]
|
||||
bpy.context.object.lock_location[2] = not bpy.context.object.lock_location[2]
|
||||
bpy.context.object.lock_rotation[0] = not bpy.context.object.lock_rotation[0]
|
||||
bpy.context.object.lock_rotation[1] = not bpy.context.object.lock_rotation[1]
|
||||
bpy.context.object.lock_rotation[2] = not bpy.context.object.lock_rotation[2]
|
||||
bpy.context.object.lock_scale[0] = not bpy.context.object.lock_scale[0]
|
||||
bpy.context.object.lock_scale[1] = not bpy.context.object.lock_scale[1]
|
||||
bpy.context.object.lock_scale[2] = not bpy.context.object.lock_scale[2]
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_CameraPopup(bpy.types.Operator):
|
||||
"""Camera - Open Camera View"""
|
||||
bl_idname = "mcp.camera_popup"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
# Modify scene settings
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1500
|
||||
render.resolution_y = 800
|
||||
render.resolution_percentage = 100
|
||||
|
||||
# Call image editor window
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
# Change area type
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "VIEW_3D"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Panels
|
||||
MCP_PT_ProPanel,
|
||||
MCP_PT_CamTools,
|
||||
# Theme Operators
|
||||
MCP_OT_ThemeMayaBlue,
|
||||
MCP_OT_ThemeMayaGradient,
|
||||
MCP_OT_ThemeModo,
|
||||
MCP_OT_ThemeBlenderDark,
|
||||
MCP_OT_ThemeWhiteBG,
|
||||
MCP_OT_ThemeSketchup,
|
||||
MCP_OT_ThemeDoubleHotkey,
|
||||
# Gizmo & Pivot
|
||||
MCP_OT_GizmoDefault,
|
||||
MCP_OT_PivotLock,
|
||||
# Subdivision
|
||||
MCP_OT_SubsurfRemove,
|
||||
MCP_OT_Subsurf2,
|
||||
MCP_OT_Subsurf3,
|
||||
# Popups
|
||||
MCP_OT_PopupGraphEditor,
|
||||
MCP_OT_PopupHypershade,
|
||||
MCP_OT_PopupAssetBrowser,
|
||||
MCP_OT_AssetAdd,
|
||||
MCP_OT_PopupKeymapText,
|
||||
# Info
|
||||
MCP_OT_ShortcutEq,
|
||||
MCP_OT_ConfigInfo,
|
||||
# Camera Tools
|
||||
MCP_OT_CameraGuides,
|
||||
MCP_OT_CamLockLocation,
|
||||
MCP_OT_CamLockRotation,
|
||||
MCP_OT_CamLockScale,
|
||||
MCP_OT_CamLockAll,
|
||||
MCP_OT_CameraPopup,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
print("MCP: Registering panel_pro module...")
|
||||
for cls in _classes:
|
||||
result = compat.safe_register_class(cls)
|
||||
if result:
|
||||
print(f"MCP: Registered {cls.__name__}")
|
||||
else:
|
||||
print(f"MCP: FAILED to register {cls.__name__}")
|
||||
print("MCP: panel_pro registration complete")
|
||||
|
||||
|
||||
def unregister():
|
||||
print("MCP: Unregistering panel_pro module...")
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,879 @@
|
||||
"""
|
||||
Maya Config Pro - Shelf Panel
|
||||
Dynamic shelf with mode switching (Modeling, Edit Poly, Nurbs, Rendering, FX, Animation).
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from ..utils import compat
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bpy.props import IntProperty, FloatProperty, FloatVectorProperty, StringProperty
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Shelf Panel
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_PT_Shelf(bpy.types.Panel):
|
||||
"""Load Desired Shelf"""
|
||||
bl_idname = "MCP_PT_Shelf"
|
||||
bl_label = "Shelf"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
draw_type = "modeling"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 1
|
||||
row.scale_x = 2
|
||||
row.popover(panel="MCP_PT_ShelfDropdown", text="", icon="COLLAPSEMENU")
|
||||
|
||||
# things we always have
|
||||
row.operator('wm.open_mainfile', text="", icon="FILEBROWSER")
|
||||
row.operator('wm.save_as_mainfile', text="", icon="FILE_TICK")
|
||||
|
||||
# things for modeling
|
||||
if MCP_PT_Shelf.draw_type == 'modeling':
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('mcp.split_area_asset', text="", icon="ASSET_MANAGER")
|
||||
row.operator('mesh.primitive_cube_add', text="", icon="MESH_CUBE")
|
||||
row.operator('mcp.cylinder_shelf', text="", icon="MESH_CYLINDER")
|
||||
row.operator('mcp.cone_shelf', text="", icon="MESH_CONE")
|
||||
row.operator('mcp.uvsphere_shelf', text="", icon="MESH_UVSPHERE")
|
||||
row.operator('mesh.primitive_torus_add', text="", icon="MESH_TORUS")
|
||||
row.operator('mesh.primitive_plane_add', text="", icon="MESH_PLANE")
|
||||
row.operator('mcp.icosphere_shelf', text="", icon="MESH_ICOSPHERE")
|
||||
row.operator('object.text_add', text="", icon="OUTLINER_OB_FONT")
|
||||
row.operator('object.empty_add', text="", icon="EMPTY_AXIS")
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('object.origin_set', text='', icon='OBJECT_ORIGIN')
|
||||
row.operator("object.transforms_to_deltas", text="", icon="FREEZE").mode="ALL"
|
||||
row.operator('object.join', text="", icon="SELECT_EXTEND")
|
||||
row.operator('mesh.separate', text="", icon="SELECT_SUBTRACT")
|
||||
row.operator('mcp.add_background_image', text="", icon="IMAGE_BACKGROUND")
|
||||
row.operator('mcp.add_reference_image', text="", icon="IMAGE_REFERENCE")
|
||||
|
||||
# things for Edit Poly
|
||||
elif MCP_PT_Shelf.draw_type == 'editpoly':
|
||||
row.operator('object.origin_set', text='', icon='OBJECT_ORIGIN')
|
||||
row.operator("object.transforms_to_deltas", text="", icon="FREEZE").mode="ALL"
|
||||
row.operator("mcp.delta_transfer", text="", icon="MOD_DATA_TRANSFER")
|
||||
row.operator("mesh.select_all", text="", icon="UV_SYNC_SELECT").action="INVERT"
|
||||
row.operator("object.make_single_user", text="", icon="UNLINKED")
|
||||
row.operator('object.join', text="", icon="SELECT_EXTEND")
|
||||
row.operator('mesh.separate', text="", icon="SELECT_SUBTRACT")
|
||||
row.operator("mesh.merge", text="", icon="AUTOMERGE_ON")
|
||||
row.operator("mesh.bridge_edge_loops", text="", icon="UV_FACESEL")
|
||||
row.operator("mesh.edge_face_add", text="", icon="NORMALS_VERTEX_FACE")
|
||||
row.operator("mesh.flip_normals", text="", icon="NORMALS_FACE")
|
||||
row.operator('mesh.select_mirror', text='', icon='MOD_MIRROR')
|
||||
row.operator('mcp.mirror_x2', text="", icon="EVENT_X")
|
||||
row.operator('mcp.mirror_z2', text="", icon="EVENT_Z")
|
||||
row.operator('mcp.mirror_apply2', text="", icon="CHECKMARK")
|
||||
row.operator('mcp.apply_transforms2', text="", icon="TRANSFORM_ORIGINS")
|
||||
|
||||
# things for nurbs
|
||||
elif MCP_PT_Shelf.draw_type == 'nurbs':
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('curve.primitive_bezier_curve_add', text="", icon="CURVE_BEZCURVE")
|
||||
row.operator('curve.primitive_bezier_circle_add', text="", icon="CURVE_BEZCIRCLE")
|
||||
row.operator('curve.primitive_nurbs_curve_add', text="", icon="CURVE_NCURVE")
|
||||
row.operator('curve.primitive_nurbs_circle_add', text="", icon="CURVE_NCIRCLE")
|
||||
row.operator('curve.primitive_nurbs_path_add', text="", icon="CURVE_PATH")
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('surface.primitive_nurbs_surface_curve_add', text="", icon="SURFACE_NCURVE")
|
||||
row.operator('surface.primitive_nurbs_surface_circle_add', text="", icon="SURFACE_NCIRCLE")
|
||||
row.operator('surface.primitive_nurbs_surface_surface_add', text="", icon="SURFACE_NSURFACE")
|
||||
row.operator('surface.primitive_nurbs_surface_cylinder_add', text="", icon="SURFACE_NCYLINDER")
|
||||
row.operator('surface.primitive_nurbs_surface_sphere_add', text="", icon="SURFACE_NSPHERE")
|
||||
row.operator('surface.primitive_nurbs_surface_torus_add', text="", icon="SURFACE_NTORUS")
|
||||
|
||||
# things for shading
|
||||
elif MCP_PT_Shelf.draw_type == 'rendering':
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator("mcp.hypershade_shelf", text="", icon='MATERIAL_DATA')
|
||||
row.operator("mcp.rendering_shelf", text="", icon='SHADING_RENDERED')
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('object.light_add', text="", icon="LIGHT_POINT").type='POINT'
|
||||
row.operator('object.light_add', text="", icon="LIGHT_SUN").type='SUN'
|
||||
row.operator('object.light_add', text="", icon="LIGHT_SPOT").type='SPOT'
|
||||
row.operator('object.light_add', text="", icon="LIGHT_AREA").type='AREA'
|
||||
row.operator('object.lightprobe_add', text="", icon="OUTLINER_OB_LIGHTPROBE").type='CUBEMAP'
|
||||
row.operator('object.lightprobe_add', text="", icon="LIGHTPROBE_PLANAR").type='PLANAR'
|
||||
row.operator('object.lightprobe_add', text="", icon="LIGHTPROBE_GRID").type='GRID'
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('object.camera_add', text="", icon="VIEW_CAMERA")
|
||||
row.operator("render.render", text='', icon='RENDER_STILL')
|
||||
row.operator("render.view_show", text='', icon='RENDER_RESULT')
|
||||
|
||||
# things for FX
|
||||
elif MCP_PT_Shelf.draw_type == 'fx':
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('object.effector_add', text="", icon="FORCE_FORCE").type='FORCE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_WIND").type='WIND'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_VORTEX").type='VORTEX'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_MAGNETIC").type='MAGNET'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_HARMONIC").type='HARMONIC'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_CHARGE").type='CHARGE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_LENNARDJONES").type='LENNARDJ'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_TEXTURE").type='TEXTURE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_CURVE").type='GUIDE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_BOID").type='BOID'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_TURBULENCE").type='TURBULENCE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_DRAG").type='DRAG'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_FLUIDFLOW").type='FLUID'
|
||||
|
||||
# things for Animation
|
||||
elif MCP_PT_Shelf.draw_type == 'animation':
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('wm.link', text="", icon="LINKED")
|
||||
row.operator('wm.append', text="", icon="APPEND_BLEND")
|
||||
row.operator('mcp.pose_mode_shelf', text="", icon="POSE_HLT")
|
||||
row.operator('object.armature_add', text="", icon="OUTLINER_OB_ARMATURE")
|
||||
row.operator('mcp.drop_to_floor', text="", icon='TRIA_DOWN')
|
||||
row.label(text="", icon='DOT')
|
||||
row.operator('mcp.popup_grapheditor_shelf', text="", icon="GRAPH")
|
||||
row.operator('mcp.popup_dopesheet_shelf', text="", icon="ACTION")
|
||||
row.operator('mcp.popup_drivers_shelf', text="", icon="DRIVER")
|
||||
row.operator('mcp.split_area_timeline', text="", icon="TIME")
|
||||
row.operator('anim.keyframe_insert_menu', text="", icon="DECORATE_ANIMATE")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Image Loading Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_AddBackgroundImage(bpy.types.Operator, ImportHelper):
|
||||
"""Open a file browser and add a background image"""
|
||||
bl_idname = "mcp.add_background_image"
|
||||
bl_label = "Select Background Image"
|
||||
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
description="Path to the image file",
|
||||
subtype='FILE_PATH'
|
||||
)
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp",
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.filepath:
|
||||
return {'CANCELLED'}
|
||||
|
||||
empty_obj = bpy.data.objects.new("Background_Image", None)
|
||||
context.collection.objects.link(empty_obj)
|
||||
empty_obj.empty_display_type = 'IMAGE'
|
||||
empty_obj.empty_image_depth = 'BACK'
|
||||
empty_obj.empty_display_size = 5.0
|
||||
|
||||
view3d = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
|
||||
if view3d:
|
||||
rv3d = view3d.spaces[0].region_3d
|
||||
view_rot = rv3d.view_matrix.inverted().to_quaternion()
|
||||
empty_obj.rotation_mode = 'QUATERNION'
|
||||
empty_obj.rotation_quaternion = view_rot
|
||||
empty_obj.location = (0.0, 0.0, 0.0)
|
||||
|
||||
try:
|
||||
img = bpy.data.images.load(self.filepath)
|
||||
empty_obj.data = img
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, f"Could not load image: {e}")
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.view_layer.objects.active = empty_obj
|
||||
empty_obj.select_set(True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_AddReferenceImage(bpy.types.Operator, ImportHelper):
|
||||
"""Open a file browser and add a reference image"""
|
||||
bl_idname = "mcp.add_reference_image"
|
||||
bl_label = "Select Reference Image"
|
||||
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
description="Path to the image file",
|
||||
subtype='FILE_PATH'
|
||||
)
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp",
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.filepath:
|
||||
return {'CANCELLED'}
|
||||
|
||||
empty_obj = bpy.data.objects.new("Reference_Image", None)
|
||||
context.collection.objects.link(empty_obj)
|
||||
empty_obj.empty_display_type = 'IMAGE'
|
||||
empty_obj.empty_image_depth = 'DEFAULT'
|
||||
empty_obj.empty_display_size = 5.0
|
||||
|
||||
view3d = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
|
||||
if view3d:
|
||||
rv3d = view3d.spaces[0].region_3d
|
||||
view_rot = rv3d.view_matrix.inverted().to_quaternion()
|
||||
empty_obj.rotation_mode = 'QUATERNION'
|
||||
empty_obj.rotation_quaternion = view_rot
|
||||
empty_obj.location = (0.0, 0.0, 0.0)
|
||||
|
||||
try:
|
||||
img = bpy.data.images.load(self.filepath)
|
||||
empty_obj.data = img
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, f"Could not load image: {e}")
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.view_layer.objects.active = empty_obj
|
||||
empty_obj.select_set(True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Function by L and K
|
||||
def update_bts(self, context):
|
||||
MCP_PT_Shelf.draw_type = self.shelf_type
|
||||
|
||||
|
||||
class MCP_PT_ShelfDropdown(bpy.types.Panel):
|
||||
"""Load Desired Shelf"""
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'HEADER'
|
||||
bl_label = "Load Desired Shelf"
|
||||
bl_idname = "MCP_PT_ShelfDropdown"
|
||||
bl_ui_units_x = 14
|
||||
|
||||
enum_shelf_names = [
|
||||
('modeling', 'Polygons', 'Polygons Shelf'),
|
||||
('editpoly', 'Edit Poly', 'Edit Poly Shelf'),
|
||||
('nurbs', 'Nurbs', 'Nurbs Shelf'),
|
||||
('rendering', 'Rendering', 'Rendering Shelf'),
|
||||
('fx', 'FX', 'FX Shelf'),
|
||||
('animation', 'Animation', 'Animation Shelf')
|
||||
]
|
||||
bpy.types.Scene.shelf_type = bpy.props.EnumProperty(items=enum_shelf_names, update=update_bts)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout = self.layout.column_flow(columns=2)
|
||||
|
||||
layout.label(text="Load Shelf:")
|
||||
layout.use_property_split = False
|
||||
layout.use_property_decorate = False
|
||||
|
||||
box = layout.box()
|
||||
box.scale_y = 2
|
||||
split = box.split(factor=0.2)
|
||||
col = split.column(align=True)
|
||||
|
||||
col.label(icon="RADIOBUT_OFF")
|
||||
col.label(icon="RADIOBUT_OFF")
|
||||
col.label(icon="RADIOBUT_OFF")
|
||||
col.label(icon="RADIOBUT_OFF")
|
||||
col.label(icon="RADIOBUT_OFF")
|
||||
col.label(icon="RADIOBUT_OFF")
|
||||
|
||||
col = split.column(align=True)
|
||||
col.prop(context.scene, 'shelf_type', expand=True)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Properties Editor
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PropertiesEditor(bpy.types.Operator):
|
||||
"""Properties Editor"""
|
||||
bl_label = "Properties Editor"
|
||||
bl_idname = 'mcp.properties_editor'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "PROPERTIES"
|
||||
|
||||
bpy.ops.pose.constraint_add(type='ACTION')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Animation
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PoseModeShelf(bpy.types.Operator):
|
||||
"""Pose Mode"""
|
||||
bl_label = "Pose Mode"
|
||||
bl_idname = 'mcp.pose_mode_shelf'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = context.active_object
|
||||
return active_object is not None and active_object.type == 'ARMATURE' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_GraphEditorShelf(bpy.types.Operator):
|
||||
"""Graph Editor"""
|
||||
bl_label = "Graph Editor"
|
||||
bl_idname = 'mcp.popup_grapheditor_shelf'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "GRAPH_EDITOR"
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_DopeSheetShelf(bpy.types.Operator):
|
||||
"""Dope Sheet"""
|
||||
bl_label = "Dope Sheet"
|
||||
bl_idname = 'mcp.popup_dopesheet_shelf'
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.context.scene.render
|
||||
render.resolution_x = 1000
|
||||
render.resolution_y = 600
|
||||
render.resolution_percentage = 100
|
||||
|
||||
bpy.ops.render.view_show("INVOKE_DEFAULT")
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "DOPESHEET_EDITOR"
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_DriversShelf(bpy.types.Operator):
|
||||
"""Drivers Editor"""
|
||||
bl_label = "Drivers Editor"
|
||||
bl_idname = 'mcp.popup_drivers_shelf'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.drivers_editor_show()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Split Area Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
my_outliner = None
|
||||
|
||||
class MCP_OT_SplitAreaAsset(bpy.types.Operator):
|
||||
"""Asset Browser"""
|
||||
bl_label = "Split Area"
|
||||
bl_idname = 'mcp.split_area_asset'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
|
||||
area = bpy.context.window_manager.windows[-1].screen.areas[0]
|
||||
area.type = "VIEW_3D"
|
||||
bpy.context.area.ui_type = 'ASSETS'
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
my_timelineshelf = None
|
||||
|
||||
class MCP_OT_SplitAreaTimeline(bpy.types.Operator):
|
||||
"""Timeline Split"""
|
||||
bl_label = "Split Area"
|
||||
bl_idname = 'mcp.split_area_timeline'
|
||||
|
||||
def execute(self, context):
|
||||
global my_timelineshelf
|
||||
|
||||
if my_timelineshelf:
|
||||
with context.temp_override(area=my_timelineshelf):
|
||||
bpy.ops.screen.area_close()
|
||||
my_timelineshelf = None
|
||||
else:
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
with bpy.context.temp_override(area=area):
|
||||
bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.20)
|
||||
bpy.context.screen.areas[-1].ui_type = 'TIMELINE'
|
||||
my_timelineshelf = bpy.context.screen.areas[-1]
|
||||
break
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Drop To Floor
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_DropToFloor(bpy.types.Operator):
|
||||
"""Drop/Raise Object to Floor"""
|
||||
bl_label = "Drop to Floor"
|
||||
bl_idname = "mcp.drop_to_floor"
|
||||
|
||||
def execute(self, context):
|
||||
context = bpy.context
|
||||
for obj in context.selected_objects:
|
||||
mx = obj.matrix_world
|
||||
minz = min((mx @ v.co).z for v in obj.data.vertices)
|
||||
mx.translation.z -= minz
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Camera Tools
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_CameraTools(bpy.types.Operator):
|
||||
"""Camera - Tools"""
|
||||
bl_idname = "mcp.camera_tools"
|
||||
bl_label = "Camera Tools"
|
||||
|
||||
def execute(self, context):
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
for space in area.spaces:
|
||||
if space.type == 'VIEW_3D':
|
||||
space.lock_camera = True
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Apply Transforms
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ApplyTransforms2(bpy.types.Operator):
|
||||
"""Apply Transforms"""
|
||||
bl_idname = "mcp.apply_transforms2"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True, properties=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Apply Delta
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_DeltaTransfer(bpy.types.Operator, AddObjectHelper):
|
||||
"""Delta Transfer"""
|
||||
bl_label = "Apply Delta Transfer"
|
||||
bl_idname = "mcp.delta_transfer"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.modifier_add(type='DATA_TRANSFER')
|
||||
bpy.context.space_data.context = 'SCENE'
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Symmetry - Mirror X
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_MirrorX2(bpy.types.Operator):
|
||||
"""Symmetry - Mirror along X axis"""
|
||||
bl_idname = "mcp.mirror_x2"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorX"
|
||||
|
||||
bpy.data.objects["mirrorX"].select_set(True)
|
||||
|
||||
bpy.ops.mesh.primitive_plane_add(size=500.0, calc_uvs=True, align='WORLD', location=(0.0, 0.0, 0.0), rotation=(0.0, 1.5708, 0.0))
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorCut"
|
||||
bpy.data.objects["mirrorCut"].select_set(False)
|
||||
bpy.data.objects["mirrorX"].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['mirrorX']
|
||||
|
||||
context = bpy.context
|
||||
scene = context.scene
|
||||
mirrorX = scene.objects.get("mirrorX")
|
||||
mirrorCut = scene.objects.get("mirrorCut")
|
||||
if mirrorX and mirrorCut:
|
||||
bool = mirrorX.modifiers.new(name='bool', type='BOOLEAN')
|
||||
bool.object = mirrorCut
|
||||
bool.operation = 'DIFFERENCE'
|
||||
bpy.ops.object.modifier_apply(modifier=bool.name)
|
||||
|
||||
bpy.data.objects["mirrorCut"].select_set(True)
|
||||
bpy.data.objects["mirrorX"].select_set(False)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
bpy.data.objects["mirrorX"].select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
ob = bpy.context.active_object
|
||||
assert ob.type == "MESH"
|
||||
mat = ob.matrix_world
|
||||
me = ob.data
|
||||
|
||||
if me.is_editmode:
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if all(v.co.x < 1e-6 for v in f.verts)],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
if bm.is_wrapped:
|
||||
bmesh.update_edit_mesh(me)
|
||||
else:
|
||||
bm.to_mesh(me)
|
||||
me.update()
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if f.normal.angle((-1, 0, 0)) < 1e-6],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
mirr = mirrorX.modifiers.new(name='mirrX', type='MIRROR')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Symmetry - Mirror Z
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_MirrorZ2(bpy.types.Operator):
|
||||
"""Symmetry - Mirror along Z axis"""
|
||||
bl_idname = "mcp.mirror_z2"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorZ"
|
||||
|
||||
bpy.data.objects["mirrorZ"].select_set(True)
|
||||
|
||||
bpy.ops.mesh.primitive_plane_add(size=500.0, calc_uvs=True, align='WORLD', location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 1.5708))
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorCut"
|
||||
bpy.data.objects["mirrorCut"].select_set(False)
|
||||
bpy.data.objects["mirrorZ"].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['mirrorZ']
|
||||
|
||||
context = bpy.context
|
||||
scene = context.scene
|
||||
mirrorZ = scene.objects.get("mirrorZ")
|
||||
mirrorCut = scene.objects.get("mirrorCut")
|
||||
if mirrorZ and mirrorCut:
|
||||
bool = mirrorZ.modifiers.new(name='bool', type='BOOLEAN')
|
||||
bool.object = mirrorCut
|
||||
bool.operation = 'DIFFERENCE'
|
||||
bpy.ops.object.modifier_apply(modifier=bool.name)
|
||||
|
||||
bpy.data.objects["mirrorCut"].select_set(True)
|
||||
bpy.data.objects["mirrorZ"].select_set(False)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
bpy.data.objects["mirrorZ"].select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
ob = bpy.context.active_object
|
||||
assert ob.type == "MESH"
|
||||
mat = ob.matrix_world
|
||||
me = ob.data
|
||||
|
||||
if me.is_editmode:
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if all(v.co.z < 1e-6 for v in f.verts)],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
if bm.is_wrapped:
|
||||
bmesh.update_edit_mesh(me)
|
||||
else:
|
||||
bm.to_mesh(me)
|
||||
me.update()
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if f.normal.angle((0, 0, -1)) < 1e-6],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
mirr = mirrorZ.modifiers.new(name='mirrZ', type='MIRROR')
|
||||
mirr.use_axis[0] = False
|
||||
mirr.use_axis[1] = False
|
||||
mirr.use_axis[2] = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Mirror Modifier Apply
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_MirrorApply2(bpy.types.Operator):
|
||||
"""Symmetry - Apply"""
|
||||
bl_idname = "mcp.mirror_apply2"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.active_object
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.modifier_apply(modifier="mirrX")
|
||||
bpy.ops.object.modifier_apply(modifier="mirrY")
|
||||
bpy.ops.object.modifier_apply(modifier="mirrZ")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Mesh Primitives
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_UVSphere(bpy.types.Operator, AddObjectHelper):
|
||||
"""Add a UV Sphere"""
|
||||
bl_label = "Add a UV Sphere"
|
||||
bl_idname = "mcp.uvsphere_shelf"
|
||||
|
||||
segments: IntProperty(
|
||||
name="Segments:",
|
||||
description="Number of Segments",
|
||||
min=0,
|
||||
default=32)
|
||||
radius: IntProperty(
|
||||
name="Radius:",
|
||||
description="Radius",
|
||||
min=0,
|
||||
default=1)
|
||||
ring_count: IntProperty(
|
||||
name="Rings:",
|
||||
description="Rings",
|
||||
min=0,
|
||||
default=16)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mesh.primitive_uv_sphere_add(
|
||||
segments=self.segments, radius=self.radius,
|
||||
ring_count=self.ring_count, align=self.align,
|
||||
location=self.location,
|
||||
rotation=self.rotation)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "segments")
|
||||
layout.prop(self, "radius")
|
||||
layout.prop(self, "ring_count")
|
||||
layout.prop(self, "align")
|
||||
layout.prop(self, "location")
|
||||
layout.prop(self, "rotation")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class MCP_OT_CylinderShelf(bpy.types.Operator, AddObjectHelper):
|
||||
"""Add a Mesh Cylinder"""
|
||||
bl_label = "Add a Mesh Cylinder"
|
||||
bl_idname = "mcp.cylinder_shelf"
|
||||
|
||||
vertices: IntProperty(
|
||||
name="Vertices:",
|
||||
description="Number of Vertices",
|
||||
min=0,
|
||||
default=32)
|
||||
radius: IntProperty(default=1)
|
||||
depth: FloatProperty(default=2)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mesh.primitive_cylinder_add(
|
||||
vertices=self.vertices, radius=self.radius,
|
||||
depth=self.depth, align=self.align, end_fill_type='TRIFAN',
|
||||
location=self.location,
|
||||
rotation=self.rotation)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "vertices")
|
||||
layout.prop(self, "radius")
|
||||
layout.prop(self, "depth")
|
||||
layout.prop(self, "align")
|
||||
layout.prop(self, "location")
|
||||
layout.prop(self, "rotation")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class MCP_OT_Cone(bpy.types.Operator, AddObjectHelper):
|
||||
"""Add a Mesh Cone"""
|
||||
bl_label = "Add a Mesh Cone"
|
||||
bl_idname = "mcp.cone_shelf"
|
||||
|
||||
vertices: IntProperty(
|
||||
name="Vertices:",
|
||||
description="Number of Vertices",
|
||||
min=0,
|
||||
default=32)
|
||||
radius1: IntProperty(default=1)
|
||||
radius2: IntProperty(default=0)
|
||||
depth: FloatProperty(default=2)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mesh.primitive_cone_add(
|
||||
vertices=self.vertices, radius1=self.radius1,
|
||||
radius2=self.radius2,
|
||||
depth=self.depth,
|
||||
align=self.align,
|
||||
location=self.location,
|
||||
rotation=self.rotation)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "vertices")
|
||||
layout.prop(self, "radius1")
|
||||
layout.prop(self, "radius2")
|
||||
layout.prop(self, "depth")
|
||||
layout.prop(self, "align")
|
||||
layout.prop(self, "location")
|
||||
layout.prop(self, "rotation")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class MCP_OT_Icosphere(bpy.types.Operator, AddObjectHelper):
|
||||
"""Add an IcoSphere"""
|
||||
bl_label = "Add a Mesh IcoSphere"
|
||||
bl_idname = "mcp.icosphere_shelf"
|
||||
|
||||
subdivisions: IntProperty(default=2)
|
||||
radius: IntProperty(default=1)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mesh.primitive_ico_sphere_add(
|
||||
subdivisions=self.subdivisions, radius=self.radius,
|
||||
align=self.align,
|
||||
location=self.location,
|
||||
rotation=self.rotation)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "subdivisions")
|
||||
layout.prop(self, "radius")
|
||||
layout.prop(self, "align")
|
||||
layout.prop(self, "location")
|
||||
layout.prop(self, "rotation")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Workspace Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_HypershadeShelf(bpy.types.Operator):
|
||||
bl_idname = "mcp.hypershade_shelf"
|
||||
bl_label = "layout"
|
||||
|
||||
def execute(self, context):
|
||||
layout = self.layout
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['HyperShade']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_RenderingShelf(bpy.types.Operator):
|
||||
bl_idname = "mcp.rendering_shelf"
|
||||
bl_label = "layout"
|
||||
|
||||
def execute(self, context):
|
||||
layout = self.layout
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Rendering']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
# Note: MCP_PT_Shelf panel removed - using shelf_header.py instead
|
||||
_classes = [
|
||||
# Dropdown (kept for compatibility)
|
||||
MCP_PT_ShelfDropdown,
|
||||
# Images
|
||||
MCP_OT_AddBackgroundImage,
|
||||
MCP_OT_AddReferenceImage,
|
||||
# Animation
|
||||
MCP_OT_PropertiesEditor,
|
||||
MCP_OT_PoseModeShelf,
|
||||
MCP_OT_GraphEditorShelf,
|
||||
MCP_OT_DopeSheetShelf,
|
||||
MCP_OT_DriversShelf,
|
||||
# Split Area
|
||||
MCP_OT_SplitAreaAsset,
|
||||
MCP_OT_SplitAreaTimeline,
|
||||
# Utilities
|
||||
MCP_OT_DropToFloor,
|
||||
MCP_OT_CameraTools,
|
||||
# Transforms
|
||||
MCP_OT_ApplyTransforms2,
|
||||
MCP_OT_DeltaTransfer,
|
||||
# Mirror
|
||||
MCP_OT_MirrorX2,
|
||||
MCP_OT_MirrorZ2,
|
||||
MCP_OT_MirrorApply2,
|
||||
# Primitives
|
||||
MCP_OT_UVSphere,
|
||||
MCP_OT_CylinderShelf,
|
||||
MCP_OT_Cone,
|
||||
MCP_OT_Icosphere,
|
||||
# Workspace
|
||||
MCP_OT_HypershadeShelf,
|
||||
MCP_OT_RenderingShelf,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,735 @@
|
||||
"""
|
||||
Maya Config Pro - Sidebar Panel
|
||||
N-panel shelf with modeling toolkit.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from ..utils import compat
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from bpy.props import IntProperty, FloatProperty, FloatVectorProperty
|
||||
|
||||
|
||||
class MCP_PT_Sidebar(bpy.types.Panel):
|
||||
"""Maya Shelf on the right side N panel"""
|
||||
bl_idname = "MCP_PT_Sidebar"
|
||||
bl_category = "Shelf"
|
||||
bl_label = "Shelf"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
row = layout.row()
|
||||
row.operator('mesh.primitive_uv_sphere_add', text="", icon="MESH_UVSPHERE")
|
||||
row.operator('mesh.primitive_cube_add', text="", icon="MESH_CUBE")
|
||||
row.operator('mcp.cylinder_sidebar', text="", icon="MESH_CYLINDER")
|
||||
row.operator('mesh.primitive_cone_add', text="", icon="MESH_CONE")
|
||||
row.operator('mesh.primitive_torus_add', text="", icon="MESH_TORUS")
|
||||
|
||||
row = layout.row()
|
||||
row.operator('mesh.primitive_plane_add', text="", icon="MESH_PLANE")
|
||||
row.operator('mesh.primitive_ico_sphere_add', text="", icon="MESH_ICOSPHERE")
|
||||
row.operator('object.text_add', text="", icon="OUTLINER_OB_FONT")
|
||||
row.operator('object.empty_add', text="", icon="EMPTY_AXIS")
|
||||
|
||||
row = layout.row()
|
||||
row.operator('curve.primitive_bezier_curve_add', text="", icon="CURVE_BEZCURVE")
|
||||
row.operator('curve.primitive_bezier_circle_add', text="", icon="CURVE_BEZCIRCLE")
|
||||
row.operator('curve.primitive_nurbs_curve_add', text="", icon="CURVE_NCURVE")
|
||||
row.operator('curve.primitive_nurbs_circle_add', text="", icon="CURVE_NCIRCLE")
|
||||
row.operator('curve.primitive_nurbs_path_add', text="", icon="CURVE_PATH")
|
||||
|
||||
row = layout.row()
|
||||
row.operator('surface.primitive_nurbs_surface_curve_add', text="", icon="SURFACE_NCURVE")
|
||||
row.operator('surface.primitive_nurbs_surface_circle_add', text="", icon="SURFACE_NCIRCLE")
|
||||
row.operator('surface.primitive_nurbs_surface_surface_add', text="", icon="SURFACE_NSURFACE")
|
||||
|
||||
row = layout.row()
|
||||
row.operator('object.origin_set', text='', icon='OBJECT_ORIGIN')
|
||||
row.operator("object.transforms_to_deltas", text="", icon="FREEZE").mode="ALL"
|
||||
row.operator('mcp.origin_show', text='', icon='OBJECT_ORIGIN')
|
||||
|
||||
row = layout.row()
|
||||
row.operator("mcp.hypershade_sidebar", text="", icon='MATERIAL_DATA')
|
||||
row.operator("mcp.rendering_sidebar", text="", icon='SHADING_RENDERED')
|
||||
row.operator('mcp.load_reference_image', text="", icon="IMAGE_REFERENCE")
|
||||
row.operator('mcp.load_background_image', text="", icon="IMAGE_BACKGROUND")
|
||||
row.operator('object.light_add', text="", icon="LIGHT")
|
||||
row.operator('object.camera_add', text="", icon="VIEW_CAMERA")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("object.mode_set", text="", icon="TOOL_SETTINGS")
|
||||
row.label(text='Modeling Toolkit')
|
||||
|
||||
row = layout.row()
|
||||
row.operator("mcp.msm_from_object", text="Vertex", icon='VERTEXSEL').mode = 'vert'
|
||||
row.operator("mcp.msm_from_object", text="Edge", icon='EDGESEL').mode = 'edge'
|
||||
|
||||
row = layout.row()
|
||||
row.operator("mcp.msm_from_object", text="Face", icon='FACESEL').mode = 'face'
|
||||
row.operator("mcp.multi", text="Multi", icon='EDITMODE_HLT')
|
||||
|
||||
row = layout.row()
|
||||
row.label(text="", icon='MOD_MIRROR')
|
||||
row.operator('mcp.mirror_x', text="", icon="EVENT_X")
|
||||
row.operator('mcp.mirror_z', text="", icon="EVENT_Z")
|
||||
row.operator('mcp.mirror_apply', text="", icon="CHECKMARK")
|
||||
row.operator('mcp.apply_transforms', text="", icon="TRANSFORM_ORIGINS")
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 2
|
||||
row.scale_x = 2
|
||||
row.operator('mcp.extrude_normal', text="", icon="SHAPEKEY_DATA")
|
||||
row.operator('mcp.extrude_axis', text="", icon="AXIS_TOP")
|
||||
row.operator('mcp.bevel', text="", icon="MOD_BEVEL")
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 2
|
||||
row.scale_x = 2
|
||||
row.operator('mcp.bridge_loops', text="", icon="SNAP_PEEL_OBJECT")
|
||||
row.operator('mcp.knife_tool', text="", icon="SCULPTMODE_HLT")
|
||||
row.operator('mcp.inset_faces', text="", icon="FACE_MAPS")
|
||||
|
||||
row = layout.row()
|
||||
row.operator('mcp.subdivide', text="", icon="MOD_MULTIRES")
|
||||
row.operator("mcp.unsubdivide", text="", icon="MESH_PLANE")
|
||||
row.operator('mcp.loop_cut', text="", icon="MESH_GRID")
|
||||
row.operator('object.join', text="", icon="SELECT_EXTEND")
|
||||
row.operator('mesh.separate', text="", icon="SELECT_SUBTRACT")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Mesh Cylinder Sidebar
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_CylinderSidebar(bpy.types.Operator, AddObjectHelper):
|
||||
"""Add a Mesh Cylinder"""
|
||||
bl_label = "Add a Mesh Cylinder"
|
||||
bl_idname = "mcp.cylinder_sidebar"
|
||||
|
||||
scale: FloatVectorProperty(name="Scale:", default=(1,1,1))
|
||||
vertices: IntProperty(
|
||||
name="Vertices:",
|
||||
description="Number of Vertices",
|
||||
min=0,
|
||||
default=32)
|
||||
radius: IntProperty(default=1)
|
||||
depth: FloatProperty(default=2)
|
||||
|
||||
def execute(self, context):
|
||||
s = self.scale
|
||||
bpy.ops.mesh.primitive_cylinder_add(
|
||||
vertices=self.vertices, radius=self.radius,
|
||||
depth=self.depth, align=self.align, end_fill_type='TRIFAN',
|
||||
location=self.location,
|
||||
rotation=self.rotation)
|
||||
obj = bpy.context.active_object
|
||||
obj.scale[0] = s[0]
|
||||
obj.scale[1] = s[1]
|
||||
obj.scale[2] = s[2]
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "vertices")
|
||||
layout.prop(self, "radius")
|
||||
layout.prop(self, "depth")
|
||||
layout.prop(self, "align")
|
||||
layout.prop(self, "location")
|
||||
layout.prop(self, "rotation")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Pivots
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_PivotLock(bpy.types.Operator):
|
||||
"""Lock/Unlock Pivot"""
|
||||
bl_idname = "mcp.pivot_lock_sidebar"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.tool_settings.use_snap = not bpy.context.scene.tool_settings.use_snap
|
||||
bpy.context.scene.tool_settings.snap_elements = {'VERTEX'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Origin Show/Hide
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_OriginShow(bpy.types.Operator):
|
||||
"""Show/Hide Origin"""
|
||||
bl_idname = "mcp.origin_show"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.tool_settings.use_transform_data_origin = not bpy.context.scene.tool_settings.use_transform_data_origin
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Extrude Along Axis
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ExtrudeAxis(bpy.types.Operator):
|
||||
"""FA Extrude Along Axis"""
|
||||
bl_idname = "mcp.extrude_axis"
|
||||
bl_label = "FA Extrude Along Axis"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
if active_object is not None:
|
||||
self.report({'INFO'}, "Axis Extrude Tool")
|
||||
else:
|
||||
self.report({'INFO'}, "Please Select a Mesh!")
|
||||
|
||||
bpy.ops.mesh.extrude_region_shrink_fatten(
|
||||
MESH_OT_extrude_region={"use_normal_flip":False, "use_dissolve_ortho_edges":False, "mirror":False},
|
||||
TRANSFORM_OT_shrink_fatten={"value":0,
|
||||
"use_even_offset":False,
|
||||
"mirror":False,
|
||||
"use_proportional_edit":False,
|
||||
"proportional_edit_falloff":'SMOOTH',
|
||||
"proportional_size":1,
|
||||
"use_proportional_connected":False,
|
||||
"snap":False,
|
||||
"release_confirm":False,
|
||||
"use_accurate":False})
|
||||
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.move")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Bridge
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_BridgeLoops(bpy.types.Operator):
|
||||
"""FA Bridge Edge Loops"""
|
||||
bl_idname = "mcp.bridge_loops"
|
||||
bl_label = "FA Bridge Edge Loops"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.context.selected_objects:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.bridge_edge_loops()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Inset Faces
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_InsetFaces(bpy.types.Operator):
|
||||
"""FA Inset Faces"""
|
||||
bl_idname = "mcp.inset_faces"
|
||||
bl_label = "FA Inset Faces"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.inset_faces")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Loop Cut
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_LoopCut(bpy.types.Operator):
|
||||
"""FA Loop Cut"""
|
||||
bl_idname = "mcp.loop_cut"
|
||||
bl_label = "FA Loop Cut"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.loop_cut")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Knife
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_KnifeTool(bpy.types.Operator):
|
||||
"""FA Knife Tool"""
|
||||
bl_idname = "mcp.knife_tool"
|
||||
bl_label = "FA Knife Tool"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.knife")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Extrude Along Normal
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ExtrudeNormal(bpy.types.Operator):
|
||||
"""FA Extrude Along Normal"""
|
||||
bl_idname = "mcp.extrude_normal"
|
||||
bl_label = "FA Extrude Along Normal"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.extrude_along_normals")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Subdivide
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_Subdivide(bpy.types.Operator):
|
||||
"""Subdivide Object"""
|
||||
bl_label = "Subdivide"
|
||||
bl_idname = "mcp.subdivide"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, True, False)
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.subdivide()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_Unsubdivide(bpy.types.Operator):
|
||||
"""Un-Subdivide Object"""
|
||||
bl_label = "Un-Subdivide"
|
||||
bl_idname = "mcp.unsubdivide"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, True, False)
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.unsubdivide()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Bevel
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_Bevel(bpy.types.Operator):
|
||||
"""FA Extrude Along Normal"""
|
||||
bl_idname = "mcp.bevel"
|
||||
bl_label = "FA Standard Bevel"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_object = bpy.context.view_layer.objects.active
|
||||
return active_object is not None and active_object.type == 'MESH' and (context.mode == 'EDIT_MESH' or active_object.select_get())
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.tool_set_by_id(name="builtin.bevel")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Apply Transforms
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_ApplyTransforms(bpy.types.Operator):
|
||||
"""Apply Transforms"""
|
||||
bl_idname = "mcp.apply_transforms"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Symmetry - Mirror X
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_MirrorX(bpy.types.Operator):
|
||||
"""Symmetry - Mirror along X axis"""
|
||||
bl_idname = "mcp.mirror_x"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorX"
|
||||
|
||||
bpy.data.objects["mirrorX"].select_set(True)
|
||||
|
||||
bpy.ops.mesh.primitive_plane_add(size=500.0, calc_uvs=True, align='WORLD', location=(0.0, 0.0, 0.0), rotation=(0.0, 1.5708, 0.0))
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorCut"
|
||||
bpy.data.objects["mirrorCut"].select_set(False)
|
||||
bpy.data.objects["mirrorX"].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['mirrorX']
|
||||
|
||||
context = bpy.context
|
||||
scene = context.scene
|
||||
mirrorX = scene.objects.get("mirrorX")
|
||||
mirrorCut = scene.objects.get("mirrorCut")
|
||||
if mirrorX and mirrorCut:
|
||||
bool = mirrorX.modifiers.new(name='bool', type='BOOLEAN')
|
||||
bool.object = mirrorCut
|
||||
bool.operation = 'DIFFERENCE'
|
||||
bpy.context.object.modifiers["bool"].solver = 'FAST'
|
||||
bpy.ops.object.modifier_apply(modifier=bool.name)
|
||||
|
||||
bpy.data.objects["mirrorCut"].select_set(True)
|
||||
bpy.data.objects["mirrorX"].select_set(False)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
bpy.data.objects["mirrorX"].select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
ob = bpy.context.active_object
|
||||
assert ob.type == "MESH"
|
||||
mat = ob.matrix_world
|
||||
me = ob.data
|
||||
|
||||
if me.is_editmode:
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if all(
|
||||
v.co.x < 1e-6 for v in f.verts)
|
||||
],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
if bm.is_wrapped:
|
||||
bmesh.update_edit_mesh(me)
|
||||
else:
|
||||
bm.to_mesh(me)
|
||||
me.update()
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if f.normal.angle((-1, 0, 0)) < 1e-6],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
mirr = mirrorX.modifiers.new(name='mirrX', type='MIRROR')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Symmetry - Mirror Z
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_MirrorZ(bpy.types.Operator):
|
||||
"""Symmetry - Mirror along Z axis"""
|
||||
bl_idname = "mcp.mirror_z"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorZ"
|
||||
|
||||
bpy.data.objects["mirrorZ"].select_set(True)
|
||||
|
||||
bpy.ops.mesh.primitive_plane_add(size=500.0, calc_uvs=True, align='WORLD', location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 1.5708))
|
||||
for obj in bpy.context.selected_objects:
|
||||
obj.name = "mirrorCut"
|
||||
bpy.data.objects["mirrorCut"].select_set(False)
|
||||
bpy.data.objects["mirrorZ"].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['mirrorZ']
|
||||
|
||||
context = bpy.context
|
||||
scene = context.scene
|
||||
mirrorZ = scene.objects.get("mirrorZ")
|
||||
mirrorCut = scene.objects.get("mirrorCut")
|
||||
if mirrorZ and mirrorCut:
|
||||
bool = mirrorZ.modifiers.new(name='bool', type='BOOLEAN')
|
||||
bool.object = mirrorCut
|
||||
bool.operation = 'DIFFERENCE'
|
||||
bpy.ops.object.modifier_apply(modifier=bool.name)
|
||||
|
||||
bpy.data.objects["mirrorCut"].select_set(True)
|
||||
bpy.data.objects["mirrorZ"].select_set(False)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
bpy.data.objects["mirrorZ"].select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
ob = bpy.context.active_object
|
||||
assert ob.type == "MESH"
|
||||
mat = ob.matrix_world
|
||||
me = ob.data
|
||||
|
||||
if me.is_editmode:
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if all(
|
||||
v.co.z < 1e-6 for v in f.verts)
|
||||
],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
if bm.is_wrapped:
|
||||
bmesh.update_edit_mesh(me)
|
||||
else:
|
||||
bm.to_mesh(me)
|
||||
me.update()
|
||||
|
||||
bmesh.ops.delete(
|
||||
bm,
|
||||
geom=[f for f in bm.faces if f.normal.angle((0, 0, -1)) < 1e-6],
|
||||
context='FACES'
|
||||
)
|
||||
|
||||
mirr = mirrorZ.modifiers.new(name='mirrZ', type='MIRROR')
|
||||
mirr.use_axis[0] = False
|
||||
mirr.use_axis[1] = False
|
||||
mirr.use_axis[2] = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Mirror Modifier Apply
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_MirrorApply(bpy.types.Operator):
|
||||
"""Symmetry - Apply"""
|
||||
bl_idname = "mcp.mirror_apply"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.active_object
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.modifier_apply(modifier="mirrX")
|
||||
bpy.ops.object.modifier_apply(modifier="mirrY")
|
||||
bpy.ops.object.modifier_apply(modifier="mirrZ")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Workspace Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
class MCP_OT_HypershadeSidebar(bpy.types.Operator):
|
||||
bl_idname = "mcp.hypershade_sidebar"
|
||||
bl_label = "layout"
|
||||
|
||||
def execute(self, context):
|
||||
layout = self.layout
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['HyperShade']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_RenderingSidebar(bpy.types.Operator):
|
||||
bl_idname = "mcp.rendering_sidebar"
|
||||
bl_label = "layout"
|
||||
|
||||
def execute(self, context):
|
||||
layout = self.layout
|
||||
bpy.data.window_managers['WinMan'].windows[0].workspace = bpy.data.workspaces['Rendering']
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Image Loading Operators
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bpy.props import StringProperty
|
||||
|
||||
class MCP_OT_LoadBackgroundImage(bpy.types.Operator, ImportHelper):
|
||||
"""Open a file browser and add a background image"""
|
||||
bl_idname = "mcp.load_background_image"
|
||||
bl_label = "Select Background Image"
|
||||
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
description="Path to the image file",
|
||||
subtype='FILE_PATH'
|
||||
)
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp",
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.filepath:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Create the Object at world center (0,0,0)
|
||||
empty_obj = bpy.data.objects.new("Background_Image", None)
|
||||
context.collection.objects.link(empty_obj)
|
||||
empty_obj.empty_display_type = 'IMAGE'
|
||||
empty_obj.empty_image_depth = 'BACK'
|
||||
empty_obj.empty_display_size = 5.0
|
||||
|
||||
# ALIGN ROTATION TO VIEW
|
||||
view3d = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
|
||||
if view3d:
|
||||
rv3d = view3d.spaces[0].region_3d
|
||||
view_rot = rv3d.view_matrix.inverted().to_quaternion()
|
||||
empty_obj.rotation_mode = 'QUATERNION'
|
||||
empty_obj.rotation_quaternion = view_rot
|
||||
empty_obj.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# Load the Image
|
||||
try:
|
||||
img = bpy.data.images.load(self.filepath)
|
||||
empty_obj.data = img
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, f"Could not load image: {e}")
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.view_layer.objects.active = empty_obj
|
||||
empty_obj.select_set(True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_OT_LoadReferenceImage(bpy.types.Operator, ImportHelper):
|
||||
"""Open a file browser and add a reference image"""
|
||||
bl_idname = "mcp.load_reference_image"
|
||||
bl_label = "Select Reference Image"
|
||||
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
description="Path to the image file",
|
||||
subtype='FILE_PATH'
|
||||
)
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp",
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.filepath:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Create the Object at world center (0,0,0)
|
||||
empty_obj = bpy.data.objects.new("Reference_Image", None)
|
||||
context.collection.objects.link(empty_obj)
|
||||
empty_obj.empty_display_type = 'IMAGE'
|
||||
empty_obj.empty_image_depth = 'DEFAULT'
|
||||
empty_obj.empty_display_size = 5.0
|
||||
|
||||
# ALIGN ROTATION TO VIEW
|
||||
view3d = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
|
||||
if view3d:
|
||||
rv3d = view3d.spaces[0].region_3d
|
||||
view_rot = rv3d.view_matrix.inverted().to_quaternion()
|
||||
empty_obj.rotation_mode = 'QUATERNION'
|
||||
empty_obj.rotation_quaternion = view_rot
|
||||
empty_obj.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# Load the Image
|
||||
try:
|
||||
img = bpy.data.images.load(self.filepath)
|
||||
empty_obj.data = img
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, f"Could not load image: {e}")
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.view_layer.objects.active = empty_obj
|
||||
empty_obj.select_set(True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Class list to register
|
||||
_classes = [
|
||||
# Panel
|
||||
MCP_PT_Sidebar,
|
||||
# Cylinder
|
||||
MCP_OT_CylinderSidebar,
|
||||
# Pivots
|
||||
MCP_OT_PivotLock,
|
||||
MCP_OT_OriginShow,
|
||||
# Modeling
|
||||
MCP_OT_ExtrudeAxis,
|
||||
MCP_OT_ExtrudeNormal,
|
||||
MCP_OT_Bevel,
|
||||
MCP_OT_BridgeLoops,
|
||||
MCP_OT_InsetFaces,
|
||||
MCP_OT_LoopCut,
|
||||
MCP_OT_KnifeTool,
|
||||
MCP_OT_Subdivide,
|
||||
MCP_OT_Unsubdivide,
|
||||
# Transforms
|
||||
MCP_OT_ApplyTransforms,
|
||||
# Mirror
|
||||
MCP_OT_MirrorX,
|
||||
MCP_OT_MirrorZ,
|
||||
MCP_OT_MirrorApply,
|
||||
# Workspace
|
||||
MCP_OT_HypershadeSidebar,
|
||||
MCP_OT_RenderingSidebar,
|
||||
# Images
|
||||
MCP_OT_LoadBackgroundImage,
|
||||
MCP_OT_LoadReferenceImage,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in _classes:
|
||||
compat.safe_register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(_classes):
|
||||
compat.safe_unregister_class(cls)
|
||||
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Maya Config Pro - Shelf Header
|
||||
Maya-like shelf in the VIEW3D header.
|
||||
Always visible, context-independent.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
# Current shelf mode
|
||||
_shelf_mode = "modeling" # modeling, editpoly, nurbs, rendering, fx, animation
|
||||
|
||||
|
||||
class MCP_OT_ShelfSetMode(bpy.types.Operator):
|
||||
"""Set the shelf mode"""
|
||||
bl_idname = "mcp.shelf_set_mode_header"
|
||||
bl_label = "Set Shelf Mode"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
mode: bpy.props.StringProperty(default="modeling")
|
||||
|
||||
def execute(self, context):
|
||||
global _shelf_mode
|
||||
_shelf_mode = self.mode
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MCP_MT_ShelfModeMenu(bpy.types.Menu):
|
||||
"""Shelf mode selection menu"""
|
||||
bl_label = "Shelf Mode"
|
||||
bl_idname = "MCP_MT_ShelfModeMenu"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
modes = [
|
||||
('modeling', 'Polygons', 'EDITMODE_HLT'),
|
||||
('editpoly', 'Edit Poly', 'MODIFIER'),
|
||||
('nurbs', 'Nurbs', 'CURVE_BEZCURVE'),
|
||||
('rendering', 'Rendering', 'SHADING_RENDERED'),
|
||||
('fx', 'FX', 'FORCE_WIND'),
|
||||
('animation', 'Animation', 'POSE_HLT'),
|
||||
]
|
||||
|
||||
for mode_id, label, icon in modes:
|
||||
row = layout.row()
|
||||
row.operator("mcp.shelf_set_mode_header", text=label, icon=icon).mode = mode_id
|
||||
|
||||
|
||||
def draw_shelf_header(self, context):
|
||||
"""Draw the Maya-like shelf in the VIEW3D header"""
|
||||
if context.area.type != 'VIEW_3D':
|
||||
return
|
||||
|
||||
layout = self.layout
|
||||
row = layout.row(align=True)
|
||||
|
||||
# Menu button for mode selection
|
||||
row.menu("MCP_MT_ShelfModeMenu", text="", icon='COLLAPSEMENU')
|
||||
|
||||
# File operations
|
||||
row.operator('wm.open_mainfile', text="", icon="FILEBROWSER")
|
||||
row.operator('wm.save_as_mainfile', text="", icon="FILE_TICK")
|
||||
|
||||
# Mode-specific tools
|
||||
global _shelf_mode
|
||||
|
||||
if _shelf_mode == 'modeling':
|
||||
row.separator()
|
||||
row.operator('mcp.split_area_asset', text="", icon="ASSET_MANAGER")
|
||||
row.operator('mesh.primitive_cube_add', text="", icon="MESH_CUBE")
|
||||
row.operator('mcp.cylinder_shelf', text="", icon="MESH_CYLINDER")
|
||||
row.operator('mcp.cone_shelf', text="", icon="MESH_CONE")
|
||||
row.operator('mcp.uvsphere_shelf', text="", icon="MESH_UVSPHERE")
|
||||
row.operator('mesh.primitive_torus_add', text="", icon="MESH_TORUS")
|
||||
row.operator('mesh.primitive_plane_add', text="", icon="MESH_PLANE")
|
||||
row.operator('mcp.icosphere_shelf', text="", icon="MESH_ICOSPHERE")
|
||||
row.operator('object.text_add', text="", icon="OUTLINER_OB_FONT")
|
||||
row.operator('object.empty_add', text="", icon="EMPTY_AXIS")
|
||||
row.separator()
|
||||
row.operator('object.origin_set', text="", icon='OBJECT_ORIGIN')
|
||||
row.operator("object.transforms_to_deltas", text="", icon="FREEZE").mode="ALL"
|
||||
row.operator('object.join', text="", icon="SELECT_EXTEND")
|
||||
row.operator('mesh.separate', text="", icon="SELECT_SUBTRACT")
|
||||
|
||||
elif _shelf_mode == 'editpoly':
|
||||
row.separator()
|
||||
row.operator('object.origin_set', text="", icon='OBJECT_ORIGIN')
|
||||
row.operator("object.transforms_to_deltas", text="", icon="FREEZE").mode="ALL"
|
||||
row.operator("mcp.delta_transfer", text="", icon="MOD_DATA_TRANSFER")
|
||||
row.operator("mesh.select_all", text="", icon="UV_SYNC_SELECT").action="INVERT"
|
||||
row.operator("object.make_single_user", text="", icon="UNLINKED")
|
||||
row.operator('object.join', text="", icon="SELECT_EXTEND")
|
||||
row.operator('mesh.separate', text="", icon="SELECT_SUBTRACT")
|
||||
row.operator("mesh.merge", text="", icon="AUTOMERGE_ON")
|
||||
row.operator("mesh.bridge_edge_loops", text="", icon="UV_FACESEL")
|
||||
row.operator("mesh.edge_face_add", text="", icon="NORMALS_VERTEX_FACE")
|
||||
row.operator("mesh.flip_normals", text="", icon="NORMALS_FACE")
|
||||
row.operator('mesh.select_mirror', text="", icon='MOD_MIRROR')
|
||||
row.operator('mcp.mirror_x2', text="", icon="EVENT_X")
|
||||
row.operator('mcp.mirror_z2', text="", icon="EVENT_Z")
|
||||
|
||||
elif _shelf_mode == 'nurbs':
|
||||
row.separator()
|
||||
row.operator('curve.primitive_bezier_curve_add', text="", icon="CURVE_BEZCURVE")
|
||||
row.operator('curve.primitive_bezier_circle_add', text="", icon="CURVE_BEZCIRCLE")
|
||||
row.operator('curve.primitive_nurbs_curve_add', text="", icon="CURVE_NCURVE")
|
||||
row.operator('curve.primitive_nurbs_circle_add', text="", icon="CURVE_NCIRCLE")
|
||||
row.operator('curve.primitive_nurbs_path_add', text="", icon="CURVE_PATH")
|
||||
row.separator()
|
||||
row.operator('surface.primitive_nurbs_surface_curve_add', text="", icon="SURFACE_NCURVE")
|
||||
row.operator('surface.primitive_nurbs_surface_circle_add', text="", icon="SURFACE_NCIRCLE")
|
||||
row.operator('surface.primitive_nurbs_surface_surface_add', text="", icon="SURFACE_NSURFACE")
|
||||
|
||||
elif _shelf_mode == 'rendering':
|
||||
row.separator()
|
||||
row.operator("mcp.hypershade_shelf", text="", icon='MATERIAL_DATA')
|
||||
row.operator("mcp.rendering_shelf", text="", icon='SHADING_RENDERED')
|
||||
row.separator()
|
||||
row.operator('object.light_add', text="", icon="LIGHT_POINT").type='POINT'
|
||||
row.operator('object.light_add', text="", icon="LIGHT_SUN").type='SUN'
|
||||
row.operator('object.light_add', text="", icon="LIGHT_SPOT").type='SPOT'
|
||||
row.operator('object.light_add', text="", icon="LIGHT_AREA").type='AREA'
|
||||
row.operator('object.lightprobe_add', text="", icon="OUTLINER_OB_LIGHTPROBE").type='CUBEMAP'
|
||||
row.operator('object.camera_add', text="", icon="VIEW_CAMERA")
|
||||
row.operator("render.render", text="", icon='RENDER_STILL')
|
||||
|
||||
elif _shelf_mode == 'fx':
|
||||
row.separator()
|
||||
row.operator('object.effector_add', text="", icon="FORCE_FORCE").type='FORCE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_WIND").type='WIND'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_VORTEX").type='VORTEX'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_MAGNETIC").type='MAGNET'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_HARMONIC").type='HARMONIC'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_CHARGE").type='CHARGE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_LENNARDJONES").type='LENNARDJ'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_TEXTURE").type='TEXTURE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_CURVE").type='GUIDE'
|
||||
row.operator('object.effector_add', text="", icon="FORCE_BOID").type='BOID'
|
||||
|
||||
elif _shelf_mode == 'animation':
|
||||
row.separator()
|
||||
row.operator('wm.link', text="", icon="LINKED")
|
||||
row.operator('wm.append', text="", icon="APPEND_BLEND")
|
||||
row.operator('mcp.pose_mode_ani', text="", icon="POSE_HLT")
|
||||
row.operator('object.armature_add', text="", icon="OUTLINER_OB_ARMATURE")
|
||||
row.operator('mcp.drop_to_floor_ani', text="", icon='TRIA_DOWN')
|
||||
row.separator()
|
||||
row.operator('mcp.graph_editor_ani', text="", icon="GRAPH")
|
||||
row.operator('mcp.dope_sheet_ani', text="", icon="ACTION")
|
||||
row.operator('mcp.drivers_ani', text="", icon="DRIVER")
|
||||
row.operator('anim.keyframe_insert_menu', text="", icon="DECORATE_ANIMATE")
|
||||
|
||||
|
||||
_classes = [
|
||||
MCP_OT_ShelfSetMode,
|
||||
MCP_MT_ShelfModeMenu,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
"""Register shelf header"""
|
||||
for cls in _classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
# Append to VIEW3D header
|
||||
bpy.types.VIEW3D_HT_header.append(draw_shelf_header)
|
||||
print("MCP: Shelf header registered")
|
||||
|
||||
|
||||
def unregister():
|
||||
"""Unregister shelf header"""
|
||||
# Remove from VIEW3D header
|
||||
bpy.types.VIEW3D_HT_header.remove(draw_shelf_header)
|
||||
|
||||
for cls in reversed(_classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
print("MCP: Shelf header unregistered")
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
+484
@@ -0,0 +1,484 @@
|
||||
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy
|
||||
import math
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
StringProperty,
|
||||
)
|
||||
|
||||
DPI = 72
|
||||
POPUP_PADDING = 10
|
||||
PANEL_PADDING = 44
|
||||
WIN_PADDING = 32
|
||||
ICON_SIZE = 20
|
||||
HISTORY_SIZE = 100
|
||||
HISTORY = []
|
||||
|
||||
|
||||
def ui_scale():
|
||||
prefs = bpy.context.preferences.system
|
||||
return prefs.dpi / DPI
|
||||
|
||||
|
||||
def prefs():
|
||||
return bpy.context.preferences.addons[__package__].preferences
|
||||
|
||||
|
||||
class Icons:
|
||||
def __init__(self, is_popup=False):
|
||||
self._filtered_icons = None
|
||||
self._filter = ""
|
||||
self.filter = ""
|
||||
self.selected_icon = ""
|
||||
self.is_popup = is_popup
|
||||
|
||||
@property
|
||||
def filter(self):
|
||||
return self._filter
|
||||
|
||||
@filter.setter
|
||||
def filter(self, value):
|
||||
if self._filter == value:
|
||||
return
|
||||
|
||||
self._filter = value
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def filtered_icons(self):
|
||||
if self._filtered_icons is None:
|
||||
self._filtered_icons = []
|
||||
icon_filter = self._filter.upper()
|
||||
self.filtered_icons.clear()
|
||||
pr = prefs()
|
||||
|
||||
icons = bpy.types.UILayout.bl_rna.functions[
|
||||
"prop"].parameters["icon"].enum_items.keys()
|
||||
for icon in icons:
|
||||
if icon == 'NONE' or \
|
||||
icon_filter and icon_filter not in icon or \
|
||||
not pr.show_brush_icons and "BRUSH_" in icon and \
|
||||
icon != 'BRUSH_DATA' or \
|
||||
not pr.show_matcap_icons and "MATCAP_" in icon or \
|
||||
not pr.show_event_icons and (
|
||||
"EVENT_" in icon or "MOUSE_" in icon
|
||||
) or \
|
||||
not pr.show_colorset_icons and "COLORSET_" in icon:
|
||||
continue
|
||||
self._filtered_icons.append(icon)
|
||||
|
||||
return self._filtered_icons
|
||||
|
||||
@property
|
||||
def num_icons(self):
|
||||
return len(self.filtered_icons)
|
||||
|
||||
def update(self):
|
||||
if self._filtered_icons is not None:
|
||||
self._filtered_icons.clear()
|
||||
self._filtered_icons = None
|
||||
|
||||
def draw(self, layout, num_cols=0, icons=None):
|
||||
if icons:
|
||||
filtered_icons = reversed(icons)
|
||||
else:
|
||||
filtered_icons = self.filtered_icons
|
||||
|
||||
column = layout.column(align=True)
|
||||
row = column.row(align=True)
|
||||
row.alignment = 'CENTER'
|
||||
|
||||
selected_icon = self.selected_icon if self.is_popup else \
|
||||
bpy.context.window_manager.clipboard
|
||||
col_idx = 0
|
||||
for i, icon in enumerate(filtered_icons):
|
||||
p = row.operator(
|
||||
IV_OT_icon_select.bl_idname, text="",
|
||||
icon=icon, emboss=icon == selected_icon)
|
||||
p.icon = icon
|
||||
p.force_copy_on_select = not self.is_popup
|
||||
|
||||
col_idx += 1
|
||||
if col_idx > num_cols - 1:
|
||||
if icons:
|
||||
break
|
||||
col_idx = 0
|
||||
if i < len(filtered_icons) - 1:
|
||||
row = column.row(align=True)
|
||||
row.alignment = 'CENTER'
|
||||
|
||||
if col_idx != 0 and not icons and i >= num_cols:
|
||||
for _ in range(num_cols - col_idx):
|
||||
row.label(text="", icon='BLANK1')
|
||||
|
||||
if not filtered_icons:
|
||||
row.label(text="No icons were found")
|
||||
|
||||
|
||||
class IV_Preferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
|
||||
panel_icons = Icons()
|
||||
popup_icons = Icons(is_popup=True)
|
||||
|
||||
def update_icons(self, context):
|
||||
self.panel_icons.update()
|
||||
self.popup_icons.update()
|
||||
|
||||
def set_panel_filter(self, value):
|
||||
self.panel_icons.filter = value
|
||||
|
||||
panel_filter: StringProperty(
|
||||
description="Filter",
|
||||
default="",
|
||||
get=lambda s: s.panel_icons.filter,
|
||||
set=set_panel_filter,
|
||||
options={'TEXTEDIT_UPDATE'})
|
||||
show_panel_icons: BoolProperty(
|
||||
name="Show Icons",
|
||||
description="Show icons", default=True)
|
||||
show_history: BoolProperty(
|
||||
name="Show History",
|
||||
description="Show history", default=True)
|
||||
show_brush_icons: BoolProperty(
|
||||
name="Show Brush Icons",
|
||||
description="Show brush icons", default=True,
|
||||
update=update_icons)
|
||||
show_matcap_icons: BoolProperty(
|
||||
name="Show Matcap Icons",
|
||||
description="Show matcap icons", default=True,
|
||||
update=update_icons)
|
||||
show_event_icons: BoolProperty(
|
||||
name="Show Event Icons",
|
||||
description="Show event icons", default=True,
|
||||
update=update_icons)
|
||||
show_colorset_icons: BoolProperty(
|
||||
name="Show Colorset Icons",
|
||||
description="Show colorset icons", default=True,
|
||||
update=update_icons)
|
||||
copy_on_select: BoolProperty(
|
||||
name="Copy Icon On Click",
|
||||
description="Copy icon on click", default=True)
|
||||
close_on_select: BoolProperty(
|
||||
name="Close Popup On Click",
|
||||
description=(
|
||||
"Close the popup on click.\n"
|
||||
"Not supported by some windows (User Preferences, Render)"
|
||||
),
|
||||
default=False)
|
||||
auto_focus_filter: BoolProperty(
|
||||
name="Auto Focus Input Field",
|
||||
description="Auto focus input field", default=True)
|
||||
show_panel: BoolProperty(
|
||||
name="Show Panel",
|
||||
description="Show the panel in the Text Editor", default=True)
|
||||
show_header: BoolProperty(
|
||||
name="Show Header",
|
||||
description="Show the header in the Python Console",
|
||||
default=True)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.scale_y = 1.5
|
||||
row.operator(IV_OT_icons_show.bl_idname)
|
||||
|
||||
row = layout.row()
|
||||
|
||||
col = row.column(align=True)
|
||||
col.label(text="Icons:")
|
||||
col.prop(self, "show_matcap_icons")
|
||||
col.prop(self, "show_brush_icons")
|
||||
col.prop(self, "show_colorset_icons")
|
||||
col.prop(self, "show_event_icons")
|
||||
col.separator()
|
||||
col.prop(self, "show_history")
|
||||
|
||||
col = row.column(align=True)
|
||||
col.label(text="Popup:")
|
||||
col.prop(self, "auto_focus_filter")
|
||||
col.prop(self, "copy_on_select")
|
||||
if self.copy_on_select:
|
||||
col.prop(self, "close_on_select")
|
||||
|
||||
col = row.column(align=True)
|
||||
col.label(text="Panel:")
|
||||
col.prop(self, "show_panel")
|
||||
if self.show_panel:
|
||||
col.prop(self, "show_panel_icons")
|
||||
|
||||
col.separator()
|
||||
col.label(text="Header:")
|
||||
col.prop(self, "show_header")
|
||||
|
||||
|
||||
class IV_PT_icons(bpy.types.Panel):
|
||||
bl_space_type = "TEXT_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_label = "Icon Viewer"
|
||||
bl_category = "Dev"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@staticmethod
|
||||
def tag_redraw():
|
||||
wm = bpy.context.window_manager
|
||||
if not wm:
|
||||
return
|
||||
|
||||
for w in wm.windows:
|
||||
for a in w.screen.areas:
|
||||
if a.type == 'TEXT_EDITOR':
|
||||
for r in a.regions:
|
||||
if r.type == 'UI':
|
||||
r.tag_redraw()
|
||||
|
||||
def draw(self, context):
|
||||
pr = prefs()
|
||||
row = self.layout.row(align=True)
|
||||
if pr.show_panel_icons:
|
||||
row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
|
||||
else:
|
||||
row.operator(IV_OT_icons_show.bl_idname)
|
||||
row.operator(
|
||||
IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
|
||||
|
||||
_, y0 = context.region.view2d.region_to_view(0, 0)
|
||||
_, y1 = context.region.view2d.region_to_view(0, 10)
|
||||
region_scale = 10 / abs(y0 - y1)
|
||||
|
||||
num_cols = max(
|
||||
1,
|
||||
(context.region.width - PANEL_PADDING) //
|
||||
math.ceil(ui_scale() * region_scale * ICON_SIZE))
|
||||
|
||||
col = None
|
||||
if HISTORY and pr.show_history:
|
||||
col = self.layout.column(align=True)
|
||||
pr.panel_icons.draw(col.box(), num_cols, HISTORY)
|
||||
|
||||
if pr.show_panel_icons:
|
||||
col = col or self.layout.column(align=True)
|
||||
pr.panel_icons.draw(col.box(), num_cols)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return prefs().show_panel
|
||||
|
||||
|
||||
class IV_OT_panel_menu_call(bpy.types.Operator):
|
||||
bl_idname = "iv.panel_menu_call"
|
||||
bl_label = ""
|
||||
bl_description = "Menu"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
def menu(self, menu, context):
|
||||
pr = prefs()
|
||||
layout = menu.layout
|
||||
layout.prop(pr, "show_panel_icons")
|
||||
layout.prop(pr, "show_history")
|
||||
|
||||
if not pr.show_panel_icons:
|
||||
return
|
||||
|
||||
layout.separator()
|
||||
layout.prop(pr, "show_matcap_icons")
|
||||
layout.prop(pr, "show_brush_icons")
|
||||
layout.prop(pr, "show_colorset_icons")
|
||||
layout.prop(pr, "show_event_icons")
|
||||
|
||||
def execute(self, context):
|
||||
context.window_manager.popup_menu(self.menu, title="Icon Viewer")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class IV_OT_icon_select(bpy.types.Operator):
|
||||
bl_idname = "iv.icon_select"
|
||||
bl_label = ""
|
||||
bl_description = "Select the icon"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
icon: StringProperty()
|
||||
force_copy_on_select: BoolProperty()
|
||||
|
||||
def execute(self, context):
|
||||
pr = prefs()
|
||||
pr.popup_icons.selected_icon = self.icon
|
||||
if pr.copy_on_select or self.force_copy_on_select:
|
||||
context.window_manager.clipboard = self.icon
|
||||
self.report({'INFO'}, self.icon)
|
||||
|
||||
if pr.close_on_select and IV_OT_icons_show.instance:
|
||||
IV_OT_icons_show.instance.close()
|
||||
|
||||
if pr.show_history:
|
||||
if self.icon in HISTORY:
|
||||
HISTORY.remove(self.icon)
|
||||
if len(HISTORY) >= HISTORY_SIZE:
|
||||
HISTORY.pop(0)
|
||||
HISTORY.append(self.icon)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class IV_OT_icons_show(bpy.types.Operator):
|
||||
bl_idname = "iv.icons_show"
|
||||
bl_label = "Icon Viewer"
|
||||
bl_description = "Icon viewer"
|
||||
bl_property = "filter_auto_focus"
|
||||
|
||||
instance = None
|
||||
|
||||
def set_filter(self, value):
|
||||
prefs().popup_icons.filter = value
|
||||
|
||||
def set_selected_icon(self, value):
|
||||
if IV_OT_icons_show.instance:
|
||||
IV_OT_icons_show.instance.auto_focusable = False
|
||||
|
||||
filter_auto_focus: StringProperty(
|
||||
description="Filter",
|
||||
get=lambda s: prefs().popup_icons.filter,
|
||||
set=set_filter,
|
||||
options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
|
||||
filter: StringProperty(
|
||||
description="Filter",
|
||||
get=lambda s: prefs().popup_icons.filter,
|
||||
set=set_filter,
|
||||
options={'TEXTEDIT_UPDATE'})
|
||||
selected_icon: StringProperty(
|
||||
description="Selected Icon",
|
||||
get=lambda s: prefs().popup_icons.selected_icon,
|
||||
set=set_selected_icon)
|
||||
|
||||
def get_num_cols(self, num_icons):
|
||||
return round(1.3 * math.sqrt(num_icons))
|
||||
|
||||
def draw_header(self, layout):
|
||||
pr = prefs()
|
||||
header = layout.box()
|
||||
header = header.split(factor=0.75) if self.selected_icon else \
|
||||
header.row()
|
||||
row = header.row(align=True)
|
||||
row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
|
||||
row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
|
||||
row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
|
||||
row.prop(pr, "show_event_icons", text="", icon='HAND')
|
||||
row.separator()
|
||||
|
||||
row.prop(
|
||||
pr, "copy_on_select", text="",
|
||||
icon='COPYDOWN', toggle=True)
|
||||
if pr.copy_on_select:
|
||||
sub = row.row(align=True)
|
||||
if bpy.context.window.screen.name == "temp":
|
||||
sub.alert = True
|
||||
sub.prop(
|
||||
pr, "close_on_select", text="",
|
||||
icon='RESTRICT_SELECT_OFF', toggle=True)
|
||||
row.prop(
|
||||
pr, "auto_focus_filter", text="",
|
||||
icon='OUTLINER_DATA_FONT', toggle=True)
|
||||
row.separator()
|
||||
|
||||
if self.auto_focusable and pr.auto_focus_filter:
|
||||
row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
|
||||
else:
|
||||
row.prop(self, "filter", text="", icon='VIEWZOOM')
|
||||
|
||||
if self.selected_icon:
|
||||
row = header.row()
|
||||
row.prop(self, "selected_icon", text="", icon=self.selected_icon)
|
||||
|
||||
def draw(self, context):
|
||||
pr = prefs()
|
||||
col = self.layout
|
||||
self.draw_header(col)
|
||||
|
||||
history_num_cols = int(
|
||||
(self.width - POPUP_PADDING) / (ui_scale() * ICON_SIZE))
|
||||
num_cols = min(
|
||||
self.get_num_cols(len(pr.popup_icons.filtered_icons)),
|
||||
history_num_cols)
|
||||
|
||||
subcol = col.column(align=True)
|
||||
|
||||
if HISTORY and pr.show_history:
|
||||
pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
|
||||
|
||||
pr.popup_icons.draw(subcol.box(), num_cols)
|
||||
|
||||
def close(self):
|
||||
bpy.context.window.screen = bpy.context.window.screen
|
||||
|
||||
def check(self, context):
|
||||
return True
|
||||
|
||||
def cancel(self, context):
|
||||
IV_OT_icons_show.instance = None
|
||||
IV_PT_icons.tag_redraw()
|
||||
|
||||
def execute(self, context):
|
||||
if not IV_OT_icons_show.instance:
|
||||
return {'CANCELLED'}
|
||||
IV_OT_icons_show.instance = None
|
||||
|
||||
pr = prefs()
|
||||
if self.selected_icon and not pr.copy_on_select:
|
||||
context.window_manager.clipboard = self.selected_icon
|
||||
self.report({'INFO'}, self.selected_icon)
|
||||
pr.popup_icons.selected_icon = ""
|
||||
|
||||
IV_PT_icons.tag_redraw()
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
pr = prefs()
|
||||
pr.popup_icons.selected_icon = ""
|
||||
pr.popup_icons.filter = ""
|
||||
IV_OT_icons_show.instance = self
|
||||
self.auto_focusable = True
|
||||
|
||||
num_cols = self.get_num_cols(len(pr.popup_icons.filtered_icons))
|
||||
self.width = int(min(
|
||||
ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
|
||||
context.window.width - WIN_PADDING))
|
||||
|
||||
return context.window_manager.invoke_props_dialog(
|
||||
self, width=self.width)
|
||||
|
||||
|
||||
def draw_console_header(self, context):
|
||||
if not prefs().show_header:
|
||||
return
|
||||
self.layout.operator(IV_OT_icons_show.bl_idname)
|
||||
|
||||
|
||||
classes = (
|
||||
IV_PT_icons,
|
||||
IV_OT_panel_menu_call,
|
||||
IV_OT_icon_select,
|
||||
IV_OT_icons_show,
|
||||
IV_Preferences,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.CONSOLE_HT_header.append(draw_console_header)
|
||||
|
||||
|
||||
def unregister():
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
bpy.types.CONSOLE_HT_header.remove(draw_console_header)
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
schema_version = "1.0.0"
|
||||
id = "icon_viewer"
|
||||
name = "Icon Viewer"
|
||||
version = "1.4.2"
|
||||
tagline = "Click an icon to copy its name to the clipboard"
|
||||
maintainer = "Community"
|
||||
type = "add-on"
|
||||
tags = ["Development"]
|
||||
blender_version_min = "4.2.0"
|
||||
license = ["SPDX:GPL-2.0-or-later"]
|
||||
website = "https://projects.blender.org/extensions/development_icon_get"
|
||||
copyright = ["2024 roaoao"]
|
||||
+1
@@ -0,0 +1 @@
|
||||
*.pyc
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
bl_info = {
|
||||
"name": "Side Shelf",
|
||||
"author": "Jesse Doyle",
|
||||
"blender": (3, 2, 0),
|
||||
"description": "E Shelf on the right side N panel",
|
||||
"category": "Side Tabs"
|
||||
}
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
|
||||
|
||||
class NPanel(bpy.types.Panel):
|
||||
"""Creates a Panel in the 3D view Tools panel"""
|
||||
bl_idname = "TEST_PT_Panel"
|
||||
bl_label = "Test"
|
||||
bl_category = "Shelf"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_context = "objectmode"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
layout = self.layout
|
||||
pcoll = preview_collections["main"]
|
||||
|
||||
row = layout.row()
|
||||
triangle_icon = pcoll["triangle"]
|
||||
square_icon = pcoll["square"]
|
||||
row.scale_x = 10.0
|
||||
row.scale_y = 8.0
|
||||
|
||||
row.operator("mesh.primitive_cube_add", text="", icon_value=triangle_icon.icon_id)
|
||||
row.operator("mesh.primitive_uv_sphere_add", text="", icon_value=square_icon.icon_id,scale=10)
|
||||
|
||||
|
||||
# global variable to store icons in
|
||||
# custom_icons = None
|
||||
|
||||
# def register():
|
||||
# global custom_icons
|
||||
# custom_icons = bpy.utils.previews.new()
|
||||
|
||||
# for z in list_raw:
|
||||
# custom_icons.load(z[:-4], os.path.join(directory, z), 'IMAGE')
|
||||
|
||||
# bpy.utils.register_class(Panel)
|
||||
|
||||
# def unregister():
|
||||
# global custom_icons
|
||||
# bpy.utils.previews.remove(custom_icons)
|
||||
|
||||
preview_collections = {}
|
||||
|
||||
|
||||
def register():
|
||||
|
||||
# Note that preview collections returned by bpy.utils.previews
|
||||
# are regular py objects - you can use them to store custom data.
|
||||
pcoll = bpy.utils.previews.new()
|
||||
|
||||
# path to the folder where the icon is
|
||||
# the path is calculated relative to this py file inside the addon folder
|
||||
|
||||
directory = os.path.join(os.path.dirname(__file__), "Data", "icons")
|
||||
list_raw = []
|
||||
onlyfiles = [f for f in listdir(directory) if isfile(join(directory, f))]
|
||||
|
||||
for f in onlyfiles:
|
||||
if f[-4:] == ".png":
|
||||
list_raw.append(f)
|
||||
|
||||
for z in list_raw:
|
||||
|
||||
# load a preview thumbnail of a file and store in the previews collection
|
||||
pcoll.load(z[:-4], os.path.join(directory, z), 'IMAGE')
|
||||
|
||||
preview_collections["main"] = pcoll
|
||||
|
||||
bpy.utils.register_class(NPanel)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
for pcoll in preview_collections.values():
|
||||
bpy.utils.previews.remove(pcoll)
|
||||
preview_collections.clear()
|
||||
|
||||
bpy.utils.unregister_class(NPanel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<bpy>
|
||||
<Theme>
|
||||
</Theme>
|
||||
<ThemeStyle>
|
||||
</ThemeStyle>
|
||||
</bpy>
|
||||
+1643
File diff suppressed because it is too large
Load Diff
+1534
File diff suppressed because it is too large
Load Diff
+1453
File diff suppressed because it is too large
Load Diff
+1534
File diff suppressed because it is too large
Load Diff
+1534
File diff suppressed because it is too large
Load Diff
+1534
File diff suppressed because it is too large
Load Diff
+1534
File diff suppressed because it is too large
Load Diff
+384
@@ -0,0 +1,384 @@
|
||||
# SPDX-FileCopyrightText: 2018-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
DIRNAME, FILENAME = os.path.split(__file__)
|
||||
IDNAME = os.path.splitext(FILENAME)[0]
|
||||
|
||||
|
||||
def update_fn(_self, _context):
|
||||
load()
|
||||
|
||||
|
||||
class Prefs(bpy.types.KeyConfigPreferences):
|
||||
bl_idname = IDNAME
|
||||
|
||||
select_mouse: EnumProperty(
|
||||
name="Select Mouse",
|
||||
items=(
|
||||
('LEFT', "Left",
|
||||
"Use left mouse button for selection. "
|
||||
"The standard behavior that works well for mouse, trackpad and tablet devices"),
|
||||
('RIGHT', "Right",
|
||||
"Use right mouse button for selection, and left mouse button for actions. "
|
||||
"This works well primarily for keyboard and mouse devices"),
|
||||
),
|
||||
description=(
|
||||
"Mouse button used for selection"
|
||||
),
|
||||
update=update_fn,
|
||||
)
|
||||
spacebar_action: EnumProperty(
|
||||
name="Spacebar Action",
|
||||
items=(
|
||||
('PLAY', "Play",
|
||||
"Toggle animation playback "
|
||||
"('Shift-Space' for Tools)",
|
||||
1),
|
||||
('TOOL', "Tools",
|
||||
"Open the popup tool-bar\n"
|
||||
"When 'Space' is held and used as a modifier:\n"
|
||||
"\u2022 Pressing the tools binding key switches to it immediately.\n"
|
||||
"\u2022 Dragging the cursor over a tool and releasing activates it (like a pie menu).\n"
|
||||
"For Play use 'Shift-Space'",
|
||||
0),
|
||||
('SEARCH', "Search",
|
||||
"Open the operator search popup",
|
||||
2),
|
||||
),
|
||||
description=(
|
||||
"Action when 'Space' is pressed"
|
||||
),
|
||||
default='PLAY',
|
||||
update=update_fn,
|
||||
)
|
||||
tool_key_mode: EnumProperty(
|
||||
name="Tool Keys",
|
||||
description=(
|
||||
"The method of keys to activate tools such as move, rotate & scale (G, R, S)"
|
||||
),
|
||||
items=(
|
||||
('IMMEDIATE', "Immediate",
|
||||
"Activate actions immediately"),
|
||||
('TOOL', "Active Tool",
|
||||
"Activate the tool for editors that support tools"),
|
||||
),
|
||||
default='IMMEDIATE',
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
rmb_action: EnumProperty(
|
||||
name="Right Mouse Select Action",
|
||||
items=(
|
||||
('TWEAK', "Select & Tweak",
|
||||
"Right mouse always tweaks"),
|
||||
('FALLBACK_TOOL', "Selection Tool",
|
||||
"Right mouse uses the selection tool"),
|
||||
),
|
||||
description=(
|
||||
"Default action for the right mouse button"
|
||||
),
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
# Experimental: only show with developer extras, see: #107785.
|
||||
use_region_toggle_pie: BoolProperty(
|
||||
name="Region Toggle Pie",
|
||||
description=(
|
||||
"N-key opens a pie menu to toggle regions"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
use_alt_click_leader: BoolProperty(
|
||||
name="Alt Click Tool Prompt",
|
||||
description=(
|
||||
"Tapping Alt (without pressing any other keys) shows a prompt in the status-bar, "
|
||||
"prompting a second keystroke to activate the tool"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
# NOTE: expose `use_alt_tool` and `use_alt_cursor` as two options in the UI
|
||||
# as the tool-tips and titles are different enough depending on RMB/LMB select.
|
||||
use_alt_tool: BoolProperty(
|
||||
name="Alt Tool Access",
|
||||
description=(
|
||||
"Hold Alt to use the active tool when the gizmo would normally be required\n"
|
||||
"Incompatible with the input preference \"Emulate 3 Button Mouse\" when the \"Alt\" key is used"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
use_alt_cursor: BoolProperty(
|
||||
name="Alt Cursor Access",
|
||||
description=(
|
||||
"Hold Alt-LMB to place the Cursor (instead of LMB), allows tools to activate on press instead of drag.\n"
|
||||
"Incompatible with the input preference \"Emulate 3 Button Mouse\" when the \"Alt\" key is used"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
# end note.
|
||||
|
||||
use_select_all_toggle: BoolProperty(
|
||||
name="Select All Toggles",
|
||||
description=(
|
||||
"Causes select-all ('A' key) to de-select in the case a selection exists"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
gizmo_action: EnumProperty(
|
||||
name="Activate Gizmo",
|
||||
items=(
|
||||
('PRESS', "Press", "Press causes immediate activation, preventing click being passed to the tool"),
|
||||
('DRAG', "Drag", "Drag allows click events to pass through to the tool, adding a small delay"),
|
||||
),
|
||||
description="Activation event for gizmos that support drag motion",
|
||||
default='DRAG',
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
# 3D View
|
||||
use_v3d_tab_menu: BoolProperty(
|
||||
name="Tab for Pie Menu",
|
||||
description=(
|
||||
"Causes tab to open pie menu (swaps 'Tab' / 'Ctrl-Tab')"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
use_v3d_shade_ex_pie: BoolProperty(
|
||||
name="Extra Shading Pie Menu Items",
|
||||
description=(
|
||||
"Show additional options in the shading menu ('Z')"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
v3d_tilde_action: EnumProperty(
|
||||
name="Tilde Action",
|
||||
items=(
|
||||
('VIEW', "Navigate",
|
||||
"View operations (useful for keyboards without a numpad)",
|
||||
0),
|
||||
('GIZMO', "Gizmos",
|
||||
"Control transform gizmos",
|
||||
1),
|
||||
),
|
||||
description=(
|
||||
"Action when 'Tilde' is pressed"
|
||||
),
|
||||
default='VIEW',
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
v3d_mmb_action: EnumProperty(
|
||||
name="MMB Action",
|
||||
items=(
|
||||
('ORBIT', "Orbit",
|
||||
"",
|
||||
0),
|
||||
('PAN', "Pan",
|
||||
"",
|
||||
1),
|
||||
),
|
||||
description=(
|
||||
"The action when Middle-Mouse dragging in the viewport. "
|
||||
"Shift-Middle-Mouse is used for the other action. "
|
||||
"This applies to trackpad as well"
|
||||
),
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
v3d_alt_mmb_drag_action: EnumProperty(
|
||||
name="Alt-MMB Drag Action",
|
||||
items=(
|
||||
('RELATIVE', "Relative",
|
||||
"Set the view axis where each mouse direction maps to an axis relative to the current orientation",
|
||||
0),
|
||||
('ABSOLUTE', "Absolute",
|
||||
"Set the view axis where each mouse direction always maps to the same axis",
|
||||
1),
|
||||
),
|
||||
description=(
|
||||
"Action when Alt-MMB dragging in the 3D viewport"
|
||||
),
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
# Developer note, this is an experimental option.
|
||||
use_pie_click_drag: BoolProperty(
|
||||
name="Pie Menu on Drag",
|
||||
description=(
|
||||
"Activate some pie menus on drag,\n"
|
||||
"allowing the tapping the same key to have a secondary action.\n"
|
||||
"\n"
|
||||
"\u2022 Tapping Tab in the 3D view toggles edit-mode, drag for mode menu.\n"
|
||||
"\u2022 Tapping Z in the 3D view toggles wireframe, drag for draw modes.\n"
|
||||
"\u2022 Tapping Tilde in the 3D view for first person navigation, drag for view axes"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
use_file_single_click: BoolProperty(
|
||||
name="Open Folders on Single Click",
|
||||
description=(
|
||||
"Navigate into folders by clicking on them once instead of twice"
|
||||
),
|
||||
default=False,
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
use_alt_navigation: BoolProperty(
|
||||
name="Transform Navigation with Alt",
|
||||
description=(
|
||||
"During transformations, use Alt to navigate in the 3D View. "
|
||||
"Note that if disabled, hotkeys for Proportional Editing, "
|
||||
"Automatic Constraints, and Auto IK Chain Length will require holding Alt"
|
||||
),
|
||||
default=True,
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
def draw(self, layout):
|
||||
from bpy import context
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
prefs = context.preferences
|
||||
|
||||
show_developer_ui = prefs.view.show_developer_ui
|
||||
is_select_left = (self.select_mouse == 'LEFT')
|
||||
use_mouse_emulate_3_button = (
|
||||
prefs.inputs.use_mouse_emulate_3_button and
|
||||
prefs.inputs.mouse_emulate_3_button_modifier == 'ALT'
|
||||
)
|
||||
|
||||
# General settings.
|
||||
col = layout.column()
|
||||
col.row().prop(self, "select_mouse", text="Select with Mouse Button", expand=True)
|
||||
col.row().prop(self, "spacebar_action", text="Spacebar Action", expand=True)
|
||||
|
||||
if is_select_left:
|
||||
col.row().prop(self, "gizmo_action", text="Activate Gizmo Event", expand=True)
|
||||
else:
|
||||
col.row().prop(self, "rmb_action", text="Right Mouse Select Action", expand=True)
|
||||
|
||||
col.row().prop(self, "tool_key_mode", expand=True)
|
||||
|
||||
# Check-box sub-layout.
|
||||
col = layout.column()
|
||||
sub = col.column(align=True)
|
||||
row = sub.row()
|
||||
row.prop(self, "use_alt_click_leader")
|
||||
|
||||
rowsub = row.row()
|
||||
if is_select_left:
|
||||
rowsub.prop(self, "use_alt_tool")
|
||||
else:
|
||||
rowsub.prop(self, "use_alt_cursor")
|
||||
rowsub.active = not use_mouse_emulate_3_button
|
||||
|
||||
row = sub.row()
|
||||
row.prop(self, "use_select_all_toggle")
|
||||
|
||||
if show_developer_ui:
|
||||
row = sub.row()
|
||||
row.prop(self, "use_region_toggle_pie")
|
||||
|
||||
# 3DView settings.
|
||||
col = layout.column()
|
||||
col.label(text="3D View")
|
||||
col.row().prop(self, "v3d_tilde_action", text="Grave Accent / Tilde Action", expand=True)
|
||||
col.row().prop(self, "v3d_mmb_action", text="Middle Mouse Action", expand=True)
|
||||
col.row().prop(self, "v3d_alt_mmb_drag_action", text="Alt Middle Mouse Drag Action", expand=True)
|
||||
|
||||
# Check-boxes sub-layout.
|
||||
col = layout.column()
|
||||
sub = col.column(align=True)
|
||||
sub.prop(self, "use_v3d_tab_menu")
|
||||
sub.prop(self, "use_pie_click_drag")
|
||||
sub.prop(self, "use_v3d_shade_ex_pie")
|
||||
sub.prop(self, "use_alt_navigation")
|
||||
|
||||
# File Browser settings.
|
||||
col = layout.column()
|
||||
col.label(text="File Browser")
|
||||
col.row().prop(self, "use_file_single_click")
|
||||
|
||||
|
||||
blender_default = bpy.utils.execfile(os.path.join(DIRNAME, "keymap_data", "blender_default.py"))
|
||||
|
||||
|
||||
def load():
|
||||
from sys import platform
|
||||
from bpy import context
|
||||
from bl_keymap_utils.io import keyconfig_init_from_data
|
||||
|
||||
prefs = context.preferences
|
||||
kc = context.window_manager.keyconfigs.new(IDNAME)
|
||||
kc_prefs = kc.preferences
|
||||
|
||||
show_developer_ui = prefs.view.show_developer_ui
|
||||
is_select_left = (kc_prefs.select_mouse == 'LEFT')
|
||||
use_mouse_emulate_3_button = (
|
||||
prefs.inputs.use_mouse_emulate_3_button and
|
||||
prefs.inputs.mouse_emulate_3_button_modifier == 'ALT'
|
||||
)
|
||||
|
||||
keyconfig_data = blender_default.generate_keymaps(
|
||||
blender_default.Params(
|
||||
select_mouse=kc_prefs.select_mouse,
|
||||
use_mouse_emulate_3_button=use_mouse_emulate_3_button,
|
||||
spacebar_action=kc_prefs.spacebar_action,
|
||||
use_key_activate_tools=(kc_prefs.tool_key_mode == 'TOOL'),
|
||||
use_region_toggle_pie=(show_developer_ui and kc_prefs.use_region_toggle_pie),
|
||||
v3d_tilde_action=kc_prefs.v3d_tilde_action,
|
||||
use_v3d_mmb_pan=(kc_prefs.v3d_mmb_action == 'PAN'),
|
||||
v3d_alt_mmb_drag_action=kc_prefs.v3d_alt_mmb_drag_action,
|
||||
use_select_all_toggle=kc_prefs.use_select_all_toggle,
|
||||
use_v3d_tab_menu=kc_prefs.use_v3d_tab_menu,
|
||||
use_v3d_shade_ex_pie=kc_prefs.use_v3d_shade_ex_pie,
|
||||
use_gizmo_drag=(is_select_left and kc_prefs.gizmo_action == 'DRAG'),
|
||||
use_fallback_tool=True,
|
||||
use_fallback_tool_select_handled=(
|
||||
# LMB doesn't need additional selection fallback key-map items.
|
||||
False if is_select_left else
|
||||
# RMB is select and RMB must trigger the fallback tool.
|
||||
# Otherwise LMB activates the fallback tool and RMB always tweak-selects.
|
||||
(kc_prefs.rmb_action != 'FALLBACK_TOOL')
|
||||
),
|
||||
use_alt_tool_or_cursor=(
|
||||
(not use_mouse_emulate_3_button) and
|
||||
(kc_prefs.use_alt_tool if is_select_left else kc_prefs.use_alt_cursor)
|
||||
),
|
||||
use_alt_click_leader=kc_prefs.use_alt_click_leader,
|
||||
use_pie_click_drag=kc_prefs.use_pie_click_drag,
|
||||
use_file_single_click=kc_prefs.use_file_single_click,
|
||||
use_alt_navigation=kc_prefs.use_alt_navigation,
|
||||
),
|
||||
)
|
||||
|
||||
if platform == "darwin":
|
||||
from bl_keymap_utils.platform_helpers import keyconfig_data_oskey_from_ctrl_for_macos
|
||||
keyconfig_data = keyconfig_data_oskey_from_ctrl_for_macos(keyconfig_data)
|
||||
|
||||
keyconfig_init_from_data(kc, keyconfig_data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bpy.utils.register_class(Prefs)
|
||||
load()
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# SPDX-FileCopyrightText: 2018-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Notes on this key-map:
|
||||
#
|
||||
# This uses Blender's key-map, running with `legacy=True`.
|
||||
#
|
||||
# The intention of this key-map is to match Blender 2.7x which had many more key-map items available.
|
||||
#
|
||||
# There are some differences with the original Blender 2.7x key-map.
|
||||
# There is no intention to change these are they are not considered significant
|
||||
# enough to make a 1:1 match with the previous Blender version.
|
||||
#
|
||||
# These include:
|
||||
#
|
||||
# 3D View
|
||||
# =======
|
||||
#
|
||||
# - Border Render (`Shift-B` -> `Ctrl-B`)
|
||||
# Both `Shift-B` and `Ctrl-B` were used.
|
||||
#
|
||||
# Time Line/Animation Views
|
||||
# =========================
|
||||
#
|
||||
# - Start Frame/End Frame (`S/E` -> `Ctrl-Home/Ctrl-End`)
|
||||
#
|
||||
|
||||
import os
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
DIRNAME, FILENAME = os.path.split(__file__)
|
||||
IDNAME = os.path.splitext(FILENAME)[0]
|
||||
|
||||
|
||||
def update_fn(_self, _context):
|
||||
load()
|
||||
|
||||
|
||||
class Prefs(bpy.types.KeyConfigPreferences):
|
||||
bl_idname = IDNAME
|
||||
|
||||
select_mouse: EnumProperty(
|
||||
name="Select Mouse",
|
||||
items=(
|
||||
('LEFT', "Left",
|
||||
"Use left mouse button for selection. "
|
||||
"The standard behavior that works well for mouse, trackpad and tablet devices"),
|
||||
('RIGHT', "Right",
|
||||
"Use right mouse button for selection, and left mouse button for actions. "
|
||||
"This works well primarily for keyboard and mouse devices"),
|
||||
),
|
||||
description=(
|
||||
"Mouse button used for selection"
|
||||
),
|
||||
default='RIGHT',
|
||||
update=update_fn,
|
||||
)
|
||||
|
||||
def draw(self, layout):
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
col = layout.column()
|
||||
col.row().prop(self, "select_mouse", text="Select with Mouse Button", expand=True)
|
||||
|
||||
|
||||
blender_default = bpy.utils.execfile(os.path.join(DIRNAME, "keymap_data", "blender_default.py"))
|
||||
|
||||
|
||||
def load():
|
||||
from sys import platform
|
||||
from bpy import context
|
||||
from bl_keymap_utils.io import keyconfig_init_from_data
|
||||
|
||||
prefs = context.preferences
|
||||
kc = context.window_manager.keyconfigs.new(IDNAME)
|
||||
kc_prefs = kc.preferences
|
||||
|
||||
keyconfig_data = blender_default.generate_keymaps(
|
||||
blender_default.Params(
|
||||
select_mouse=kc_prefs.select_mouse,
|
||||
use_mouse_emulate_3_button=(
|
||||
prefs.inputs.use_mouse_emulate_3_button and
|
||||
prefs.inputs.mouse_emulate_3_button_modifier == 'ALT'
|
||||
),
|
||||
spacebar_action='SEARCH',
|
||||
use_select_all_toggle=True,
|
||||
use_gizmo_drag=False,
|
||||
legacy=True,
|
||||
),
|
||||
)
|
||||
|
||||
if platform == "darwin":
|
||||
from bl_keymap_utils.platform_helpers import keyconfig_data_oskey_from_ctrl_for_macos
|
||||
keyconfig_data = keyconfig_data_oskey_from_ctrl_for_macos(keyconfig_data)
|
||||
|
||||
keyconfig_init_from_data(kc, keyconfig_data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bpy.utils.register_class(Prefs)
|
||||
load()
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# SPDX-FileCopyrightText: 2019-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
import bpy
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Keymap
|
||||
|
||||
DIRNAME, FILENAME = os.path.split(__file__)
|
||||
IDNAME = os.path.splitext(FILENAME)[0]
|
||||
|
||||
|
||||
def update_fn(_self, _context):
|
||||
load()
|
||||
|
||||
|
||||
industry_compatible = bpy.utils.execfile(os.path.join(DIRNAME, "keymap_data", "industry_compatible_data.py"))
|
||||
|
||||
|
||||
def load():
|
||||
from sys import platform
|
||||
from bl_keymap_utils.io import keyconfig_init_from_data
|
||||
|
||||
prefs = bpy.context.preferences
|
||||
|
||||
kc = bpy.context.window_manager.keyconfigs.new(IDNAME)
|
||||
params = industry_compatible.Params(use_mouse_emulate_3_button=prefs.inputs.use_mouse_emulate_3_button)
|
||||
keyconfig_data = industry_compatible.generate_keymaps(params)
|
||||
|
||||
if platform == "darwin":
|
||||
from bl_keymap_utils.platform_helpers import keyconfig_data_oskey_from_ctrl_for_macos
|
||||
keyconfig_data = keyconfig_data_oskey_from_ctrl_for_macos(keyconfig_data)
|
||||
|
||||
keyconfig_init_from_data(kc, keyconfig_data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
load()
|
||||
+5509
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Maya Config Pro - Utilities Package
|
||||
Provides version detection and compatibility utilities for multi-version Blender support.
|
||||
"""
|
||||
|
||||
from . import version
|
||||
from . import compat
|
||||
|
||||
__all__ = ['version', 'compat']
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
This module provides API compatibility functions for handling differences
|
||||
between Blender 4.2, 4.4, 4.5, and 5.0.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from . import version
|
||||
|
||||
|
||||
def safe_register_class(cls):
|
||||
"""
|
||||
Safely register a class, handling any version-specific registration issues.
|
||||
|
||||
Args:
|
||||
cls: The class to register
|
||||
|
||||
Returns:
|
||||
bool: True if registration succeeded, False otherwise
|
||||
"""
|
||||
try:
|
||||
register_class(cls)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to register {cls.__name__}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def safe_unregister_class(cls):
|
||||
"""
|
||||
Safely unregister a class, handling any version-specific unregistration issues.
|
||||
|
||||
Args:
|
||||
cls: The class to unregister
|
||||
|
||||
Returns:
|
||||
bool: True if unregistration succeeded, False otherwise
|
||||
"""
|
||||
try:
|
||||
unregister_class(cls)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to unregister {cls.__name__}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_tools_region_type():
|
||||
"""
|
||||
Returns the correct region type for tools panels.
|
||||
In Blender 2.8+, 'TOOLS' was replaced by 'UI'.
|
||||
|
||||
Returns:
|
||||
str: 'UI' (for all supported versions 4.2+)
|
||||
"""
|
||||
# For Blender 4.2 and later, always use 'UI'
|
||||
return 'UI'
|
||||
|
||||
|
||||
def temp_override(context, **kwargs):
|
||||
"""
|
||||
Provides a context manager for temporary context overrides.
|
||||
Handles the API change from context.copy() to context.temp_override().
|
||||
|
||||
Args:
|
||||
context: The current Blender context
|
||||
**kwargs: Keyword arguments for override values
|
||||
|
||||
Returns:
|
||||
context manager for temporary override
|
||||
"""
|
||||
if version.is_version_at_least(3, 2, 0):
|
||||
# Blender 3.2+ uses temp_override
|
||||
return context.temp_override(**kwargs)
|
||||
else:
|
||||
# Older versions - this shouldn't happen for 4.2+ minimum
|
||||
# but keeping for safety
|
||||
override = context.copy()
|
||||
override.update(kwargs)
|
||||
return override
|
||||
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Deploy Maya Config Pro bundled assets into the user's Blender config/scripts folders.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def addon_root() -> str:
|
||||
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def user_keyconfig_dir() -> str:
|
||||
return bpy.utils.user_resource(
|
||||
"SCRIPTS",
|
||||
path=os.path.join("presets", "keyconfig"),
|
||||
create=True,
|
||||
)
|
||||
|
||||
|
||||
def bundled_keyconfig_dir() -> str:
|
||||
return os.path.join(
|
||||
addon_root(), "portable", "scripts", "presets", "keyconfig"
|
||||
)
|
||||
|
||||
|
||||
def bundled_startup_blend() -> str:
|
||||
return os.path.join(addon_root(), "portable", "config", "startup.blend")
|
||||
|
||||
|
||||
def user_startup_blend() -> str:
|
||||
return os.path.join(bpy.utils.user_resource("CONFIG"), "startup.blend")
|
||||
|
||||
|
||||
def blender_bundled_keyconfig_dir() -> str | None:
|
||||
"""Directory shipped with the running Blender install (contains keymap_data)."""
|
||||
try:
|
||||
local = bpy.utils.resource_path("LOCAL")
|
||||
except Exception:
|
||||
return None
|
||||
if not local:
|
||||
return None
|
||||
path = os.path.join(local, "scripts", "presets", "keyconfig")
|
||||
return path if os.path.isdir(path) else None
|
||||
|
||||
|
||||
def is_keyconfig_deployed() -> bool:
|
||||
kc = user_keyconfig_dir()
|
||||
if not os.path.isdir(os.path.join(kc, "keymap_data")):
|
||||
return False
|
||||
for name in ("Blender.py", "Industry_Compatible.py", "fa_hotkeys.py"):
|
||||
if not os.path.isfile(os.path.join(kc, name)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_startup_bundle_available() -> bool:
|
||||
return os.path.isfile(bundled_startup_blend())
|
||||
|
||||
|
||||
def deploy_keymap_presets() -> tuple[bool, str]:
|
||||
"""
|
||||
Copy Blender's default keyconfig tree from LOCAL, then overlay FA hotkeys from this add-on.
|
||||
"""
|
||||
src_install = blender_bundled_keyconfig_dir()
|
||||
if not src_install:
|
||||
return False, "Could not find Blender's bundled keyconfig folder (full installation required)."
|
||||
|
||||
dst = user_keyconfig_dir()
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
|
||||
try:
|
||||
shutil.copytree(src_install, dst, dirs_exist_ok=True)
|
||||
except OSError as e:
|
||||
return False, f"Could not copy keyconfig presets: {e}"
|
||||
|
||||
portable_fa = os.path.join(bundled_keyconfig_dir(), "fa_hotkeys.py")
|
||||
if os.path.isfile(portable_fa):
|
||||
try:
|
||||
shutil.copy2(portable_fa, os.path.join(dst, "fa_hotkeys.py"))
|
||||
except OSError as e:
|
||||
return False, f"Could not install fa_hotkeys.py: {e}"
|
||||
else:
|
||||
return False, "Add-on is missing portable/scripts/presets/keyconfig/fa_hotkeys.py."
|
||||
|
||||
return True, f"Keymap presets deployed to:\n{dst}"
|
||||
|
||||
|
||||
def deploy_startup_blend() -> tuple[bool, str]:
|
||||
src = bundled_startup_blend()
|
||||
if not os.path.isfile(src):
|
||||
return False, "No startup.blend is bundled with this add-on (see portable/config/)."
|
||||
|
||||
dst = user_startup_blend()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
except OSError as e:
|
||||
return False, f"Could not copy startup.blend: {e}"
|
||||
|
||||
return True, f"startup.blend installed to:\n{dst}\nReload startup file or restart Blender to apply."
|
||||
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
This module provides version detection and comparison utilities for
|
||||
multi-version Blender support (4.2, 4.4, 4.5, and 5.0).
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def get_blender_version():
|
||||
"""
|
||||
Returns the current Blender version as a tuple (major, minor, patch).
|
||||
|
||||
Returns:
|
||||
tuple: (major, minor, patch) version numbers
|
||||
"""
|
||||
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"
|
||||
"""
|
||||
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
|
||||
"""
|
||||
current = get_blender_version()
|
||||
target = (major, minor, patch)
|
||||
|
||||
if current[0] != target[0]:
|
||||
return current[0] > target[0]
|
||||
if current[1] != target[1]:
|
||||
return current[1] > target[1]
|
||||
return current[2] >= target[2]
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
def is_version_4_4():
|
||||
"""Check if running Blender 4.4 (4.4.x only, not 4.5)."""
|
||||
version = get_blender_version()
|
||||
return version[0] == 4 and version[1] == 4
|
||||
|
||||
|
||||
def is_version_4_5():
|
||||
"""Check if running 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)
|
||||
Reference in New Issue
Block a user