Init Repo

This commit is contained in:
indigo 2026-03-30 09:55:19 +08:00
commit 873f4e97e1
24 changed files with 2158 additions and 0 deletions

147
.gitignore vendored Normal file
View File

@ -0,0 +1,147 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,c,c++,cmake
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,c,c++,cmake
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### C++ ###
# Prerequisites
# Compiled Object files
*.slo
# Precompiled Headers
# Compiled Dynamic libraries
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
# Executables
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
### CMake Patch ###
CMakeUserPresets.json
# External projects
*-prefix/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,c,c++,cmake
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
build/
install/

31
AGENT.md Normal file
View File

@ -0,0 +1,31 @@
IMPORTANT : You must use agent team feature. Do not do any work your self.
你唯一的工作就是與我溝通對話並擔任團隊領導與協調工作。做其他事情前務必都透過子任務交付給隊友不要阻塞與我對話的主線程我要隨時追蹤進度建立多少隊友或團隊如何分工都可以但至少配置一名QA人員負責撰寫測試項目並做所有開發完成後的功能驗收確認如有錯誤需指派給原本負責的人員處理目標最快速完成所有功能
Create a Maya Deformer Node use CMake + C++ API project which is deform mesh as checke valid license file
## Tech
開發語言請使用C++.cpp與.h請放置在src下外部prebuild library請放在third_party下加入CMakePresets.json並支援Maya 2022到Maya 2026的版本編譯
## 功能
這是 Maya 的 Plugin Node, 功能包含
1. Deform Object to noise with invalid license
2. license format is contain valid date
7. 建立Node完成後請編寫AETemplate, 名稱規則為 AE[NodeName]Template.mel
8. `.mll` 放到plug-ins/[MayaVersion]內, `.mel` 放到scripts, `.png` `.svg` 放到icons下
## 測試環境
使用Maya 2023作為測試環境
Maya路徑為 `C:\Program Files\Autodesk\Maya2023\bin\maya.exe`
MayaBatch路徑為 `C:\Program Files\Autodesk\Maya2023\bin\mayabatch.exe`
MayaPy路徑為 `C:\Program Files\Autodesk\Maya2023\bin\mayapy.exe`
* 啟動 Plugin 編譯完成後測試請使用環境變數 `MAYA_MODULE_PATH` 指定到編譯install下包含.mod的目錄下
* 測試 Load Plugin 使用 mayapy.exe
* 測試 viewport 顯示部分請使用maya.exe + MEL 測試
團隊自行決定未定義的的其他技術細節與檔案名稱,確認開發功能都完成後告訴我

25
CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.21)
project(ValidToolkit)
# C++ Standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build configuration
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "" FORCE)
# Output directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# Add cmake modules path
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# Source files - include subdirectory CMakeLists.txt
add_subdirectory(src)

124
CMakePresets.json Normal file
View File

@ -0,0 +1,124 @@
{
"version": 3,
"configurePresets": [
{
"name": "base",
"hidden": true,
"binaryDir": "${sourceDir}/build",
"installDir": "${sourceDir}/install",
"generator": "Visual Studio 17 2022",
"architecture": {
"value": "x64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "maya2022",
"displayName": "Maya 2022",
"inherits": "base",
"cacheVariables": {
"MAYA_VERSION": "2022",
"MAYA_LOCATION": "C:/Program Files/Autodesk/Maya2022",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "maya2023",
"displayName": "Maya 2023",
"inherits": "base",
"cacheVariables": {
"MAYA_VERSION": "2023",
"MAYA_LOCATION": "C:/Program Files/Autodesk/Maya2023",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "maya2024",
"displayName": "Maya 2024",
"inherits": "base",
"cacheVariables": {
"MAYA_VERSION": "2024",
"MAYA_LOCATION": "C:/Program Files/Autodesk/Maya2024",
"MAYALIB_LIBRARY": "C:/Program Files/Autodesk/Maya2024/lib",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "maya2025",
"displayName": "Maya 2025",
"inherits": "base",
"cacheVariables": {
"MAYA_VERSION": "2025",
"MAYA_LOCATION": "C:/Program Files/Autodesk/Maya2025",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "maya2026",
"displayName": "Maya 2026",
"inherits": "base",
"cacheVariables": {
"MAYA_VERSION": "2026",
"MAYA_LOCATION": "C:/Program Files/Autodesk/Maya2026",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "debug",
"displayName": "Debug Build",
"inherits": "maya2023",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release Build",
"inherits": "maya2023",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
],
"buildPresets": [
{
"name": "debug",
"configurePreset": "debug",
"configuration": "Debug"
},
{
"name": "release",
"configurePreset": "release",
"configuration": "Release"
},
{
"name": "maya2022",
"configurePreset": "maya2022",
"configuration": "Release"
},
{
"name": "maya2023",
"configurePreset": "maya2023",
"configuration": "Release"
},
{
"name": "maya2024",
"configurePreset": "maya2024",
"configuration": "Release"
},
{
"name": "maya2025",
"configurePreset": "maya2025",
"configuration": "Release"
},
{
"name": "maya2026",
"configurePreset": "maya2026",
"configuration": "Release"
}
]
}

116
README.md Normal file
View File

@ -0,0 +1,116 @@
# ValidShape
Maya Deformer Node Plugin for mesh validation with license checking
## 功能
### ValidShape Plugin
- **ValidShape** - Maya Deformer Node可根據授權檔案驗證並對 Mesh 進行變形
- 檢查授權檔案格式是否包含有效日期
- 當授權無效時,使用雜訊對 Mesh 進行變形Deform Object to noise
- 支援 Maya 2022 至 Maya 2026
### ProtectAsset Plugin
- **ProtectAsset** - 加密保護 Maya 資產的命令
- 對 Mesh 幾何進行加密處理
- 自動掛載解密 Deformer
## 專案結構
```
ValidShape/
├── CMakeLists.txt # Root CMake configuration
├── CMakePresets.json # CMake presets (Maya 2022-2026)
├── src/
│ ├── ValidAsset/ # ValidShape plugin source
│ │ ├── ValidShape.cpp/h # Deformer node implementation
│ │ ├── LicenseChecker.cpp/h
│ │ ├── EncryptCommand.cpp/h
│ │ ├── pluginMain.cpp
│ │ └── scripts/
│ │ └── AEValidShapeTemplate.mel
│ ├── ProtectAsset/ # ProtectAsset plugin source
│ │ ├── ProtectAssetCommand.cpp/h
│ │ └── plugin.cpp
│ └── CipherProvider.h # Shared encryption utilities
├── cmake/modules/
│ └── FindMaya.cmake # Maya SDK finder
├── install/ # Installed plugins
├── icons/ # Icons
└── build/ # Build output
```
## 編譯需求
### 必要工具
- Visual Studio 2022
- CMake 3.21+
- Maya SDK 2022/2023/2024/2025/2026
### 編譯方式
```bash
# 使用 CMake Preset 編譯
cmake --preset release
cmake --build build --config Release
# 安裝
cmake --install build --config Release
```
### Maya 版本對應
| CMake Preset | Maya 版本 |
|-------------|-----------|
| maya2022 | Maya 2022 |
| maya2023 | Maya 2023 |
| maya2024 | Maya 2024 |
| maya2025 | Maya 2025 |
| maya2026 | Maya 2026 |
## 測試方式
### 設定環境變數
```bash
# 設定 Maya module 路徑
set MAYYA_MODULE_PATH=D:\workspace\ValidShape\install
```
### 測試指令
#### 使用 mayapy.exe 測試 Load Plugin
```bash
"C:\Program Files\Autodesk\Maya2023\bin\mayapy.exe"
```
#### 使用 maya.exe + MEL 測試 Viewport 顯示
```bash
"C:\Program Files\Autodesk\Maya2023\bin\maya.exe"
```
### MEL 測試指令
```mel
# Load plugin
loadPlugin "D:/workspace/ValidShape/install/plug-ins/2023/ValidShape.mll";
# Create test mesh
polySphere;
# Apply ValidShape deformer
select pSphere1;
deformer -type "ValidShape";
```
## 輸出檔案
編譯後的 plugin 會輸出至:
| 類型 | 路徑 |
|-----|------|
| `.mll` | `build/bin/Release/[PluginName].mll` |
| `.mod` | `install/ValidShape.mod` |
| MEL | `install/scripts/AEValidShapeTemplate.mel` |
## 授權
Copyright © 2024. All rights reserved.

View File

@ -0,0 +1,184 @@
# FindMaya.cmake - CMake module for finding Autodesk Maya SDK
# This module provides functions to find Maya include directories and libraries
#[=======================================================================[
FindMaya
--------
This module finds the Maya SDK installation and provides variables
and functions for building Maya plugins.
Imported Targets
^^^^^^^^^^^^^^^^
This module does not define any library targets.
Result Variables
^^^^^^^^^^^^^^^
This module defines the following variables:
::
MAYA_FOUND - True if Maya SDK was found
MAYA_INCLUDE_DIR - Maya include directories
MAYA_LIBRARIES - Maya libraries to link against
MAYA_VERSION - The version of Maya found (e.g., 2023)
MAYA_LOCATION - The root Maya installation directory
MAYA_BIN_DIR - Maya bin directory
MAYA_LIB_DIR - Maya lib directory
MAYA_PLUGINS_DIR - Maya plugins directory
Hints
^^^^^
The user may set the following variables to help this module find what
they want:
::
MAYA_VERSION - Specify a specific Maya version to search for
(e.g., 2022, 2023, 2024, 2025, 2026)
MAYA_LOCATION - Specify the root Maya installation directory
(e.g., C:/Program Files/Autodesk/Maya2023)
#]=======================================================================]
# Check if already found
if(MAYA_FOUND AND MAYA_INCLUDE_DIR)
return()
endif()
# Default Maya version if not specified
if(NOT DEFINED MAYA_VERSION)
set(MAYA_VERSION 2023)
endif()
# List of supported Maya versions
set(_MAYA_SUPPORTED_VERSIONS "2026;2025;2024;2023;2022")
# Check if the specified Maya version is supported
if(NOT MAYA_VERSION IN_LIST _MAYA_SUPPORTED_VERSIONS)
message(WARNING "Maya version ${MAYA_VERSION} is not in the supported list: ${_MAYA_SUPPORTED_VERSIONS}")
endif()
# Search paths
set(_MAYA_SEARCH_PATHS)
# Add user-specified MAYA_LOCATION
if(MAYA_LOCATION)
list(APPEND _MAYA_SEARCH_PATHS "${MAYA_LOCATION}")
endif()
if(ENV{MAYA_LOCATION})
list(APPEND _MAYA_SEARCH_PATHS "$ENV{MAYA_LOCATION}")
endif()
message(STATUS ${_MAYA_SEARCH_PATHS})
# Add environment variable paths
foreach(_env_var IN ITEMS MAYA_LOCATION MAYA_SDK_DIR MAYA_SDK_ROOT)
if(NOT "${${_env_var}}" STREQUAL "")
list(APPEND _MAYA_SEARCH_PATHS "${${_env_var}}")
endif()
endforeach()
# Add Program Files paths for all supported versions
#foreach(_version IN ITEMS 2026 2025 2024 2023 2022)
# list(APPEND _MAYA_SEARCH_PATHS "C:/Program Files/Autodesk/Maya${_version}")
#endforeach()
# Find Maya include directory
find_path(MAYA_INCLUDE_DIR
NAMES maya/MFn.h mapi_ui_control.h
HINTS ${_MAYA_SEARCH_PATHS}
PATH_SUFFIXES include
)
# Find Maya library directory
find_path(MAYA_LIB_DIR
NAMES OpenMaya.lib
HINTS ${_MAYA_SEARCH_PATHS}
PATH_SUFFIXES lib
)
# Find Maya bin directory
find_path(MAYA_BIN_DIR
NAMES maya.exe
HINTS ${_MAYA_SEARCH_PATHS}
PATH_SUFFIXES bin
)
# Extract Maya version from include path if not specified
if(MAYA_INCLUDE_DIR AND NOT MAYA_VERSION)
foreach(_version IN ITEMS 2026 2025 2024 2023 2022)
if(MAYA_INCLUDE_DIR MATCHES "Maya${_version}")
set(MAYA_VERSION ${_version})
break()
endif()
endforeach()
endif()
# Find Maya libraries
set(_MAYA_REQUIRED_LIBS
OpenMaya
Foundation
)
set(_MAYA_OPTIONAL_LIBS
OpenMayaUI
OpenMayaAnim
OpenMayaRender
)
set(MAYA_LIBRARIES)
foreach(_lib IN ITEMS ${_MAYA_REQUIRED_LIBS})
find_library(_MAYA_LIB_${_lib}
NAMES "${_lib}.lib"
HINTS ${MAYA_LIB_DIR}
NO_DEFAULT_PATH
)
if(_MAYA_LIB_${_lib})
list(APPEND MAYA_LIBRARIES "${_MAYA_LIB_${_lib}}")
else()
message(FATAL_ERROR "Could not find required Maya library: ${_lib}.lib")
endif()
endforeach()
foreach(_lib IN ITEMS ${_MAYA_OPTIONAL_LIBS})
find_library(_MAYA_LIB_${_lib}
NAMES "${_lib}.lib"
HINTS ${MAYA_LIB_DIR}
NO_DEFAULT_PATH
)
if(_MAYA_LIB_${_lib})
list(APPEND MAYA_LIBRARIES "${_MAYA_LIB_${_lib}}")
endif()
endforeach()
# Get Maya root directory from include path
get_filename_component(MAYA_LOCATION "${MAYA_INCLUDE_DIR}" DIRECTORY)
get_filename_component(MAYA_LOCATION "${MAYA_LOCATION}" DIRECTORY)
# Set plugins directory
set(MAYA_PLUGINS_DIR "${MAYA_LOCATION}/plug-ins")
# Handle success/failure
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Maya
REQUIRED_VARS
MAYA_INCLUDE_DIR
MAYA_LIB_DIR
MAYA_LIBRARIES
VERSION_VAR MAYA_VERSION
)
# Print configuration info
if(MAYA_FOUND)
message(STATUS "Maya SDK found:")
message(STATUS " Version: ${MAYA_VERSION}")
message(STATUS " Location: ${MAYA_LOCATION}")
message(STATUS " Include: ${MAYA_INCLUDE_DIR}")
message(STATUS " Library: ${MAYA_LIB_DIR}")
endif()
# Mark as found
mark_as_advanced(MAYA_INCLUDE_DIR MAYA_LIB_DIR MAYA_BIN_DIR)

177
plans/plan.md Normal file
View File

@ -0,0 +1,177 @@
# ValidShape - Maya Deformer Node 開發計畫
## 專案概述
建立一個 Maya Deformer Node Plugin透過 License 檔案驗證來控制網格變形效果。
## 技術規格
- **開發語言**: C++
- **建置系統**: CMake + CMakePresets.json
- **Maya 版本支援**: Maya 2022 - Maya 2026
- **測試環境**: Maya 2023
## 功能需求
1. **Deform Object**: 當 License 無效時,網格會被 Noise 變形
2. **License 格式**: 包含有效日期 (格式: YYYY-MM-DD)
3. **License 檔案**: 透過節點屬性指定路徑
## 團隊分工
### 角色分配
| 角色 | 姓名 | 職責 |
|------|------|------|
| Team Lead | - | 專案協調與進度追蹤 |
| C++ Developer | Dev1 | Maya Node 核心程式開發 |
| C++ Developer | Dev2 | License 檢查與解析模組 |
| QA Engineer | QA1 | 測試腳本撰寫與功能驗證 |
| Build Engineer | Build1 | CMake 建置系統與編譯環境 |
### 工作流程
1. 先由 Build Engineer 建立 CMake 專案結構
2. C++ Developers 實作核心功能
3. QA Engineer 撰寫測試腳本
4. 團隊協作進行編譯與測試
## 檔案結構
```
ValidShape/
├── CMakeLists.txt
├── CMakePresets.json
├── src/
│ ├── ValidShapeNode.cpp
│ ├── ValidShapeNode.h
│ ├── LicenseChecker.cpp
│ └── LicenseChecker.h
├── third_party/
│ └── (Maya SDK headers)
├── plug-ins/
│ └── [MayaVersion]/
├── scripts/
├── icons/
├── plans/
│ └── todo.md
└── install/
└── (編譯產出)
```
## 預期產出
- `.mll` plugin 檔案 (置於 plug-ins/[Version]/)
- `.mel` AETemplate 檔案 (置於 scripts/)
- `.png` / `.svg` 圖示檔案 (置於 icons/)
- `.mod` module 檔案 (用於 MAYA_MODULE_PATH)
## 專案概述
建立一個 Maya Deformer Node Plugin透過 License 檔案驗證來控制網格變形效果。
## 技術規格
- **開發語言**: C++
- **建置系統**: CMake + CMakePresets.json
- **Maya 版本支援**: Maya 2022 - Maya 2026
- **測試環境**: Maya 2023
## 功能需求
1. **Deform Object**: 當 License 無效時,網格會被 Noise 變形
2. **License 格式**: 包含有效日期 (格式: YYYY-MM-DD)
3. **License 檔案**: 透過節點屬性指定路徑
## 團隊分工
### 角色分配
| 角色 | 姓名 | 職責 |
|------|------|------|
| Team Lead | - | 專案協調與進度追蹤 |
| C++ Developer | Dev1 | Maya Node 核心程式開發 |
| C++ Developer | Dev2 | License 檢查與解析模組 |
| QA Engineer | QA1 | 測試腳本撰寫與功能驗證 |
| Build Engineer | Build1 | CMake 建置系統與編譯環境 |
### 工作流程
1. 先由 Build Engineer 建立 CMake 專案結構
2. C++ Developers 實作核心功能
3. QA Engineer 撰寫測試腳本
4. 團隊協作進行編譯與測試
## 檔案結構
```
ValidShape/
├── CMakeLists.txt
├── CMakePresets.json
├── src/
│ ├── ValidShapeNode.cpp
│ ├── ValidShapeNode.h
│ ├── LicenseChecker.cpp
│ └── LicenseChecker.h
├── third_party/
│ └── (Maya SDK headers)
├── plug-ins/
│ └── [MayaVersion]/
├── scripts/
├── icons/
├── plans/
│ └── todo.md
└── install/
└── (編譯產出)
```
## 預期產出
- `.mll` plugin 檔案 (置於 plug-ins/[Version]/)
- `.mel` AETemplate 檔案 (置於 scripts/)
- `.png` / `.svg` 圖示檔案 (置於 icons/)
- `.mod` module 檔案 (用於 MAYA_MODULE_PATH)
## 專案概述
建立一個 Maya Deformer Node Plugin透過 License 檔案驗證來控制網格變形效果。
## 技術規格
- **開發語言**: C++
- **建置系統**: CMake + CMakePresets.json
- **Maya 版本支援**: Maya 2022 - Maya 2026
- **測試環境**: Maya 2023
## 功能需求
1. **Deform Object**: 當 License 無效時,網格會被 Noise 變形
2. **License 格式**: 包含有效日期 (格式: YYYY-MM-DD)
3. **License 檔案**: 透過節點屬性指定路徑
## 團隊分工
### 角色分配
| 角色 | 姓名 | 職責 |
|------|------|------|
| Team Lead | - | 專案協調與進度追蹤 |
| C++ Developer | Dev1 | Maya Node 核心程式開發 |
| C++ Developer | Dev2 | License 檢查與解析模組 |
| QA Engineer | QA1 | 測試腳本撰寫與功能驗證 |
| Build Engineer | Build1 | CMake 建置系統與編譯環境 |
### 工作流程
1. 先由 Build Engineer 建立 CMake 專案結構
2. C++ Developers 實作核心功能
3. QA Engineer 撰寫測試腳本
4. 團隊協作進行編譯與測試
## 檔案結構
```
ValidShape/
├── CMakeLists.txt
├── CMakePresets.json
├── src/
│ ├── ValidShapeNode.cpp
│ ├── ValidShapeNode.h
│ ├── LicenseChecker.cpp
│ └── LicenseChecker.h
├── third_party/
│ └── (Maya SDK headers)
├── plug-ins/
│ └── [MayaVersion]/
├── scripts/
├── icons/
├── plans/
│ └── todo.md
└── install/
└── (編譯產出)
```
## 預期產出
- `.mll` plugin 檔案 (置於 plug-ins/[Version]/)
- `.mel` AETemplate 檔案 (置於 scripts/)
- `.png` / `.svg` 圖示檔案 (置於 icons/)
- `.mod` module 檔案 (用於 MAYA_MODULE_PATH)

2
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,2 @@
add_subdirectory(ProtectAsset)
add_subdirectory(ValidAsset)

35
src/CipherProvider.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef CIPHER_PROVIDER_H
#define CIPHER_PROVIDER_H
#include <maya/MApiNamespace.h>
#include <maya/MPoint.h>
#include <random>
class CipherProvider {
public:
// 加密與解密共用同一個邏輯 (異或偏移或對稱位移)
// mode: true 為加密 (加上位移), false 為解密 (減去位移)
static MPoint processVertex(int vtxId, const MPoint& inputPt, double seed, bool encrypt) {
// 使用頂點 ID 作為隨機生成器的種子偏移,確保每個點位移不同但可預測
std::mt19937 gen(static_cast<unsigned int>(vtxId + seed));
std::uniform_real_distribution<double> dist(-5.0, 5.0); // 位移範圍 5 單位
double offsetX = dist(gen);
double offsetY = dist(gen);
double offsetZ = dist(gen);
MPoint result = inputPt;
if (encrypt) {
result.x += offsetX;
result.y += offsetY;
result.z += offsetZ;
} else {
result.x -= offsetX;
result.y -= offsetY;
result.z -= offsetZ;
}
return result;
}
};
#endif // CIPHER_PROVIDER_H

View File

@ -0,0 +1,55 @@
project(ProtectAsset)
set(SOURCE_FILES
"ProtectAssetCommand.cpp"
"ProtectAssetCommand.h"
"plugin.cpp"
)
# Find Maya SDK using custom FindMaya module
find_package(Maya REQUIRED)
# Include directories
include_directories(
${MAYA_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src
)
# Create the plugin library
add_library(ProtectAsset SHARED
${SOURCE_FILES}
)
# Target libraries
target_link_libraries(ProtectAsset
${MAYA_LIBRARIES}
)
# Platform-specific settings
if(WIN32)
target_compile_definitions(ProtectAsset PRIVATE
_CRT_SECURE_NO_WARNINGS
_USRDLL
_WINDLL
NT_PLUGIN
)
# Ignore code page 950 (Traditional Chinese) warnings for source files
target_compile_options(ProtectAsset PRIVATE "/wd4819")
set_target_properties(ProtectAsset PROPERTIES
OUTPUT_NAME "ProtectAsset"
PREFIX ""
SUFFIX ".mll"
)
elseif(UNIX)
set_target_properties(ProtectAsset PROPERTIES
OUTPUT_NAME "ProtectAsset"
PREFIX ""
SUFFIX ".so"
)
endif()
# Install rules
install(TARGETS ProtectAsset
RUNTIME DESTINATION plug-ins/${MAYA_VERSION}
LIBRARY DESTINATION plug-ins/${MAYA_VERSION}
)

View File

@ -0,0 +1,87 @@
#include "ProtectAssetCommand.h"
#include "CipherProvider.h"
#include <maya/MGlobal.h>
#include <maya/MSelectionList.h>
#include <maya/MFnDagNode.h>
#include <maya/MItGeometry.h>
#include <maya/MPlug.h>
MStatus ProtectAssetCommand::doIt(const MArgList& args) {
MSelectionList sel;
MGlobal::getActiveSelectionList(sel);
if (sel.length() == 0) {
// MGlobal::displayError("請選取要加密的 Mesh (Transform)。");
return MS::kFailure;
}
double secretSeed = 98765.4321; // 與 Deformer 相同的金鑰
for (unsigned int i = 0; i < sel.length(); ++i) {
MDagPath transformPath;
sel.getDagPath(i, transformPath);
// 1. 尋找該 Transform 下的原始數據層 (intermediateObject)
MObject targetShape = findOriginalShape(transformPath);
if (targetShape.isNull()) continue;
MFnDagNode shapeFn(targetShape);
// MGlobal::displayInfo("正在處理原始節點: " + shapeFn.fullPathName());
// 2. 執行物理座標加密 (直接修改 Base Mesh)
encryptBaseMesh(targetShape, secretSeed);
// 3. 自動掛載解密 Deformer (放在 SkinCluster 之後)
// 使用 MEL 以確保變形順序正確且自動處理連線
MString meshName = transformPath.partialPathName();
MString melCmd = "deformer -type \"SecureDeformer\" -after " + meshName;
MStringArray res;
if (MGlobal::executeCommand(melCmd, res) == MS::kSuccess) {
// MGlobal::displayInfo("已成功掛載解密節點: " + res[0]);
// 4. 對解密節點執行額外保護 (隱藏與鎖定)
lockDeformerNode(res[0]);
}
}
// MGlobal::displayInfo("資產加密保護程序已完成。");
return MS::kSuccess;
}
MObject ProtectAssetCommand::findOriginalShape(const MDagPath& transformPath) {
unsigned int childCount = transformPath.childCount();
MObject visibleShape;
for (unsigned int i = 0; i < childCount; ++i) {
MObject child = transformPath.child(i);
if (!child.hasFn(MFn::kMesh)) continue;
MFnDagNode dagFn(child);
MPlug ioPlug = dagFn.findPlug("intermediateObject", false);
bool isIo = false;
ioPlug.getValue(isIo);
if (isIo) return child; // 優先回傳原始層
visibleShape = child; // 備份可見層
}
return visibleShape; // 若無 Deform 歷史,則回傳唯一 Shape
}
void ProtectAssetCommand::encryptBaseMesh(MObject& shape, double seed) {
MItGeometry iter(shape);
for (; !iter.isDone(); iter.next()) {
MPoint pt = iter.position();
// 加密模式 (true)
MPoint encrypted = CipherProvider::processVertex(iter.index(), pt, seed, true);
iter.setPosition(encrypted);
}
}
void ProtectAssetCommand::lockDeformerNode(MString nodeName) {
// 鎖定節點防止刪除
MGlobal::executeCommand("lockNode -lock 1 " + nodeName);
// 隱藏在 UI 之外
MGlobal::executeCommand("setAttr -keyable false -channelBox false " + nodeName + ".envelope");
}

View File

@ -0,0 +1,14 @@
#include <maya/MPxCommand.h>
#include <maya/MSelectionList.h>
#include <maya/MDagPath.h>
#include <maya/MApiNamespace.h>
class ProtectAssetCommand : public MPxCommand {
public:
MStatus doIt(const MArgList& args) override;
static void* creator() { return new ProtectAssetCommand(); }
MObject findOriginalShape(const MDagPath& transformPath);
void encryptBaseMesh(MObject& shape, double seed);
void lockDeformerNode(MString nodeName);
};

View File

@ -0,0 +1,37 @@
#include <maya/MFnPlugin.h>
#include <maya/MStatus.h>
#include <maya/MGlobal.h>
#include <maya/MObject.h>
#include <maya/MApiNamespace.h>
#include "ProtectAssetCommand.h"
MStatus initializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj, "ProtectAsset", "1.0", "Any", &status);
if (status != MStatus::kSuccess) {
MGlobal::displayError("Failed to initialize ProtectAsset plugin");
return status;
}
status = plugin.registerCommand("protectAsset", ProtectAssetCommand::creator);
if (status != MStatus::kSuccess) {
MGlobal::displayError("Failed to register protectAsset command");
return status;
}
return status;
}
MStatus uninitializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj);
status = plugin.deregisterCommand("protectAsset");
if (!status) {
status.perror("Failed to deregister protectAsset command");
return status;
}
MGlobal::displayInfo("ProtectAsset plugin uninitialized successfully.");
return status;
}

View File

@ -0,0 +1,81 @@
# src/CMakeLists.txt - Source files for ValidShape plugin
#
# This file defines source files for the plugin.
# The root CMakeLists.txt includes this file via add_subdirectory().
project(ValidAsset)
# Full paths to source files (relative to project root)
set(SOURCE_FILES
ValidShape.cpp
ValidShape.h
LicenseChecker.cpp
LicenseChecker.h
EncryptCommand.cpp
EncryptCommand.h
pluginMain.cpp
)
# Find Maya SDK using custom FindMaya module
find_package(Maya REQUIRED)
# Include directories
include_directories(
${MAYA_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src/
)
# Create the plugin library
add_library(ValidAsset SHARED
${SOURCE_FILES}
)
# Target libraries
target_link_libraries(ValidAsset
${MAYA_LIBRARIES}
)
# Platform-specific settings
if(WIN32)
target_compile_definitions(ValidAsset PRIVATE
_CRT_SECURE_NO_WARNINGS
_USRDLL
_WINDLL
NT_PLUGIN
)
# Ignore code page 950 (Traditional Chinese) warnings for source files
target_compile_options(ValidAsset PRIVATE "/wd4819")
set_target_properties(ValidAsset PROPERTIES
OUTPUT_NAME "ValidShape"
PREFIX ""
SUFFIX ".mll"
)
elseif(UNIX)
set_target_properties(ValidAsset PROPERTIES
OUTPUT_NAME "ValidShape"
PREFIX ""
SUFFIX ".so"
)
endif()
# Install rules
install(TARGETS ValidAsset
RUNTIME DESTINATION plug-ins/${MAYA_VERSION}
LIBRARY DESTINATION plug-ins/${MAYA_VERSION}
)
install(FILES scripts/AEValidShapeTemplate.mel
DESTINATION scripts
)
install(DIRECTORY icons/
DESTINATION icons
FILES_MATCHING PATTERN "*.png" PATTERN "*.svg"
)
# Make these variables available to parent scope
set(VALIDSHAPE_SOURCES ${VALIDSHAPE_SOURCES} PARENT_SCOPE)
set(VALIDSHAPE_HEADERS ${VALIDSHAPE_HEADERS} PARENT_SCOPE)
# Group source files in IDEs
source_group("Source Files" FILES ${VALIDSHAPE_SOURCES})
source_group("Header Files" FILES ${VALIDSHAPE_HEADERS})

View File

@ -0,0 +1,27 @@
#include "EncryptCommand.h"
#include "CipherProvider.h"
#include <maya/MGlobal.h>
#include <maya/MItGeometry.h>
MStatus doIt(const MArgList& args){
MSelectionList sel;
MGlobal::getActiveSelectionList(sel);
MDagPath path;
sel.getDagPath(0, path);
MItGeometry iter(path);
double secretSeed = 98765.4321;
for (; !iter.isDone(); iter.next()) {
MPoint pt = iter.position();
// 執行加密:把正確的點「炸開」
pt = CipherProvider::processVertex(iter.index(), pt, secretSeed, true);
iter.setPosition(pt);
}
// 提醒使用者凍結位移 (Freeze Transformations)
MGlobal::displayInfo("Encryption Complete. Please Freeze Transformations now.");
return MS::kSuccess;
}

View File

@ -0,0 +1,20 @@
#include <maya/MPxCommand.h>
#include <maya/MSelectionList.h>
#include <maya/MDagPath.h>
#include <maya/MPxCommand.h>
#include <maya/MSelectionList.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItGeometry.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <maya/MStringArray.h>
#include "CipherProvider.h"
class EncryptCommand : public MPxCommand {
public:
MStatus doIt(const MArgList& args);
static void* creator() { return new EncryptCommand(); }
};

View File

@ -0,0 +1,158 @@
#include "LicenseChecker.h"
#include <fstream>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <algorithm>
LicenseChecker::LicenseChecker()
: m_expirationDate(-1)
, m_lastCheckTime(0)
{
}
LicenseChecker::~LicenseChecker()
{
}
bool LicenseChecker::isValidLicense(const std::string& licenseFilePath)
{
// Reset state
m_expirationDate = -1;
// Try to parse the license file
if (!parseLicenseFile(licenseFilePath))
{
return false;
}
// Update last check time
auto now = std::chrono::system_clock::now();
m_lastCheckTime = std::chrono::system_clock::to_time_t(now);
// Check if expired
return !isExpired();
}
time_t LicenseChecker::getExpirationDate() const
{
return m_expirationDate;
}
time_t LicenseChecker::getLastCheckTime() const
{
return m_lastCheckTime;
}
bool LicenseChecker::parseLicenseFile(const std::string& licenseFilePath)
{
std::ifstream file(licenseFilePath);
if (!file.is_open())
{
return false;
}
std::string line;
while (std::getline(file, line))
{
// Trim whitespace
line.erase(line.begin(), std::find_if(line.begin(), line.end(), [](int ch) {
return !std::isspace(ch);
}));
line.erase(std::find_if(line.rbegin(), line.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), line.end());
// Skip empty lines and comments
if (line.empty() || line[0] == '#')
{
continue;
}
// Look for date in format EXPIRE_DATE=YYYY-MM-DD or just YYYY-MM-DD
std::string dateValue;
// Check for EXPIRE_DATE=YYYY-MM-DD format
const std::string expirePrefix = "EXPIRE_DATE=";
if (line.compare(0, expirePrefix.length(), expirePrefix) == 0)
{
dateValue = line.substr(expirePrefix.length());
}
// Check for DATE=YYYY-MM-DD format
else if (line.compare(0, 5, "DATE=") == 0)
{
dateValue = line.substr(5);
}
// Check for EXPIRES=YYYY-MM-DD format
else if (line.compare(0, 8, "EXPIRES=") == 0)
{
dateValue = line.substr(8);
}
// Check if the line itself is a date in YYYY-MM-DD format
else if (line.length() == 10 && line[4] == '-' && line[7] == '-')
{
dateValue = line;
}
// If we found a date value, parse it
if (!dateValue.empty())
{
m_expirationDate = parseDateString(dateValue);
return true;
}
}
// If we couldn't find any date, the license is invalid
return false;
}
time_t LicenseChecker::parseDateString(const std::string& dateString)
{
// Expected format: YYYY-MM-DD
if (dateString.length() != 10)
{
return -1;
}
int year, month, day;
char dash1, dash2;
std::istringstream iss(dateString);
iss >> year >> dash1 >> month >> dash2 >> day;
if (dash1 != '-' || dash2 != '-')
{
return -1;
}
if (month < 1 || month > 12 || day < 1 || day > 31)
{
return -1;
}
// Create tm structure
std::tm tm = {};
tm.tm_year = year - 1900; // Years since 1900
tm.tm_mon = month - 1; // Months since January
tm.tm_mday = day;
tm.tm_hour = 23;
tm.tm_min = 59;
tm.tm_sec = 59;
// Convert to time_t
return std::mktime(&tm);
}
bool LicenseChecker::isExpired() const
{
if (m_expirationDate == -1)
{
return true; // No expiration date means expired
}
auto now = std::chrono::system_clock::now();
time_t nowTime = std::chrono::system_clock::to_time_t(now);
// Compare with expiration date
return nowTime > m_expirationDate;
}

View File

@ -0,0 +1,58 @@
#ifndef LICENSE_CHECKER_H
#define LICENSE_CHECKER_H
#include <string>
#include <ctime>
class LicenseChecker
{
public:
LicenseChecker();
~LicenseChecker();
/**
* Check if the license file is valid
* @param licenseFilePath Path to the license file
* @return true if license is valid (not expired), false otherwise
*/
bool isValidLicense(const std::string& licenseFilePath);
/**
* Get the expiration date from the license file
* @return Unix timestamp of expiration date, or -1 if not found
*/
time_t getExpirationDate() const;
/**
* Get the last check timestamp
* @return Unix timestamp of last check
*/
time_t getLastCheckTime() const;
private:
time_t m_expirationDate;
time_t m_lastCheckTime;
/**
* Parse the license file and extract expiration date
* Expected format: YYYY-MM-DD
* @param licenseFilePath Path to the license file
* @return true if parsing successful, false otherwise
*/
bool parseLicenseFile(const std::string& licenseFilePath);
/**
* Convert date string to Unix timestamp
* @param dateString Date in format YYYY-MM-DD
* @return Unix timestamp
*/
time_t parseDateString(const std::string& dateString);
/**
* Check if the license is expired
* @return true if expired, false if still valid
*/
bool isExpired() const;
};
#endif // LICENSE_CHECKER_H

View File

@ -0,0 +1,199 @@
#include "ValidShape.h"
#include "LicenseChecker.h"
#include <maya/MItGeometry.h>
#include <maya/MFnMessageAttribute.h>
#include <maya/MFnStringData.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <maya/MVector.h>
#include <maya/MDGModifier.h>
#include <cmath>
#include <ctime>
#include <cstdlib>
#include <cstring>
#include "CipherProvider.h"
// Static member initialization
MTypeId ValidShape::typeId(0x0013A2F0); // Reserved Maya ID
MString ValidShape::typeName("ValidShape");
MObject ValidShape::aLicenseFile;
MObject ValidShape::aValidLicense;
ValidShape::ValidShape()
{
m_licenseChecker = new LicenseChecker();
m_deleteCallbackId = 0;
}
// PostConstructor - called after the node is fully constructed
// This is where we can register callbacks safely
void ValidShape::postConstructor()
{
// Get the MObject for this node
MObject thisNode = this->thisMObject();
MFnDependencyNode depNode(thisNode);
depNode.setLocked(true);
depNode.setDoNotWrite(false);
MGlobal::executeCommand("setAttr -keyable false -channelBox false " + depNode.name() + ".visibility");
// Register the delete callback
registerDeleteCallback(thisNode);
}
ValidShape::~ValidShape()
{
// Remove delete callback
removeDeleteCallback();
delete m_licenseChecker;
m_licenseChecker = nullptr;
}
// DeleteAware callback - called when Maya tries to delete the node
// Returns false to prevent deletion if CGCG env var is not set
bool ValidShape::deleteAware()
{
// Check if CGCG environment variable is set
const char* cggcValue = std::getenv("CGCG");
// If CGCG is not set, prevent deletion
if (cggcValue == nullptr)
{
MGlobal::displayWarning("ValidShape cannot be deleted without CGCG environment variable set.");
return false;
}
// If CGCG is set, allow deletion
return true;
}
// Static callback function for node about to be deleted
void nodeAboutToDeleteCallback(MObject& node, MDGModifier& modifier, void* clientData)
{
// Check if CGCG environment variable is set
const char* cggcValue = std::getenv("CGCG");
// If CGCG is not set, try to prevent deletion by modifying the modifier
if (cggcValue == nullptr)
{
MGlobal::displayWarning("ValidShape cannot be deleted.");
// Note: We cannot truly prevent deletion with this callback,
// but we can display a warning
}
}
// Register the delete callback
MCallbackId ValidShape::registerDeleteCallback(MObject& node)
{
MStatus status;
m_deleteCallbackId = MNodeMessage::addNodeAboutToDeleteCallback(
node,
nodeAboutToDeleteCallback,
this,
&status
);
if (!status)
{
MGlobal::displayError("Failed to register delete callback for ValidShape.");
}
return m_deleteCallbackId;
}
// Remove the delete callback
void ValidShape::removeDeleteCallback()
{
if (m_deleteCallbackId != 0)
{
MMessage::removeCallback(m_deleteCallbackId);
m_deleteCallbackId = 0;
}
}
void* ValidShape::creator()
{
return new ValidShape();
}
MStatus ValidShape::initialize()
{
return MS::kSuccess;
}
MStatus ValidShape::deform(MDataBlock& dataBlock, MItGeometry& iter,
const MMatrix& mat, unsigned int multiIndex)
{
bool isLicenseValid = checkLicense();
// 獲取 Envelope (Maya 內建屬性0 為不作用1 為全作用)
float env = dataBlock.inputValue(envelope).asFloat();
if (env <= 0.001f) return MS::kSuccess;
double secretSeed = 98765.4321; // 必須與加密工具的 Seed 一致
for (; !iter.isDone(); iter.next()) {
MPoint pt = iter.position();
int id = iter.index();
if (isLicenseValid) {
// 執行解密:將「炸開」的點拉回正確位置
pt = CipherProvider::processVertex(id, pt, secretSeed, false);
} else {
// 授權失敗:可以故意讓它變更亂,或者乾脆不處理(維持炸開狀態)
MGlobal::displayError("INVALID LICENSE: Asset remains encrypted.");
}
iter.setPosition(pt);
}
return MS::kSuccess;
}
MStatus ValidShape::connectionBroken(const MPlug& plug, const MPlug& otherPlug, bool asSrc)
{
// 如果有人試圖斷開 inputGeom 或 outputGeom 的連線
if (plug.attribute() == inputGeom || plug.attribute() == outputGeom) {
MGlobal::displayError("Security Violation: Connections to this node are PERMANENT.");
return MS::kFailure; // 拒絕斷開
}
return MS::kUnknownParameter;
}
// Perlin-like noise implementation
double ValidShape::fade(double t) const
{
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}
double ValidShape::lerp(double a, double b, double t) const
{
return a + t * (b - a);
}
double ValidShape::noise3D(double x, double y, double z) const
{
// Simple 3D noise using sine waves
double value = sin(x * 1.5 + y * 2.3 + z * 0.8) * 0.5;
value += sin(x * 3.1 - y * 1.7 + z * 2.2) * 0.3;
value += sin(x * 0.9 + y * 3.8 - z * 1.3) * 0.2;
return value;
}
double ValidShape::smoothNoise3D(double x, double y, double z) const
{
// For more complex noise, use smooth interpolation
int xi = (int)floor(x) & 255;
int yi = (int)floor(y) & 255;
int zi = (int)floor(z) & 255;
double xf = x - floor(x);
double yf = y - floor(y);
double zf = z - floor(z);
double u = fade(xf);
double v = fade(yf);
double w = fade(zf);
// This is a simplified smooth noise
return noise3D(x, y, z);
}

View File

@ -0,0 +1,71 @@
#ifndef VALID_SHAPE_H
#define VALID_SHAPE_H
#include <maya/MPxDeformerNode.h>
#include <maya/MPxNode.h>
#include <maya/MNodeMessage.h>
#include <maya/MMessage.h>
#include <maya/MCallbackIdArray.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MObject.h>
#include <maya/MStatus.h>
#include <maya/MString.h>
// License Checker forward declaration
class LicenseChecker;
class ValidShape : public MPxDeformerNode
{
public:
ValidShape();
~ValidShape() override;
// Maya Deformer Node virtual methods
MStatus deform(MDataBlock& dataBlock, MItGeometry& iter,
const MMatrix& mat, unsigned int multiIndex) override;
// DeleteAware - prevent deletion when CGCG env var is not set
// Now using MNodeMessage::addNodeAboutToDeleteCallback instead
bool deleteAware();
// Register delete callback - called after node is fully constructed
// Note: MPxNode::postConstructor() returns void, not MStatus
void postConstructor() override;
// Register the delete callback
MCallbackId registerDeleteCallback(MObject& node);
void removeDeleteCallback();
static void* creator();
static MStatus initialize();
// Attribute creators
static MObject aLicenseFile;
static MObject aValidLicense;
// Node type name
static MString typeName;
static MTypeId typeId;
private:
// Internal methods
double noise3D(double x, double y, double z) const;
double smoothNoise3D(double x, double y, double z) const;
double fade(double t) const;
double lerp(double a, double b, double t) const;
// License checker instance
LicenseChecker* m_licenseChecker;
// Delete callback ID
MCallbackId m_deleteCallbackId;
virtual MStatus connectionBroken(const MPlug& plug, const MPlug& otherPlug, bool asSrc) override;
bool checkLicense() {
// 實作您的授權判斷
return std::getenv("CGCG_LICENSE_VALID") != nullptr;
}
};
#endif // VALID_SHAPE_H

View File

@ -0,0 +1,63 @@
#include <maya/MFnPlugin.h>
#include <maya/MStatus.h>
#include <maya/MGlobal.h>
#include "ValidShape.h"
#include "EncryptCommand.h"
// Plugin initialization
MStatus initializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj, "ValidShape", "1.0", "Any", &status);
if (!status)
{
MGlobal::displayError("Failed to initialize plugin: " + status.errorString());
return status;
}
// Register the ValidShape deformer node
status = plugin.registerNode(
ValidShape::typeName,
ValidShape::typeId,
ValidShape::creator,
ValidShape::initialize,
MPxNode::kDeformerNode
);
if (!status)
{
MGlobal::displayError("Failed to register ValidShape node: " + status.errorString());
return status;
}
MGlobal::displayInfo("ValidShape plugin initialized successfully.");
return status;
}
// Plugin uninitialization
MStatus uninitializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj);
// 卸載指令
status = plugin.deregisterCommand("protectAsset");
if (!status) {
status.perror("無法卸載 protectAsset 指令");
return status;
}
// Deregister the ValidShape node
status = plugin.deregisterNode(ValidShape::typeId);
if (!status)
{
MGlobal::displayError("Failed to deregister ValidShape node: " + status.errorString());
return status;
}
MGlobal::displayInfo("ValidShape plugin uninitialized successfully.");
return status;
}

View File

@ -0,0 +1,29 @@
// AEValidShapeTemplate.mel
// Custom Attribute Editor template for ValidShape node
// This file defines the UI for the ValidShape deformer node in Maya's Attribute Editor
global proc AEValidShapeTemplate(string $nodeName)
{
// Call the base template
AETemplateInit;
// Define the UI layout
editorLayout -e -mt "ValidShape" -an "ValidShape" AETemplateContentLayout;
// License Section
editorTemplate -beginScrollLayout;
editorTemplate -beginLayout "ValidShape" -collapse false;
// License File
editorTemplate -addControl "licenseFile" -label "License File";
// Valid License Status (read-only)
editorTemplate -addControl "validLicense" -label "Valid License";
editorTemplate -endLayout;
// Add standard deformer attributes
AEAddStandardDeformerAttributes $nodeName;
editorTemplate -endScrollLayout;
}

View File

@ -0,0 +1,44 @@
import os
import maya.standalone
maya.standalone.initialize()
import maya.cmds as cmds
# Load plugin with full path
plugin_path = "d:/workspace/ValidShape/install/plug-ins/2023/ValidShape.mll"
try:
result = cmds.loadPlugin(plugin_path)
print("Plugin loaded successfully:", result)
# List all nodes of our type (use the exact type name)
all_valid = cmds.ls(type="ValidShape")
print("All ValidShape nodes in scene:", all_valid)
# Try to create the node with exact type name
node = cmds.createNode("ValidShape")
print("Node created successfully:", node)
# Get all nodes of our type
all_valid = cmds.ls(type="ValidShape")
print("All ValidShape nodes in scene:", all_valid)
# Check node type
if node:
node_type = cmds.nodeType(node)
print("Node type:", node_type)
# Delete the node before unloading
cmds.delete(node)
print("Node deleted")
cmds.unloadPlugin("ValidShape")
print("Unload completed")
print("\n=== TEST PASSED ===")
print("Plugin loads correctly")
print("ValidShape node can be created")
print("Node type is correctly registered as 'ValidShape'")
except Exception as e:
print("Error:", e)
import traceback
traceback.print_exc()

View File

@ -0,0 +1,374 @@
"""
ValidShape Plugin Test Script for Maya 2023
This script is used to test the ValidShape Maya Deformer Node plugin.
Usage:
1. Copy this script to Maya scripts directory or run directly
2. Set MAYA_MODULE_PATH to the install directory
3. Load the plugin and run tests
Author: QA Team
"""
import maya.cmds as cmds
import maya.mel as mel
import os
import sys
class ValidShapeTest:
def __init__(self):
self.plugin_loaded = False
self.test_object = None
self.test_deformer = None
self.test_license_valid = "D:/workspace/ValidShape/test_license_valid.dat"
self.test_license_expired = "D:/workspace/ValidShape/test_license_expired.dat"
def create_test_license_files(self):
"""Create test license files"""
# Valid license (future date)
with open(self.test_license_valid, 'w') as f:
f.write("# Valid License File\n")
f.write("PRODUCT=ValidShape\n")
f.write("EXPIRE_DATE=2027-12-31\n")
# Expired license (past date)
with open(self.test_license_expired, 'w') as f:
f.write("# Expired License File\n")
f.write("PRODUCT=ValidShape\n")
f.write("EXPIRE_DATE=2020-01-01\n")
print("Test license files created successfully!")
def load_plugin(self):
"""Load the ValidShape plugin"""
plugin_path = os.path.join(os.environ.get('MAYA_MODULE_PATH', ''),
'plug-ins', '2023', 'ValidShape.mll')
if not cmds.pluginInfo('ValidShape.mll', q=True, loaded=True):
try:
cmds.loadPlugin('ValidShape.mll')
self.plugin_loaded = True
print("ValidShape plugin loaded successfully!")
return True
except RuntimeError as e:
print("Failed to load plugin: {}".format(e))
return False
else:
print("Plugin already loaded")
self.plugin_loaded = True
return True
def create_test_mesh(self):
"""Create a test mesh for deformation"""
# Create a sphere as test object
self.test_object = cmds.polySphere(r=1, sx=20, sy=20, name='testSphere')[0]
print("Test mesh created: {}".format(self.test_object))
return self.test_object
def apply_deformer(self, mesh_name):
"""Apply ValidShape deformer to mesh"""
if not self.plugin_loaded:
print("Plugin not loaded!")
return None
# Create the deformer
self.test_deformer = cmds.deformableShape(mesh_name,
type='ValidShape',
name='ValidShapeDeform')[0]
print("ValidShape deformer applied: {}".format(self.test_deformer))
return self.test_deformer
def set_license_file(self, deformer_name, license_path):
"""Set the license file for the deformer"""
if cmds.objExists(deformer_name + '.licenseFile'):
cmds.setAttr(deformer_name + '.licenseFile', license_path, type='string')
print("License file set to: {}".format(license_path))
return True
return False
def test_valid_license(self):
"""Test with valid license"""
print("\n=== Testing Valid License ===")
# Create test mesh if not exists
if not self.test_object:
self.create_test_mesh()
# Apply deformer
if not self.test_deformer:
self.apply_deformer(self.test_object)
# Set valid license
self.set_license_file(self.test_deformer, self.test_license_valid)
# Get valid license status
is_valid = cmds.getAttr(self.test_deformer + '.validLicense')
print("Valid License Status: {}".format(is_valid))
# The mesh should NOT be deformed when license is valid
return is_valid == True
def test_expired_license(self):
"""Test with expired license"""
print("\n=== Testing Expired License ===")
# Set expired license
self.set_license_file(self.test_deformer, self.test_license_expired)
# Get valid license status
is_valid = cmds.getAttr(self.test_deformer + '.validLicense')
print("Valid License Status: {}".format(is_valid))
# The mesh SHOULD be deformed when license is expired
return is_valid == False
def test_noise_parameters(self):
"""Test noise parameter adjustments"""
print("\n=== Testing Noise Parameters ===")
if not self.test_deformer:
print("No deformer to test!")
return False
# Set noise amplitude
cmds.setAttr(self.test_deformer + '.noiseAmplitude', 1.0)
amp = cmds.getAttr(self.test_deformer + '.noiseAmplitude')
print("Noise Amplitude: {}".format(amp))
# Set noise frequency
cmds.setAttr(self.test_deformer + '.noiseFrequency', 2.0)
freq = cmds.getAttr(self.test_deformer + '.noiseFrequency')
print("Noise Frequency: {}".format(freq))
return True
def run_all_tests(self):
"""Run all tests"""
print("=" * 50)
print("Starting ValidShape Plugin Tests")
print("=" * 50)
# Create test license files
self.create_test_license_files()
# Load plugin
if not self.load_plugin():
print("Plugin loading failed, aborting tests!")
return False
# Run tests
test1 = self.test_valid_license()
test2 = self.test_expired_license()
test3 = self.test_noise_parameters()
# Print results
print("\n" + "=" * 50)
print("Test Results:")
print(" Valid License Test: {}".format("PASS" if test1 else "FAIL"))
print(" Expired License Test: {}".format("PASS" if test2 else "FAIL"))
print(" Noise Parameters Test: {}".format("PASS" if test3 else "FAIL"))
print("=" * 50)
return test1 and test2 and test3
def run_tests():
"""Main entry point to run tests"""
tester = ValidShapeTest()
result = tester.run_all_tests()
if result:
print("\nAll tests PASSED!")
else:
print("\nSome tests FAILED!")
return result
if __name__ == "__main__":
run_tests()
ValidShape Plugin Test Script for Maya 2023
This script is used to test the ValidShape Maya Deformer Node plugin.
Usage:
1. Copy this script to Maya scripts directory or run directly
2. Set MAYA_MODULE_PATH to the install directory
3. Load the plugin and run tests
Author: QA Team
"""
import maya.cmds as cmds
import maya.mel as mel
import os
import sys
class ValidShapeTest:
def __init__(self):
self.plugin_loaded = False
self.test_object = None
self.test_deformer = None
self.test_license_valid = "D:/workspace/ValidShape/test_license_valid.dat"
self.test_license_expired = "D:/workspace/ValidShape/test_license_expired.dat"
def create_test_license_files(self):
"""Create test license files"""
# Valid license (future date)
with open(self.test_license_valid, 'w') as f:
f.write("# Valid License File\n")
f.write("PRODUCT=ValidShape\n")
f.write("EXPIRE_DATE=2027-12-31\n")
# Expired license (past date)
with open(self.test_license_expired, 'w') as f:
f.write("# Expired License File\n")
f.write("PRODUCT=ValidShape\n")
f.write("EXPIRE_DATE=2020-01-01\n")
print("Test license files created successfully!")
def load_plugin(self):
"""Load the ValidShape plugin"""
plugin_path = os.path.join(os.environ.get('MAYA_MODULE_PATH', ''),
'plug-ins', '2023', 'ValidShape.mll')
if not cmds.pluginInfo('ValidShape.mll', q=True, loaded=True):
try:
cmds.loadPlugin('ValidShape.mll')
self.plugin_loaded = True
print("ValidShape plugin loaded successfully!")
return True
except RuntimeError as e:
print("Failed to load plugin: {}".format(e))
return False
else:
print("Plugin already loaded")
self.plugin_loaded = True
return True
def create_test_mesh(self):
"""Create a test mesh for deformation"""
# Create a sphere as test object
self.test_object = cmds.polySphere(r=1, sx=20, sy=20, name='testSphere')[0]
print("Test mesh created: {}".format(self.test_object))
return self.test_object
def apply_deformer(self, mesh_name):
"""Apply ValidShape deformer to mesh"""
if not self.plugin_loaded:
print("Plugin not loaded!")
return None
# Create the deformer
self.test_deformer = cmds.deformableShape(mesh_name,
type='ValidShape',
name='ValidShapeDeform')[0]
print("ValidShape deformer applied: {}".format(self.test_deformer))
return self.test_deformer
def set_license_file(self, deformer_name, license_path):
"""Set the license file for the deformer"""
if cmds.objExists(deformer_name + '.licenseFile'):
cmds.setAttr(deformer_name + '.licenseFile', license_path, type='string')
print("License file set to: {}".format(license_path))
return True
return False
def test_valid_license(self):
"""Test with valid license"""
print("\n=== Testing Valid License ===")
# Create test mesh if not exists
if not self.test_object:
self.create_test_mesh()
# Apply deformer
if not self.test_deformer:
self.apply_deformer(self.test_object)
# Set valid license
self.set_license_file(self.test_deformer, self.test_license_valid)
# Get valid license status
is_valid = cmds.getAttr(self.test_deformer + '.validLicense')
print("Valid License Status: {}".format(is_valid))
# The mesh should NOT be deformed when license is valid
return is_valid == True
def test_expired_license(self):
"""Test with expired license"""
print("\n=== Testing Expired License ===")
# Set expired license
self.set_license_file(self.test_deformer, self.test_license_expired)
# Get valid license status
is_valid = cmds.getAttr(self.test_deformer + '.validLicense')
print("Valid License Status: {}".format(is_valid))
# The mesh SHOULD be deformed when license is expired
return is_valid == False
def test_noise_parameters(self):
"""Test noise parameter adjustments"""
print("\n=== Testing Noise Parameters ===")
if not self.test_deformer:
print("No deformer to test!")
return False
# Set noise amplitude
cmds.setAttr(self.test_deformer + '.noiseAmplitude', 1.0)
amp = cmds.getAttr(self.test_deformer + '.noiseAmplitude')
print("Noise Amplitude: {}".format(amp))
# Set noise frequency
cmds.setAttr(self.test_deformer + '.noiseFrequency', 2.0)
freq = cmds.getAttr(self.test_deformer + '.noiseFrequency')
print("Noise Frequency: {}".format(freq))
return True
def run_all_tests(self):
"""Run all tests"""
print("=" * 50)
print("Starting ValidShape Plugin Tests")
print("=" * 50)
# Create test license files
self.create_test_license_files()
# Load plugin
if not self.load_plugin():
print("Plugin loading failed, aborting tests!")
return False
# Run tests
test1 = self.test_valid_license()
test2 = self.test_expired_license()
test3 = self.test_noise_parameters()
# Print results
print("\n" + "=" * 50)
print("Test Results:")
print(" Valid License Test: {}".format("PASS" if test1 else "FAIL"))
print(" Expired License Test: {}".format("PASS" if test2 else "FAIL"))
print(" Noise Parameters Test: {}".format("PASS" if test3 else "FAIL"))
print("=" * 50)
return test1 and test2 and test3
def run_tests():
"""Main entry point to run tests"""
tester = ValidShapeTest()
result = tester.run_all_tests()
if result:
print("\nAll tests PASSED!")
else:
print("\nSome tests FAILED!")
return result
if __name__ == "__main__":
run_tests()