feat: Add VRM Blender addon with complete import/export functionality
- 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
This commit is contained in:
198
importer/license_validation.py
Normal file
198
importer/license_validation.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user