feat: integrate SimpleCADAPI 2.0.2 CAD workflows
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
"""Declarative required, nullable, and discriminated-union mutations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import Any, Iterable
|
||||
|
||||
from simplecadapi.scene import canonical_json_bytes, with_scene_revision
|
||||
|
||||
from .common import sha256_hex
|
||||
|
||||
|
||||
Path = tuple[str | int, ...]
|
||||
|
||||
|
||||
def _case(
|
||||
name: str,
|
||||
operation: str,
|
||||
path: Path,
|
||||
value: Any = None,
|
||||
*,
|
||||
recompute_revision: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
mutation = {"operation": operation, "path": list(path)}
|
||||
if operation != "delete":
|
||||
mutation["value"] = value
|
||||
return {
|
||||
"mutations": [mutation],
|
||||
"name": name,
|
||||
"recompute_revision": recompute_revision,
|
||||
}
|
||||
|
||||
|
||||
def _missing_fields(path: Path, fields: Iterable[str]) -> Iterable[dict[str, Any]]:
|
||||
for field in fields:
|
||||
yield _case(
|
||||
"missing_" + "_".join((*map(str, path), field)),
|
||||
"delete",
|
||||
(*path, field),
|
||||
)
|
||||
|
||||
|
||||
def scene_field_matrix(scene: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Return compact mutations covering the frozen structural field matrix."""
|
||||
|
||||
records: list[tuple[Path, tuple[str, ...]]] = [
|
||||
(
|
||||
(),
|
||||
(
|
||||
"schema_version", "extensions_used", "extensions_required",
|
||||
"extensions", "scene_id", "revision", "generator", "source",
|
||||
"coordinate_system", "compile_options", "definitions", "nodes",
|
||||
"geometry_assets", "edge_assets", "appearances", "entity_assets",
|
||||
"connectors", "cameras", "lights", "annotations", "diagnostics",
|
||||
),
|
||||
),
|
||||
(
|
||||
("compile_options",),
|
||||
("linear_tolerance", "angular_tolerance", "embed_source", "embed_presentation"),
|
||||
),
|
||||
(
|
||||
("definitions", 0),
|
||||
("definition_id", "kind", "name", "source", "sdk_metadata"),
|
||||
),
|
||||
(
|
||||
("nodes", 0),
|
||||
(
|
||||
"node_id", "parent_node_id", "order", "definition_id", "name",
|
||||
"transform", "visible", "selectable", "appearance_override_id",
|
||||
"source", "sdk_metadata",
|
||||
),
|
||||
),
|
||||
(
|
||||
("appearances", 0),
|
||||
(
|
||||
"appearance_id", "name", "source", "base_color", "metallic",
|
||||
"roughness", "alpha_mode", "double_sided", "edge_color", "sdk_metadata",
|
||||
),
|
||||
),
|
||||
(
|
||||
("geometry_assets", 0),
|
||||
(
|
||||
"asset_id", "uri", "media_type", "byte_length", "content_hash",
|
||||
"scene_local_bounds", "asset_to_scene", "tessellation",
|
||||
),
|
||||
),
|
||||
(
|
||||
("entity_assets", 0),
|
||||
("entity_asset_id", "uri", "media_type", "byte_length", "content_hash"),
|
||||
),
|
||||
]
|
||||
result = [
|
||||
mutation
|
||||
for path, fields in records
|
||||
for mutation in _missing_fields(path, fields)
|
||||
]
|
||||
result.extend(
|
||||
_case(name, "add", path, True)
|
||||
for name, path in (
|
||||
("unknown_scene_field", ("unknown",)),
|
||||
("unknown_generator_field", ("generator", "unknown")),
|
||||
("unknown_compile_options_field", ("compile_options", "unknown")),
|
||||
("unknown_definition_field", ("definitions", 0, "unknown")),
|
||||
("unknown_node_field", ("nodes", 0, "unknown")),
|
||||
("unknown_appearance_field", ("appearances", 0, "unknown")),
|
||||
)
|
||||
)
|
||||
result.extend(
|
||||
(
|
||||
_case("optional_presentation_source_null", "add", ("presentation_source",), None),
|
||||
_case("unknown_scene_source_kind", "set", ("source", "kind"), "unknown"),
|
||||
_case(
|
||||
"unknown_definition_source_kind",
|
||||
"set",
|
||||
("definitions", 0, "source", "kind"),
|
||||
"unknown",
|
||||
),
|
||||
_case("missing_node_source_kind", "delete", ("nodes", 0, "source", "kind")),
|
||||
_case("unsupported_scene_schema_version", "set", ("schema_version",), "2.0"),
|
||||
_case(
|
||||
"unsupported_generator_profile",
|
||||
"set",
|
||||
("generator", "profile"),
|
||||
"scene-2.0",
|
||||
),
|
||||
_case(
|
||||
"no_presentation_visibility_override",
|
||||
"set",
|
||||
("nodes", 0, "visible"),
|
||||
False,
|
||||
recompute_revision=True,
|
||||
),
|
||||
_case(
|
||||
"no_presentation_selectability_override",
|
||||
"set",
|
||||
("nodes", 0, "selectable"),
|
||||
False,
|
||||
recompute_revision=True,
|
||||
),
|
||||
_case(
|
||||
"no_presentation_appearance_override",
|
||||
"set",
|
||||
("nodes", 0, "appearance_override_id"),
|
||||
scene["appearances"][0]["appearance_id"],
|
||||
recompute_revision=True,
|
||||
),
|
||||
_case(
|
||||
"no_presentation_camera",
|
||||
"set",
|
||||
("cameras",),
|
||||
[
|
||||
{
|
||||
"camera_id": "camera/fixture/overview",
|
||||
"far": 1000,
|
||||
"name": "overview",
|
||||
"near": 1,
|
||||
"parent_node_id": None,
|
||||
"projection": "perspective",
|
||||
"transform": {
|
||||
"origin": [10, -10, 10],
|
||||
"x_axis": [1, 0, 0],
|
||||
"y_axis": [0, 1, 0],
|
||||
"z_axis": [0, 0, 1],
|
||||
},
|
||||
"vertical_fov_degrees": 45,
|
||||
}
|
||||
],
|
||||
recompute_revision=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def apply_scene_field_case(scene: dict[str, Any], case: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Replay one mutation descriptor to produce its validator input."""
|
||||
|
||||
result = deepcopy(scene)
|
||||
for mutation in case["mutations"]:
|
||||
path = mutation["path"]
|
||||
current: Any = result
|
||||
for part in path[:-1]:
|
||||
current = current[part]
|
||||
if mutation["operation"] == "delete":
|
||||
del current[path[-1]]
|
||||
else:
|
||||
current[path[-1]] = deepcopy(mutation.get("value"))
|
||||
if case.get("recompute_revision"):
|
||||
result = with_scene_revision(result)
|
||||
return result
|
||||
|
||||
|
||||
def nullable_fields_nonnull_case(scene: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
"""Return the multi-field valid nullable vector and its compact descriptor."""
|
||||
|
||||
value = deepcopy(scene)
|
||||
value["nodes"][0]["name"] = "Named occurrence"
|
||||
value["appearances"][0]["name"] = "Named appearance"
|
||||
appearance = value["appearances"][0]
|
||||
draft = dict(appearance)
|
||||
draft.pop("appearance_id")
|
||||
appearance_id = "appearance/evaluated/" + sha256_hex(canonical_json_bytes(draft))
|
||||
appearance["appearance_id"] = appearance_id
|
||||
value["definitions"][0]["appearance_id"] = appearance_id
|
||||
descriptor = {
|
||||
"mutations": [
|
||||
{"operation": "set", "path": ["nodes", 0, "name"], "value": "Named occurrence"},
|
||||
{"operation": "set", "path": ["appearances", 0, "name"], "value": "Named appearance"},
|
||||
{"operation": "set", "path": ["appearances", 0, "appearance_id"], "value": appearance_id},
|
||||
{"operation": "set", "path": ["definitions", 0, "appearance_id"], "value": appearance_id},
|
||||
],
|
||||
"name": "nullable_fields_nonnull",
|
||||
"recompute_revision": True,
|
||||
}
|
||||
return with_scene_revision(value), descriptor
|
||||
Reference in New Issue
Block a user