python-cad-tools 0.1.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. python_cad_tools/__init__.py +10 -0
  2. python_cad_tools/_locking.py +63 -0
  3. python_cad_tools/_project_loader.py +273 -0
  4. python_cad_tools/_project_state.py +65 -0
  5. python_cad_tools/_scaffold.py +66 -0
  6. python_cad_tools/_transaction.py +208 -0
  7. python_cad_tools/_viewer_static/app.webmanifest +18 -0
  8. python_cad_tools/_viewer_static/asset-manifest.json +1 -0
  9. python_cad_tools/_viewer_static/assets/animationGroup-Sd34ntWg.js +1 -0
  10. python_cad_tools/_viewer_static/assets/bakedVertexAnimation-Cd_pRdrn.js +316 -0
  11. python_cad_tools/_viewer_static/assets/basisTextureLoader-DWgvS_1G.js +1 -0
  12. python_cad_tools/_viewer_static/assets/brdfTextureTools-CWGiG1W2.js +2 -0
  13. python_cad_tools/_viewer_static/assets/bumpFragment-CWULheF1.js +127 -0
  14. python_cad_tools/_viewer_static/assets/bumpVertex-DqzFZayE.js +6 -0
  15. python_cad_tools/_viewer_static/assets/bumpVertexDeclaration-C5EFyTio.js +6 -0
  16. python_cad_tools/_viewer_static/assets/clipPlaneFragment-BXRtt4jj.js +46 -0
  17. python_cad_tools/_viewer_static/assets/clipPlaneVertex-B_VhD1oQ.js +37 -0
  18. python_cad_tools/_viewer_static/assets/cubemapToSphericalPolynomial-DRpaukza.js +1 -0
  19. python_cad_tools/_viewer_static/assets/dds-Dkb2Cx2N.js +1 -0
  20. python_cad_tools/_viewer_static/assets/dds.pure-DtvJsKvY.js +1 -0
  21. python_cad_tools/_viewer_static/assets/ddsTextureLoader-H78xux1g.js +1 -0
  22. python_cad_tools/_viewer_static/assets/default.fragment-oSQ-tWhe.js +469 -0
  23. python_cad_tools/_viewer_static/assets/default.vertex-DhFjw_Da.js +209 -0
  24. python_cad_tools/_viewer_static/assets/defaultUboDeclaration-BMDHNppx.js +5 -0
  25. python_cad_tools/_viewer_static/assets/dumpTools-BaNZPjUX.js +2 -0
  26. python_cad_tools/_viewer_static/assets/envTextureLoader-V0YnLcIM.js +1 -0
  27. python_cad_tools/_viewer_static/assets/exrTextureLoader-CjGyIK3x.js +1 -0
  28. python_cad_tools/_viewer_static/assets/flowGraphApplyForceBlock-CW5nuobR.js +1 -0
  29. python_cad_tools/_viewer_static/assets/flowGraphApplyImpulseBlock-LFqYIkhj.js +1 -0
  30. python_cad_tools/_viewer_static/assets/flowGraphArrayIndexBlock-BMOHUbBm.js +1 -0
  31. python_cad_tools/_viewer_static/assets/flowGraphBezierCurveEasingBlock-knmTiuEk.js +1 -0
  32. python_cad_tools/_viewer_static/assets/flowGraphBinaryOperationBlock-DFLuCAKJ.js +1 -0
  33. python_cad_tools/_viewer_static/assets/flowGraphBranchBlock-BJRHMXMo.js +1 -0
  34. python_cad_tools/_viewer_static/assets/flowGraphCachedOperationBlock-Dng-6jE0.js +1 -0
  35. python_cad_tools/_viewer_static/assets/flowGraphCancelDelayBlock-CgbK3CQZ.js +1 -0
  36. python_cad_tools/_viewer_static/assets/flowGraphCodeExecutionBlock-8zMtq9tg.js +1 -0
  37. python_cad_tools/_viewer_static/assets/flowGraphConditionalDataBlock-BiLr2rkI.js +1 -0
  38. python_cad_tools/_viewer_static/assets/flowGraphConsoleLogBlock-J1vcCtNP.js +1 -0
  39. python_cad_tools/_viewer_static/assets/flowGraphConstantBlock-CFb5nuaV.js +1 -0
  40. python_cad_tools/_viewer_static/assets/flowGraphContextBlock-DAeGVzhC.js +1 -0
  41. python_cad_tools/_viewer_static/assets/flowGraphCounterBlock-lZlAQiJd.js +1 -0
  42. python_cad_tools/_viewer_static/assets/flowGraphDataSwitchBlock-BIzvmbVy.js +1 -0
  43. python_cad_tools/_viewer_static/assets/flowGraphDebounceBlock-DvNuMVwy.js +1 -0
  44. python_cad_tools/_viewer_static/assets/flowGraphDebugBlock-Dymd2VEE.js +1 -0
  45. python_cad_tools/_viewer_static/assets/flowGraphDoNBlock-v68aoNLH.js +1 -0
  46. python_cad_tools/_viewer_static/assets/flowGraphEasingBlock-CTnGv92q.js +1 -0
  47. python_cad_tools/_viewer_static/assets/flowGraphFlipFlopBlock-DXsO0Chw.js +1 -0
  48. python_cad_tools/_viewer_static/assets/flowGraphForLoopBlock-CPpe9T75.js +1 -0
  49. python_cad_tools/_viewer_static/assets/flowGraphFunctionReferenceBlock-DjDev4Pq.js +1 -0
  50. python_cad_tools/_viewer_static/assets/flowGraphGLTFDataProvider-GIYCz4AQ.js +1 -0
  51. python_cad_tools/_viewer_static/assets/flowGraphGetAngularVelocityBlock-BStkOVeL.js +1 -0
  52. python_cad_tools/_viewer_static/assets/flowGraphGetAssetBlock-Eiqoivbz.js +1 -0
  53. python_cad_tools/_viewer_static/assets/flowGraphGetLinearVelocityBlock-B0cpV5ws.js +1 -0
  54. python_cad_tools/_viewer_static/assets/flowGraphGetPhysicsMassPropertiesBlock-B-H64yaI.js +1 -0
  55. python_cad_tools/_viewer_static/assets/flowGraphGetPropertyBlock-CUyOsPw0.js +1 -0
  56. python_cad_tools/_viewer_static/assets/flowGraphGetSoundVolumeBlock-C0FjrsUz.js +1 -0
  57. python_cad_tools/_viewer_static/assets/flowGraphGetVariableBlock-O4597zBl.js +1 -0
  58. python_cad_tools/_viewer_static/assets/flowGraphIndexOfBlock-E00Z3KXM.js +1 -0
  59. python_cad_tools/_viewer_static/assets/flowGraphInterpolationBlock-DnkA9MNH.js +1 -0
  60. python_cad_tools/_viewer_static/assets/flowGraphIsKeyPressedBlock-DDfeuAyf.js +1 -0
  61. python_cad_tools/_viewer_static/assets/flowGraphIsSoundPlayingBlock-DMXg8jdM.js +1 -0
  62. python_cad_tools/_viewer_static/assets/flowGraphJsonPointerParserBlock-OmCorK0y.js +1 -0
  63. python_cad_tools/_viewer_static/assets/flowGraphKeyDownEventBlock-RuVtj6g2.js +1 -0
  64. python_cad_tools/_viewer_static/assets/flowGraphKeyUpEventBlock-DO4pAfHi.js +1 -0
  65. python_cad_tools/_viewer_static/assets/flowGraphKeyboardEventBlock-Bx3pAzzh.js +1 -0
  66. python_cad_tools/_viewer_static/assets/flowGraphMathBlocks-7W1XYOC8.js +1 -0
  67. python_cad_tools/_viewer_static/assets/flowGraphMathCombineExtractBlocks-a4f0Kv6e.js +1 -0
  68. python_cad_tools/_viewer_static/assets/flowGraphMatrixMathBlocks-C5Z-y1Au.js +1 -0
  69. python_cad_tools/_viewer_static/assets/flowGraphMeshPickEventBlock-D4m-I4CS.js +1 -0
  70. python_cad_tools/_viewer_static/assets/flowGraphMultiGateBlock-BISVSMw6.js +1 -0
  71. python_cad_tools/_viewer_static/assets/flowGraphPauseAnimationBlock-BYP2Z-HG.js +1 -0
  72. python_cad_tools/_viewer_static/assets/flowGraphPauseSoundBlock-Drh1EeW6.js +1 -0
  73. python_cad_tools/_viewer_static/assets/flowGraphPhysicsCollisionEventBlock-BoziAQ75.js +1 -0
  74. python_cad_tools/_viewer_static/assets/flowGraphPlayAnimationBlock-Clry-68M.js +1 -0
  75. python_cad_tools/_viewer_static/assets/flowGraphPlaySoundBlock-LbBVxGBE.js +1 -0
  76. python_cad_tools/_viewer_static/assets/flowGraphPointerDownEventBlock-DIVXCSHh.js +1 -0
  77. python_cad_tools/_viewer_static/assets/flowGraphPointerMoveEventBlock-BNNOzY2W.js +1 -0
  78. python_cad_tools/_viewer_static/assets/flowGraphPointerOutEventBlock-B8f2dOFT.js +1 -0
  79. python_cad_tools/_viewer_static/assets/flowGraphPointerOverEventBlock-CQr8LCee.js +1 -0
  80. python_cad_tools/_viewer_static/assets/flowGraphPointerUpEventBlock-DMg_iEHl.js +1 -0
  81. python_cad_tools/_viewer_static/assets/flowGraphReceiveCustomEventBlock-B8GJm9Ai.js +1 -0
  82. python_cad_tools/_viewer_static/assets/flowGraphSceneReadyEventBlock-Dcdb29K2.js +1 -0
  83. python_cad_tools/_viewer_static/assets/flowGraphSceneTickEventBlock-BLE5p4mi.js +1 -0
  84. python_cad_tools/_viewer_static/assets/flowGraphSendCustomEventBlock-BMzDItQ5.js +1 -0
  85. python_cad_tools/_viewer_static/assets/flowGraphSequenceBlock-DRURovjn.js +1 -0
  86. python_cad_tools/_viewer_static/assets/flowGraphSetAngularVelocityBlock-Ba8dhkLM.js +1 -0
  87. python_cad_tools/_viewer_static/assets/flowGraphSetDelayBlock-CnQR--Gn.js +1 -0
  88. python_cad_tools/_viewer_static/assets/flowGraphSetLinearVelocityBlock-Dm1r3vkh.js +1 -0
  89. python_cad_tools/_viewer_static/assets/flowGraphSetPhysicsMotionTypeBlock-BNtMLimi.js +1 -0
  90. python_cad_tools/_viewer_static/assets/flowGraphSetPropertyBlock-C-uv2jhP.js +1 -0
  91. python_cad_tools/_viewer_static/assets/flowGraphSetSoundVolumeBlock-D9LDx3lg.js +1 -0
  92. python_cad_tools/_viewer_static/assets/flowGraphSetVariableBlock-DszzRB5X.js +1 -0
  93. python_cad_tools/_viewer_static/assets/flowGraphSoundEndedEventBlock-DeTDSudb.js +1 -0
  94. python_cad_tools/_viewer_static/assets/flowGraphStopAnimationBlock-DWuwoFWr.js +1 -0
  95. python_cad_tools/_viewer_static/assets/flowGraphStopSoundBlock-DEnj2cQw.js +1 -0
  96. python_cad_tools/_viewer_static/assets/flowGraphSwitchBlock-zs8Fp9ky.js +1 -0
  97. python_cad_tools/_viewer_static/assets/flowGraphThrottleBlock-BYAKUQXd.js +1 -0
  98. python_cad_tools/_viewer_static/assets/flowGraphTransformCoordinatesSystemBlock-DYP54m5P.js +1 -0
  99. python_cad_tools/_viewer_static/assets/flowGraphTypeToTypeBlocks-vFwn79bS.js +1 -0
  100. python_cad_tools/_viewer_static/assets/flowGraphUnaryOperationBlock-CaRh4Ef0.js +1 -0
  101. python_cad_tools/_viewer_static/assets/flowGraphVectorMathBlocks-BEIhMs-A.js +1 -0
  102. python_cad_tools/_viewer_static/assets/flowGraphWaitAllBlock-DFbELP_j.js +1 -0
  103. python_cad_tools/_viewer_static/assets/flowGraphWhileLoopBlock-C1OKQ-_F.js +1 -0
  104. python_cad_tools/_viewer_static/assets/fogFragment-DtDlQBnz.js +25 -0
  105. python_cad_tools/_viewer_static/assets/gaussianSplatting-Cfigjb15.js +242 -0
  106. python_cad_tools/_viewer_static/assets/gaussianSplatting-DIn8R8oF.js +296 -0
  107. python_cad_tools/_viewer_static/assets/gaussianSplatting.fragment-BxeRJ2k-.js +38 -0
  108. python_cad_tools/_viewer_static/assets/gaussianSplatting.fragment-DNDmvRs-.js +37 -0
  109. python_cad_tools/_viewer_static/assets/gaussianSplatting.vertex-D92z5jYn.js +86 -0
  110. python_cad_tools/_viewer_static/assets/gaussianSplatting.vertex-DSWfFnKQ.js +86 -0
  111. python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.fragment-DBQE2gDk.js +12 -0
  112. python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.fragment-qwx5ntqJ.js +17 -0
  113. python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.vertex-CP2kd3jU.js +30 -0
  114. python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.vertex-Dr-fEg_W.js +19 -0
  115. python_cad_tools/_viewer_static/assets/geometry.fragment-CsJIm2H0.js +242 -0
  116. python_cad_tools/_viewer_static/assets/geometry.fragment-Dph3muZt.js +262 -0
  117. python_cad_tools/_viewer_static/assets/geometry.vertex-BXGMTA_J.js +212 -0
  118. python_cad_tools/_viewer_static/assets/geometry.vertex-Bm9Wg3Gi.js +214 -0
  119. python_cad_tools/_viewer_static/assets/glowBlurPostProcess.fragment-BJSwKIJD.js +7 -0
  120. python_cad_tools/_viewer_static/assets/glowMapGeneration.fragment-DcyS1cTF.js +63 -0
  121. python_cad_tools/_viewer_static/assets/glowMapGeneration.vertex-BGq1Fakj.js +75 -0
  122. python_cad_tools/_viewer_static/assets/glowMapMerge.fragment-DxcneU_F.js +25 -0
  123. python_cad_tools/_viewer_static/assets/glowMapMerge.vertex-DaJJKqMV.js +8 -0
  124. python_cad_tools/_viewer_static/assets/harmonicsFunctions-DzJcvNWG.js +16 -0
  125. python_cad_tools/_viewer_static/assets/hdrTextureLoader-BGQvgh0h.js +2 -0
  126. python_cad_tools/_viewer_static/assets/helperFunctions-Cugfp1Fz.js +87 -0
  127. python_cad_tools/_viewer_static/assets/iesTextureLoader-CO4G0p2M.js +2 -0
  128. python_cad_tools/_viewer_static/assets/index-B2zBEWsq.js +7007 -0
  129. python_cad_tools/_viewer_static/assets/index-DcTTrc-8.css +1 -0
  130. python_cad_tools/_viewer_static/assets/kernelBlur.fragment-B6CTXTxY.js +57 -0
  131. python_cad_tools/_viewer_static/assets/kernelBlur.fragment-BkKbudbW.js +57 -0
  132. python_cad_tools/_viewer_static/assets/kernelBlur.vertex-BYwFU5IS.js +11 -0
  133. python_cad_tools/_viewer_static/assets/kernelBlur.vertex-D371zUfT.js +11 -0
  134. python_cad_tools/_viewer_static/assets/kernelBlurVaryingDeclaration-CgBGaCHg.js +1 -0
  135. python_cad_tools/_viewer_static/assets/kernelBlurVaryingDeclaration-YS8JpqdO.js +1 -0
  136. python_cad_tools/_viewer_static/assets/ktxTextureLoader-ncMSJTjl.js +1 -0
  137. python_cad_tools/_viewer_static/assets/lightFragment-skHiFbKB.js +398 -0
  138. python_cad_tools/_viewer_static/assets/logDepthDeclaration-BR8XRz9F.js +4 -0
  139. python_cad_tools/_viewer_static/assets/logDepthVertex-UKrhEIvg.js +14 -0
  140. python_cad_tools/_viewer_static/assets/mainUVVaryingDeclaration-BxQWGFcG.js +4 -0
  141. python_cad_tools/_viewer_static/assets/mesh.vertexData.functions-ayNlGC5K.js +1 -0
  142. python_cad_tools/_viewer_static/assets/meshUboDeclaration-BnX046aQ.js +4 -0
  143. python_cad_tools/_viewer_static/assets/oitFragment-CTMCOfNP.js +605 -0
  144. python_cad_tools/_viewer_static/assets/openpbr.fragment-DPJPqi6p.js +1530 -0
  145. python_cad_tools/_viewer_static/assets/openpbr.fragment-Dv6qTVVi.js +1384 -0
  146. python_cad_tools/_viewer_static/assets/openpbr.vertex-Bn2wrdGL.js +364 -0
  147. python_cad_tools/_viewer_static/assets/openpbr.vertex-CHESW3gB.js +251 -0
  148. python_cad_tools/_viewer_static/assets/openpbrMaterial.pure-BdK7VgO_.js +3 -0
  149. python_cad_tools/_viewer_static/assets/openpbrMaterialLoadingAdapter-Bm_AHDUN.js +4 -0
  150. python_cad_tools/_viewer_static/assets/openpbrTransmissionLayerData-BDa9qEoN.js +339 -0
  151. python_cad_tools/_viewer_static/assets/openpbrTransmissionLayerData-Ba_ly4LE.js +343 -0
  152. python_cad_tools/_viewer_static/assets/openpbrUboDeclaration-BY2lSH2C.js +5 -0
  153. python_cad_tools/_viewer_static/assets/openpbrUboDeclaration-DajhikDS.js +6 -0
  154. python_cad_tools/_viewer_static/assets/packingFunctions-BHPwcGG2.js +4 -0
  155. python_cad_tools/_viewer_static/assets/packingFunctions-DTwzBRBx.js +4 -0
  156. python_cad_tools/_viewer_static/assets/pass.fragment-9oKU4pI3.js +4 -0
  157. python_cad_tools/_viewer_static/assets/passCube.fragment-CpAeiYx1.js +23 -0
  158. python_cad_tools/_viewer_static/assets/passCube.fragment-DHtSWVRF.js +23 -0
  159. python_cad_tools/_viewer_static/assets/pbr.fragment-cIhMKwLs.js +2560 -0
  160. python_cad_tools/_viewer_static/assets/pbr.vertex-BfdONsf2.js +255 -0
  161. python_cad_tools/_viewer_static/assets/pbrBRDFFunctions-B_ppOzPO.js +173 -0
  162. python_cad_tools/_viewer_static/assets/pbrDebug-DDdwL5_t.js +693 -0
  163. python_cad_tools/_viewer_static/assets/pbrIBLFunctions-b1zdpzOP.js +43 -0
  164. python_cad_tools/_viewer_static/assets/pbrMaterial.pure-BZA7yVhu.js +2 -0
  165. python_cad_tools/_viewer_static/assets/pbrMaterialLoadingAdapter-BRvKRnjl.js +1 -0
  166. python_cad_tools/_viewer_static/assets/pbrUboDeclaration-CMZp25k3.js +5 -0
  167. python_cad_tools/_viewer_static/assets/postprocess.vertex-DzdUpNbW.js +9 -0
  168. python_cad_tools/_viewer_static/assets/procedural.vertex-BnbuQ-1f.js +7 -0
  169. python_cad_tools/_viewer_static/assets/procedural.vertex-Dp-Ng7Pc.js +8 -0
  170. python_cad_tools/_viewer_static/assets/reflectionFunction-DBlr3oDs.js +83 -0
  171. python_cad_tools/_viewer_static/assets/rgbdDecode.fragment-CyvnZFOJ.js +5 -0
  172. python_cad_tools/_viewer_static/assets/rgbdEncode.fragment-Dz4zLnoD.js +5 -0
  173. python_cad_tools/_viewer_static/assets/rgbdEncode.fragment-iscgpA1_.js +5 -0
  174. python_cad_tools/_viewer_static/assets/sceneUboDeclaration-BzFIc4mb.js +11 -0
  175. python_cad_tools/_viewer_static/assets/textureProcessor.fragment-C9D0rd-M.js +151 -0
  176. python_cad_tools/_viewer_static/assets/textureProcessor.fragment-JMKyWZng.js +146 -0
  177. python_cad_tools/_viewer_static/assets/tgaTextureLoader-oxTElIQJ.js +1 -0
  178. python_cad_tools/_viewer_static/assets/vertexColorMixing-mdC9xZe1.js +418 -0
  179. python_cad_tools/_viewer_static/favicon.svg +10 -0
  180. python_cad_tools/_viewer_static/index.html +18 -0
  181. python_cad_tools/_viewer_static/sw.js +61 -0
  182. python_cad_tools/artifacts.py +546 -0
  183. python_cad_tools/build-info.json +1 -0
  184. python_cad_tools/build.py +270 -0
  185. python_cad_tools/cli.py +212 -0
  186. python_cad_tools/context.py +67 -0
  187. python_cad_tools/determinism.py +66 -0
  188. python_cad_tools/drawings/__init__.py +124 -0
  189. python_cad_tools/drawings/_catalog.py +79 -0
  190. python_cad_tools/drawings/_validation.py +238 -0
  191. python_cad_tools/elements.py +121 -0
  192. python_cad_tools/exceptions.py +45 -0
  193. python_cad_tools/exporters/__init__.py +8 -0
  194. python_cad_tools/exporters/base.py +24 -0
  195. python_cad_tools/exporters/drawings/__init__.py +5 -0
  196. python_cad_tools/exporters/drawings/annotations.py +444 -0
  197. python_cad_tools/exporters/drawings/dxf.py +110 -0
  198. python_cad_tools/exporters/drawings/model.py +70 -0
  199. python_cad_tools/exporters/drawings/pdf.py +119 -0
  200. python_cad_tools/exporters/drawings/primitives.py +93 -0
  201. python_cad_tools/exporters/drawings/projection.py +64 -0
  202. python_cad_tools/exporters/drawings/svg.py +135 -0
  203. python_cad_tools/exporters/glb.py +100 -0
  204. python_cad_tools/exporters/ifc.py +229 -0
  205. python_cad_tools/exporters/quantities.py +136 -0
  206. python_cad_tools/exporters/step.py +117 -0
  207. python_cad_tools/geometry.py +187 -0
  208. python_cad_tools/manifests.py +338 -0
  209. python_cad_tools/project_config.py +174 -0
  210. python_cad_tools/py.typed +1 -0
  211. python_cad_tools/runtime_sync.py +126 -0
  212. python_cad_tools/schemas/build-manifest-1.json +1 -0
  213. python_cad_tools/schemas/design-manifest-1.json +1 -0
  214. python_cad_tools/schemas/download-manifest-2.json +46 -0
  215. python_cad_tools/schemas/drawing-annotations-1.json +66 -0
  216. python_cad_tools/schemas/glb-manifest-1.json +1 -0
  217. python_cad_tools/schemas/ifc-validation-1.json +1 -0
  218. python_cad_tools/schemas/quantities-1.json +1 -0
  219. python_cad_tools/schemas/run-metadata-1.json +1 -0
  220. python_cad_tools/schemas/site-metadata-1.json +34 -0
  221. python_cad_tools/schemas/step-validation-1.json +1 -0
  222. python_cad_tools/site.py +676 -0
  223. python_cad_tools/templates/project/.gitignore +9 -0
  224. python_cad_tools/templates/project/README.md +38 -0
  225. python_cad_tools/templates/project/config.py +36 -0
  226. python_cad_tools/templates/project/drawing_annotations.py +117 -0
  227. python_cad_tools/templates/project/model.py +87 -0
  228. python_cad_tools/templates/project/pyproject.toml +27 -0
  229. python_cad_tools/units.py +207 -0
  230. python_cad_tools/validation.py +153 -0
  231. python_cad_tools-0.1.0rc1.dist-info/METADATA +149 -0
  232. python_cad_tools-0.1.0rc1.dist-info/RECORD +236 -0
  233. python_cad_tools-0.1.0rc1.dist-info/WHEEL +5 -0
  234. python_cad_tools-0.1.0rc1.dist-info/entry_points.txt +2 -0
  235. python_cad_tools-0.1.0rc1.dist-info/licenses/LICENSE +679 -0
  236. python_cad_tools-0.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,546 @@
1
+ """Artifact inventory, parser-level validation, and final-byte verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import mimetypes
7
+ import os
8
+ import re
9
+ from dataclasses import asdict, dataclass
10
+ from importlib import metadata, resources
11
+ from pathlib import Path, PurePosixPath
12
+ from typing import Any, Literal, cast
13
+ from xml.etree import ElementTree
14
+
15
+ from .determinism import semantic_hash, sha256_file
16
+ from .exceptions import ArtifactIntegrityError, BuildExportError
17
+ from .manifests import load_json_document, validate_json_document
18
+ from .validation import ValidationIssue
19
+
20
+ BUILD_MANIFEST_RELATIVE = "manifests/build-manifest.json"
21
+ _HEX = re.compile(r"[0-9a-f]{64}\Z")
22
+ _ARTIFACT_FIELDS = {"path", "size", "sha256", "media_type", "producer", "schema_id", "schema_version", "volatile"}
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class ArtifactRecord:
27
+ path: str
28
+ size: int
29
+ sha256: str
30
+ media_type: str
31
+ producer: str
32
+ schema_id: str | None
33
+ schema_version: int | None
34
+ volatile: bool
35
+
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class VerificationResult:
39
+ ok: bool
40
+ output_root: Path
41
+ issues: tuple[ValidationIssue, ...]
42
+ artifacts: tuple[ArtifactRecord, ...]
43
+ build_manifest: Path
44
+ artifact_set_hash: str
45
+ stable_artifact_set_hash: str
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class PackageBuildInfo:
50
+ schema_id: Literal["urn:python-cad-tools:schema:package-build-info:1"]
51
+ schema_version: Literal[1]
52
+ distribution: Literal["python-cad-tools"]
53
+ version: str
54
+ source_commit: str
55
+ source_dirty: bool
56
+ viewer_shell_sha256: str
57
+
58
+
59
+ def load_build_manifest(path: Path) -> dict[str, Any]:
60
+ value = validate_json_document(path)
61
+ if value["schema_id"] != "urn:python-cad-tools:schema:build-manifest:1":
62
+ raise ArtifactIntegrityError(f"Not a build manifest: {path}")
63
+ return value
64
+
65
+
66
+ def load_design_manifest(path: Path) -> dict[str, Any]:
67
+ value = validate_json_document(path)
68
+ if value["schema_id"] != "urn:python-cad-tools:schema:design-manifest:1":
69
+ raise ArtifactIntegrityError(f"Not a design manifest: {path}")
70
+ return value
71
+
72
+
73
+ def artifact_records(output_root: Path) -> tuple[ArtifactRecord, ...]:
74
+ records: list[ArtifactRecord] = []
75
+ for relative, path in _inventory(output_root).items():
76
+ if relative == BUILD_MANIFEST_RELATIVE:
77
+ continue
78
+ schema_id: str | None = None
79
+ schema_version: int | None = None
80
+ if path.suffix == ".json":
81
+ document = validate_json_document(path)
82
+ schema_id = cast(str, document["schema_id"])
83
+ schema_version = cast(int, document["schema_version"])
84
+ records.append(
85
+ ArtifactRecord(
86
+ path=relative,
87
+ size=path.stat().st_size,
88
+ sha256=sha256_file(path),
89
+ media_type=_media_type(path),
90
+ producer="python-cad-tools",
91
+ schema_id=schema_id,
92
+ schema_version=schema_version,
93
+ volatile=relative == "manifests/run-metadata.json",
94
+ )
95
+ )
96
+ return tuple(sorted(records, key=lambda record: record.path))
97
+
98
+
99
+ def validate_export_tree(output_root: Path, selected_formats: tuple[str, ...], artifact_stem: str) -> None:
100
+ """Validate the complete exporter output before inventory hashing."""
101
+ actual = set(_inventory(output_root))
102
+ expected = _expected_paths(selected_formats, artifact_stem, ".gitkeep" in actual)
103
+ if BUILD_MANIFEST_RELATIVE in actual:
104
+ expected.add(BUILD_MANIFEST_RELATIVE)
105
+ missing = sorted(expected - actual)
106
+ unexpected = sorted(actual - expected)
107
+ if missing or unexpected:
108
+ parts = []
109
+ if missing:
110
+ parts.append("missing: " + ", ".join(missing))
111
+ if unexpected:
112
+ parts.append("unexpected: " + ", ".join(unexpected))
113
+ raise ArtifactIntegrityError("Invalid staged artifact set (" + "; ".join(parts) + ")")
114
+ for relative in sorted(actual):
115
+ if relative.endswith(".json"):
116
+ validate_json_document(output_root / relative)
117
+ _parse_formats(output_root, selected_formats, artifact_stem)
118
+ _reconcile_formats(output_root, selected_formats)
119
+
120
+
121
+ def verify_output_root(output_root: Path, project_root: Path | None = None) -> VerificationResult:
122
+ if output_root.is_symlink() or not output_root.is_dir():
123
+ raise ArtifactIntegrityError(f"Generated output is missing or unsafe: {output_root}")
124
+ manifest_path = output_root / BUILD_MANIFEST_RELATIVE
125
+ document = load_build_manifest(manifest_path)
126
+ records = _records_from_manifest(document)
127
+ actual = _inventory(output_root)
128
+ expected = {record.path for record in records} | {BUILD_MANIFEST_RELATIVE}
129
+ if set(actual) != expected:
130
+ missing = sorted(expected - set(actual))
131
+ unexpected = sorted(set(actual) - expected)
132
+ raise ArtifactIntegrityError(f"Artifact inventory mismatch; missing={missing}, unexpected={unexpected}")
133
+ for record in records:
134
+ path = actual[record.path]
135
+ size = path.stat().st_size
136
+ digest = sha256_file(path)
137
+ if size != record.size:
138
+ raise ArtifactIntegrityError(f"Artifact size mismatch for {record.path}: expected {record.size}, got {size}")
139
+ if digest != record.sha256:
140
+ raise ArtifactIntegrityError(f"Artifact SHA-256 mismatch for {record.path}: expected {record.sha256}, got {digest}")
141
+ if path.suffix == ".json":
142
+ value = validate_json_document(path)
143
+ if value["schema_id"] != record.schema_id or value["schema_version"] != record.schema_version:
144
+ raise ArtifactIntegrityError(f"Recorded schema mismatch for {record.path}")
145
+ artifact_hash = semantic_hash([asdict(record) for record in records])
146
+ stable_hash = semantic_hash([asdict(record) for record in records if not record.volatile])
147
+ if document["artifact_set_hash"] != artifact_hash:
148
+ raise ArtifactIntegrityError("Build manifest artifact_set_hash does not reproduce")
149
+ if document["stable_artifact_set_hash"] != stable_hash:
150
+ raise ArtifactIntegrityError("Build manifest stable_artifact_set_hash does not reproduce")
151
+ if project_root is not None:
152
+ _verify_sources(project_root, document)
153
+ selected = _selected_formats(document)
154
+ design = load_design_manifest(output_root / "manifests/design-manifest.json")
155
+ design_without_hash = dict(design)
156
+ recorded_design_hash = design_without_hash.pop("semantic_hash", None)
157
+ if recorded_design_hash != semantic_hash(design_without_hash):
158
+ raise ArtifactIntegrityError("Design manifest semantic_hash does not reproduce")
159
+ if document["design_semantic_hash"] != design["semantic_hash"]:
160
+ raise ArtifactIntegrityError("Build and design manifest semantic hashes disagree")
161
+ validate_export_tree(output_root, selected, str(design["artifact_stem"]))
162
+ return VerificationResult(
163
+ ok=True,
164
+ output_root=output_root,
165
+ issues=(),
166
+ artifacts=records,
167
+ build_manifest=manifest_path,
168
+ artifact_set_hash=artifact_hash,
169
+ stable_artifact_set_hash=stable_hash,
170
+ )
171
+
172
+
173
+ def verify_build_output(project_root: Path) -> VerificationResult:
174
+ from ._locking import project_lock
175
+ from ._transaction import recover_promotion
176
+ from .project_config import load_project_config, output_path, resolve_project_root
177
+
178
+ root = resolve_project_root(project_root)
179
+ config = load_project_config(root)
180
+ output = output_path(root, config)
181
+ with project_lock(root):
182
+ recover_promotion(output, lambda candidate: verify_output_root(candidate))
183
+ return verify_output_root(output, root)
184
+
185
+
186
+ def get_package_build_info() -> PackageBuildInfo:
187
+ try:
188
+ document = json.loads(resources.files("python_cad_tools").joinpath("build-info.json").read_text(encoding="utf-8"))
189
+ except (FileNotFoundError, json.JSONDecodeError) as error:
190
+ raise ArtifactIntegrityError("The installed distribution has no valid build-info.json") from error
191
+ fields = {"schema_id", "schema_version", "distribution", "version", "source_commit", "source_dirty", "viewer_shell_sha256"}
192
+ if not isinstance(document, dict) or set(document) != fields:
193
+ raise ArtifactIntegrityError("build-info.json must contain an object")
194
+ installed_version = metadata.version("python-cad-tools")
195
+ if (
196
+ document.get("schema_id") != "urn:python-cad-tools:schema:package-build-info:1"
197
+ or document.get("schema_version") != 1
198
+ or document.get("distribution") != "python-cad-tools"
199
+ or document.get("version") != installed_version
200
+ or type(document.get("source_dirty")) is not bool
201
+ or not isinstance(document.get("source_commit"), str)
202
+ or not isinstance(document.get("viewer_shell_sha256"), str)
203
+ or _HEX.fullmatch(document["viewer_shell_sha256"]) is None
204
+ ):
205
+ raise ArtifactIntegrityError("build-info.json contains invalid package identity or integrity fields")
206
+ try:
207
+ return PackageBuildInfo(
208
+ schema_id=cast(Any, document["schema_id"]),
209
+ schema_version=cast(Any, document["schema_version"]),
210
+ distribution=cast(Any, document["distribution"]),
211
+ version=installed_version,
212
+ source_commit=str(document["source_commit"]),
213
+ source_dirty=bool(document["source_dirty"]),
214
+ viewer_shell_sha256=str(document["viewer_shell_sha256"]),
215
+ )
216
+ except KeyError as error:
217
+ raise ArtifactIntegrityError(f"build-info.json is missing {error.args[0]!r}") from error
218
+
219
+
220
+ def _records_from_manifest(document: dict[str, Any]) -> tuple[ArtifactRecord, ...]:
221
+ raw = document.get("artifacts")
222
+ if not isinstance(raw, list):
223
+ raise ArtifactIntegrityError("Build manifest artifacts must be an array")
224
+ records: list[ArtifactRecord] = []
225
+ seen: set[str] = set()
226
+ for value in raw:
227
+ if not isinstance(value, dict) or set(value) != _ARTIFACT_FIELDS:
228
+ raise ArtifactIntegrityError("Build manifest contains an invalid artifact record")
229
+ relative = _safe_relative(value.get("path"))
230
+ if relative in seen:
231
+ raise ArtifactIntegrityError(f"Build manifest contains duplicate path: {relative}")
232
+ seen.add(relative)
233
+ if type(value.get("size")) is not int or value["size"] < 0:
234
+ raise ArtifactIntegrityError(f"Invalid artifact size for {relative}")
235
+ if not isinstance(value.get("sha256"), str) or _HEX.fullmatch(value["sha256"]) is None:
236
+ raise ArtifactIntegrityError(f"Invalid artifact SHA-256 for {relative}")
237
+ if not isinstance(value.get("media_type"), str) or not isinstance(value.get("producer"), str):
238
+ raise ArtifactIntegrityError(f"Invalid artifact metadata for {relative}")
239
+ schema_id = value.get("schema_id")
240
+ schema_version = value.get("schema_version")
241
+ if (schema_id is None) != (schema_version is None):
242
+ raise ArtifactIntegrityError(f"Incomplete schema metadata for {relative}")
243
+ if schema_id is not None and (not isinstance(schema_id, str) or type(schema_version) is not int):
244
+ raise ArtifactIntegrityError(f"Invalid schema metadata for {relative}")
245
+ if type(value.get("volatile")) is not bool:
246
+ raise ArtifactIntegrityError(f"Invalid volatile flag for {relative}")
247
+ records.append(ArtifactRecord(**value))
248
+ if [record.path for record in records] != sorted(record.path for record in records):
249
+ raise ArtifactIntegrityError("Build manifest artifact records are not path-sorted")
250
+ volatile = [record.path for record in records if record.volatile]
251
+ if volatile != ["manifests/run-metadata.json"]:
252
+ raise ArtifactIntegrityError(f"Unsupported volatile artifact set: {volatile}")
253
+ return tuple(records)
254
+
255
+
256
+ def _inventory(root: Path) -> dict[str, Path]:
257
+ if root.is_symlink() or not root.is_dir():
258
+ raise ArtifactIntegrityError(f"Artifact root is not a regular directory: {root}")
259
+ result: dict[str, Path] = {}
260
+ for directory, names, files in os.walk(root, followlinks=False):
261
+ current = Path(directory)
262
+ for name in sorted(names):
263
+ path = current / name
264
+ if path.is_symlink():
265
+ raise ArtifactIntegrityError(f"Artifact tree contains a symlink: {path.relative_to(root)}")
266
+ for name in sorted(files):
267
+ path = current / name
268
+ if path.is_symlink() or not path.is_file():
269
+ raise ArtifactIntegrityError(f"Artifact is not a regular file: {path.relative_to(root)}")
270
+ relative = _safe_relative(path.relative_to(root).as_posix())
271
+ if relative in result:
272
+ raise ArtifactIntegrityError(f"Duplicate artifact path: {relative}")
273
+ result[relative] = path
274
+ return dict(sorted(result.items()))
275
+
276
+
277
+ def _safe_relative(value: object) -> str:
278
+ if not isinstance(value, str) or not value or "\\" in value or "\x00" in value:
279
+ raise ArtifactIntegrityError(f"Unsafe artifact path: {value!r}")
280
+ path = PurePosixPath(value)
281
+ if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts) or path.as_posix() != value:
282
+ raise ArtifactIntegrityError(f"Unsafe artifact path: {value!r}")
283
+ return value
284
+
285
+
286
+ def _expected_paths(formats: tuple[str, ...], stem: str, anchor: bool) -> set[str]:
287
+ expected = {"manifests/design-manifest.json", "manifests/run-metadata.json"}
288
+ if anchor:
289
+ expected.add(".gitkeep")
290
+ if "step" in formats:
291
+ expected |= {f"step/{stem}.step", "step/validation.json"}
292
+ if "ifc" in formats:
293
+ expected |= {f"ifc/{stem}.ifc", "ifc/validation.json"}
294
+ if "glb" in formats:
295
+ expected |= {f"glb/{stem}.glb", "glb/manifest.json"}
296
+ if "quantities" in formats:
297
+ expected |= {
298
+ "quantities/quantities.json",
299
+ "quantities/quantities.csv",
300
+ "quantities/materials.csv",
301
+ "quantities/summary.md",
302
+ }
303
+ if "drawings" in formats:
304
+ expected.add("drawings/annotation-manifest.json")
305
+ expected.add(f"drawings/pdf/{stem}_Conceptual_Drawings.pdf")
306
+ for view in ("plan", "front", "side", "section"):
307
+ expected.add(f"drawings/svg/{stem}_{view}.svg")
308
+ expected.add(f"drawings/dxf/{stem}_{view}.dxf")
309
+ return expected
310
+
311
+
312
+ def _parse_formats(root: Path, formats: tuple[str, ...], stem: str) -> None:
313
+ try:
314
+ if "step" in formats:
315
+ from build123d import import_step
316
+
317
+ from .exporters.step import step_schema
318
+
319
+ step = root / f"step/{stem}.step"
320
+ if not step_schema(step).startswith("AP242_") or not import_step(step).is_valid:
321
+ raise ArtifactIntegrityError("STEP parser validation failed")
322
+ if "ifc" in formats:
323
+ import ifcopenshell
324
+
325
+ value = ifcopenshell.open(root / f"ifc/{stem}.ifc")
326
+ if value.schema != "IFC4":
327
+ raise ArtifactIntegrityError(f"Unsupported emitted IFC schema: {value.schema}")
328
+ if "glb" in formats:
329
+ import trimesh
330
+
331
+ from .exporters.glb import glb_json
332
+
333
+ glb = root / f"glb/{stem}.glb"
334
+ glb_json(glb)
335
+ if not isinstance(trimesh.load(glb, force="scene", process=False), trimesh.Scene):
336
+ raise ArtifactIntegrityError("GLB parser did not return a scene")
337
+ if "drawings" in formats:
338
+ import ezdxf
339
+ from pypdf import PdfReader
340
+
341
+ for svg in sorted((root / "drawings/svg").glob("*.svg")):
342
+ ElementTree.parse(svg)
343
+ for dxf in sorted((root / "drawings/dxf").glob("*.dxf")):
344
+ if ezdxf.readfile(dxf).audit().has_errors:
345
+ raise ArtifactIntegrityError(f"DXF audit failed: {dxf.name}")
346
+ if not PdfReader(root / f"drawings/pdf/{stem}_Conceptual_Drawings.pdf").pages:
347
+ raise ArtifactIntegrityError("PDF contains no pages")
348
+ _reconcile_drawing_annotations(root, stem)
349
+ except ArtifactIntegrityError:
350
+ raise
351
+ except (OSError, ValueError, RuntimeError, BuildExportError) as error:
352
+ raise ArtifactIntegrityError(f"Final artifact parser validation failed: {error}") from error
353
+
354
+
355
+ def _reconcile_formats(root: Path, formats: tuple[str, ...]) -> None:
356
+ design = load_design_manifest(root / "manifests/design-manifest.json")
357
+ elements = design["elements"]
358
+ if not isinstance(elements, list):
359
+ raise ArtifactIntegrityError("Design manifest elements must be an array")
360
+ if "step" in formats:
361
+ report = load_json_document(root / "step/validation.json")
362
+ expected_elements = [item for item in elements if item["physical"] and "step" in item["export_formats"]]
363
+ if report["solid_count"] != len(expected_elements):
364
+ raise ArtifactIntegrityError("STEP/design solid-count mismatch")
365
+ _require_matching_bounds("STEP", report["bounds_mm"], _combined_manifest_bounds(expected_elements))
366
+ if "ifc" in formats:
367
+ import ifcopenshell
368
+
369
+ from .exporters.ifc import property_value
370
+
371
+ ifc_path = next((root / "ifc").glob("*.ifc"))
372
+ actual = sorted(property_value(item, "StableId") for item in ifcopenshell.open(ifc_path).by_type("IfcBuildingElementProxy"))
373
+ expected = sorted(
374
+ item["id"]
375
+ for item in elements
376
+ if item["physical"] and "ifc" in item["export_formats"] and item["ifc_class"] is not None
377
+ )
378
+ if actual != expected:
379
+ raise ArtifactIntegrityError(f"IFC/design identity mismatch: expected {expected}, got {actual}")
380
+ report = load_json_document(root / "ifc/validation.json")
381
+ expected_elements = [
382
+ item
383
+ for item in elements
384
+ if item["physical"] and "ifc" in item["export_formats"] and item["ifc_class"] is not None
385
+ ]
386
+ _require_matching_bounds("IFC", report["bounds_mm"], _combined_manifest_bounds(expected_elements))
387
+ if "glb" in formats:
388
+ from .exporters.glb import glb_json
389
+
390
+ glb_path = next((root / "glb").glob("*.glb"))
391
+ document = glb_json(glb_path)
392
+ actual = sorted(node.get("extras", {}).get("stable_id") for node in document["nodes"] if node.get("extras", {}).get("stable_id"))
393
+ expected = sorted(item["id"] for item in elements if item["physical"] and "glb" in item["export_formats"])
394
+ if actual != expected:
395
+ raise ArtifactIntegrityError(f"GLB/design identity mismatch: expected {expected}, got {actual}")
396
+ report = load_json_document(root / "glb/manifest.json")
397
+ expected_elements = [item for item in elements if item["physical"] and "glb" in item["export_formats"]]
398
+ _require_matching_bounds("GLB", report["bounds_cad_mm"], _combined_manifest_bounds(expected_elements))
399
+ if "quantities" in formats:
400
+ quantities = load_json_document(root / "quantities/quantities.json")
401
+ actual = sorted(record["element_id"] for record in quantities["records"])
402
+ expected = sorted(item["id"] for item in elements if item["physical"] and "quantities" in item["export_formats"])
403
+ if actual != expected:
404
+ raise ArtifactIntegrityError(f"Quantity/design identity mismatch: expected {expected}, got {actual}")
405
+
406
+
407
+ def _reconcile_drawing_annotations(root: Path, stem: str) -> None:
408
+ import ezdxf
409
+ from pypdf import PdfReader
410
+
411
+ manifest = load_json_document(root / "drawings/annotation-manifest.json")
412
+ sheets = manifest.get("sheets")
413
+ annotations = manifest.get("annotations")
414
+ if not isinstance(sheets, list) or not isinstance(annotations, list):
415
+ raise ArtifactIntegrityError("Drawing annotation manifest arrays are invalid")
416
+ rendered = {record["id"] for record in annotations if record.get("rendered_entity") is True}
417
+ declared_by_sheet: set[str] = set()
418
+ reader = PdfReader(root / f"drawings/pdf/{stem}_Conceptual_Drawings.pdf")
419
+ if len(reader.pages) != len(sheets):
420
+ raise ArtifactIntegrityError("Drawing annotation manifest/PDF page-count mismatch")
421
+ for expected_page, sheet in enumerate(sheets, start=1):
422
+ if not isinstance(sheet, dict):
423
+ raise ArtifactIntegrityError("Drawing annotation manifest contains an invalid sheet record")
424
+ sheet_id = sheet.get("sheet_id")
425
+ view_id = sheet.get("view_id")
426
+ page_number = sheet.get("page_number")
427
+ expected_ids = sheet.get("annotation_ids")
428
+ expected_text = sheet.get("pdf_text")
429
+ if (
430
+ not isinstance(sheet_id, str)
431
+ or not isinstance(view_id, str)
432
+ or page_number != expected_page
433
+ or not isinstance(expected_ids, list)
434
+ or not all(isinstance(value, str) for value in expected_ids)
435
+ or not isinstance(expected_text, list)
436
+ or not all(isinstance(value, str) for value in expected_text)
437
+ ):
438
+ raise ArtifactIntegrityError("Drawing annotation manifest sheet mapping is invalid")
439
+ svg = root / f"drawings/svg/{stem}_{view_id}.svg"
440
+ svg_root = ElementTree.parse(svg).getroot()
441
+ if svg_root.get("data-sheet") != sheet_id:
442
+ raise ArtifactIntegrityError(f"SVG sheet identity mismatch for {view_id}")
443
+ svg_ids = sorted(
444
+ element.attrib["data-annotation-id"]
445
+ for element in svg_root.iter()
446
+ if "data-annotation-id" in element.attrib
447
+ )
448
+ dxf = ezdxf.readfile(root / f"drawings/dxf/{stem}_{view_id}.dxf")
449
+ dxf_ids: list[str] = []
450
+ for entity in dxf.modelspace():
451
+ if entity.has_xdata("PYTHON_CAD_TOOLS"):
452
+ tags = entity.get_xdata("PYTHON_CAD_TOOLS")
453
+ dxf_ids.extend(str(tag.value) for tag in tags if tag.code == 1000)
454
+ if expected_ids != sorted(expected_ids) or svg_ids != expected_ids or sorted(dxf_ids) != expected_ids:
455
+ raise ArtifactIntegrityError(
456
+ f"Drawing annotation identity mismatch on {sheet_id}: expected {expected_ids}, SVG {svg_ids}, DXF {sorted(dxf_ids)}"
457
+ )
458
+ declared_by_sheet.update(expected_ids)
459
+ extracted = _normalized_text(reader.pages[expected_page - 1].extract_text() or "")
460
+ for value in expected_text:
461
+ if _normalized_text(value) not in extracted:
462
+ raise ArtifactIntegrityError(
463
+ f"Drawing annotation PDF text mismatch on {sheet_id}: missing {value!r}"
464
+ )
465
+ if declared_by_sheet != rendered:
466
+ raise ArtifactIntegrityError("Drawing annotation sidecar rendered-entity inventory is inconsistent")
467
+
468
+
469
+ def _normalized_text(value: str) -> str:
470
+ return " ".join(value.split())
471
+
472
+
473
+ def _combined_manifest_bounds(elements: list[dict[str, Any]]) -> list[float]:
474
+ if not elements:
475
+ return [0.0] * 6
476
+ return [
477
+ round(min(float(item["bounds_mm"][index]) for item in elements), 6)
478
+ for index in range(3)
479
+ ] + [
480
+ round(max(float(item["bounds_mm"][index]) for item in elements), 6)
481
+ for index in range(3, 6)
482
+ ]
483
+
484
+
485
+ def _require_matching_bounds(label: str, actual: object, expected: list[float]) -> None:
486
+ if not isinstance(actual, list) or len(actual) != 6:
487
+ raise ArtifactIntegrityError(f"{label} bounds are missing or invalid")
488
+ try:
489
+ matches = all(abs(float(left) - right) <= 1e-6 for left, right in zip(actual, expected, strict=True))
490
+ except (TypeError, ValueError):
491
+ matches = False
492
+ if not matches:
493
+ raise ArtifactIntegrityError(f"{label}/design bounds mismatch: expected {expected}, got {actual}")
494
+
495
+
496
+ def _verify_sources(project_root: Path, document: dict[str, Any]) -> None:
497
+ raw = document.get("source_inputs")
498
+ if not isinstance(raw, list):
499
+ raise ArtifactIntegrityError("Build manifest source_inputs must be an array")
500
+ current = []
501
+ for record in raw:
502
+ if not isinstance(record, dict) or set(record) != {"path", "size", "sha256"}:
503
+ raise ArtifactIntegrityError("Invalid source input record")
504
+ relative = _safe_relative(record["path"])
505
+ path = project_root / PurePosixPath(relative)
506
+ if path.is_symlink() or not path.is_file() or not path.resolve().is_relative_to(project_root):
507
+ raise ArtifactIntegrityError(f"Authoritative source is missing or unsafe: {relative}")
508
+ current.append({"path": relative, "size": path.stat().st_size, "sha256": sha256_file(path)})
509
+ if current != raw or semantic_hash(current) != document.get("source_set_hash"):
510
+ raise ArtifactIntegrityError("Authoritative project sources differ from the verified build")
511
+
512
+
513
+ def _selected_formats(document: dict[str, Any]) -> tuple[str, ...]:
514
+ from .project_config import FORMAT_ORDER
515
+
516
+ raw = document.get("selected_formats")
517
+ if not isinstance(raw, list) or not all(isinstance(item, str) for item in raw):
518
+ raise ArtifactIntegrityError("Build manifest selected_formats is invalid")
519
+ if not raw or len(raw) != len(set(raw)) or any(item not in FORMAT_ORDER for item in raw):
520
+ raise ArtifactIntegrityError("Build manifest selected_formats contains an unsupported or duplicate format")
521
+ if raw != [item for item in FORMAT_ORDER if item in raw]:
522
+ raise ArtifactIntegrityError("Build manifest selected_formats is not in canonical order")
523
+ return tuple(raw)
524
+
525
+
526
+ def _media_type(path: Path) -> str:
527
+ explicit = {
528
+ ".step": "model/step",
529
+ ".ifc": "application/x-step",
530
+ ".glb": "model/gltf-binary",
531
+ ".dxf": "image/vnd.dxf",
532
+ ".csv": "text/csv; charset=utf-8",
533
+ ".md": "text/markdown; charset=utf-8",
534
+ }
535
+ return explicit.get(path.suffix.lower()) or mimetypes.guess_type(path.name)[0] or "application/octet-stream"
536
+
537
+
538
+ __all__ = [
539
+ "ArtifactRecord",
540
+ "PackageBuildInfo",
541
+ "VerificationResult",
542
+ "get_package_build_info",
543
+ "load_build_manifest",
544
+ "load_design_manifest",
545
+ "verify_build_output",
546
+ ]
@@ -0,0 +1 @@
1
+ {"distribution":"python-cad-tools","schema_id":"urn:python-cad-tools:schema:package-build-info:1","schema_version":1,"source_commit":"2266dd501edbb417c10eba356311808f5d29cce0","source_dirty":true,"version":"0.1.0rc1","viewer_shell_sha256":"5be46f14101b955176328ba78760be7ebaee08a3b73ea76a884d11ce38b25882"}