Init Repo
This commit is contained in:
commit
873f4e97e1
|
|
@ -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/
|
||||
|
|
@ -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 測試
|
||||
|
||||
|
||||
團隊自行決定未定義的的其他技術細節與檔案名稱,確認開發功能都完成後告訴我
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
add_subdirectory(ProtectAsset)
|
||||
add_subdirectory(ValidAsset)
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
)
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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})
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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(); }
|
||||
};
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue