- Add core VRM addon infrastructure with manifest and registration - Add common utilities module with file system, logging, and conversion helpers - Add human bone mapper with support for multiple rigging standards (Mixamo, MMD, Unreal, Rigify, etc.) - Add VRM 0.x and 1.x format support with property groups and handlers - Add editor UI panels for VRM metadata, spring bones, and MToon materials - Add exporter with glTF2 extension support for VRM format serialization - Add importer with scene reconstruction and armature generation - Add MToon shader support with auto-setup and material migration - Add spring bone physics simulation with constraint handling - Add node constraint editor for advanced rigging control - Add comprehensive validation and error handling with user dialogs - Add scene watcher for real-time property synchronization - Add workspace management and preference system - Include Python cache files and Blender manifest configuration - This is the initial commit establishing the complete VRM addon ecosystem for Blender
199 lines
6.0 KiB
Python
199 lines
6.0 KiB
Python
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
import contextlib
|
|
from typing import Optional
|
|
from urllib.parse import ParseResult, parse_qsl, urlparse
|
|
|
|
from bpy.app.translations import pgettext
|
|
|
|
from ..common.convert import Json
|
|
|
|
|
|
class LicenseConfirmationRequiredProp:
|
|
def __init__(
|
|
self,
|
|
url: Optional[str],
|
|
json_key: Optional[str],
|
|
message: str,
|
|
) -> None:
|
|
self.url = url
|
|
self.json_key = json_key
|
|
self.message = message
|
|
|
|
def description(self) -> str:
|
|
return f"""class=LicenseConfirmationRequired
|
|
url={self.url}
|
|
json_key={self.json_key}
|
|
message={self.message}
|
|
"""
|
|
|
|
|
|
class LicenseConfirmationRequiredError(Exception):
|
|
def __init__(self, props: list[LicenseConfirmationRequiredProp]) -> None:
|
|
self.props = props
|
|
super().__init__(self.description())
|
|
|
|
def description(self) -> str:
|
|
return "\n".join(prop.description() for prop in self.props)
|
|
|
|
def license_confirmations(self) -> list[dict[str, str]]:
|
|
return [
|
|
{
|
|
"name": "LicenseConfirmation" + str(index),
|
|
"url": prop.url or "",
|
|
"json_key": prop.json_key or "",
|
|
"message": prop.message or "",
|
|
}
|
|
for index, prop in enumerate(self.props)
|
|
]
|
|
|
|
|
|
def validate_license_url(
|
|
url_str: str, json_key: str, props: list[LicenseConfirmationRequiredProp]
|
|
) -> None:
|
|
if not url_str:
|
|
return
|
|
url = None
|
|
with contextlib.suppress(ValueError):
|
|
url = urlparse(url_str)
|
|
if url:
|
|
query_dict = dict(parse_qsl(url.query))
|
|
if validate_vroid_hub_license_url(
|
|
url, query_dict, json_key, props
|
|
) or validate_uni_virtual_license_url(url, query_dict, json_key, props):
|
|
return
|
|
props.append(
|
|
LicenseConfirmationRequiredProp(
|
|
url_str,
|
|
json_key,
|
|
pgettext("This VRM is not allowed to be edited. Please check its license"),
|
|
)
|
|
)
|
|
|
|
|
|
def validate_vroid_hub_license_url(
|
|
url: ParseResult,
|
|
query_dict: dict[str, str],
|
|
json_key: str,
|
|
props: list[LicenseConfirmationRequiredProp],
|
|
) -> bool:
|
|
# https://hub.vroid.com/en/license?allowed_to_use_user=everyone&characterization_allowed_user=everyone&corporate_commercial_use=allow&credit=unnecessary&modification=allow&personal_commercial_use=profit&redistribution=allow&sexual_expression=allow&version=1&violent_expression=allow
|
|
if url.hostname != "hub.vroid.com" or not url.path.endswith("/license"):
|
|
return False
|
|
if query_dict.get("modification") == "disallow":
|
|
props.append(
|
|
LicenseConfirmationRequiredProp(
|
|
url.geturl(),
|
|
json_key,
|
|
pgettext(
|
|
'This VRM is licensed by VRoid Hub License "Alterations: No".'
|
|
),
|
|
)
|
|
)
|
|
return True
|
|
|
|
|
|
def validate_uni_virtual_license_url(
|
|
url: ParseResult,
|
|
query_dict: dict[str, str],
|
|
json_key: str,
|
|
props: list[LicenseConfirmationRequiredProp],
|
|
) -> bool:
|
|
# https://uv-license.com/en/license?utf8=%E2%9C%93&pcu=true
|
|
if url.hostname != "uv-license.com" or not url.path.endswith("/license"):
|
|
return False
|
|
if query_dict.get("remarks") == "true":
|
|
props.append(
|
|
LicenseConfirmationRequiredProp(
|
|
url.geturl(),
|
|
json_key,
|
|
pgettext('This VRM is licensed by UV License with "Remarks".'),
|
|
)
|
|
)
|
|
return True
|
|
|
|
|
|
def validate_vrm1_license(
|
|
json_dict: dict[str, Json], _confirmations: list[LicenseConfirmationRequiredProp]
|
|
) -> None:
|
|
extensions_dict = json_dict.get("extensions")
|
|
if not isinstance(extensions_dict, dict):
|
|
return
|
|
|
|
vrmc_vrm_dict = extensions_dict.get("VRMC_vrm")
|
|
if not isinstance(vrmc_vrm_dict, dict):
|
|
return
|
|
|
|
return
|
|
|
|
|
|
def validate_vrm0_license(
|
|
json_dict: dict[str, Json], confirmations: list[LicenseConfirmationRequiredProp]
|
|
) -> None:
|
|
extensions_dict = json_dict.get("extensions")
|
|
if not isinstance(extensions_dict, dict):
|
|
return
|
|
|
|
vrm_dict = extensions_dict.get("VRM")
|
|
if not isinstance(vrm_dict, dict):
|
|
return
|
|
|
|
meta_dict = vrm_dict.get("meta")
|
|
if not isinstance(meta_dict, dict):
|
|
return
|
|
|
|
license_name = meta_dict.get("licenseName")
|
|
if license_name in [
|
|
# https://github.com/vrm-c/vrm-specification/blob/master/specification/0.0/schema/vrm.meta.schema.json#L56
|
|
"CC_BY_ND",
|
|
"CC_BY_NC_ND",
|
|
]:
|
|
confirmations.append(
|
|
LicenseConfirmationRequiredProp(
|
|
None,
|
|
None,
|
|
pgettext(
|
|
"This VRM is not allowed to be edited. Please check its license"
|
|
),
|
|
)
|
|
)
|
|
|
|
if license_name == "Other":
|
|
other_license_url = meta_dict.get("otherLicenseUrl")
|
|
if other_license_url is None:
|
|
confirmations.append(
|
|
LicenseConfirmationRequiredProp(
|
|
None,
|
|
None,
|
|
pgettext(
|
|
'The VRM selects "Other" license but no license url is found.'
|
|
),
|
|
)
|
|
)
|
|
else:
|
|
validate_license_url(
|
|
str(other_license_url), "otherLicenseUrl", confirmations
|
|
)
|
|
|
|
other_permission_url = meta_dict.get("otherPermissionUrl")
|
|
if other_permission_url is not None:
|
|
validate_license_url(
|
|
str(other_permission_url),
|
|
"otherPermissionUrl",
|
|
confirmations,
|
|
)
|
|
|
|
|
|
def validate_license(
|
|
json_dict: dict[str, Json], spec_version_number: tuple[int, int]
|
|
) -> None:
|
|
"""Validate that the license is not a non-modifiable license, such as CC_ND."""
|
|
confirmations: list[LicenseConfirmationRequiredProp] = []
|
|
|
|
if tuple(spec_version_number) >= (1, 0):
|
|
validate_vrm1_license(json_dict, confirmations)
|
|
else:
|
|
validate_vrm0_license(json_dict, confirmations)
|
|
|
|
if confirmations:
|
|
raise LicenseConfirmationRequiredError(confirmations)
|