Files
cadSet/SimpleCADAPI/tools/scene_contract_fixtures/base_scene.py

472 lines
16 KiB
Python

"""Base Scene package and canonical tetrahedron asset construction."""
from __future__ import annotations
import hashlib
import math
import struct
from simplecadapi.scene import canonical_json_bytes, with_scene_revision
from .binary_cases import glb
from .common import content_hash, sha256_hex
IDENTITY = {
"origin": [0, 0, 0],
"x_axis": [1, 0, 0],
"y_axis": [0, 1, 0],
"z_axis": [0, 0, 1],
}
ASSET_TO_SCENE = [1000, 0, 0, 0, 0, 0, -1000, 0, 0, 1000, 0, 0, 0, 0, 0, 1]
def _normalize(vector):
length = math.sqrt(sum(component * component for component in vector))
return tuple(component / length for component in vector)
def _cross(left, right):
return (
left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0],
)
def _frame(origin, x_axis):
x_axis = _normalize(x_axis)
seed = (0.0, 0.0, 1.0) if abs(x_axis[2]) < 0.9 else (0.0, 1.0, 0.0)
projection = sum(left * right for left, right in zip(seed, x_axis))
y_axis = _normalize(
tuple(seed[index] - projection * x_axis[index] for index in range(3))
)
z_axis = _normalize(_cross(x_axis, y_axis))
return {
"origin": list(origin),
"x_axis": list(x_axis),
"y_axis": list(y_axis),
"z_axis": list(z_axis),
}
def _canonical_triangle_asset():
points = {
"a": (0.0, 0.0, 0.0),
"b": (1.0, 0.0, 0.0),
"c": (0.0, 1.0, 0.0),
"d": (0.0, 0.0, 1.0),
}
oriented_faces = {
"abc": ("a", "c", "b"),
"abd": ("a", "b", "d"),
"acd": ("a", "d", "c"),
"bcd": ("b", "c", "d"),
}
blocks = []
for name, corners in oriented_faces.items():
a, b, c = (points[corner] for corner in corners)
normal = _normalize(
_cross(
tuple(b[i] - a[i] for i in range(3)),
tuple(c[i] - a[i] for i in range(3)),
)
)
local_positions = sorted(
(points[corner] for corner in corners),
key=lambda point: struct.pack("<fff", *point),
)
remap = {point: index for index, point in enumerate(local_positions)}
triple = tuple(remap[points[corner]] for corner in corners)
rotations = (triple, triple[1:] + triple[:1], triple[2:] + triple[:2])
triple = min(rotations)
block = b"".join(
struct.pack("<ffffff", *point, *normal) for point in local_positions
) + struct.pack("<HHH", *triple)
blocks.append(
(
hashlib.sha256(b"\x01" + block).digest(),
block,
name,
local_positions,
normal,
triple,
)
)
blocks.sort(key=lambda item: (item[0], item[1]))
positions = []
normals = []
indices = []
group_names = []
for _render_key, _block, name, local_positions, normal, triple in blocks:
offset = len(positions)
positions.extend(local_positions)
normals.extend([normal] * len(local_positions))
indices.extend(offset + index for index in triple)
group_names.append(name)
return glb("triangle", positions, indices, normals), group_names
def _canonical_line_asset():
points = {
"a": (0.0, 0.0, 0.0),
"b": (1.0, 0.0, 0.0),
"c": (0.0, 1.0, 0.0),
"d": (0.0, 0.0, 1.0),
}
blocks = []
for name in ("ab", "ac", "ad", "bc", "bd", "cd"):
local_positions = sorted(
(points[name[0]], points[name[1]]),
key=lambda point: struct.pack("<fff", *point),
)
block = b"".join(struct.pack("<fff", *point) for point in local_positions)
block += struct.pack("<HH", 0, 1)
blocks.append(
(
hashlib.sha256(b"\x02" + block).digest(),
block,
name,
local_positions,
)
)
blocks.sort(key=lambda item: (item[0], item[1]))
positions = []
indices = []
group_names = []
for _render_key, _block, name, local_positions in blocks:
offset = len(positions)
positions.extend(local_positions)
indices.extend((offset, offset + 1))
group_names.append(name)
return glb("line", positions, indices), group_names
def _entity(kind, index, parents, children, geometry, properties, frame, status):
return {
"child_entity_ids": sorted(children),
"connector_binding_status": status,
"entity_id": f"entity/{kind}/{index}",
"evaluated_tags": [],
"geometry": geometry,
"kind": kind,
"parent_entity_ids": sorted(parents),
"properties": properties,
"render_status": "rendered",
"sdk_connector_frame": frame,
"sdk_metadata": {},
"semantic_binding_ids": [],
"source": {"kind": "unbound"},
}
def build_scene_package():
triangle_glb, face_order = _canonical_triangle_asset()
line_glb, edge_order = _canonical_line_asset()
geometry_hash = content_hash(triangle_glb)
edge_hash = content_hash(line_glb)
face_ids = {name: f"entity/face/{index}" for index, name in enumerate(face_order)}
edge_ids = {name: f"entity/edge/{index}" for index, name in enumerate(edge_order)}
vertex_ids = {name: f"entity/vertex/{index}" for index, name in enumerate("abcd")}
cad_points = {
"a": (0.0, 0.0, 0.0),
"b": (1000.0, 0.0, 0.0),
"c": (0.0, 0.0, 1000.0),
"d": (0.0, -1000.0, 0.0),
}
face_vertices = {
"abc": "abc",
"abd": "abd",
"acd": "acd",
"bcd": "bcd",
}
face_edges = {
"abc": ("ab", "ac", "bc"),
"abd": ("ab", "ad", "bd"),
"acd": ("ac", "ad", "cd"),
"bcd": ("bc", "bd", "cd"),
}
edge_faces = {
edge: tuple(name for name, edges in face_edges.items() if edge in edges)
for edge in edge_order
}
vertex_edges = {
vertex: tuple(edge for edge in edge_order if vertex in edge)
for vertex in "abcd"
}
entities = []
entities.append(
_entity(
"solid",
0,
[],
list(face_ids.values()),
{"type": "brep_solid"},
{
"bounds": {"max": [1000, 0, 1000], "min": [0, -1000, 0]},
"centroid": [250, -250, 250],
"quality": "kernel_evaluated",
"surface_area": 1_500_000 + 500_000 * math.sqrt(3),
"volume": 1_000_000_000 / 6,
},
None,
"not_applicable",
)
)
face_normals = {
"abc": (0.0, 1.0, 0.0),
"abd": (0.0, 0.0, -1.0),
"acd": (-1.0, 0.0, 0.0),
"bcd": _normalize((1.0, -1.0, 1.0)),
}
for name in face_order:
vertices = [cad_points[vertex] for vertex in face_vertices[name]]
minimum = [min(point[axis] for point in vertices) for axis in range(3)]
maximum = [max(point[axis] for point in vertices) for axis in range(3)]
centroid = [sum(point[axis] for point in vertices) / 3 for axis in range(3)]
edge_vector = tuple(vertices[1][axis] - vertices[0][axis] for axis in range(3))
frame = _frame(centroid, edge_vector)
frame["z_axis"] = list(face_normals[name])
frame["y_axis"] = list(_normalize(_cross(face_normals[name], frame["x_axis"])))
entities.append(
_entity(
"face",
int(face_ids[name].rsplit("/", 1)[1]),
["entity/solid/0"],
[edge_ids[edge] for edge in face_edges[name]],
{
"normal": list(face_normals[name]),
"origin": centroid,
"type": "plane",
"x_direction": frame["x_axis"],
},
{
"area": 500_000 * (math.sqrt(3) if name == "bcd" else 1),
"bounds": {"max": maximum, "min": minimum},
"centroid": centroid,
"orientation": "forward",
"quality": "kernel_evaluated",
},
frame,
"owner_not_part",
)
)
for name in edge_order:
start, end = (cad_points[vertex] for vertex in name)
direction = tuple(end[axis] - start[axis] for axis in range(3))
midpoint = [(start[axis] + end[axis]) / 2 for axis in range(3)]
minimum = [min(start[axis], end[axis]) for axis in range(3)]
maximum = [max(start[axis], end[axis]) for axis in range(3)]
entities.append(
_entity(
"edge",
int(edge_ids[name].rsplit("/", 1)[1]),
[face_ids[face] for face in edge_faces[name]],
[vertex_ids[vertex] for vertex in name],
{
"direction": list(_normalize(direction)),
"origin": list(start),
"type": "line",
},
{
"bounds": {"max": maximum, "min": minimum},
"centroid": midpoint,
"length": math.sqrt(
sum(component * component for component in direction)
),
"quality": "kernel_evaluated",
},
_frame(midpoint, direction),
"owner_not_part",
)
)
for name in "abcd":
point = cad_points[name]
entities.append(
_entity(
"vertex",
int(vertex_ids[name].rsplit("/", 1)[1]),
[edge_ids[edge] for edge in vertex_edges[name]],
[],
{"position": list(point), "type": "point"},
{
"bounds": {"max": list(point), "min": list(point)},
"position": list(point),
"quality": "kernel_evaluated",
},
{**IDENTITY, "origin": list(point)},
"owner_not_part",
)
)
definition_id = "definition/root/shape/manual/fixture"
entity_asset = {
"definition_id": definition_id,
"edge_asset_id": edge_hash,
"edge_groups": [
{
"entity_id": edge_ids[name],
"first_index": 2 * index,
"group_id": index,
"index_count": 2,
"mesh_index": 0,
"primitive_index": 0,
}
for index, name in enumerate(edge_order)
],
"entities": entities,
"face_groups": [
{
"entity_id": face_ids[name],
"first_index": 3 * index,
"group_id": index,
"index_count": 3,
"mesh_index": 0,
"primitive_index": 0,
}
for index, name in enumerate(face_order)
],
"geometry_asset_id": geometry_hash,
"geometry_engine": {
"name": "OpenCascade",
"profile": "ocp-evaluated-properties-1",
"version": "fixture",
},
"schema_version": "1.0",
}
entity_asset["entities"].sort(
key=lambda entity: entity["entity_id"].encode("utf-8")
)
entity_bytes = canonical_json_bytes(entity_asset)
entity_hash = content_hash(entity_bytes)
appearance_draft = {
"alpha_mode": "opaque",
"base_color": [0.72, 0.75, 0.78, 1],
"double_sided": False,
"edge_color": [0.08, 0.09, 0.1, 1],
"metallic": 0,
"name": None,
"roughness": 0.55,
"sdk_metadata": {},
"source": None,
}
appearance_id = "appearance/evaluated/" + sha256_hex(
canonical_json_bytes(appearance_draft)
)
appearance = {"appearance_id": appearance_id, **appearance_draft}
geometry_uri = f"geometry/sha256-{geometry_hash.removeprefix('sha256:')}.glb"
edge_uri = f"edges/sha256-{edge_hash.removeprefix('sha256:')}.glb"
entity_uri = f"entities/sha256-{entity_hash.removeprefix('sha256:')}.json"
bounds = {"max": [1000, 0, 1000], "min": [0, -1000, 0]}
scene = with_scene_revision(
{
"annotations": [],
"appearances": [appearance],
"cameras": [],
"compile_options": {
"angular_tolerance": 0.22,
"embed_presentation": False,
"embed_source": False,
"linear_tolerance": 0.35,
},
"connectors": [],
"coordinate_system": {
"handedness": "right",
"length_unit": "mm",
"up_axis": "+Z",
},
"definitions": [
{
"appearance_id": appearance_id,
"definition_id": definition_id,
"edge_asset_id": edge_hash,
"entity_asset_id": entity_hash,
"geometry_asset_id": geometry_hash,
"kind": "shape",
"name": None,
"sdk_metadata": {},
"source": {
"kind": "manual",
"root_id": "root",
"source_id": "fixture",
},
}
],
"diagnostics": [],
"edge_assets": [
{
"asset_id": edge_hash,
"asset_to_scene": ASSET_TO_SCENE,
"byte_length": len(line_glb),
"content_hash": edge_hash,
"media_type": "model/gltf-binary",
"scene_local_bounds": bounds,
"tessellation": {"linear_tolerance": 0.35},
"uri": edge_uri,
}
],
"entity_assets": [
{
"byte_length": len(entity_bytes),
"content_hash": entity_hash,
"entity_asset_id": entity_hash,
"media_type": "application/vnd.simplecad.entities+json",
"uri": entity_uri,
}
],
"extensions": {},
"extensions_required": [],
"extensions_used": [],
"generator": {
"name": "simplecadapi",
"ocp_bindings_version": "fixture",
"ocp_version": "fixture",
"platform_tag": "fixture",
"profile": "scene-1.0-ocp-glb-2",
"python_abi": "fixture",
"simplecadapi_version": "fixture",
"toolchain_hash": "sha256:" + "0" * 64,
},
"geometry_assets": [
{
"asset_id": geometry_hash,
"asset_to_scene": ASSET_TO_SCENE,
"byte_length": len(triangle_glb),
"content_hash": geometry_hash,
"media_type": "model/gltf-binary",
"scene_local_bounds": bounds,
"tessellation": {
"angular_tolerance": 0.22,
"linear_tolerance": 0.35,
},
"uri": geometry_uri,
}
],
"lights": [],
"nodes": [
{
"appearance_override_id": None,
"definition_id": definition_id,
"name": None,
"node_id": "instance/root",
"order": 0,
"parent_node_id": None,
"sdk_metadata": {},
"selectable": True,
"source": {"kind": "shape_root", "root_id": "root"},
"transform": IDENTITY,
"visible": True,
}
],
"scene_id": "fixture",
"schema_version": "1.0",
"source": {"kind": "manual", "source_id": "fixture"},
}
)
scene_bytes = canonical_json_bytes(scene)
blobs = {
geometry_uri: triangle_glb,
edge_uri: line_glb,
entity_uri: entity_bytes,
}
return scene, scene_bytes, entity_asset, entity_bytes, blobs