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,444 @@
1
+ """Deterministic expansion of neutral annotations into renderer entities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from math import cos, radians, sin
7
+ from typing import Any, Literal
8
+
9
+ from ...drawings import (
10
+ AnnotationPrimitive,
11
+ DrawingAnnotationSet,
12
+ ElevationMarker,
13
+ Line,
14
+ Polyline,
15
+ SectionCallout,
16
+ Table,
17
+ Text,
18
+ )
19
+
20
+ type EntityKind = Literal["line", "polyline", "circle", "text"]
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class AnnotationEntity:
25
+ id: str
26
+ parent_id: str | None
27
+ sheet_id: str
28
+ primitive_type: str
29
+ role: str
30
+ kind: EntityKind
31
+ style: str
32
+ points: tuple[tuple[float, float], ...] = ()
33
+ closed: bool = False
34
+ center: tuple[float, float] | None = None
35
+ radius: float | None = None
36
+ text: str | None = None
37
+ height_mm: float = 4.0
38
+ rotation_degrees: float = 0.0
39
+
40
+
41
+ def annotations_for_sheet(
42
+ annotations: DrawingAnnotationSet | None,
43
+ sheet_id: str,
44
+ ) -> tuple[AnnotationPrimitive, ...]:
45
+ if annotations is None:
46
+ return ()
47
+ record = next((record for record in annotations.sheets if record.sheet_id == sheet_id), None)
48
+ return () if record is None else record.annotations
49
+
50
+
51
+ def flatten_annotations(annotations: tuple[AnnotationPrimitive, ...]) -> tuple[AnnotationEntity, ...]:
52
+ entities: list[AnnotationEntity] = []
53
+ for annotation in annotations:
54
+ if isinstance(annotation, Line):
55
+ entities.append(
56
+ AnnotationEntity(
57
+ annotation.id,
58
+ None,
59
+ annotation.sheet_id,
60
+ "line",
61
+ "line",
62
+ "line",
63
+ annotation.style,
64
+ (annotation.start, annotation.end),
65
+ )
66
+ )
67
+ elif isinstance(annotation, Polyline):
68
+ entities.append(
69
+ AnnotationEntity(
70
+ annotation.id,
71
+ None,
72
+ annotation.sheet_id,
73
+ "polyline",
74
+ "polyline",
75
+ "polyline",
76
+ annotation.style,
77
+ annotation.points,
78
+ annotation.closed,
79
+ )
80
+ )
81
+ elif isinstance(annotation, Text):
82
+ entities.append(
83
+ AnnotationEntity(
84
+ annotation.id,
85
+ None,
86
+ annotation.sheet_id,
87
+ "text",
88
+ "text",
89
+ "text",
90
+ annotation.style,
91
+ points=(annotation.position,),
92
+ text=annotation.value,
93
+ height_mm=annotation.height_mm,
94
+ rotation_degrees=annotation.rotation_degrees,
95
+ )
96
+ )
97
+ elif isinstance(annotation, SectionCallout):
98
+ entities.extend(_section_entities(annotation))
99
+ elif isinstance(annotation, ElevationMarker):
100
+ entities.extend(_elevation_entities(annotation))
101
+ elif isinstance(annotation, Table):
102
+ entities.extend(_table_entities(annotation))
103
+ return tuple(entities)
104
+
105
+
106
+ def annotation_bounds(entities: tuple[AnnotationEntity, ...]) -> tuple[float, float, float, float] | None:
107
+ bounds: list[tuple[float, float, float, float]] = []
108
+ for entity in entities:
109
+ if entity.kind in {"line", "polyline"}:
110
+ bounds.append(
111
+ (
112
+ min(point[0] for point in entity.points),
113
+ min(point[1] for point in entity.points),
114
+ max(point[0] for point in entity.points),
115
+ max(point[1] for point in entity.points),
116
+ )
117
+ )
118
+ elif entity.kind == "circle" and entity.center is not None and entity.radius is not None:
119
+ bounds.append(
120
+ (
121
+ entity.center[0] - entity.radius,
122
+ entity.center[1] - entity.radius,
123
+ entity.center[0] + entity.radius,
124
+ entity.center[1] + entity.radius,
125
+ )
126
+ )
127
+ elif entity.kind == "text" and entity.points and entity.text is not None:
128
+ x, y = entity.points[0]
129
+ width = max(entity.height_mm * 0.65 * len(entity.text), entity.height_mm)
130
+ angle = radians(entity.rotation_degrees)
131
+ dx = abs(width * cos(angle)) + abs(entity.height_mm * sin(angle))
132
+ dy = abs(width * sin(angle)) + abs(entity.height_mm * cos(angle))
133
+ bounds.append((x, y - entity.height_mm * 0.25, x + dx, y + dy))
134
+ if not bounds:
135
+ return None
136
+ return (
137
+ min(item[0] for item in bounds),
138
+ min(item[1] for item in bounds),
139
+ max(item[2] for item in bounds),
140
+ max(item[3] for item in bounds),
141
+ )
142
+
143
+
144
+ def manifest_records(
145
+ annotations: DrawingAnnotationSet | None,
146
+ entities_by_sheet: dict[str, tuple[AnnotationEntity, ...]],
147
+ ) -> list[dict[str, Any]]:
148
+ if annotations is None:
149
+ return []
150
+ result: list[dict[str, Any]] = []
151
+ entities = {entity.id: entity for values in entities_by_sheet.values() for entity in values}
152
+ for sheet in annotations.sheets:
153
+ for annotation in sheet.annotations:
154
+ child_ids = [entity.id for entity in entities_by_sheet[sheet.sheet_id] if entity.parent_id == annotation.id]
155
+ direct = annotation.id in entities
156
+ result.append(
157
+ {
158
+ "id": annotation.id,
159
+ "parent_id": None,
160
+ "sheet_id": sheet.sheet_id,
161
+ "primitive_type": _primitive_type(annotation),
162
+ "role": "annotation",
163
+ "content": _annotation_content(annotation),
164
+ "child_ids": child_ids,
165
+ "rendered_entity": direct,
166
+ "renderer_presence": {"svg": True, "dxf": True, "pdf": True},
167
+ }
168
+ )
169
+ for child_id in child_ids:
170
+ entity = entities[child_id]
171
+ result.append(
172
+ {
173
+ "id": entity.id,
174
+ "parent_id": annotation.id,
175
+ "sheet_id": sheet.sheet_id,
176
+ "primitive_type": entity.primitive_type,
177
+ "role": entity.role,
178
+ "content": {"text": normalize_text(entity.text)} if entity.text is not None else {},
179
+ "child_ids": [],
180
+ "rendered_entity": True,
181
+ "renderer_presence": {"svg": True, "dxf": True, "pdf": True},
182
+ }
183
+ )
184
+ return sorted(result, key=lambda item: item["id"])
185
+
186
+
187
+ def pdf_texts(entities: tuple[AnnotationEntity, ...]) -> list[str]:
188
+ return [normalize_text(entity.text) for entity in entities if entity.kind == "text" and entity.text is not None]
189
+
190
+
191
+ def normalize_text(value: str | None) -> str:
192
+ return "" if value is None else " ".join(value.split())
193
+
194
+
195
+ def _section_entities(annotation: SectionCallout) -> tuple[AnnotationEntity, ...]:
196
+ direction = _direction_vector(annotation.view_direction)
197
+ arrow_size = 8.0
198
+ normal = (-direction[1], direction[0])
199
+ arrows = []
200
+ for role, point in (("arrow.start", annotation.start), ("arrow.end", annotation.end)):
201
+ tip = (point[0] + direction[0] * arrow_size, point[1] + direction[1] * arrow_size)
202
+ base = (point[0] - direction[0] * arrow_size * 0.35, point[1] - direction[1] * arrow_size * 0.35)
203
+ arrows.append(
204
+ AnnotationEntity(
205
+ f"{annotation.id}.{role}",
206
+ annotation.id,
207
+ annotation.sheet_id,
208
+ "section_callout",
209
+ role,
210
+ "polyline",
211
+ "annotation-section",
212
+ (
213
+ (base[0] + normal[0] * arrow_size * 0.45, base[1] + normal[1] * arrow_size * 0.45),
214
+ tip,
215
+ (base[0] - normal[0] * arrow_size * 0.45, base[1] - normal[1] * arrow_size * 0.45),
216
+ ),
217
+ )
218
+ )
219
+ label_text = f"{normalize_text(annotation.label)} / {annotation.reference_sheet_id}"
220
+ return (
221
+ AnnotationEntity(
222
+ f"{annotation.id}.line",
223
+ annotation.id,
224
+ annotation.sheet_id,
225
+ "section_callout",
226
+ "line",
227
+ "line",
228
+ "annotation-section",
229
+ (annotation.start, annotation.end),
230
+ ),
231
+ *arrows,
232
+ AnnotationEntity(
233
+ f"{annotation.id}.label.start",
234
+ annotation.id,
235
+ annotation.sheet_id,
236
+ "section_callout",
237
+ "label.start",
238
+ "text",
239
+ "annotation-section",
240
+ points=((annotation.start[0] + direction[0] * 12, annotation.start[1] + direction[1] * 12),),
241
+ text=label_text,
242
+ ),
243
+ AnnotationEntity(
244
+ f"{annotation.id}.label.end",
245
+ annotation.id,
246
+ annotation.sheet_id,
247
+ "section_callout",
248
+ "label.end",
249
+ "text",
250
+ "annotation-section",
251
+ points=((annotation.end[0] + direction[0] * 12, annotation.end[1] + direction[1] * 12),),
252
+ text=label_text,
253
+ ),
254
+ )
255
+
256
+
257
+ def _elevation_entities(annotation: ElevationMarker) -> tuple[AnnotationEntity, ...]:
258
+ direction = _direction_vector(annotation.direction)
259
+ normal = (-direction[1], direction[0])
260
+ radius = 9.0
261
+ tip = (annotation.position[0] + direction[0] * radius, annotation.position[1] + direction[1] * radius)
262
+ back = (
263
+ annotation.position[0] - direction[0] * radius * 0.45,
264
+ annotation.position[1] - direction[1] * radius * 0.45,
265
+ )
266
+ label = f"{normalize_text(annotation.label)} / {annotation.reference_sheet_id}"
267
+ return (
268
+ AnnotationEntity(
269
+ f"{annotation.id}.outline",
270
+ annotation.id,
271
+ annotation.sheet_id,
272
+ "elevation_marker",
273
+ "outline",
274
+ "circle",
275
+ "annotation-elevation",
276
+ center=annotation.position,
277
+ radius=radius,
278
+ ),
279
+ AnnotationEntity(
280
+ f"{annotation.id}.pointer",
281
+ annotation.id,
282
+ annotation.sheet_id,
283
+ "elevation_marker",
284
+ "pointer",
285
+ "polyline",
286
+ "annotation-elevation",
287
+ (
288
+ tip,
289
+ (back[0] + normal[0] * radius * 0.5, back[1] + normal[1] * radius * 0.5),
290
+ (back[0] - normal[0] * radius * 0.5, back[1] - normal[1] * radius * 0.5),
291
+ ),
292
+ True,
293
+ ),
294
+ AnnotationEntity(
295
+ f"{annotation.id}.label",
296
+ annotation.id,
297
+ annotation.sheet_id,
298
+ "elevation_marker",
299
+ "label",
300
+ "text",
301
+ "annotation-elevation",
302
+ points=((annotation.position[0] + radius * 1.4, annotation.position[1]),),
303
+ text=label,
304
+ ),
305
+ )
306
+
307
+
308
+ def _table_entities(annotation: Table) -> tuple[AnnotationEntity, ...]:
309
+ height = 4.0
310
+ row_height = 7.0
311
+ widths = []
312
+ for index, column in enumerate(annotation.columns):
313
+ values = [column, *(row.cells[index] for row in annotation.rows)]
314
+ widths.append(max(28.0, max(len(normalize_text(value)) for value in values) * height * 0.65 + 8.0))
315
+ total_width = sum(widths)
316
+ total_height = row_height * (len(annotation.rows) + 2)
317
+ x0, y0 = annotation.position
318
+ x1, y1 = x0 + total_width, y0 + total_height
319
+ entities = [
320
+ AnnotationEntity(
321
+ f"{annotation.id}.title",
322
+ annotation.id,
323
+ annotation.sheet_id,
324
+ "table",
325
+ "title",
326
+ "text",
327
+ "annotation-table",
328
+ points=((x0 + 2.0, y1 - row_height + 1.5),),
329
+ text=annotation.title,
330
+ height_mm=height,
331
+ ),
332
+ AnnotationEntity(
333
+ f"{annotation.id}.border",
334
+ annotation.id,
335
+ annotation.sheet_id,
336
+ "table",
337
+ "border",
338
+ "polyline",
339
+ "annotation-table",
340
+ ((x0, y0), (x1, y0), (x1, y1), (x0, y1)),
341
+ True,
342
+ ),
343
+ ]
344
+ cursor = x0
345
+ header_y = y1 - row_height * 2 + 1.5
346
+ for index, (column, width) in enumerate(zip(annotation.columns, widths, strict=True)):
347
+ entities.append(
348
+ AnnotationEntity(
349
+ f"{annotation.id}.header.{index}",
350
+ annotation.id,
351
+ annotation.sheet_id,
352
+ "table",
353
+ f"header.{index}",
354
+ "text",
355
+ "annotation-table",
356
+ points=((cursor + 2.0, header_y),),
357
+ text=column,
358
+ height_mm=height,
359
+ )
360
+ )
361
+ cursor += width
362
+ for row_index, row in enumerate(annotation.rows):
363
+ cursor = x0
364
+ baseline = y1 - row_height * (row_index + 3) + 1.5
365
+ for column_index, (cell, width) in enumerate(zip(row.cells, widths, strict=True)):
366
+ entities.append(
367
+ AnnotationEntity(
368
+ f"{annotation.id}.row.{row.id}.cell.{column_index}",
369
+ annotation.id,
370
+ annotation.sheet_id,
371
+ "table",
372
+ f"row.{row.id}.cell.{column_index}",
373
+ "text",
374
+ "annotation-table",
375
+ points=((cursor + 2.0, baseline),),
376
+ text=cell,
377
+ height_mm=height,
378
+ )
379
+ )
380
+ cursor += width
381
+ separator_y = y1 - row_height * (row_index + 3)
382
+ entities.append(
383
+ AnnotationEntity(
384
+ f"{annotation.id}.row.{row.id}.separator",
385
+ annotation.id,
386
+ annotation.sheet_id,
387
+ "table",
388
+ f"row.{row.id}.separator",
389
+ "line",
390
+ "annotation-table",
391
+ ((x0, separator_y), (x1, separator_y)),
392
+ )
393
+ )
394
+ return tuple(entities)
395
+
396
+
397
+ def _direction_vector(direction: str) -> tuple[float, float]:
398
+ return {
399
+ "right": (1.0, 0.0),
400
+ "left": (-1.0, 0.0),
401
+ "up": (0.0, 1.0),
402
+ "down": (0.0, -1.0),
403
+ }[direction]
404
+
405
+
406
+ def _primitive_type(annotation: AnnotationPrimitive) -> str:
407
+ names = {
408
+ Line: "line",
409
+ Polyline: "polyline",
410
+ Text: "text",
411
+ SectionCallout: "section_callout",
412
+ ElevationMarker: "elevation_marker",
413
+ Table: "table",
414
+ }
415
+ return names[type(annotation)]
416
+
417
+
418
+ def _annotation_content(annotation: AnnotationPrimitive) -> dict[str, Any]:
419
+ if isinstance(annotation, Text):
420
+ return {"text": normalize_text(annotation.value)}
421
+ if isinstance(annotation, SectionCallout):
422
+ return {
423
+ "label": normalize_text(annotation.label),
424
+ "reference_sheet_id": annotation.reference_sheet_id,
425
+ "view_direction": annotation.view_direction,
426
+ }
427
+ if isinstance(annotation, ElevationMarker):
428
+ return {
429
+ "label": normalize_text(annotation.label),
430
+ "reference_sheet_id": annotation.reference_sheet_id,
431
+ "direction": annotation.direction,
432
+ }
433
+ if isinstance(annotation, Table):
434
+ return {
435
+ "title": normalize_text(annotation.title),
436
+ "columns": [normalize_text(value) for value in annotation.columns],
437
+ "rows": [
438
+ {"id": row.id, "cells": [normalize_text(value) for value in row.cells]} for row in annotation.rows
439
+ ],
440
+ }
441
+ return {}
442
+
443
+
444
+ __all__: list[str] = []
@@ -0,0 +1,110 @@
1
+ """Python CAD Tools DXF writer with explicit conceptual layers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import ezdxf
10
+
11
+ from ...determinism import normalized_build_epoch
12
+ from .primitives import DrawingScene
13
+
14
+ LAYERS: dict[str, dict[str, Any]] = {
15
+ "VISIBLE": {"color": 7, "lineweight": 35},
16
+ "HIDDEN": {"color": 8, "lineweight": 18, "linetype": "DASHED"},
17
+ "TEXT": {"color": 2, "lineweight": 18},
18
+ "DIMENSIONS": {"color": 3, "lineweight": 18},
19
+ "REFERENCE": {"color": 4, "lineweight": 18, "linetype": "CENTER"},
20
+ "NOTICE": {"color": 1, "lineweight": 35},
21
+ "ANNOTATION_LINE": {"color": 1, "lineweight": 25},
22
+ "ANNOTATION_TEXT": {"color": 1, "lineweight": 18},
23
+ "ANNOTATION_SECTION": {"color": 1, "lineweight": 35},
24
+ "ANNOTATION_ELEVATION": {"color": 1, "lineweight": 35},
25
+ "ANNOTATION_TABLE": {"color": 1, "lineweight": 25},
26
+ }
27
+
28
+ APP_ID = "PYTHON_CAD_TOOLS"
29
+
30
+
31
+ def write_dxf(scene: DrawingScene, path: Path) -> Path:
32
+ document = ezdxf.new("R2018", setup=True)
33
+ document.units = ezdxf.units.MM
34
+ epoch = normalized_build_epoch()
35
+ # AutoCAD stores these values as Julian dates. Fixed GUIDs and a generous
36
+ # hand seed remove the last writer-created per-run fields.
37
+ julian = epoch.timestamp() / 86400.0 + 2440587.5
38
+ document.header["$TDCREATE"] = julian
39
+ document.header["$TDUPDATE"] = julian
40
+ document.header["$TDUCREATE"] = 0.0
41
+ document.header["$TDUUPDATE"] = 0.0
42
+ document.header["$FINGERPRINTGUID"] = "{00000000-0000-5000-8000-000000000001}"
43
+ document.header["$VERSIONGUID"] = "{00000000-0000-5000-8000-000000000002}"
44
+ document.header["$HANDSEED"] = "FFFF"
45
+ for name, attributes in LAYERS.items():
46
+ if name not in document.layers:
47
+ document.layers.add(name, **attributes)
48
+ if APP_ID not in document.appids:
49
+ document.appids.add(APP_ID)
50
+ modelspace = document.modelspace()
51
+ for polyline in scene.polylines:
52
+ modelspace.add_lwpolyline(polyline.points, close=polyline.closed, dxfattribs={"layer": polyline.layer})
53
+ for line in scene.lines:
54
+ modelspace.add_line(line.start, line.end, dxfattribs={"layer": line.layer})
55
+ for text in scene.texts:
56
+ modelspace.add_text(text.value, height=text.height, dxfattribs={"layer": text.layer}).set_placement(text.position)
57
+ for dimension in scene.dimensions:
58
+ modelspace.add_aligned_dim(
59
+ dimension.start,
60
+ dimension.end,
61
+ distance=dimension.offset,
62
+ text=dimension.label,
63
+ override={"dimtxt": 12},
64
+ dxfattribs={"layer": "DIMENSIONS"},
65
+ ).render()
66
+ for entity in scene.annotation_entities:
67
+ layer = entity.style.replace("-", "_").upper()
68
+ created: Any = None
69
+ if entity.kind == "line":
70
+ created = modelspace.add_line(entity.points[0], entity.points[1], dxfattribs={"layer": layer})
71
+ elif entity.kind == "polyline":
72
+ created = modelspace.add_lwpolyline(entity.points, close=entity.closed, dxfattribs={"layer": layer})
73
+ elif entity.kind == "circle" and entity.center is not None and entity.radius is not None:
74
+ created = modelspace.add_circle(entity.center, entity.radius, dxfattribs={"layer": layer})
75
+ elif entity.kind == "text" and entity.text is not None:
76
+ created = modelspace.add_text(
77
+ entity.text,
78
+ height=entity.height_mm,
79
+ dxfattribs={"layer": layer, "rotation": entity.rotation_degrees},
80
+ ).set_placement(entity.points[0])
81
+ if created is not None:
82
+ created.set_xdata(APP_ID, [(1000, entity.id)])
83
+ x0, y0, x1, y1 = scene.viewport.bounds
84
+ modelspace.add_text(scene.title_block.notice, height=max((y1 - y0) * 0.025, 10), dxfattribs={"layer": "NOTICE"}).set_placement((x0, y0))
85
+ path.parent.mkdir(parents=True, exist_ok=True)
86
+ document.classes.add_required_classes(document.dxfversion)
87
+ ordered_classes = sorted(document.classes.classes.items())
88
+ document.classes.classes.clear()
89
+ document.classes.classes.update(ordered_classes)
90
+ document.saveas(path)
91
+ _normalize_saved_dxf(path, julian, epoch.strftime("%Y-%m-%dT%H:%M:%S+00:00"))
92
+ reloaded = ezdxf.readfile(path)
93
+ auditor = reloaded.audit()
94
+ if auditor.has_errors:
95
+ raise RuntimeError(f"DXF audit failed with {len(auditor.errors)} error(s)")
96
+ return path
97
+
98
+
99
+ def _normalize_saved_dxf(path: Path, julian: float, timestamp: str) -> None:
100
+ text = path.read_text(encoding="utf-8")
101
+ replacements = (
102
+ (r"(\$TDUPDATE\n 40\n)[^\n]+", rf"\g<1>{julian}"),
103
+ (r"(\$VERSIONGUID\n 2\n)[^\n]+", r"\g<1>{00000000-0000-5000-8000-000000000002}"),
104
+ (r"(\d+\.\d+\.\d+ @ )\d{4}-\d{2}-\d{2}T[^\n]+", rf"\g<1>{timestamp}"),
105
+ )
106
+ for pattern, replacement in replacements:
107
+ text, count = re.subn(pattern, replacement, text)
108
+ if count == 0:
109
+ raise RuntimeError(f"DXF normalization pattern was not present: {pattern}")
110
+ path.write_text(text, encoding="utf-8", newline="\n")
@@ -0,0 +1,70 @@
1
+ """Conceptual sheet-set exporter coordinating Python CAD Tools writers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from pypdf import PdfReader
8
+
9
+ from ...determinism import write_json
10
+ from ...drawings import DrawingAnnotationSet, DrawingSheet
11
+ from ...drawings._catalog import standard_sheet_catalog
12
+ from .annotations import annotations_for_sheet, manifest_records, pdf_texts
13
+ from .dxf import write_dxf
14
+ from .pdf import write_pdf
15
+ from .projection import project_model
16
+ from .svg import write_svg
17
+
18
+
19
+ class DrawingExporter:
20
+ name = "drawings"
21
+
22
+ def export(
23
+ self,
24
+ model,
25
+ output_dir: Path,
26
+ sheets: tuple[DrawingSheet, ...] = (),
27
+ annotations: DrawingAnnotationSet | None = None,
28
+ ) -> list[Path]:
29
+ drawing_dir = output_dir / "drawings"
30
+ catalog = sheets or standard_sheet_catalog(model)
31
+ scenes = [
32
+ project_model(model, sheet, annotations_for_sheet(annotations, sheet.sheet_id))
33
+ for sheet in catalog
34
+ ]
35
+ paths: list[Path] = []
36
+ for scene in scenes:
37
+ slug = scene.viewport.name.replace(" ", "_")
38
+ paths.append(write_svg(scene, drawing_dir / "svg" / f"{model.artifact_stem}_{slug}.svg"))
39
+ paths.append(write_dxf(scene, drawing_dir / "dxf" / f"{model.artifact_stem}_{slug}.dxf"))
40
+ pdf = write_pdf(scenes, drawing_dir / "pdf" / f"{model.artifact_stem}_Conceptual_Drawings.pdf")
41
+ if len(PdfReader(pdf).pages) != len(scenes):
42
+ raise RuntimeError("PDF validation failed: unexpected page count")
43
+ paths.append(pdf)
44
+ entities_by_sheet = {
45
+ scene.title_block.sheet_number: scene.annotation_entities
46
+ for scene in scenes
47
+ }
48
+ paths.append(
49
+ write_json(
50
+ drawing_dir / "annotation-manifest.json",
51
+ {
52
+ "schema_id": "urn:python-cad-tools:schema:drawing-annotations:1",
53
+ "schema_version": 1,
54
+ "provider_id": None if annotations is None else annotations.provider_id,
55
+ "annotations": manifest_records(annotations, entities_by_sheet),
56
+ "sheets": [
57
+ {
58
+ "sheet_id": scene.title_block.sheet_number,
59
+ "view_id": scene.viewport.name,
60
+ "page_number": index,
61
+ "viewport_bounds_mm": list(scene.viewport.bounds),
62
+ "annotation_ids": sorted(entity.id for entity in scene.annotation_entities),
63
+ "pdf_text": pdf_texts(scene.annotation_entities),
64
+ }
65
+ for index, scene in enumerate(scenes, start=1)
66
+ ],
67
+ },
68
+ )
69
+ )
70
+ return paths