三维显示骨架:可执行合同 + 模块签名 + 分阶段计划 - What: 按 docs/architecture/studio-3d-design.md 与 studio-3d-contracts.pyi 搭建骨架(阶段 0)。shared/src/orisys_app_shared/scene3d/:contracts.py 把 .pyi 的规格变成可执行代码——全部 DTO/枚举(FrameIdentity/FieldDescriptor/SurfaceFieldFrame/MeshPart/SurfaceBinding/CameraPreset/ExpectedGeometry/SceneAssets/ColorScale/DisplaySettings/VertexFields/RenderFrame/CameraMatrices/GLCapabilities/RenderStats/CompileOptions/CompileReport)、标准诊断码表(DIAGNOSTIC_SEVERITY,含 stale_frame=info、field_unavailable=warning 等)、ThreeDError、freeze()(拥有数据 + C 连续 + 只读)、五个校验器(validate_surface_frame/validate_binding/validate_mesh_part/validate_assets/validate_display_settings/validate_render_frame)、LatestFrameMailbox(容量 1、替换计数、锁内只换引用)、UNAVAILABLE_DESCRIPTOR/rest_descriptor() 与 summarize_parts(),以及全部 Protocol;该模块不 import Qt/OpenGL/SDK。assets.py/sampling.py/composer.py 为签名骨架(未实现处抛 NotImplementedError 并写明归属阶段)。apps/studio/src/orisys_studio/scene3d/:camera/adapters/coordinator/gl_backend/widget/panel 签名骨架,其中 configure_surface_format() 已实现(QApplication 之前调用,desktop33=GL3.3 Core / gles30=GLES3.0,深度 24、无 MSAA);模块级一律不 import GL 绑定,PyOpenGL 只在 GLDispatch.bind_current_context 里加载。tools/scene_assets/{probe_gl,validate_scene,compile_scene}.py 提供入口与参数,未实现时输出 not_implemented 并返回 2(避免被 CI 当成通过)。docs/architecture/studio-3d-stages.md:阶段 0-5 的产出、每阶段的验收矩阵、并行度与回归门。测试:shared/tests/test_scene3d_contracts.py 26 项(freeze 拥有数据、可写/非连续数组被拒、scalar 与 valid 形状一致、位移缺 quality 被拒、非有限 scalar 标为 valid 只报警不静默、绑定越界/-1 约定/权重和为 1、三角形索引越界、感知法线单位长度、恰好一个 sensing part、binding 必须属于 sensing part、设备几何/域/尺寸不符被拒、色标 lower<upper、邮箱替换计数与多线程不阻塞生产者、身份区分 source/generation、骨架可导入且不引入 PyOpenGL);apps/studio/tests/test_scene3d_skeleton.py 4 项(骨架不 import OpenGL、surface format 两个 profile、widget 构造不需要 context、各 stub 报出归属阶段)。 - Why: 用户要求"先从骨架搭起,然后计划功能实现顺序,最后逐阶段实现与验收"。骨架阶段的价值在于把接口变成可执行的(.pyi 本身无法被测试),并把"数组只读、单位与质量必须声明、近似位移不得当毫米、参考帧不得冒充测量、显示丢帧不得计入记录丢帧"这些容易在实现时被绕过的规则先固定下来;同时明确不 import Qt/OpenGL 的边界,使采集线程与 CI 能在无 GPU 环境导入它。 - Verification: shared 全量 187 passed(原 161 + 26);studio 95 passed(原 91 + 4);ui_check 554 项检查 0 失败;compileall 通过;probe_gl --profile desktop33 在未实现时返回 2 并说明将产出什么报告。 - Not in this change: 三维功能本身(阶段 1 起);app_window/main.py 未接线,骨架对现有应用完全惰性(无任何模块被应用导入)。
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
"""Compile a validated scene, and optionally a housing GLB, into a runtime package.
|
||||
|
||||
Stage 2 and stage 5 deliverables (``docs/architecture/studio-3d-stages.md``).
|
||||
|
||||
Offline only: this tool is what turns a Blender export and the legacy NPZ geometry
|
||||
into the ``.oryscene`` payload the runtime loads. It bakes the coordinate
|
||||
transform, the sampling table and the LOD once, so the runtime never repeats that
|
||||
work; it signs with a key from the build environment, so no signing key ever
|
||||
reaches a customer runtime.
|
||||
|
||||
Until stage 2 lands, no package is produced and the exit code is non-zero, so a
|
||||
release script cannot ship a package that was never compiled.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_STAGE = "stage 2 (asset contracts) and stage 5 (release protection)"
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("--legacy-dir", type=Path, default=None, help="legacy NPZ export")
|
||||
parser.add_argument("--map", type=Path, default=None, help="run-time UV mapping (npz)")
|
||||
parser.add_argument("--housing", type=Path, default=None, help="housing GLB")
|
||||
parser.add_argument("--output", type=Path, required=True, help="package to write")
|
||||
parser.add_argument("--asset-id", default="finger-display")
|
||||
parser.add_argument("--version", default="0.0.0")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
print(
|
||||
"not_implemented: "
|
||||
f"{_STAGE}. Would write {args.output} from "
|
||||
f"legacy={args.legacy_dir} map={args.map} housing={args.housing} "
|
||||
f"as {args.asset_id} {args.version}."
|
||||
)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Probe the real GL stack from inside a Qt window and write a diagnostic report.
|
||||
|
||||
Stage 1 deliverable (``docs/architecture/studio-3d-stages.md``).
|
||||
|
||||
It must create an **actual** Qt widget and read the context that Qt actually
|
||||
obtained — asking the system whether ``libGL`` exists proves nothing, and an
|
||||
offscreen UI test cannot stand in for a board's GPU. What it writes is the input
|
||||
to the platform decision: which profile a board gets, and whether the renderer is
|
||||
a real GPU.
|
||||
|
||||
Until stage 1 lands, the script explains what it will do and exits non-zero, so a
|
||||
build script cannot mistake "not implemented" for "probe passed".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_STAGE = "stage 1 (platform PoC)"
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument(
|
||||
"--profile",
|
||||
choices=("desktop33", "gles30"),
|
||||
default="desktop33",
|
||||
help="the start-up profile to probe (one per process)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="write the diagnostic report here as JSON",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--windowed",
|
||||
action="store_true",
|
||||
help="create a visible window instead of a short-lived one",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
report = {
|
||||
"status": "not_implemented",
|
||||
"profile": args.profile,
|
||||
"stage": _STAGE,
|
||||
"note": (
|
||||
"The probe creates a real QOpenGLWidget and reports the context Qt "
|
||||
"obtained: vendor, renderer, GL/GLSL version, DPR, framebuffer size, "
|
||||
"and whether the renderer is a software rasteriser. It never reports "
|
||||
"a board as verified from an offscreen or virtual context."
|
||||
),
|
||||
}
|
||||
text = json.dumps(report, indent=2, ensure_ascii=False)
|
||||
if args.output is not None:
|
||||
args.output.write_text(text + "\n", encoding="utf-8")
|
||||
print(text)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Validate a scene package (or a legacy import) without a GPU.
|
||||
|
||||
Stage 2 deliverable (``docs/architecture/studio-3d-stages.md``): the checks the
|
||||
loader and the compiler must agree on, runnable on a build machine and in CI.
|
||||
|
||||
Until stage 2 lands, importing an asset is not implemented; the argument parsing
|
||||
and the exit contract exist so the CI job can be wired now.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_STAGE = "stage 2 (asset contracts)"
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("asset", type=Path, help="scene package or legacy asset directory")
|
||||
parser.add_argument("--housing", type=Path, default=None, help="housing GLB to check")
|
||||
parser.add_argument("--json", action="store_true", help="machine-readable output")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
report = {
|
||||
"status": "not_implemented",
|
||||
"asset": str(args.asset),
|
||||
"stage": _STAGE,
|
||||
"note": (
|
||||
"Checks: manifest/schema, signature and entry hashes, coordinate frame "
|
||||
"and units, part roles, index ranges, binding weights and domain, "
|
||||
"geometry/domain match against the device description."
|
||||
),
|
||||
}
|
||||
if args.json:
|
||||
print(json.dumps(report, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
print(f"{report['status']}: {report['note']}")
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user