195 lines
6.6 KiB
GDScript
Vendored
195 lines
6.6 KiB
GDScript
Vendored
@tool
|
|
extends RefCounted
|
|
|
|
## Value coercion helpers for material authoring.
|
|
##
|
|
## Extends node_handler._coerce_value with material-specific cases:
|
|
## - enum-by-name (transparency="alpha" → TRANSPARENCY_ALPHA)
|
|
## - texture path → Texture2D
|
|
## - {r,g,b,a} dict → Color (also handled by node coerce, but we want it inline)
|
|
|
|
|
|
const _ENUM_TABLES := {
|
|
"transparency": {
|
|
"disabled": BaseMaterial3D.TRANSPARENCY_DISABLED,
|
|
"alpha": BaseMaterial3D.TRANSPARENCY_ALPHA,
|
|
"alpha_scissor": BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR,
|
|
"alpha_hash": BaseMaterial3D.TRANSPARENCY_ALPHA_HASH,
|
|
"alpha_depth_pre_pass": BaseMaterial3D.TRANSPARENCY_ALPHA_DEPTH_PRE_PASS,
|
|
},
|
|
"shading_mode": {
|
|
"unshaded": BaseMaterial3D.SHADING_MODE_UNSHADED,
|
|
"per_pixel": BaseMaterial3D.SHADING_MODE_PER_PIXEL,
|
|
"per_vertex": BaseMaterial3D.SHADING_MODE_PER_VERTEX,
|
|
},
|
|
"blend_mode": {
|
|
"mix": BaseMaterial3D.BLEND_MODE_MIX,
|
|
"add": BaseMaterial3D.BLEND_MODE_ADD,
|
|
"sub": BaseMaterial3D.BLEND_MODE_SUB,
|
|
"mul": BaseMaterial3D.BLEND_MODE_MUL,
|
|
},
|
|
"cull_mode": {
|
|
"back": BaseMaterial3D.CULL_BACK,
|
|
"front": BaseMaterial3D.CULL_FRONT,
|
|
"disabled": BaseMaterial3D.CULL_DISABLED,
|
|
},
|
|
"depth_draw_mode": {
|
|
"opaque_only": BaseMaterial3D.DEPTH_DRAW_OPAQUE_ONLY,
|
|
"always": BaseMaterial3D.DEPTH_DRAW_ALWAYS,
|
|
"disabled": BaseMaterial3D.DEPTH_DRAW_DISABLED,
|
|
},
|
|
"diffuse_mode": {
|
|
"burley": BaseMaterial3D.DIFFUSE_BURLEY,
|
|
"lambert": BaseMaterial3D.DIFFUSE_LAMBERT,
|
|
"lambert_wrap": BaseMaterial3D.DIFFUSE_LAMBERT_WRAP,
|
|
"toon": BaseMaterial3D.DIFFUSE_TOON,
|
|
},
|
|
"specular_mode": {
|
|
"schlick_ggx": BaseMaterial3D.SPECULAR_SCHLICK_GGX,
|
|
"toon": BaseMaterial3D.SPECULAR_TOON,
|
|
"disabled": BaseMaterial3D.SPECULAR_DISABLED,
|
|
},
|
|
"billboard_mode": {
|
|
"disabled": BaseMaterial3D.BILLBOARD_DISABLED,
|
|
"enabled": BaseMaterial3D.BILLBOARD_ENABLED,
|
|
"fixed_y": BaseMaterial3D.BILLBOARD_FIXED_Y,
|
|
"particles": BaseMaterial3D.BILLBOARD_PARTICLES,
|
|
},
|
|
"texture_filter": {
|
|
"nearest": BaseMaterial3D.TEXTURE_FILTER_NEAREST,
|
|
"linear": BaseMaterial3D.TEXTURE_FILTER_LINEAR,
|
|
"nearest_mipmap": BaseMaterial3D.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS,
|
|
"linear_mipmap": BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS,
|
|
},
|
|
}
|
|
|
|
|
|
## Return the enum int for (property, string_name), or null if not a known enum string.
|
|
static func resolve_enum(property: String, value: Variant) -> Variant:
|
|
if not (value is String):
|
|
return null
|
|
if not _ENUM_TABLES.has(property):
|
|
return null
|
|
var table: Dictionary = _ENUM_TABLES[property]
|
|
var key: String = String(value).to_lower()
|
|
if table.has(key):
|
|
return table[key]
|
|
return null
|
|
|
|
|
|
## Parse a color from Color, "#rrggbb(aa)", named string, {r,g,b[,a]} dict,
|
|
## or [r,g,b(,a)] array. Delegates to the canonical parser (#714); returns
|
|
## null if the input cannot be parsed.
|
|
static func parse_color(value: Variant) -> Variant:
|
|
return McpJsonValues.parse_color(value)
|
|
|
|
|
|
static func parse_vector3(value: Variant) -> Variant:
|
|
return McpJsonValues.parse_vector3(value)
|
|
|
|
|
|
static func parse_vector2(value: Variant) -> Variant:
|
|
return McpJsonValues.parse_vector2(value)
|
|
|
|
|
|
## Load a Texture2D from a res:// / uid:// / user:// path (validate_loadable_path).
|
|
## Returns null on failure (including a path that fails confinement / traversal).
|
|
static func load_texture(path: String) -> Texture2D:
|
|
if not McpPathValidator.validate_loadable_path(path).is_empty():
|
|
return null
|
|
if not ResourceLoader.exists(path):
|
|
return null
|
|
var res := ResourceLoader.load(path)
|
|
if res is Texture2D:
|
|
return res
|
|
return null
|
|
|
|
|
|
## Coerce a JSON-shaped value for a material property.
|
|
## Returns a dict {ok: true, value: ...} on success, or {ok: false, error: "..."} on failure.
|
|
## For properties the coercer doesn't have special logic for, falls back to target_type.
|
|
static func coerce_material_value(property: String, value: Variant, target_type: int) -> Dictionary:
|
|
# Enum-by-name: must match before generic TYPE_INT coercion.
|
|
if _ENUM_TABLES.has(property):
|
|
if value is String:
|
|
var enum_val = resolve_enum(property, value)
|
|
if enum_val == null:
|
|
return {
|
|
"ok": false,
|
|
"error": "Invalid %s value: '%s'. Valid: %s" % [
|
|
property, value, ", ".join(_ENUM_TABLES[property].keys())
|
|
],
|
|
}
|
|
return {"ok": true, "value": int(enum_val)}
|
|
if value is int or value is float:
|
|
return {"ok": true, "value": int(value)}
|
|
|
|
match target_type:
|
|
TYPE_COLOR:
|
|
var c = parse_color(value)
|
|
if c == null:
|
|
return {"ok": false, "error": "Invalid color for %s: %s" % [property, value]}
|
|
return {"ok": true, "value": c}
|
|
TYPE_VECTOR3:
|
|
var v3 = parse_vector3(value)
|
|
if v3 == null:
|
|
return {"ok": false, "error": "Invalid vector3 for %s: %s" % [property, value]}
|
|
return {"ok": true, "value": v3}
|
|
TYPE_VECTOR2:
|
|
var v2 = parse_vector2(value)
|
|
if v2 == null:
|
|
return {"ok": false, "error": "Invalid vector2 for %s: %s" % [property, value]}
|
|
return {"ok": true, "value": v2}
|
|
TYPE_BOOL:
|
|
if value is bool:
|
|
return {"ok": true, "value": value}
|
|
if value is int or value is float:
|
|
return {"ok": true, "value": bool(value)}
|
|
return {"ok": false, "error": "Expected bool for %s" % property}
|
|
TYPE_INT:
|
|
if value is int:
|
|
return {"ok": true, "value": value}
|
|
if value is float:
|
|
return {"ok": true, "value": int(value)}
|
|
return {"ok": false, "error": "Expected int for %s" % property}
|
|
TYPE_FLOAT:
|
|
if value is float:
|
|
return {"ok": true, "value": value}
|
|
if value is int:
|
|
return {"ok": true, "value": float(value)}
|
|
return {"ok": false, "error": "Expected number for %s" % property}
|
|
TYPE_OBJECT:
|
|
if value == null:
|
|
return {"ok": true, "value": null}
|
|
if value is Object:
|
|
return {"ok": true, "value": value}
|
|
if value is String:
|
|
var tex := load_texture(value)
|
|
if tex == null:
|
|
return {"ok": false, "error": "Resource not found or wrong type: %s" % value}
|
|
return {"ok": true, "value": tex}
|
|
return {"ok": false, "error": "Expected resource path (string) for %s" % property}
|
|
TYPE_STRING:
|
|
return {"ok": true, "value": String(value)}
|
|
|
|
# Unknown target type — pass through.
|
|
return {"ok": true, "value": value}
|
|
|
|
|
|
## Serialize a Variant into JSON-friendly shape for responses.
|
|
static func serialize_value(value: Variant) -> Variant:
|
|
if value == null:
|
|
return null
|
|
if value is Color:
|
|
return {"r": value.r, "g": value.g, "b": value.b, "a": value.a}
|
|
if value is Vector3:
|
|
return {"x": value.x, "y": value.y, "z": value.z}
|
|
if value is Vector2:
|
|
return {"x": value.x, "y": value.y}
|
|
if value is Resource:
|
|
var path := (value as Resource).resource_path
|
|
if path.is_empty():
|
|
return {"type": value.get_class(), "path": ""}
|
|
return {"type": value.get_class(), "path": path}
|
|
return value
|