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.
- python_cad_tools/__init__.py +10 -0
- python_cad_tools/_locking.py +63 -0
- python_cad_tools/_project_loader.py +273 -0
- python_cad_tools/_project_state.py +65 -0
- python_cad_tools/_scaffold.py +66 -0
- python_cad_tools/_transaction.py +208 -0
- python_cad_tools/_viewer_static/app.webmanifest +18 -0
- python_cad_tools/_viewer_static/asset-manifest.json +1 -0
- python_cad_tools/_viewer_static/assets/animationGroup-Sd34ntWg.js +1 -0
- python_cad_tools/_viewer_static/assets/bakedVertexAnimation-Cd_pRdrn.js +316 -0
- python_cad_tools/_viewer_static/assets/basisTextureLoader-DWgvS_1G.js +1 -0
- python_cad_tools/_viewer_static/assets/brdfTextureTools-CWGiG1W2.js +2 -0
- python_cad_tools/_viewer_static/assets/bumpFragment-CWULheF1.js +127 -0
- python_cad_tools/_viewer_static/assets/bumpVertex-DqzFZayE.js +6 -0
- python_cad_tools/_viewer_static/assets/bumpVertexDeclaration-C5EFyTio.js +6 -0
- python_cad_tools/_viewer_static/assets/clipPlaneFragment-BXRtt4jj.js +46 -0
- python_cad_tools/_viewer_static/assets/clipPlaneVertex-B_VhD1oQ.js +37 -0
- python_cad_tools/_viewer_static/assets/cubemapToSphericalPolynomial-DRpaukza.js +1 -0
- python_cad_tools/_viewer_static/assets/dds-Dkb2Cx2N.js +1 -0
- python_cad_tools/_viewer_static/assets/dds.pure-DtvJsKvY.js +1 -0
- python_cad_tools/_viewer_static/assets/ddsTextureLoader-H78xux1g.js +1 -0
- python_cad_tools/_viewer_static/assets/default.fragment-oSQ-tWhe.js +469 -0
- python_cad_tools/_viewer_static/assets/default.vertex-DhFjw_Da.js +209 -0
- python_cad_tools/_viewer_static/assets/defaultUboDeclaration-BMDHNppx.js +5 -0
- python_cad_tools/_viewer_static/assets/dumpTools-BaNZPjUX.js +2 -0
- python_cad_tools/_viewer_static/assets/envTextureLoader-V0YnLcIM.js +1 -0
- python_cad_tools/_viewer_static/assets/exrTextureLoader-CjGyIK3x.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphApplyForceBlock-CW5nuobR.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphApplyImpulseBlock-LFqYIkhj.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphArrayIndexBlock-BMOHUbBm.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphBezierCurveEasingBlock-knmTiuEk.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphBinaryOperationBlock-DFLuCAKJ.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphBranchBlock-BJRHMXMo.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphCachedOperationBlock-Dng-6jE0.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphCancelDelayBlock-CgbK3CQZ.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphCodeExecutionBlock-8zMtq9tg.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphConditionalDataBlock-BiLr2rkI.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphConsoleLogBlock-J1vcCtNP.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphConstantBlock-CFb5nuaV.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphContextBlock-DAeGVzhC.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphCounterBlock-lZlAQiJd.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphDataSwitchBlock-BIzvmbVy.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphDebounceBlock-DvNuMVwy.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphDebugBlock-Dymd2VEE.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphDoNBlock-v68aoNLH.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphEasingBlock-CTnGv92q.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphFlipFlopBlock-DXsO0Chw.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphForLoopBlock-CPpe9T75.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphFunctionReferenceBlock-DjDev4Pq.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGLTFDataProvider-GIYCz4AQ.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetAngularVelocityBlock-BStkOVeL.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetAssetBlock-Eiqoivbz.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetLinearVelocityBlock-B0cpV5ws.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetPhysicsMassPropertiesBlock-B-H64yaI.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetPropertyBlock-CUyOsPw0.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetSoundVolumeBlock-C0FjrsUz.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphGetVariableBlock-O4597zBl.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphIndexOfBlock-E00Z3KXM.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphInterpolationBlock-DnkA9MNH.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphIsKeyPressedBlock-DDfeuAyf.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphIsSoundPlayingBlock-DMXg8jdM.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphJsonPointerParserBlock-OmCorK0y.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphKeyDownEventBlock-RuVtj6g2.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphKeyUpEventBlock-DO4pAfHi.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphKeyboardEventBlock-Bx3pAzzh.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphMathBlocks-7W1XYOC8.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphMathCombineExtractBlocks-a4f0Kv6e.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphMatrixMathBlocks-C5Z-y1Au.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphMeshPickEventBlock-D4m-I4CS.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphMultiGateBlock-BISVSMw6.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPauseAnimationBlock-BYP2Z-HG.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPauseSoundBlock-Drh1EeW6.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPhysicsCollisionEventBlock-BoziAQ75.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPlayAnimationBlock-Clry-68M.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPlaySoundBlock-LbBVxGBE.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPointerDownEventBlock-DIVXCSHh.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPointerMoveEventBlock-BNNOzY2W.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPointerOutEventBlock-B8f2dOFT.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPointerOverEventBlock-CQr8LCee.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphPointerUpEventBlock-DMg_iEHl.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphReceiveCustomEventBlock-B8GJm9Ai.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSceneReadyEventBlock-Dcdb29K2.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSceneTickEventBlock-BLE5p4mi.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSendCustomEventBlock-BMzDItQ5.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSequenceBlock-DRURovjn.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetAngularVelocityBlock-Ba8dhkLM.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetDelayBlock-CnQR--Gn.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetLinearVelocityBlock-Dm1r3vkh.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetPhysicsMotionTypeBlock-BNtMLimi.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetPropertyBlock-C-uv2jhP.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetSoundVolumeBlock-D9LDx3lg.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSetVariableBlock-DszzRB5X.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSoundEndedEventBlock-DeTDSudb.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphStopAnimationBlock-DWuwoFWr.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphStopSoundBlock-DEnj2cQw.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphSwitchBlock-zs8Fp9ky.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphThrottleBlock-BYAKUQXd.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphTransformCoordinatesSystemBlock-DYP54m5P.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphTypeToTypeBlocks-vFwn79bS.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphUnaryOperationBlock-CaRh4Ef0.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphVectorMathBlocks-BEIhMs-A.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphWaitAllBlock-DFbELP_j.js +1 -0
- python_cad_tools/_viewer_static/assets/flowGraphWhileLoopBlock-C1OKQ-_F.js +1 -0
- python_cad_tools/_viewer_static/assets/fogFragment-DtDlQBnz.js +25 -0
- python_cad_tools/_viewer_static/assets/gaussianSplatting-Cfigjb15.js +242 -0
- python_cad_tools/_viewer_static/assets/gaussianSplatting-DIn8R8oF.js +296 -0
- python_cad_tools/_viewer_static/assets/gaussianSplatting.fragment-BxeRJ2k-.js +38 -0
- python_cad_tools/_viewer_static/assets/gaussianSplatting.fragment-DNDmvRs-.js +37 -0
- python_cad_tools/_viewer_static/assets/gaussianSplatting.vertex-D92z5jYn.js +86 -0
- python_cad_tools/_viewer_static/assets/gaussianSplatting.vertex-DSWfFnKQ.js +86 -0
- python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.fragment-DBQE2gDk.js +12 -0
- python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.fragment-qwx5ntqJ.js +17 -0
- python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.vertex-CP2kd3jU.js +30 -0
- python_cad_tools/_viewer_static/assets/gaussianSplattingVoxel.vertex-Dr-fEg_W.js +19 -0
- python_cad_tools/_viewer_static/assets/geometry.fragment-CsJIm2H0.js +242 -0
- python_cad_tools/_viewer_static/assets/geometry.fragment-Dph3muZt.js +262 -0
- python_cad_tools/_viewer_static/assets/geometry.vertex-BXGMTA_J.js +212 -0
- python_cad_tools/_viewer_static/assets/geometry.vertex-Bm9Wg3Gi.js +214 -0
- python_cad_tools/_viewer_static/assets/glowBlurPostProcess.fragment-BJSwKIJD.js +7 -0
- python_cad_tools/_viewer_static/assets/glowMapGeneration.fragment-DcyS1cTF.js +63 -0
- python_cad_tools/_viewer_static/assets/glowMapGeneration.vertex-BGq1Fakj.js +75 -0
- python_cad_tools/_viewer_static/assets/glowMapMerge.fragment-DxcneU_F.js +25 -0
- python_cad_tools/_viewer_static/assets/glowMapMerge.vertex-DaJJKqMV.js +8 -0
- python_cad_tools/_viewer_static/assets/harmonicsFunctions-DzJcvNWG.js +16 -0
- python_cad_tools/_viewer_static/assets/hdrTextureLoader-BGQvgh0h.js +2 -0
- python_cad_tools/_viewer_static/assets/helperFunctions-Cugfp1Fz.js +87 -0
- python_cad_tools/_viewer_static/assets/iesTextureLoader-CO4G0p2M.js +2 -0
- python_cad_tools/_viewer_static/assets/index-B2zBEWsq.js +7007 -0
- python_cad_tools/_viewer_static/assets/index-DcTTrc-8.css +1 -0
- python_cad_tools/_viewer_static/assets/kernelBlur.fragment-B6CTXTxY.js +57 -0
- python_cad_tools/_viewer_static/assets/kernelBlur.fragment-BkKbudbW.js +57 -0
- python_cad_tools/_viewer_static/assets/kernelBlur.vertex-BYwFU5IS.js +11 -0
- python_cad_tools/_viewer_static/assets/kernelBlur.vertex-D371zUfT.js +11 -0
- python_cad_tools/_viewer_static/assets/kernelBlurVaryingDeclaration-CgBGaCHg.js +1 -0
- python_cad_tools/_viewer_static/assets/kernelBlurVaryingDeclaration-YS8JpqdO.js +1 -0
- python_cad_tools/_viewer_static/assets/ktxTextureLoader-ncMSJTjl.js +1 -0
- python_cad_tools/_viewer_static/assets/lightFragment-skHiFbKB.js +398 -0
- python_cad_tools/_viewer_static/assets/logDepthDeclaration-BR8XRz9F.js +4 -0
- python_cad_tools/_viewer_static/assets/logDepthVertex-UKrhEIvg.js +14 -0
- python_cad_tools/_viewer_static/assets/mainUVVaryingDeclaration-BxQWGFcG.js +4 -0
- python_cad_tools/_viewer_static/assets/mesh.vertexData.functions-ayNlGC5K.js +1 -0
- python_cad_tools/_viewer_static/assets/meshUboDeclaration-BnX046aQ.js +4 -0
- python_cad_tools/_viewer_static/assets/oitFragment-CTMCOfNP.js +605 -0
- python_cad_tools/_viewer_static/assets/openpbr.fragment-DPJPqi6p.js +1530 -0
- python_cad_tools/_viewer_static/assets/openpbr.fragment-Dv6qTVVi.js +1384 -0
- python_cad_tools/_viewer_static/assets/openpbr.vertex-Bn2wrdGL.js +364 -0
- python_cad_tools/_viewer_static/assets/openpbr.vertex-CHESW3gB.js +251 -0
- python_cad_tools/_viewer_static/assets/openpbrMaterial.pure-BdK7VgO_.js +3 -0
- python_cad_tools/_viewer_static/assets/openpbrMaterialLoadingAdapter-Bm_AHDUN.js +4 -0
- python_cad_tools/_viewer_static/assets/openpbrTransmissionLayerData-BDa9qEoN.js +339 -0
- python_cad_tools/_viewer_static/assets/openpbrTransmissionLayerData-Ba_ly4LE.js +343 -0
- python_cad_tools/_viewer_static/assets/openpbrUboDeclaration-BY2lSH2C.js +5 -0
- python_cad_tools/_viewer_static/assets/openpbrUboDeclaration-DajhikDS.js +6 -0
- python_cad_tools/_viewer_static/assets/packingFunctions-BHPwcGG2.js +4 -0
- python_cad_tools/_viewer_static/assets/packingFunctions-DTwzBRBx.js +4 -0
- python_cad_tools/_viewer_static/assets/pass.fragment-9oKU4pI3.js +4 -0
- python_cad_tools/_viewer_static/assets/passCube.fragment-CpAeiYx1.js +23 -0
- python_cad_tools/_viewer_static/assets/passCube.fragment-DHtSWVRF.js +23 -0
- python_cad_tools/_viewer_static/assets/pbr.fragment-cIhMKwLs.js +2560 -0
- python_cad_tools/_viewer_static/assets/pbr.vertex-BfdONsf2.js +255 -0
- python_cad_tools/_viewer_static/assets/pbrBRDFFunctions-B_ppOzPO.js +173 -0
- python_cad_tools/_viewer_static/assets/pbrDebug-DDdwL5_t.js +693 -0
- python_cad_tools/_viewer_static/assets/pbrIBLFunctions-b1zdpzOP.js +43 -0
- python_cad_tools/_viewer_static/assets/pbrMaterial.pure-BZA7yVhu.js +2 -0
- python_cad_tools/_viewer_static/assets/pbrMaterialLoadingAdapter-BRvKRnjl.js +1 -0
- python_cad_tools/_viewer_static/assets/pbrUboDeclaration-CMZp25k3.js +5 -0
- python_cad_tools/_viewer_static/assets/postprocess.vertex-DzdUpNbW.js +9 -0
- python_cad_tools/_viewer_static/assets/procedural.vertex-BnbuQ-1f.js +7 -0
- python_cad_tools/_viewer_static/assets/procedural.vertex-Dp-Ng7Pc.js +8 -0
- python_cad_tools/_viewer_static/assets/reflectionFunction-DBlr3oDs.js +83 -0
- python_cad_tools/_viewer_static/assets/rgbdDecode.fragment-CyvnZFOJ.js +5 -0
- python_cad_tools/_viewer_static/assets/rgbdEncode.fragment-Dz4zLnoD.js +5 -0
- python_cad_tools/_viewer_static/assets/rgbdEncode.fragment-iscgpA1_.js +5 -0
- python_cad_tools/_viewer_static/assets/sceneUboDeclaration-BzFIc4mb.js +11 -0
- python_cad_tools/_viewer_static/assets/textureProcessor.fragment-C9D0rd-M.js +151 -0
- python_cad_tools/_viewer_static/assets/textureProcessor.fragment-JMKyWZng.js +146 -0
- python_cad_tools/_viewer_static/assets/tgaTextureLoader-oxTElIQJ.js +1 -0
- python_cad_tools/_viewer_static/assets/vertexColorMixing-mdC9xZe1.js +418 -0
- python_cad_tools/_viewer_static/favicon.svg +10 -0
- python_cad_tools/_viewer_static/index.html +18 -0
- python_cad_tools/_viewer_static/sw.js +61 -0
- python_cad_tools/artifacts.py +546 -0
- python_cad_tools/build-info.json +1 -0
- python_cad_tools/build.py +270 -0
- python_cad_tools/cli.py +212 -0
- python_cad_tools/context.py +67 -0
- python_cad_tools/determinism.py +66 -0
- python_cad_tools/drawings/__init__.py +124 -0
- python_cad_tools/drawings/_catalog.py +79 -0
- python_cad_tools/drawings/_validation.py +238 -0
- python_cad_tools/elements.py +121 -0
- python_cad_tools/exceptions.py +45 -0
- python_cad_tools/exporters/__init__.py +8 -0
- python_cad_tools/exporters/base.py +24 -0
- python_cad_tools/exporters/drawings/__init__.py +5 -0
- python_cad_tools/exporters/drawings/annotations.py +444 -0
- python_cad_tools/exporters/drawings/dxf.py +110 -0
- python_cad_tools/exporters/drawings/model.py +70 -0
- python_cad_tools/exporters/drawings/pdf.py +119 -0
- python_cad_tools/exporters/drawings/primitives.py +93 -0
- python_cad_tools/exporters/drawings/projection.py +64 -0
- python_cad_tools/exporters/drawings/svg.py +135 -0
- python_cad_tools/exporters/glb.py +100 -0
- python_cad_tools/exporters/ifc.py +229 -0
- python_cad_tools/exporters/quantities.py +136 -0
- python_cad_tools/exporters/step.py +117 -0
- python_cad_tools/geometry.py +187 -0
- python_cad_tools/manifests.py +338 -0
- python_cad_tools/project_config.py +174 -0
- python_cad_tools/py.typed +1 -0
- python_cad_tools/runtime_sync.py +126 -0
- python_cad_tools/schemas/build-manifest-1.json +1 -0
- python_cad_tools/schemas/design-manifest-1.json +1 -0
- python_cad_tools/schemas/download-manifest-2.json +46 -0
- python_cad_tools/schemas/drawing-annotations-1.json +66 -0
- python_cad_tools/schemas/glb-manifest-1.json +1 -0
- python_cad_tools/schemas/ifc-validation-1.json +1 -0
- python_cad_tools/schemas/quantities-1.json +1 -0
- python_cad_tools/schemas/run-metadata-1.json +1 -0
- python_cad_tools/schemas/site-metadata-1.json +34 -0
- python_cad_tools/schemas/step-validation-1.json +1 -0
- python_cad_tools/site.py +676 -0
- python_cad_tools/templates/project/.gitignore +9 -0
- python_cad_tools/templates/project/README.md +38 -0
- python_cad_tools/templates/project/config.py +36 -0
- python_cad_tools/templates/project/drawing_annotations.py +117 -0
- python_cad_tools/templates/project/model.py +87 -0
- python_cad_tools/templates/project/pyproject.toml +27 -0
- python_cad_tools/units.py +207 -0
- python_cad_tools/validation.py +153 -0
- python_cad_tools-0.1.0rc1.dist-info/METADATA +149 -0
- python_cad_tools-0.1.0rc1.dist-info/RECORD +236 -0
- python_cad_tools-0.1.0rc1.dist-info/WHEEL +5 -0
- python_cad_tools-0.1.0rc1.dist-info/entry_points.txt +2 -0
- python_cad_tools-0.1.0rc1.dist-info/licenses/LICENSE +679 -0
- 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
|