mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-10 18:55:14 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d01515911 | |||
| 8ffb97d541 | |||
| 8f13e948a9 | |||
| 59a85a1646 | |||
| 4890cebd36 | |||
| 27d5a264f3 | |||
| 3b82c40ff7 | |||
| a05b47339f | |||
| b53fa7a569 | |||
| 4579e1ad50 | |||
| bc63b3670b | |||
| dfb240a982 | |||
| a22d42ad98 | |||
| 7c03f57fd9 | |||
| 91a08fc63a | |||
| 193a13d7fc | |||
| 2e8e68c64d | |||
| 11d408d8c2 | |||
| 1b4547f2b3 | |||
| ae4f93a7cf | |||
| 59b78db068 | |||
| 9210343c9d | |||
| cce1c9dcf5 | |||
| 11bb98683f | |||
| 55297e5b30 |
Vendored
+14
@@ -230,6 +230,20 @@ jobs:
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- run: cargo check -p solidcore --target wasm32-unknown-unknown
|
||||
|
||||
# Each package builds on the Rust release its `rust-version` declares (scripts/ci/
|
||||
# check-rust-version.sh). Downstream users build on older pinned toolchains than
|
||||
# rust-toolchain.toml's, and nothing else here runs one.
|
||||
rust-version:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.full == 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: ". -> bin"
|
||||
- run: scripts/ci/check-rust-version.sh
|
||||
|
||||
# Same packaging check the release workflow runs before publishing (no secrets).
|
||||
publish-dry-run:
|
||||
needs: changes
|
||||
|
||||
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
> **solidcore is developed inside [SolidCraft](https://github.com/tangency-labs/solidcraft), at [`solidcore/`](https://github.com/tangency-labs/solidcraft/tree/main/solidcore).** The [tangency-labs/solidcore](https://github.com/tangency-labs/solidcore) repository is an automatic, read-only mirror of that folder. Open issues and pull requests in [tangency-labs/solidcraft](https://github.com/tangency-labs/solidcraft): kernel-only work against the `kernel` branch.
|
||||
|
||||
<p align="center"><img src="assets/logo.svg" width="140" alt="solidcore logo: an isometric solid with one filleted edge and a NURBS curve on its top face"></p>
|
||||
|
||||
# solidcore
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{"format":1,"unit":"blend.feature_executive"}
|
||||
{"case":"add_by_faces_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2774,380658],[2585,341911],[6017,756869],[2124,294143],[7047,692538],[7860,1181034],[13215,1649521],[2767,78418]],"n":1843},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176984],[2441,349823],[4137,677755],[748,14692]],"n":804},"feature2_graph":{"blocks":[[1305,197906],[1748,275169],[1932,107176]],"n":623}},"topology":null,"values":{"base_geometry":{"blocks":[[2.1599999999999874e+22,2.1599999999999874e+22,2.3436e+24],[550.6115412070844,687.1779118214433,52744.46080298995]],"n":442},"base_record":{"blocks":[[155445.255470152,155489.82184076635,18956276.02122212],[155029.88762338806,155061.88762338806,20154954.18077134],[116389.18300344986,116429.18300344986,12866754.328732701],[106102.57307303276,106122.57307303276,13684737.362150839],[45934.73239171605,45934.73239171605,3587621.6467926963]],"n":1183},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87791.71681469283,87832.84955592154,6389177.88502191]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155065.5,155109.5,18928751.0],[105021.5,105109.5,12645808.0],[135.0,135.0,1804.0]],"n":536},"feature2_geometry":{"blocks":[[7.2e+21,7.2e+21,2.628e+23]],"n":148},"feature2_record":{"blocks":[[142729.0772253012,142729.0772253012,15852924.877656039],[60998.52675559926,60998.52675559926,4015371.2724186946]],"n":423},"record":{"blocks":[[326.0,346.0,12744.0]],"n":124}}}
|
||||
{"case":"all_faces_defillet","deferred":null,"error":null,"ints":{"base_graph":[1,0,0,0,0,0,0,0,0,0,1],"feature0_graph":{"blocks":[[1743,230242],[2881,397902],[3189,537461],[5001,812093],[4768,323418]],"n":1165}},"topology":null,"values":{"base_geometry":[],"base_record":[0.0,0.0],"feature0_geometry":{"blocks":[[1.3599999999999958e+22,1.3599999999999958e+22,9.316000000000001e+23],[14.000069999999997,14.000069999999997,140.00077]],"n":276},"feature0_record":{"blocks":[[154466.63959673684,154466.63959673684,18841428.541271232],[129333.9587499411,129333.9587499411,14745007.961884614],[90266.21715728752,90266.21715728752,11236079.691673888],[9.0,9.0,68.0]],"n":779},"record":[1.0,0.0,0.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0,9.0,1.0,0.0]}}
|
||||
{"case":"all_faces_filleted_box","deferred":null,"error":null,"ints":{"base_graph":[1,0,0,0,0,0,0,0,0,0,1],"feature0_graph":{"blocks":[[1903,220645],[3189,516856],[3449,269618],[5567,663498],[7339,1115480],[11638,1409058]],"n":1528}},"topology":null,"values":{"base_geometry":[],"base_record":[0.0,0.0],"feature0_geometry":{"blocks":[[1.91999999999999e+22,1.91999999999999e+22,1.8528000000000002e+24],[452.0003399999995,452.0003399999995,26406.02375]],"n":378},"feature0_record":{"blocks":[[155397.5,155397.5,18957409.781469982],[154927.4900204164,154927.4900204164,20151093.307439756],[103966.76733889646,103996.76733889646,12124882.750080192],[75978.24264068715,76068.24264068715,7458816.37737983]],"n":988},"record":[1.0,0.0,0.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0,10.0,1.0,0.0]}}
|
||||
{"case":"attributes_add_by_faces_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2774,380658],[2585,341911],[6017,756869],[2124,294143],[7047,692538],[7860,1181034],[13215,1649521],[2767,78418]],"n":1843},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176984],[2441,349823],[4137,677755],[748,14692]],"n":804},"feature2_graph":{"blocks":[[1305,197906],[1748,275169],[1932,107176]],"n":623}},"topology":null,"values":{"base_geometry":{"blocks":[[2.1599999999999874e+22,2.1599999999999874e+22,2.3436e+24],[550.6115412070844,687.1779118214433,52744.46080298995]],"n":442},"base_record":{"blocks":[[155445.255470152,155489.82184076635,18956276.02122212],[155029.88762338806,155061.88762338806,20154954.18077134],[116389.18300344986,116429.18300344986,12866754.328732701],[106102.57307303276,106122.57307303276,13684737.362150839],[45934.73239171605,45934.73239171605,3587621.6467926963]],"n":1183},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87791.71681469283,87832.84955592154,6389177.88502191]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155065.5,155109.5,18928751.0],[105021.5,105109.5,12645808.0],[135.0,135.0,1804.0]],"n":536},"feature2_geometry":{"blocks":[[7.2e+21,7.2e+21,2.628e+23]],"n":148},"feature2_record":{"blocks":[[142729.0772253012,142729.0772253012,15852924.877656039],[60998.52675559926,60998.52675559926,4015371.2724186946]],"n":423},"record":{"blocks":[[337.0,357.0,14391.0]],"n":164}}}
|
||||
{"case":"attributes_inner_loop_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2548,339314],[2808,388653],[5366,597446],[3316,572452],[5984,574809],[9450,1382528],[11491,1234945]],"n":1760},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261}},"topology":null,"values":{"base_geometry":{"blocks":[[2.0799999999999883e+22,2.0799999999999883e+22,2.1736000000000002e+24],[363.8030081955735,560.3693788099324,25313.152184450642]],"n":418},"base_record":{"blocks":[[155416.25547015207,155492.8218407664,18950600.3615193],[155118.1152126431,155118.1152126431,20152219.349649835],[116983.14328273362,117023.14328273362,13745846.636862068],[91170.82416150477,91234.82416150477,11435584.550245512],[30337.0,30353.0,1019935.0]],"n":1118},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87791.71681469283,87832.84955592154,6385487.88502191]],"n":197},"record":{"blocks":[[81.0,89.0,1463.0]],"n":58}}}
|
||||
{"case":"attributes_rebuild_fillets_defillet","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[1906,252991],[3366,516788],[2314,400069],[4443,571496],[8162,998322]],"n":1264},"feature0_graph":{"blocks":[[853,122476],[847,63880]],"n":377}},"topology":null,"values":{"base_geometry":{"blocks":[[1.439999999999995e+22,1.439999999999995e+22,1.044e+24],[21.200139999999998,21.200139999999998,379.20287]],"n":296},"base_record":{"blocks":[[154399.14159265358,154399.14159265358,18832865.4645943],[128406.54159265356,128406.54159265356,14075364.427283542],[105272.0,105272.0,13472688.57781746],[15078.741592653581,15082.741592653596,542971.1221134894]],"n":844},"feature0_geometry":{"blocks":[[4e+21,4e+21,8.199999999999998e+22]],"n":82},"feature0_record":{"blocks":[[115424.47391131187,115428.47391131187,10922544.14210909]],"n":245},"record":{"blocks":[[79.0,89.0,1508.0]],"n":70}}}
|
||||
{"case":"attributes_restore_defillet","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[1508,183428],[2419,270746],[3057,403507],[6277,856170],[551,3370]],"n":1035},"feature0_graph":{"blocks":[[278,24878]],"n":146}},"topology":null,"values":{"base_geometry":{"blocks":[[1.1999999999999975e+22,1.1999999999999975e+22,7.260000000000001e+23]],"n":252},"base_record":{"blocks":[[154397.14159265358,154397.14159265358,18832863.4645943],[130360.84159265357,130360.84159265357,15657691.00865363],[60190.0,60190.0,6048065.0]],"n":714},"feature0_geometry":{"blocks":[[1.6e+21,1.6e+21,1.36e+22]],"n":38},"feature0_record":{"blocks":[[57216.03231865831,57216.03231865831,2227522.302144891]],"n":115},"record":{"blocks":[[59.0,67.0,823.0]],"n":55}}}
|
||||
@@ -54,6 +56,9 @@
|
||||
{"case":"fillet_group_torii_fillet_rb11","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2573,312590],[2235,302121],[7287,1092477],[3374,249678],[9425,1441704],[5510,902128],[13186,1872602],[16615,2004017],[1666,20910]],"n":2071}},"topology":null,"values":{"base_geometry":{"blocks":[[2.5599999999999832e+22,2.5599999999999832e+22,3.2896e+24],[8e+20,8e+20,3.6e+21],[1.0000200000000001,1.0000200000000001,3.00005]],"n":516},"base_record":{"blocks":[[155035.83440579096,155661.89765365858,18948680.82052572],[155281.77152757163,155574.77152757163,20161266.305777643],[141134.20495401855,141407.41630458267,17778418.33330449],[105017.50209477526,105586.50209477526,11821412.01749248],[91174.28875598182,91629.28875598182,10910463.960739516],[15238.813143693185,15288.813143693185,109863.88984156726]],"n":1327},"record":[0.0,0.0,1.0,0.0,0.0]}}
|
||||
{"case":"fillet_group_torii_fillet_rb23","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2573,312590],[2235,302121],[7287,1092477],[3374,249678],[9425,1441704],[5510,902128],[13186,1872602],[16615,2004017],[1666,20910]],"n":2071}},"topology":null,"values":{"base_geometry":{"blocks":[[2.5599999999999832e+22,2.5599999999999832e+22,3.2896e+24],[8e+20,8e+20,3.6e+21],[1.0000200000000001,1.0000200000000001,3.00005]],"n":516},"base_record":{"blocks":[[155035.83440579096,155661.89765365858,18948680.82052572],[155281.77152757163,155574.77152757163,20161266.305777643],[141134.20495401855,141407.41630458267,17778418.33330449],[105017.50209477526,105586.50209477526,11821412.01749248],[91174.28875598182,91629.28875598182,10910463.960739516],[15238.813143693185,15288.813143693185,109863.88984156726]],"n":1327},"record":[0.0,0.0,1.0,0.0,0.0]}}
|
||||
{"case":"mitred_rounds_box_top_r3","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2083,253864],[3024,468943],[3863,321220],[5564,804198],[5754,904563],[11792,1526882],[1838,41095]],"n":1577},"feature0_graph":{"blocks":[[1806,224222],[3712,613264],[2805,507349],[4462,572148],[9492,1266469],[1232,21604]],"n":1313}},"topology":null,"values":{"base_geometry":{"blocks":[[1.91999999999999e+22,1.91999999999999e+22,1.8528000000000002e+24],[782.0829429411759,782.082942941176,29519.122368823526]],"n":383},"base_record":{"blocks":[[156507.5,156507.5,19111185.0],[156364.54117647058,156364.54117647058,20322396.93382353],[105433.0411764706,105433.0411764706,12327102.061764706],[94630.0,95718.0,11327548.0],[210.0,210.0,660.0]],"n":1029},"feature0_geometry":{"blocks":[[1.5999999999999933e+22,1.5999999999999933e+22,1.288e+24],[37.98568732985196,37.98568732985196,788.0151850985815]],"n":313},"feature0_record":{"blocks":[[156703.22494602168,156703.22494602168,19162418.692349844],[143258.58235294116,143258.58235294116,17060525.736758336],[93725.45584412271,94813.45584412271,10865237.012552246],[31834.0,31834.0,749544.5]],"n":851},"record":{"blocks":[[180.0,190.0,3080.0]],"n":52}}}
|
||||
{"case":"multi_feature_adv_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2764,379783],[2576,339959],[6104,754984],[2151,298312],[7054,677922],[7816,1172299],[13400,1684853],[2574,69936]],"n":1843},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176978],[2441,349693],[4111,670924],[774,14683]],"n":804},"feature2_graph":{"blocks":[[1305,198056],[1736,271264],[1944,108295]],"n":623}},"topology":null,"values":{"base_geometry":{"blocks":[[2.1599999999999874e+22,2.1599999999999874e+22,2.3436e+24],[638.8841584798115,775.4505290941705,56271.317757521465]],"n":442},"base_record":{"blocks":[[155445.255470152,155489.82184076635,18956276.02122212],[155094.1600378454,155126.1600378454,20164389.693007357],[116325.18331626526,116365.18331626526,12865628.669316046],[106093.77870914956,106113.77870914956,13675114.593415532],[45943.52615059925,45943.52615059925,3582894.5606996478]],"n":1183},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87791.71681469283,87832.84955592154,6385487.88502191]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155083.5,155119.5,18928798.5],[105063.5,105159.5,12655567.5],[75.0,75.0,967.0]],"n":536},"feature2_geometry":{"blocks":[[7.2e+21,7.2e+21,2.628e+23]],"n":148},"feature2_record":{"blocks":[[142729.34995257392,142729.34995257392,15852336.370148472],[60998.52615059925,60998.52615059925,4010652.7699044417]],"n":423},"record":{"blocks":[[326.0,346.0,13094.0]],"n":124}}}
|
||||
{"case":"multi_feature_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2771,380151],[2581,340435],[6027,751066],[2174,302712],[6982,679564],[7880,1185829],[13383,1678399],[2591,69948]],"n":1843},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176978],[2441,349693],[4111,670924],[774,14683]],"n":804},"feature2_graph":{"blocks":[[1305,197879],[1748,275250],[1932,107087]],"n":623}},"topology":null,"values":{"base_geometry":{"blocks":[[2.1599999999999874e+22,2.1599999999999874e+22,2.3436e+24],[550.6115312070845,687.1779218214433,52984.82530328035]],"n":442},"base_record":{"blocks":[[155445.255470152,155489.82184076635,18956276.02122212],[155029.88762338806,155061.88762338806,20148234.18077134],[116389.18300344986,116429.18300344986,12864740.769140378],[106102.57306303276,106122.57308303277,13677357.359610839],[45934.73234671234,45934.73240671234,3583931.6460126126]],"n":1183},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87791.71681469283,87832.84955592154,6385487.88502191]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155083.5,155119.5,18928798.5],[105063.5,105159.5,12655567.5],[75.0,75.0,967.0]],"n":536},"feature2_geometry":{"blocks":[[7.2e+21,7.2e+21,2.628e+23]],"n":148},"feature2_record":{"blocks":[[142729.0772253012,142729.0772253012,15851718.332201496],[60998.526700595554,60998.52678059555,4011681.271218581]],"n":423},"record":{"blocks":[[326.0,346.0,13090.0]],"n":124}}}
|
||||
{"case":"multi_rebuild_0_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2771,380151],[2581,340435],[6027,751042],[2174,302712],[6995,682597],[7865,1184614],[13385,1678999],[2591,70004]],"n":1843},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176993],[2441,349514],[4107,669726],[778,14797]],"n":804},"feature2_graph":{"blocks":[[1305,197879],[1748,275250],[1932,107087]],"n":623}},"topology":null,"values":{"base_geometry":{"blocks":[[2.1599999999999874e+22,2.1599999999999874e+22,2.3436e+24],[550.6115312070845,667.1779218214434,52652.187164461]],"n":442},"base_record":{"blocks":[[155451.53865545918,155483.53865545918,18959902.682866313],[155023.60443808086,155068.17080869523,20150495.966324273],[116389.18300344986,116429.18300344986,12864740.769140378],[106102.57306303276,106103.90832107945,13677333.40532395],[45934.73234671234,45934.73240671234,3583931.6460126126]],"n":1183},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87795.71681469283,87822.18479396813,6386245.248830656]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155083.5,155119.5,18928798.5],[105057.83309524418,105149.83309524418,12654314.892937822],[76.66690475583123,76.66690475583123,1007.5613490585596]],"n":536},"feature2_geometry":{"blocks":[[7.2e+21,7.2e+21,2.628e+23]],"n":148},"feature2_record":{"blocks":[[142729.0772253012,142729.0772253012,15851718.332201496],[60998.526700595554,60998.52678059555,4011681.271218581]],"n":423},"record":{"blocks":[[322.0,342.0,12969.0]],"n":127}}}
|
||||
{"case":"multi_rebuild_1_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2077,266206],[3633,579612],[2138,226476],[4989,506469],[8927,1280475],[3025,147354]],"n":1369},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176993],[2441,349514],[4107,669726],[778,14797]],"n":804},"feature2_graph":{"blocks":[[293,26021]],"n":149}},"topology":null,"values":{"base_geometry":{"blocks":[[1.5999999999999933e+22,1.5999999999999933e+22,1.288e+24],[110.8481596440759,207.41453025843512,3679.688704523623]],"n":343},"base_record":{"blocks":[[155448.14519604263,155480.14519604263,18960494.546111137],[155020.50867205963,155065.07504267397,20143946.545116752],[91052.57229961093,91093.23991863422,11082444.561509266],[45484.33380951166,45485.00142853499,2282412.032856306]],"n":899},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87795.71681469283,87822.18479396813,6386245.248830656]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155083.5,155119.5,18928798.5],[105057.83309524418,105149.83309524418,12654314.892937822],[76.66690475583123,76.66690475583123,1007.5613490585596]],"n":536},"feature2_geometry":{"blocks":[[1.6e+21,1.6e+21,1.36e+22]],"n":49},"feature2_record":{"blocks":[[71831.53239638661,71831.53239638661,3671408.0937480605]],"n":139},"record":{"blocks":[[287.0,307.0,9792.0]],"n":127}}}
|
||||
{"case":"multi_rebuild_2_extrusion","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[2207,279844],[3288,509422],[3158,278508],[4947,640592],[6593,1014126],[9416,988308]],"n":1500},"feature0_graph":{"blocks":[[683,104052],[34,109]],"n":261},"feature1_graph":{"blocks":[[1287,176993],[2441,349514],[4107,669726],[778,14797]],"n":804}},"topology":null,"values":{"base_geometry":{"blocks":[[1.7599999999999916e+22,1.7599999999999916e+22,1.5576000000000002e+24],[152.8067495591884,269.37312017354753,7235.722609831468]],"n":364},"base_record":{"blocks":[[155260.6164362172,155305.18280683155,18929403.35444251],[155162.03743188508,155194.03743188508,20182286.063773222],[91107.57229961093,91148.23991863422,11083020.561509266],[60725.11195932037,60725.779578343696,4299053.8873962285]],"n":940},"feature0_geometry":{"blocks":[[2.4e+21,2.4e+21,2.9999999999999996e+22]],"n":56},"feature0_record":{"blocks":[[87795.71681469283,87822.18479396813,6386245.248830656]],"n":197},"feature1_geometry":{"blocks":[[9.6e+21,9.6e+21,4.6559999999999995e+23]],"n":194},"feature1_record":{"blocks":[[155083.5,155119.5,18928798.5],[105057.83309524418,105149.83309524418,12654314.892937822],[76.66690475583123,76.66690475583123,1007.5613490585596]],"n":536},"record":{"blocks":[[271.0,287.0,7844.0]],"n":102}}}
|
||||
{"case":"no_ops_defillet","deferred":null,"error":null,"ints":{"base_graph":{"blocks":[[1728,230155],[2865,397674],[3250,542715],[4992,815381],[4729,327785]],"n":1165},"feature0_none":[1]},"topology":null,"values":{"base_geometry":{"blocks":[[1.3599999999999958e+22,1.3599999999999958e+22,9.316000000000001e+23],[11.000069999999997,11.000069999999997,92.00077000000002]],"n":276},"base_record":{"blocks":[[154398.14159265358,154398.14159265358,18832864.4645943],[129390.13959673682,129390.13959673682,14750795.369363222],[90273.51715728751,90273.51715728751,11239145.200591717],[14.017157287525382,14.017157287525382,85.60294372515229]],"n":779},"record":{"blocks":[[48.0,64.0,438.0]],"n":43}}}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
{"case":"gen0039","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1745.6446517672211,1759.0009086963332,53080.473545951456]],"n":61}}}
|
||||
{"case":"gen0040","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1293.1780750967052,1331.5983705234573,43731.711951791374]],"n":54}}}
|
||||
{"case":"gen0041","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-1.512782707950123,1082.7428297938636,-509.6925401146473]],"n":89}}}
|
||||
{"case":"gen0042","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[247.59502854734214,267.6372546180361,10403.49931860245]],"n":67}}}
|
||||
{"case":"gen0042","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[247.59500994892622,267.637273216452,10403.498983830963]],"n":67}}}
|
||||
{"case":"gen0043","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["inf","inf","inf"]],"n":58}}}
|
||||
{"case":"gen0044","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2363.331389460768,2381.342638010389,23505.946664766696]],"n":54}}}
|
||||
{"case":"gen0045","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[46563.40296431203,46630.561956428515,823550.7565376083]],"n":131}}}
|
||||
@@ -909,7 +909,7 @@
|
||||
{"case":"gen0852","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[160.1181334263939,588.6357976688842,7315.822570183931]],"n":78}}}
|
||||
{"case":"gen0853","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[483.08853328132193,583.7951633580492,21580.922615116793]],"n":68}}}
|
||||
{"case":"gen0854","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1904.368161806397,1986.2846515331662,41598.7870653989]],"n":65}}}
|
||||
{"case":"gen0855","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-156.02409151847633,410.5124223109191,-4682.4589894044575]],"n":68}}}
|
||||
{"case":"gen0855","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-156.02408784125566,410.5124186336983,-4682.458967341133]],"n":68}}}
|
||||
{"case":"gen0856","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1840.2670017776718,2073.924694260032,52771.46827067364]],"n":81}}}
|
||||
{"case":"gen0857","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[344.30209778448113,387.76064706770353,11668.607945433409]],"n":56}}}
|
||||
{"case":"gen0858","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.44331673962174,1051.7564644076338,4225.38697740385]],"n":89}}}
|
||||
@@ -1655,7 +1655,7 @@
|
||||
{"case":"gen1598","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1107.122912136224,2062.064170269247,18820.15307682447]],"n":100}}}
|
||||
{"case":"gen1599","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1649.8413176227855,1688.9076570088273,82824.75732259153]],"n":80}}}
|
||||
{"case":"gen1600","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1328.8306468841884,1537.8237072141824,64621.894244369585]],"n":89}}}
|
||||
{"case":"gen1601","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[245.6209888918087,260.6012897777472,8049.2557268355995]],"n":55}}}
|
||||
{"case":"gen1601","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[245.62098559210304,260.60129307745285,8049.255707037365]],"n":55}}}
|
||||
{"case":"gen1602","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1565.0240348651373,1591.1848258051928,97009.86974045317]],"n":97}}}
|
||||
{"case":"gen1603","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1295.903574378681,1475.9072442360741,96443.64845655844]],"n":103}}}
|
||||
{"case":"gen1604","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44185.678798756344,46310.99065267807,787573.7979235228]],"n":172}}}
|
||||
|
||||
+5
-5
@@ -26,7 +26,7 @@
|
||||
{"case":"edge_negate","deferred":null,"error":null,"ints":{"status":[0]},"topology":null,"values":{"extent":[-3.0,-1.0]}}
|
||||
{"case":"edge_periodic_degenerate_wrap","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[3.0,1.0,0.0,0.0,1.0,0.0]}}
|
||||
{"case":"edge_periodic_huge_value","deferred":null,"error":null,"ints":{"flags":[0,0,1,1]},"topology":null,"values":{"values":[1e+20,1e+20,-1e+20,-1e+20,1.0,-1e+20]}}
|
||||
{"case":"edge_periodic_near_top","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[89.99999000000003,0.0,0.0,90.0,0.0,0.0]}}
|
||||
{"case":"edge_periodic_near_top","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[89.99999000000003,-9.999999974752427e-6,9.999999974752427e-6,90.00000999999997,-9.999999974752427e-6,9.999999974752427e-6]}}
|
||||
{"case":"edge_periodic_negative_period","deferred":null,"error":null,"ints":{"flags":[0,0,1,0]},"topology":null,"values":{"values":[20.0,-160.0,-200.0,-610.0,90.0,-610.0]}}
|
||||
{"case":"edge_periodic_zero_period","deferred":null,"error":null,"ints":{"flags":[0,0,0,0]},"topology":null,"values":{"values":[1.0,3.0,3.0,2.0,1.0,2.0]}}
|
||||
{"case":"edge_same_scaled_zero","deferred":null,"error":null,"ints":{"flags":[0,0,0,0,0,0,1]},"topology":null,"values":{"intersect":[5e-13,1000000.0],"union":[0.0,1000000.0000005]}}
|
||||
@@ -921,7 +921,7 @@
|
||||
{"case":"gen0867","deferred":null,"error":null,"ints":{},"topology":null,"values":{"extents":[-4032.5445657100436,0.0,-4032.5445657100436,-4032.5445657100436,1e+20,-1e+20]}}
|
||||
{"case":"gen0868","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[-4.547682406915637,-4.547682406915637,2.000177801164682e-12,3.547682406915637,-4.547682406915637,2.000177801164682e-12]}}
|
||||
{"case":"gen0869","deferred":null,"error":null,"ints":{"disjoint":[0]},"topology":null,"values":{}}
|
||||
{"case":"gen0870","deferred":null,"error":null,"ints":{"flags":[1,1,1,0]},"topology":null,"values":{"values":[-3.999999999999,-4.0,0.0,1.000088900582341e-12,-4.0,0.0]}}
|
||||
{"case":"gen0870","deferred":null,"error":null,"ints":{"flags":[1,1,1,0]},"topology":null,"values":{"values":[-3.999999999999,-3.999999999999,1.000088900582341e-12,0.0,-3.999999999999,0.0]}}
|
||||
{"case":"gen0871","deferred":null,"error":null,"ints":{"flags":[1,1,1,0]},"topology":null,"values":{"values":[-999999.999901,-2.999901,999997.000099,0.00009900000000007125,-2.999901,0.00009900000000007125]}}
|
||||
{"case":"gen0872","deferred":null,"error":null,"ints":{"status_count":[0,0]},"topology":null,"values":{"intervals":[]}}
|
||||
{"case":"gen0873","deferred":null,"error":null,"ints":{"flags":[1,1,1,0]},"topology":null,"values":{"values":[-420.0475459692425,-5864.660909553929,0.0,-5444.613363584686,-5864.660909553929,-5444.613363584686]}}
|
||||
@@ -1554,7 +1554,7 @@
|
||||
{"case":"gen1500","deferred":null,"error":null,"ints":{"flags":[0,0,0,0]},"topology":null,"values":{"values":[1e-6,1e-6,1e+20,1e+20,-1e+20,1e+20]}}
|
||||
{"case":"gen1501","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[-0.9587557303154033,-0.9587557303154033,4.041244269684597,0.3265002390765175,-0.9587557303154033,0.3265002390765175]}}
|
||||
{"case":"gen1502","deferred":null,"error":null,"ints":{"counts":[0],"flag":[0]},"topology":null,"values":{"clamped":[-3.6531332205612728]}}
|
||||
{"case":"gen1503","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[0.8431568102231495,-0.8412598042009409,0.0,3.841259804200941,-0.8412598042009409,0.0]}}
|
||||
{"case":"gen1503","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[0.8431568102231495,-0.8412598042009449,3.9968028886505635e-15,3.841259804200945,-0.8412598042009449,3.9968028886505635e-15]}}
|
||||
{"case":"gen1504","deferred":null,"error":null,"ints":{"flags":[1,1,0,0,0,1,1,1,1,1,0,0,0]},"topology":null,"values":{"values":[-5.0,8.696559494945177,1.8482797474725885,13.696559494945177,8.696559494945177,0.0,0.0,-5.0,0.0,-5.0,0.3650551805981122]}}
|
||||
{"case":"gen1505","deferred":null,"error":null,"ints":{"flags":[0,0,0,0,0,0,0]},"topology":null,"values":{"intersect":[2.5565017038286015,3.0],"union":[-7.213387952269873e-10,3.165025619210411]}}
|
||||
{"case":"gen1506","deferred":null,"error":null,"ints":{"flags":[1,1,0,1]},"topology":null,"values":{"values":[5605.960734602222,5605.960734602222,0.0,0.0,5605.960734602222,0.0]}}
|
||||
@@ -2114,7 +2114,7 @@
|
||||
{"case":"gen2060","deferred":null,"error":null,"ints":{"flags":[0,1,0,0,0,1,1,1,0,4,0,0,1001]},"topology":null,"values":{"values":[3564.9311334680096,3564.9311344680095,3564.93113396801,9.99999883788405e-7,3564.9311344680095,3564.9311344680095,3564.9311344680095,3564.93469839883,0.00010000000020227162,3564.9311334680096,101.0000118234325]}}
|
||||
{"case":"gen2061","deferred":null,"error":null,"ints":{"counts":[],"flag":[0]},"topology":null,"values":{"clamped":[]}}
|
||||
{"case":"gen2062","deferred":null,"error":null,"ints":{"status":[0]},"topology":null,"values":{"extent":[2.0,360.0]}}
|
||||
{"case":"gen2063","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[-0.0001752855794858732,-0.0009064160095653894,0.0,0.0009064156015241429,-0.0009064160095653894,0.0]}}
|
||||
{"case":"gen2063","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[-0.0001752855794858732,-0.0009064160095650919,2.975050761300224e-16,0.0009064156015238454,-0.0009064160095650919,2.975050761300224e-16]}}
|
||||
{"case":"gen2064","deferred":null,"error":null,"ints":{"flags":[1001,1,1,0,0,0,0]},"topology":null,"values":{"intersect":[1e+20,-1e+20],"union":[0.465547398032645,1000000.0]}}
|
||||
{"case":"gen2065","deferred":null,"error":null,"ints":{"flag":[1]},"topology":null,"values":{"clamped":[7.390963598478955,-5.0,-0.8157526895288466,-3.0,2.9999,0.0,-1e+20,-3.3306754088323505,7.774316697598074,4.0,-4.903134706608148,0.0,-1.5550380498551388,-4.0]}}
|
||||
{"case":"gen2066","deferred":null,"error":null,"ints":{"flags":[1,1,0,1]},"topology":null,"values":{"values":[-3482.7204020819313,-3482.7204020819313,9.99999883788405e-7,3478.7204020819313,-3482.7204020819313,9.99999883788405e-7]}}
|
||||
@@ -2318,7 +2318,7 @@
|
||||
{"case":"gen2264","deferred":null,"error":null,"ints":{"status":[1007]},"topology":null,"values":{"extent":[1e+20,-1e+20]}}
|
||||
{"case":"gen2265","deferred":null,"error":null,"ints":{"flags":[1001,1,1,0,0,0,0]},"topology":null,"values":{"intersect":[1e+20,-1e+20],"union":[-1234567.0,1234567.0]}}
|
||||
{"case":"gen2266","deferred":null,"error":null,"ints":{"flags":[0,0,0,0]},"topology":null,"values":{"values":[3.5,-0.5005336876101969,0.5,4.500533687610197,-0.0005336876101968466,0.5]}}
|
||||
{"case":"gen2267","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[0.005902837378700454,0.000983806229783426,0.0,2.0009838062297836,0.000983806229783426,0.0]}}
|
||||
{"case":"gen2267","deferred":null,"error":null,"ints":{"flags":[1,1,0,0]},"topology":null,"values":{"values":[0.005902837378700454,0.000983806229783113,3.131175874138137e-16,2.000983806229783,0.000983806229783113,3.131175874138137e-16]}}
|
||||
{"case":"gen2268","deferred":null,"error":null,"ints":{},"topology":null,"values":{"approx":[-0.0,0.7741788387164605],"approx_plain":[-0.0,0.7741788387164605],"expanded":[-0.0,0.7741788387164605]}}
|
||||
{"case":"gen2269","deferred":null,"error":null,"ints":{"flags":[1,1,1,1]},"topology":null,"values":{"values":[4.700315805143418,302.8236045754655,0.0,1.9895196601282805e-12,302.8236045754655,0.0]}}
|
||||
{"case":"gen2270","deferred":null,"error":null,"ints":{"counts":[],"flag":[0]},"topology":null,"values":{"clamped":[]}}
|
||||
|
||||
+1
-1
@@ -3029,7 +3029,7 @@
|
||||
{"case":"my_test_extent2d_classify_point_tol_v","deferred":null,"error":null,"ints":{"classify":[1,0],"contains":[1,1],"left_eval":[1,1],"on_boundary":[0],"on_boundary_bits":[0,0]},"topology":null,"values":{"binormal":[0.0,0.0],"clamp":[1.1,2.1],"outside_dist":[0.0]}}
|
||||
{"case":"my_test_extent2d_clip_line","deferred":null,"error":null,"ints":{},"topology":null,"values":{"t":[1.0]}}
|
||||
{"case":"my_test_extent2d_contains_periodic","deferred":null,"error":null,"ints":{"contained":[0],"contained_again":[0]},"topology":null,"values":{"periodic_point":[4.9,3.0]}}
|
||||
{"case":"my_test_extent2d_contains_periodic_tol","deferred":null,"error":null,"ints":{"contained":[1],"contained_again":[1]},"topology":null,"values":{"periodic_point":[1.0,3.0]}}
|
||||
{"case":"my_test_extent2d_contains_periodic_tol","deferred":null,"error":null,"ints":{"contained":[1],"contained_again":[1]},"topology":null,"values":{"periodic_point":[0.9000000000000004,3.0]}}
|
||||
{"case":"my_test_extent2d_contains_point","deferred":null,"error":null,"ints":{"classify":[4,1],"contains":[0,0],"left_eval":[1,1],"on_boundary":[0],"on_boundary_bits":[0,0]},"topology":null,"values":{"binormal":[0.0,0.0],"clamp":[1.0,3.0],"outside_dist":[0.09999999999999998]}}
|
||||
{"case":"my_test_extent2d_contains_point_tol","deferred":null,"error":null,"ints":{"classify":[0,1],"contains":[1,1],"left_eval":[1,1],"on_boundary":[1],"on_boundary_bits":[1,1]},"topology":null,"values":{"binormal":[1.0,0.0],"clamp":[1.0,3.0],"outside_dist":[0.0]}}
|
||||
{"case":"my_test_extent2d_e1_from_points","deferred":null,"error":null,"ints":{},"topology":null,"values":{"extent":[1.0,2.0,3.0,4.0]}}
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
{"case":"gen0203","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[102.24307334463859,195.6967864829654,4724.6063373082625],[33.71725371634546,131.9491457325187,4753.355611784782],[20.297873453304668,93.18953768098099,1067.0544313355372]],"n":687}}}
|
||||
{"case":"gen0204","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4981.349046579901,31377.464965164152,-176911.15258042538],[-11130.065111554599,28355.09140047426,-1269062.1228540512],[-2133.653306094631,2660.156958964759,-30941.40571815177]],"n":537}}}
|
||||
{"case":"gen0205","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[44.86967270530531,78.05512663807073,6209.961532611203],[28.052396093057517,63.65411805859518,1342.88034859231]],"n":681}}}
|
||||
{"case":"gen0206","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3752.165891346726,6938.646196372975,131766.4280432012],[62.1468953731291,130.67948208921155,5053.420004786234]],"n":403}}}
|
||||
{"case":"gen0206","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3751.165891346726,6937.646196372975,131720.4280432012],[62.1468953731291,130.67948208921155,5053.420004786234]],"n":403}}}
|
||||
{"case":"gen0207","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3469.0061709167853,5100.276369680855,102797.93247894447],[67.16673384034242,196.30848800652367,10148.502524389934],[103.53626870933635,148.85054952359698,12182.933779612064],[4.0,4.0,161.0]],"n":885}}}
|
||||
{"case":"gen0208","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1194.0961091743866,1261.7621179179177,29421.01203828354],[51.68564234762305,127.9942953525583,6506.897862676861],[60.65074013315851,133.04450628707548,9941.73758470576],[39.362564314850964,118.24443702847711,6425.421514603329],[46.48170859612891,134.99256740895132,5705.257564853699],[1084.5587397572863,1152.2221607384015,125079.33394990404],[42.112517023294394,120.27229294204348,6619.556672065577],[44.76169233739341,131.4650215426646,5432.154621431303],[33.51992600121396,62.58548398600037,2921.67884247806]],"n":2229}}}
|
||||
{"case":"gen0209","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1113.8455207908178,1158.5086949919878,12109.772526630913],[74.46360249322522,122.67401350597164,9439.755019602107],[2195.100394484995,2238.7252338390813,244981.18313951252],[58.94055201871431,103.58883069928768,8870.935947429676],[2224.5635638071362,2265.470661607989,458976.61125412723],[49.15676647371497,100.3867206880556,7943.949622929454],[96.1721430456891,141.0474060514182,14738.807625536128],[21.2236529032045,29.641490779605345,829.6996414144846]],"n":1835}}}
|
||||
@@ -302,7 +302,7 @@
|
||||
{"case":"gen0290","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1157.8185212687683,1266.8841675164047,32697.55801649505],[36.86299009624461,173.45884704598646,5130.751206144237],[12.79943248367548,68.4548328570085,553.048259016427]],"n":625}}}
|
||||
{"case":"gen0291","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[11399.554892680173,21080.758969428276,895232.6578702261],[-795.9069729318918,9718.651442777771,-54562.03876181785]],"n":377}}}
|
||||
{"case":"gen0292","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[39.51433944805936,66.84051189797442,5132.758022952734],[19.45023479523018,41.276650635879626,1884.3494418247483]],"n":662}}}
|
||||
{"case":"gen0293","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1178.5419902829203,1212.7582535550373,13308.707220438539],[27.649226870031214,80.55674446006834,3718.951067452295],[5.841263495784869,14.656686469269697,153.39436012270207]],"n":541}}}
|
||||
{"case":"gen0293","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1177.5419902829203,1211.7582535550373,13283.707220438539],[27.649226870031214,80.55674446006834,3718.951067452295],[5.841263495784869,14.656686469269697,153.39436012270207]],"n":541}}}
|
||||
{"case":"gen0294","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[200.7716947042887,242.03606576637694,17455.642767559566],[33.63720927082592,90.44373023412352,6334.855719608325],[40.043826797534585,96.55460310369476,3203.267534333933],[7.7767973429101955,28.556855202523025,152.86103671158816]],"n":849}}}
|
||||
{"case":"gen0295","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1391.0376662928236,2979.1748689777487,47585.34156257201],[60.04922674162154,104.34453948759001,8361.786070209198],[44.5510292254879,65.77760717695531,2603.51887136433]],"n":622}}}
|
||||
{"case":"gen0296","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[1092348.5178945067,1329496.0499032086,52101601.81824983]],"n":341}}}
|
||||
@@ -822,7 +822,7 @@
|
||||
{"case":"gen0810","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2262.788116119793,2300.708492169487,39238.158391608835],[15.485162045792093,34.52902324159598,1244.322330945389]],"n":405}}}
|
||||
{"case":"gen0811","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-210.6284722087713,1157.6595361970328,-78279.1565931907],[402.76693330252454,660.8761158644649,384.5052250663357]],"n":305}}}
|
||||
{"case":"gen0812","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[12.129963321419211,26.240720896430936,252.1919760675649]],"n":336}}}
|
||||
{"case":"gen0813","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2320.145018290183,2386.1109885279143,160524.54501838668],[28.955374564310883,163.8378783510562,3597.5967495228274],[19.608680445961348,126.30332246220345,3604.951432220665],[67.87922160352736,182.59248875802956,9559.562848603524],[13.631112583398526,146.0905883288126,2899.2529724792967],[6.702677876233801,115.35042684863713,390.65514768752763]],"n":1484}}}
|
||||
{"case":"gen0813","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2319.145018290183,2385.1109885279143,160406.54501838668],[28.955374564310883,163.8378783510562,3597.5967495228274],[19.608680445961348,126.30332246220345,3604.951432220665],[67.87922160352736,182.59248875802956,9559.562848603524],[13.631112583398526,146.0905883288126,2899.2529724792967],[6.702677876233801,115.35042684863713,390.65514768752763]],"n":1484}}}
|
||||
{"case":"gen0814","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[108.16621082934424,221.87969080188245,8676.592361755107],[45.297466507140115,180.96027206806988,5977.324762751533],[2.633805172533949,15.951200945850884,52.916496005871636]],"n":529}}}
|
||||
{"case":"gen0815","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1209.9709554557123,1305.6329737768986,34787.86729388861],[66.00466960575217,156.209869555538,7773.013040819706]],"n":463}}}
|
||||
{"case":"gen0816","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[107.55344429358178,209.49964561977276,3100.063864095908],[36.78300583196494,146.59442298075538,3986.479945282126],[15.649778006887608,62.514652727336106,16.254293303288843]],"n":610}}}
|
||||
@@ -950,12 +950,12 @@
|
||||
{"case":"gen0938","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[84.58976906636367,95.15437333846775,7406.9195258132695],[38.50205228055802,63.61471659107631,4144.319330513367],[98.35940643749596,109.62775077280469,11479.244350081048],[40.75227600729181,66.95209392997931,4121.950065467486],[1134.8380394470205,1146.1834234350827,201095.32983972508],[53.65496145620496,68.42916215697633,5941.300603607814],[38.62924331592706,59.92328170433056,4618.616889637051],[2.356128301930311,2.527296094959116,19.878638408980752]],"n":1802}}}
|
||||
{"case":"gen0939","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[37.577276485923726,4e+20,-4e+20],[27.596335613587335,4e+20,-4.000000000000042e+20],[16.732392630353264,119.39384541074453,2538.98903052722],[2.7013524235941677,48.551745125391655,278.7741152342598]],"n":892}}}
|
||||
{"case":"gen0940","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[90.2996978038041,154.2380862728316,9988.567371457335],[8.641920000000001e-61,8.641920000000001e-61,3.456768e-60]],"n":526}}}
|
||||
{"case":"gen0941","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[116.10273607087329,207.47363182985453,14954.099733615836],[1158.479867256966,1272.534629827929,166405.9580822918],[30.30760702251555,70.72961889266972,949.3745722694941]],"n":595}}}
|
||||
{"case":"gen0941","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[116.10273607087329,207.47363182985453,14954.099733615836],[1157.479867256966,1271.534629827929,166262.9580822918],[30.30760702251556,70.72961889266972,949.3745722694944]],"n":595}}}
|
||||
{"case":"gen0942","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.384013007441666,4e+20,-4e+20],[3.628016215948248,4e+20,-3.999999999999958e+20],[58.481607553529805,102.56002132279787,6258.9801208105155],[44.963941659310294,68.98002239140592,4991.541294337729]],"n":1006}}}
|
||||
{"case":"gen0943","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2212.506556195631,2336.3371783368943,46493.159775201086],[40.593541689909515,193.3578755414017,6132.270318547428],[-14.007314797294232,17.087296513105485,-141.63580840783777]],"n":540}}}
|
||||
{"case":"gen0944","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2276.7912481967933,2406.3605038957458,50652.94649053479],[237.1563055050407,324.03598682131224,25886.858679119534],[232.3337850614327,274.80547794802874,20776.817837624945],[274.6328029699603,394.33416563482626,34771.681979050205],[167.33402198011956,222.28802251362148,17014.773121474613]],"n":1227}}}
|
||||
{"case":"gen0945","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[7331.96678541959,7365.337129142497,186611.91198272633],[55.18940806955303,102.7366981650053,8308.672962799128],[79.10427410613143,116.98262247455756,12503.851443027594],[3.9505920000000005e-60,3.9505920000000005e-60,6.5184768e-59]],"n":807}}}
|
||||
{"case":"gen0946","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[173.5037580335539,226.2986891869448,20287.07285188763],[1222.052074602999,1273.829177549883,54040.60034157928],[38.06215880020495,50.624934027474275,846.9625360253542]],"n":566}}}
|
||||
{"case":"gen0946","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[173.5037580335539,226.2986891869448,20287.07285188763],[1221.052074602999,1272.829177549883,54003.60034157928],[38.06215880020495,50.624934027474275,846.9625360253542]],"n":566}}}
|
||||
{"case":"gen0947","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[32.385646267696195,8e+20,-8e+20],[44.736954347669894,108.50929015052331,4183.363939424485]],"n":446}}}
|
||||
{"case":"gen0948","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[208.6914251670898,294.2694825086566,19419.32089093567],[55.921181581935556,129.00164914070655,6703.186594945071]],"n":464}}}
|
||||
{"case":"gen0949","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[23378.49611861585,36310.24254654308,3129639.0904818294],[14440.32611246857,21896.798037263754,1583313.551097337],[4284.329070221114,5381.5235583018675,86838.5218984899]],"n":560}}}
|
||||
@@ -1219,13 +1219,13 @@
|
||||
{"case":"gen1207","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[238.38133651570698,303.2870845006625,22509.001526090928],[23.425365087483165,123.14293755783024,3186.688939967014],[36.75983920375303,150.3529380047939,5487.698029052931],[49.624246833704056,114.42560419202255,1994.1252614631298],[11.25273914893725,22.051721904734233,200.6441849961833]],"n":1046}}}
|
||||
{"case":"gen1208","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[51.964016862088336,114.47689690732672,5704.932765380546],[2.2372489997082146,3.7497942633835173,2.463059612864409]],"n":519}}}
|
||||
{"case":"gen1209","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[30.048688040615456,4e+20,-4e+20],[63.59527154920069,115.8388438313856,8713.20996864377],[55.15841678784259,71.69363615439337,4043.270948002466]],"n":704}}}
|
||||
{"case":"gen1210","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3010.879700995211,23598.183831646387,235368.71670005462],[2006.9856182669869,25175.504675876757,227656.42718378484],[4186.002833122052,24304.181807658326,372169.7927023151],[1862.7512985237668,23276.987361371863,280971.89435575105],[3527.97820740122,24519.65181287664,380540.40158555645]],"n":1277}}}
|
||||
{"case":"gen1210","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3009.879700995211,23597.183831646387,235364.71670005462],[2006.9856182669869,25175.504675876757,227656.42718378484],[4186.002833122052,24304.181807658326,372169.7927023151],[1862.7512985237668,23276.987361371863,280971.89435575105],[3527.97820740122,24519.65181287664,380540.40158555645]],"n":1277}}}
|
||||
{"case":"gen1211","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8619.064264398123,30111.758539506503,1213891.170951394],[513.3761551263186,5801.34943548601,2170.0923870482407]],"n":322}}}
|
||||
{"case":"gen1212","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1423.3440286583163,18011.711111463963,276421.22719599947],[2897.891645549603,25041.270380723,516965.41883073084],[114.31271792048358,15789.981264296703,-85815.81197188087]],"n":766}}}
|
||||
{"case":"gen1213","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.73731923470713,4e+20,-4e+20],[25.57410599087165,4e+20,-3.999999999999958e+20],[42.369035566997965,61.81120391522402,6453.673250773775],[28.898526161474265,40.88262688745764,2206.040252090517]],"n":910}}}
|
||||
{"case":"gen1214","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[181.63783875285873,209.85377264527352,11998.693636754027],[31.955239837210733,100.09242423093633,4674.719838196061],[27.928288758369185,101.42245822028256,4000.659261710136],[1091.3441169894993,1132.14029012063,196263.43193452168],[30.919412325763247,101.57722522910788,4110.277262773355],[27.685376246760477,101.03208871867534,2600.1176241401026],[39.53871879563745,63.336220353194506,2601.912562566984]],"n":1712}}}
|
||||
{"case":"gen1215","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2207.6594793328104,2244.975003933074,61556.352390379194],[11.867730151010939,62.70125242333262,1305.0988476419147],[1118.5334880328483,1140.6432957840557,139363.72281102405],[29.07196818109851,74.9072979328024,2689.0368641607397],[26.564731345287147,54.03813833964112,3147.377034719935]],"n":1235}}}
|
||||
{"case":"gen1216","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[185.1034298834975,867.6333450906714,13251.547236114111],[114.13086642622395,781.2695242480182,8522.887878295825]],"n":451}}}
|
||||
{"case":"gen1216","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[184.1034298834974,866.6333450906711,13247.54723611409],[114.13086642622386,781.269524248018,8522.88787829581]],"n":451}}}
|
||||
{"case":"gen1217","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[42.73337048388167,4e+20,-3.999999999999979e+20],[26.386623490865116,74.35582554975906,3250.022584615073],[61.97927740473568,134.0834438963224,7459.561416295942],[23.4346486333411,23.786107937077208,193.7114944362455]],"n":782}}}
|
||||
{"case":"gen1218","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1525.5255609310436,3145.511359808279,197811.06941840387],[103.24725386734532,181.8472316570281,14425.576875220482],[1108.520304972617,1163.0689644788154,235059.64186313865],[108.57924377199134,188.17791355839748,15099.446203599698],[65.60790784310743,109.15188028009395,6749.489367348091]],"n":1244}}}
|
||||
{"case":"gen1219","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1464.4484628588252,3079.4121857999576,133781.51650949146],[38.83529369444865,90.57326660327644,4452.393662306173]],"n":470}}}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{"case":"gen0005","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[21459.27305368452,6257077.467017859,13558767.045404688],[-965554.6532761813,7575388.347382277,-3672264.47423472],[-66833.85158723981,6464437.3550479,21569988.99483848],[-1226040.9358556927,6920300.4584089,-126040764.04929101],[68578.49170271149,4659418.668339789,-19064107.61530851]],"n":1172}}}
|
||||
{"case":"gen0006","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[15823.125949621153,20643.348407240253,1928141.2144939548],[10681.01640948366,14210.817295380655,1329960.8580274954],[15786.475017216882,20612.697474835983,2141123.84584417],[10681.101371392724,14186.330190861185,1364411.838453853],[15932.310845795006,21038.85255089838,1964775.2304460858],[0.0,0.0,0.0]],"n":1349}}}
|
||||
{"case":"gen0007","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4001.030383839175,4162.68790399988,504700.8621153822],[1007.266417337648,1175.2714772815973,81917.02849138294],[-29.75346620939316,138.78736403290412,-5007.440075233057],[-16.564168556778306,145.32673144073783,-609.0318819954091],[-5.545234621259068,109.10975556747029,-1714.311266585183],[-25.941814250486104,135.94908574703004,-342.34948812925177],[0.0,0.0,0.0]],"n":1590}}}
|
||||
{"case":"gen0008","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1604382.6738963162,1604407.5699434367,227319319.9840681],[2558902.368550021,2558927.2645971416,343296278.14978254],[1747259.307508971,1747344.203556091,198225233.00210908],[1174564.1164607834,1174595.0125079039,149914290.96107095],[2669479.653071003,2669504.549118123,369302719.7174447],[1553689.3389397578,1553714.234986878,161298405.71601918],[1197350.8498715328,1197376.19644846,206862358.21948877],[2422466.171882945,2422495.512920775,333162647.796647],[1428118.8117421877,1428136.513586418,118185964.36701787],[0.0,0.0,0.0]],"n":2323}}}
|
||||
{"case":"gen0008","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1604382.6738963162,1604407.5699434367,227319319.9840681],[2558902.368550021,2558927.2645971416,343296278.14978254],[1747259.307508971,1747344.203556091,198225233.00210908],[1174567.1164607834,1174598.0125079039,150738874.032133],[2669481.3151374743,2669506.2111845943,371972114.52159876],[1553689.6768732865,1553714.5729204065,162852095.39289254],[1197355.8498715328,1197381.19644846,209257255.14880794],[2422466.945275225,2422494.621283943,338007778.84310657],[1428118.0383499078,1428137.4052232502,121042201.27623229],[0.0,0.0,0.0]],"n":2325}}}
|
||||
{"case":"gen0009","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-8293.446919581043,60215.15226801933,-877769.5599370121],[4929.081567893583,71983.56557779052,749442.4551836315],[-7293.080835261042,62819.584042433096,-1795706.2368456442],[1152.2731995814715,68198.77001009103,-322827.5471307034],[7626.386136832957,44819.100812630495,454994.6177794878]],"n":1163}}}
|
||||
{"case":"gen0010","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4067.437258811244,4139.872502348844,514152.0842839106],[3103.1901512111394,3154.4281846203894,502679.40726210433],[47.699179531399224,120.13442306899688,4657.2874764131375],[64.13070633033847,119.51227089186901,8275.904019139674],[73.78451152594384,92.25705871364374,10995.914784714589],[40.73862592083679,111.29541384649306,7324.595908032971],[58.25561054520546,104.98728525682334,8456.08877005734],[21.0,21.0,785.0]],"n":1906}}}
|
||||
{"case":"gen0011","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4104.285696636644,4127.517673591581,517535.86442609824],[2098.996108635182,2156.2886655912057,256728.6024045167],[1082.770839574064,1141.86723647983,177503.520000455],[71.6089921724644,96.83616505499488,8754.571141290013],[72.71593239468069,96.17891766775895,9550.585792217043],[79.6844017852741,102.86798202306845,10570.948588013865],[69.51741237714822,96.7227879585886,10415.997605609007],[0.0,0.0,0.0]],"n":1840}}}
|
||||
@@ -39,7 +39,7 @@
|
||||
{"case":"gen0020","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-420860.00050855195,1843824.7199796166,-67523965.44505915],[-420704.2158691106,1844113.308532626,-65405794.0644136],[-62108.403723145806,2427634.6774180676,-60062556.52870487],[-93148.1161002781,2171646.8991635665,15910553.638407003],[-249365.55721615176,1485482.6170956714,-34615974.225505844],[-382485.53916420636,2133911.726633903,-53807421.61340594],[34965.15207480046,1518221.58499025,6602913.739762945]],"n":1754}}}
|
||||
{"case":"gen0021","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[834020.3174988818,6371886.82910553,113253304.78367993],[-1053278.885447429,6615366.795881273,-148532243.60604626],[-570853.6491920039,6709808.155325015,-99853495.07901773],[787296.647001362,6759959.497516437,66824531.15572775],[-1019811.1156467496,6238397.112037226,-132082336.61201689],[-226940.4130653015,7266890.40585044,-5808684.888347078],[318357.6980730442,1678097.9819065034,2722656.274998542]],"n":1589}}}
|
||||
{"case":"gen0022","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.87165274025595,134.96320848576596,5298.719586718976],[73.12982006026684,168.26300133125886,11252.812968122269],[-9.088475141541647,113.26722422054122,-1698.7967255648173],[13.933239817934723,120.02479556344474,653.3613174941565],[21.04865349401368,96.79309381055113,2472.285033316496],[26.625990083603057,124.19570982907265,3170.289478111987],[10.493312711937692,70.7595890285155,2393.2693744043668]],"n":1761}}}
|
||||
{"case":"gen0023","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-111.27515288063978,12832.288356985697,-20702.687239898834],[374.91922861172065,17463.23275772318,69628.22157006929],[-1484.428270384197,12549.295933208577,-284903.94477491744],[1671.3559503140204,19635.102131175037,246251.4114657836],[314.3921751598846,4245.930121675456,330075.9498616786],[-1111.60204906238,13609.65829181508,77718.78737106144],[-610.4341686368571,14698.182529463475,-66265.07169161722],[21.0,21.0,848.0]],"n":1909}}}
|
||||
{"case":"gen0023","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-111.27515288063978,12832.288356985697,-20702.687239898834],[374.91922861172065,17463.23275772318,69628.22157006929],[-1479.428270384197,12554.295933208577,-285253.99381468305],[1472.63640104899,19436.382581910006,196851.84325498482],[-761.1800043942604,3172.357942121311,82989.78156188094],[-233.31032024320535,14487.950020634255,77252.16673057493],[-1011.4341686368571,14299.182529463475,-151294.94002889103],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0024","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4060.0282183176987,4111.2575240991755,507339.8006981912],[59.73555503566303,119.37228715666018,6804.622978075441],[47.261865925754236,100.89860323051641,7812.262978899294],[44.03199770673579,66.30595665676358,5859.937222093143],[44.2618662216955,97.89860093457516,7250.12058207122],[21.0,21.0,1352.0]],"n":1421}}}
|
||||
{"case":"gen0025","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94647.82942449366,6225475.923920281,7315521.285212205],[869343.594180337,5482764.036483983,176541612.94587085],[1642263.0665172902,4738307.054444931,215899456.65327415],[122204.7544151225,5746689.145015486,-37013917.61158477],[3109219.5234007705,4188799.0572766736,377976445.97671735],[869468.5533739126,4016530.874210765,25327375.76695013]],"n":1454}}}
|
||||
{"case":"gen0026","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4041.623606081624,4080.4900657274693,505368.50063701434],[1050.6236036630937,1123.4900633108753,85899.30006641368],[16.972672275237993,91.83912940426951,2927.740684511156],[13.623603494796,52.49006565842583,1950.2420339990144],[38.43147108538156,70.34244418261329,7922.307268065844],[8.180481363301562,49.93319305066176,1410.4810616591187],[24.066728381199326,60.04693598650049,5021.371999947856],[0.0,0.0,0.0]],"n":1837}}}
|
||||
@@ -58,7 +58,7 @@
|
||||
{"case":"gen0039","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.15244710748844,184.18229000536604,9887.148985504382],[81.55351730876232,199.9750992330375,12014.143951303075],[88.2183421244927,197.00274538756577,11783.161933381134],[63.57577272765989,175.22062237187393,8792.771212853337],[51.99127228766615,167.55240783080035,8662.375952986244],[80.82865804026055,126.94070358090953,11217.296386096095],[50.16646027892852,163.03757576429643,7211.114768442215],[52.7721103628608,154.40200626727656,7177.659029135055],[21.0,21.0,554.0]],"n":2151}}}
|
||||
{"case":"gen0040","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.823238125209194,97.43528703928307,7297.879404713108],[22.82423912520919,71.43628803928307,1994.726125825787],[48.94208047983874,67.14609659153535,5498.371866969706],[43.82323812520919,86.43528703928308,6534.702642419242],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
{"case":"gen0041","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.039148955902235,90.69753086595534,6802.70650711837],[42.039115197835365,104.69723840578243,3459.661783717759],[28.040174956003295,64.69855686605632,1824.140111601128],[35.36039012123543,63.798793990392056,4255.877449839501],[35.040448955902285,65.69883086595534,3452.9546701742984],[25.358063121134442,47.796466990291066,3391.959264240108]],"n":1508}}}
|
||||
{"case":"gen0042","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[49.57890044028224,85.89896442416715,6047.524786649497],[53.34885592917254,89.6642361742989,9355.7983467188],[61.66559711524397,103.97915593821259,11576.087089666193],[1073.1453817868614,1109.4636243167843,78105.41714576709],[24.629918642534818,62.344549360664764,3185.429297228315],[19.889814327458826,61.10958740952553,2855.9687764509076],[91.13262193016793,103.23792215034928,15082.70668149778],[18.784928369765154,59.08585800704039,2607.63476641213],[21.774251301015163,59.225929641108024,2649.8673507860435],[31.662094732813834,64.55337201893555,4616.6773058274985],[21.0,21.0,176.0]],"n":2645}}}
|
||||
{"case":"gen0042","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[49.57890044028224,85.89896442416715,6047.524786649497],[53.34885592917254,89.6642361742989,9355.7983467188],[62.6654113934019,104.97897021637053,11821.040102040151],[1080.145106324208,1116.4638692695485,78470.96845799683],[26.74383489387806,64.2303134029549,3241.3375658041323],[20.775854765764606,60.22430047230202,3013.7920603381895],[99.13211119510225,113.2374114152836,17218.639493779934],[18.35461271018519,58.65554234746043,2534.5317238141592],[22.204105776099517,59.65630453161001,2693.857276483471],[31.662001871892805,64.5532791580145,4519.918199046961],[21.0,21.0,218.0]],"n":2647}}}
|
||||
{"case":"gen0043","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[10529.891384274419,29145.794709251175,1490864.2493497496],[8055.073940913807,70560.97404740994,933437.6892698944],[9119.453328105907,52884.64822342944,699047.3883941383],[14045.836541355728,39511.98633958951,1532154.3219242222],[10183.081108950533,85594.48845768519,2041338.5585894717],[-774.9334836260379,15031.330087891833,-33341.58567736445]],"n":1331}}}
|
||||
{"case":"gen0044","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.23375248549496,100.14958477941805,8281.19990705643],[84.23375239519726,117.14958542946785,14636.520296671857],[37.54916826881418,64.15972276125582,4602.665010592654],[49.23375239519727,82.14958542946783,7741.797354800354],[55.80458590698251,72.10986296811647,7887.631308549],[27.26873550053811,64.33675261793591,3890.0600273431032],[44.709599256490826,50.86269260467124,4066.7391403631245]],"n":1748}}}
|
||||
{"case":"gen0045","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[74.7943840163076,103.5099145394285,8450.175467888972],[63.794385458212666,94.50991598133356,8916.409448499635],[74.85538315016507,107.570913673286,8500.276170096407],[67.79438356430431,102.50991408742522,5405.941652715496],[41.46111670886491,72.1766472319858,5727.344773403031],[81.65906550622424,99.23090901393121,13725.166205291891],[38.87188372782063,68.88351369398441,5582.70247331247],[36.25606341959342,69.3216838988291,4973.398210741115],[41.725612632283415,49.65126674083278,3249.7415288929355]],"n":2242}}}
|
||||
@@ -88,7 +88,7 @@
|
||||
{"case":"gen0069","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4032.941308735473,4122.433237888164,504160.63060763635],[12.755635673953876,113.33008468747936,1053.5332508118581],[19.180925328050513,107.75537434157604,4032.2123210972095],[32.484834114979606,74.62130387893333,3660.6166388070483],[11.763665704401772,100.3381147179273,3824.054451749591],[21.0,21.0,1226.0]],"n":1415}}}
|
||||
{"case":"gen0070","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4133.216853926887,4163.518564509345,524069.5323174038],[3156.791590478014,3203.940210897499,514882.2692080066],[79.43005654342758,208.48751719469982,7931.811291857208],[107.31575151146582,145.6174620939256,12187.494797557245],[109.9010117878354,151.3977363459258,13981.239464382148],[93.70936477320441,130.23632465528277,12230.765967630017],[106.52623364194258,139.81646038574405,14674.446904325281],[96.42549786886514,141.94805017623077,13025.962781356175],[30.550720137181685,39.53083533978287,1184.8978547476]],"n":2173}}}
|
||||
{"case":"gen0071","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1797975.4959941453,2132368.0807861853,230858340.93178418],[1746421.7520576636,2546226.2180710533,226059518.56411853],[1511178.8827772073,2226154.040412939,184425291.70205128],[1640751.9694591665,1975156.5542512066,208995738.92610043],[2046747.8291260842,2908743.032638169,262485874.5109756],[986227.572841815,1110652.6161665807,166804026.56369948],[1883522.9083962745,2570136.093404142,264313704.01922175],[1330023.381045272,1964175.0013079601,146634615.2815736],[0.0,0.0,0.0]],"n":2085}}}
|
||||
{"case":"gen0072","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[628633.4448590993,2583734.982734498,68317532.9751259],[628673.8345926722,2583894.3935787566,66014042.57278828],[628730.4448590993,2583837.982734498,73341983.97575931],[419135.2367364527,1722613.6206454437,25432287.954494007],[669831.4611050189,1973198.8239033243,123643052.63790889],[378016.2805005328,2333234.8394866176,55853020.65627187],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0072","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[628633.4448590993,2583734.982734498,68317532.9751259],[628671.9956366868,2583888.588377576,66013650.14200941],[628714.605903114,2583818.1775333174,73969786.9561036],[419135.2367364527,1722613.6206454437,25852951.191230457],[527446.7521768502,1830814.1149751558,88281779.36577594],[520385.1504727162,2475599.743213605,56863079.125205204],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0073","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.79767656506127,182.9745656625255,10395.434511113905],[1075.2092160706695,1154.8225466132626,261184.2792732134],[26.14674241546114,154.32363151292535,2696.1422807039958],[40.77704147534779,130.94899125508516,3608.5151337143034],[48.469537233643706,166.8084974753915,7829.648806945503],[0.0,0.0,0.0]],"n":1347}}}
|
||||
{"case":"gen0074","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-2183.544437450179,61206.56532581696,-379790.4198465226],[-3026.0687918458043,59196.280572483374,-263678.3755441502],[-4913.470315615611,66155.56639704767,-809692.2927032098],[-968.5857558452816,18313.669725309308,-17885.60440897335]],"n":852}}}
|
||||
{"case":"gen0075","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.74033907371135,120.42995656538027,10839.03221206558],[94.18922412772386,119.8686047774714,14715.725981477543],[104.88370048458827,127.74565732116992,15559.760829608149],[74.28243643166452,110.93510384513418,8579.698783957088],[68.96267725577846,94.64547018636755,8977.70755134494],[74.92181780793136,103.00559225624275,11708.966340832787],[63.45825240245576,91.64893214138986,8504.217124460672],[60.57774846292442,85.91185822889886,8314.200709249726],[26.141336815129243,31.51066377711397,1078.1346377692553]],"n":2170}}}
|
||||
@@ -98,7 +98,7 @@
|
||||
{"case":"gen0079","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[28855.630684619813,28891.953525833953,3622107.157686657],[27460.75761853326,27527.080459747394,3284774.45917512],[1062.414213562373,1122.414213562373,262117.62445840513],[28134.724033342543,28177.046874556683,3831724.776326618],[18793.486798569797,18817.702026045878,1498558.3621288782],[19574.734868735224,19604.950096211305,3289512.715775175],[27334.8901767395,27371.213017953636,3033495.246747597],[21.0,21.0,1205.0]],"n":1926}}}
|
||||
{"case":"gen0080","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.28074815089904,93.7108496980794,6322.261893226474],[59.28074612842546,95.71085692652495,9679.296470069328],[26.381746345280654,74.81185020519077,2746.2785890430623],[42.28074633404384,78.71085481941358,6561.857807107421],[47.19458411977553,71.3379501458041,6317.624263421118],[22.04201048200709,61.677111631553885,3288.8462713971703],[40.3323169690031,49.27069148721947,4010.4132284947746]],"n":1750}}}
|
||||
{"case":"gen0081","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4121.679568529357,4133.251729381609,522430.9775290661],[3119.106048515987,3130.678209368237,501487.523695402],[124.4338433919378,150.00600622540472,15914.721228451603],[87.82129145249765,99.39345032352723,11363.299668096126],[97.69492601483108,109.26709086707939,13571.831006621931],[83.28565031185585,101.14303992927194,12957.813915948615],[86.12470286952878,99.0162836105989,11467.34869152088],[88.48890196073737,98.7416429241638,11285.615835862933],[21.0,21.0,764.0]],"n":2161}}}
|
||||
{"case":"gen0082","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8217.735386198814,14522.735962742541,1090905.9377064663],[12373.591278311083,20115.720583893726,1672033.5804136612],[15312.444863930763,23589.4033384049,2150832.8371398593],[5371.127925933442,12262.574663118054,613847.7534868228],[13805.849144035466,21870.42713545859,1914087.3014476954],[10331.062050152046,17691.316962544883,1175784.345767359],[3984.138352013139,6789.997584142534,778104.189909683],[9672.257073856523,17285.854334608768,1447275.5570636701],[13593.767978506958,21811.961321794566,1677697.3244593688],[851.5659763306372,1771.209769487393,10438.079563575971]],"n":2482}}}
|
||||
{"case":"gen0082","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8217.735386198814,14522.735962742541,1090905.9377064663],[12373.591278311083,20115.720583893726,1672033.5804136612],[15311.444863930763,23588.4033384049,2150588.8371398593],[5378.127925933442,12269.574663118054,618226.8083921843],[13806.849144035466,21871.42713545859,1927894.1505917308],[10331.062050152046,17691.316962544883,1186115.4078175102],[3992.138352013139,6797.997584142534,787204.7033325509],[9672.415938832712,17284.822281793073,1466660.4629163437],[13593.609113530769,21812.99337461026,1704885.1381453257],[851.5659763306372,1771.209769487393,12141.211516237247]],"n":2484}}}
|
||||
{"case":"gen0083","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[41.94931369106733,95.23198315161466,2272.016771855452],[31.519515753354316,66.41569673604951,4085.1655623486286],[24.99031139682466,77.71686202645756,4376.133656578295],[27.385656290378876,64.79349144630814,5474.996152738668],[2.9489462181485586,22.91891640956763,236.55401070944055]],"n":1165}}}
|
||||
{"case":"gen0084","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1165362.4608411498,1728103.7135782805,158403743.81277278],[1579611.342745023,1752995.4744403916,206661477.06841266],[1422337.6855768142,1567918.2430733999,176559835.3948445],["NaN","NaN","NaN"],[1474135.691662266,1870009.6068100692,212507458.5145344],["NaN","NaN","NaN"],[603256.6471530325,684136.7787233271,43218788.43947772],[903711.3595209995,1378491.9864787683,145093842.8705262],[1548671.5185892954,1793852.6176856756,206649488.71591172],["NaN","NaN","NaN"],[138687.58105337038,138688.9467990709,496221.4791049028]],"n":2732}}}
|
||||
{"case":"gen0085","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[35.68003331888968,402.19014536147245,1143.9693146361813],[1074.0456613620024,1276.9133702049373,11027.704290497135],[24.36451891300714,307.61605225719086,1931.662321596439]],"n":761}}}
|
||||
@@ -107,7 +107,7 @@
|
||||
{"case":"gen0088","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[25022.281930845915,33675.69848227808,3339920.8725660923],[35434.129116316726,36976.80321852325,4583782.791618872],[31202.81345424002,34106.011358050244,3572512.833436777],[23624.01697727977,31436.897739601256,2797086.8923467444],[36538.52919068972,40102.179836983465,5127447.875171034],[28897.470875170555,30099.83867234703,3268250.5286836755],[18954.6477212562,26260.256571610473,3147249.509085818],[34974.74307814512,37865.65626625902,4899709.5813158685],[26189.91364524175,27388.64873538829,2497230.6512632547],[0.0,0.0,0.0]],"n":2323}}}
|
||||
{"case":"gen0089","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[20125.0631370164,21520.973152824383,2535726.6989411837],[24876.77952524619,24892.452449714878,2909952.8070849995],[24674.43192167412,24700.55976779761,2914284.950859487],[23603.369842966706,23625.042767435392,2797196.5552203343],[23903.538418153665,24576.304084051524,3205312.315466632],[26822.6250470261,26849.025886970438,3510009.9096796783],[11555.982211719742,11566.157834211348,777609.2811225147],[18356.0534975684,18900.525375142388,2712505.5366573585],[25873.632765045117,26022.812810904674,3364841.9830024177],[25738.115117285575,25764.242963409062,3236326.4987119916],[392.95322829193697,393.73989522493787,3643.3746809182676]],"n":2730}}}
|
||||
{"case":"gen0090","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.1910148246429,289.3694193544048,6923.585526459182],[64.92014548238448,279.3038010812879,7220.074969632122],[-3.360020143456727,6.023532528081323,-20.51746974914992]],"n":523}}}
|
||||
{"case":"gen0091","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.26759086719653,95.2869332884665,8459.224542165373],[69.33159153144449,97.34322963374593,11613.690556057281],[79.73817848885868,113.75067262660112,10637.22925020941],[43.6344976168439,71.6512719317909,5592.361281676781],[50.026082104676604,78.0351521006553,8118.627634906084],[71.44442872647014,89.36249168220029,9746.211045048363],[37.842095408933574,65.85801368843966,5111.353945072446],[47.83206495626179,73.26456487448215,6853.923519699062],[21.0,21.0,638.0]],"n":2155}}}
|
||||
{"case":"gen0091","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.26759086719655,95.2869332884665,8459.224542165373],[69.3315915314445,97.34322963374593,11613.690556057281],[77.73782312769059,111.75074528315345,10392.188581067461],[43.63449761684391,71.6512719317909,5592.361281676782],[50.02572674350847,78.03522475720763,8118.589808653478],[70.44442872647015,88.36249168220027,9604.211045048363],[37.842095408933574,65.85801368843966,5111.353945072446],[47.831709595093656,73.26463753103447,6853.894932836828],[21.0,21.0,638.0]],"n":2155}}}
|
||||
{"case":"gen0092","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4100.015668577045,4145.505575702222,517856.0190715755],[1091.144126580105,1209.7524565869899,90803.4643740104],[67.3609084665371,114.4608211180705,7576.286505259987],[89.31416302881678,143.92246378370413,12157.531156749743],[54.91891777861004,87.11672686172489,8802.326894963375],[79.92099161087242,134.5292923657598,11030.487753552827],[0.0,0.0,0.0]],"n":1594}}}
|
||||
{"case":"gen0093","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[107.25316469037176,162.81739789285277,14299.463660232224],[1108.1855850422357,1174.0890499609056,175624.44509041062],[100.39447887572253,153.2597077555148,15347.694713708928],[46.39698708220946,80.50834130132824,6841.753230258374],[94.25831773768442,146.11268358637335,14056.91723667381],[21.0,21.0,1310.0]],"n":1419}}}
|
||||
{"case":"gen0094","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[688398.9197420802,1096025.005254783,79924744.55326815],[487643.9271219153,959555.3225602629,51608719.938460395],[197027.44165139514,337397.5928308475,36591509.225549266],[607489.3997755258,1015127.4852882286,67670123.68954521],[588880.8313389558,1028393.9910227632,56298457.13256586],[491576.33802825946,759534.7261524195,78453461.02111682],[698541.2284317052,1184173.1152305098,95163317.07955624],[6252.664654516148,99813.6349280632,430552.47836652864]],"n":1928}}}
|
||||
@@ -125,7 +125,7 @@
|
||||
{"case":"gen0106","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[20615.774887247335,21224.887446846362,2902623.7056659157],[34408.15077812208,34423.43223308789,4314440.415981375],[21497.50542284549,22118.61798244451,2296253.8498155833],[35284.621318081896,35299.90277304772,5062858.906972785],[7177.295274865727,7194.389093187671,358289.8041696152],[24190.91847940735,24800.5640610166,3580259.741271128],[28613.220604034417,28627.969036990013,3264017.9625877617],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0107","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[152.03408149899417,214.10273488386156,20420.325785651843],[114.38822990381637,182.45688328868368,13257.795523720257],[83.7363520422613,105.2303352748561,7118.100395478875],[134.03408149899417,202.10273488386156,17996.94465759773],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
{"case":"gen0108","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[79.08493698224868,110.67778467769597,5974.336410722579],[56.914592798987016,93.1917499301705,6882.859834919433],[50.78899319060971,80.02403220135454,5581.734027711273],[18.87048727725337,27.393255054409767,534.5607487747504]],"n":850}}}
|
||||
{"case":"gen0109","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.17956427539623,120.09638585808283,8106.419772209134],[67.22986928269941,121.15339545379496,11285.77000193251],[62.92506711314974,148.85030667906088,8473.379894345822],[59.62289368852768,119.87744832422753,6311.083535349026],[43.48280042797054,100.88873439391422,5251.828754235046],[34.90115547780822,90.96984672007689,4827.64683599675],[76.37166757568693,118.54112234806423,10010.509183911157],[36.03623510020645,90.66856129406582,5064.24206872859],[33.66666510602487,93.10766963969613,4352.39940579932],[31.83857520733545,56.37953275149884,2541.4079591181617]],"n":2486}}}
|
||||
{"case":"gen0109","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.17956427539623,120.09638585808283,8106.419772209134],[67.22986928269941,121.15339545379496,11285.77000193251],[61.92506711314974,147.85030667906088,8312.37989434582],[58.62289368852768,118.87744832422753,6228.083535349024],[43.48280042797054,100.88873439391422,5251.828754235046],[33.89963452123016,91.96832576349883,4609.315267462733],[75.37166757568693,117.54112234806423,9873.509183911157],[36.03623510020645,90.66856129406582,5064.24206872859],[33.66666510602487,93.10766963969613,4352.39940579932],[30.83705425075739,57.37801179492078,2525.3836238129124]],"n":2486}}}
|
||||
{"case":"gen0110","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19.056498946627148,134.44074618618393,2930.2200814778894],[33.965694371942405,109.64492463516343,5523.370142792808],[27.28588209643218,149.35542307531054,3051.693556552635],[27.81406063577988,115.50361840519787,4376.826618947725],[29.777687979646466,106.51022934586796,5348.6534504880965],[34.98871507845154,131.81695038603652,4524.7761984801955],[1076.9943303659134,1170.2975352840833,59882.81670571094],[23.202382172306013,102.37044052179935,4009.588638365861],[21.54890761279802,108.99657923853289,2019.3407489892386],[2065.415288214785,2172.6990533654966,162827.45848988913],[22.927063496265248,98.54369813401415,2221.4096992312448],[19.480870285199018,100.36000470965604,1890.0821610594326],[34.629943886526696,130.72493666272803,4263.450665633775],[12.450147743762844,97.83785990572501,1333.3955203299938],[19.31652364211915,109.61563587855287,846.5214150535867]],"n":3827}}}
|
||||
{"case":"gen0111","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[59.8746788490061,92.78725182613373,3695.587430532418],[49.24673464589206,82.01823361139634,6930.452078863244],[34.023253753745145,73.15976617070761,3460.2885321589492],[47.340357924795505,75.4035210831801,6100.890662232747]],"n":1010}}}
|
||||
{"case":"gen0112","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.60558798598461,99.01967841555745,9054.447478808976],[1090.9556536844884,1112.369744120006,263982.8231746581],[50.6055874742365,84.0196779038093,5251.897608183787],[66.40372558590785,80.67978587600537,10093.989847428547],[47.75379365007716,74.02985393571588,7005.705892833059],[51.60558526046705,73.01967569449863,8413.941252009117],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
@@ -136,7 +136,7 @@
|
||||
{"case":"gen0117","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[36.9216009909622,112.64096671040593,3620.91094081327],[35.910275459283916,114.36067322027716,8010.422505415585],[13.784284720082976,102.41399445337662,938.7703730902731],[8.660735485111811,86.20078923225516,1266.0171340641587],[58.2755355184137,96.72911611002807,10788.862668318821],[0.03714433934335526,81.77892403572201,671.1176555299955],[13.201124032307234,85.62910781636548,3736.216733002865],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0118","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[28139.8806437825,28172.21528507277,3359254.3361417004],[28094.943924940617,28139.278566230885,3588227.651654678],[9675.019659725103,9685.797873488531,388857.2146343355],[28112.8806437825,28157.21528507277,3494160.3547047214],[21.0,21.0,1604.0]],"n":1177}}}
|
||||
{"case":"gen0119","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.333757603639576,90.08275311391377,4563.583480946541],[55.88882460315714,131.05582139828374,6830.3271348946855],[28.022929916542513,79.52236318106038,1611.0794336294057],[46.75606698867661,79.58912763199623,8873.149794283308],[18.889823018911358,56.055570078691474,2455.703311692349],[41.88917388630777,73.05592073436509,8274.323590658938],[0.0,0.0,0.0]],"n":1595}}}
|
||||
{"case":"gen0120","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19440.926324089192,27073.926981889308,2523777.3435773402],[24532.354995450714,24752.717757915467,2717616.620563259],[20131.84020690312,26398.98686898355,2134102.3528963756],[28217.47910233996,29813.695860524378,3820861.012626412],[6399.482411207433,6413.229492113797,513771.7339842719],[22115.942223702947,29820.518465894656,3202069.86314161],[21065.85238565685,21212.639563730012,2342044.622045178],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0120","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19440.926324089192,27073.926981889308,2523777.3435773402],[24531.354995450714,24751.717757915467,2717365.620563259],[20336.84020690312,26603.986868983557,2146472.1614125487],[28376.79354025692,29973.01029844134,3872822.302273411],[5643.167973290472,5658.9150541968365,473681.38866774243],[22117.147582055535,29821.247064231306,3246355.763065638],[20464.64702730426,20613.910965393366,2228164.8830781323],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0121","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[45.85804829741673,122.71833149263858,5691.694005143827],[31.65554642802623,120.09912010365647,3951.5484226407584],[9.85804829741673,98.71833149263857,717.5568733498795],[28.701725032258167,103.52232960980696,1934.9137279630604],[31.653481503834882,118.0960969462404,6091.085127073391],[0.0,0.0,0.0]],"n":1345}}}
|
||||
{"case":"gen0122","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[72.99058491802141,143.23497044098656,5615.584082125279],[73.69725674810807,142.62744038888908,6812.457483498679],[18.24973542217801,39.17310224516882,457.40313240598175]],"n":608}}}
|
||||
{"case":"gen0123","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1105288.9636335587,2143224.4004346,131588974.86740077],[1063699.1963939029,2165758.3550938605,120797789.71807927],[1108545.42597199,2210598.584674796,131199334.54693337],[334838.6041891952,659450.5091560957,66720801.05556024],[1108542.4259710305,2210595.5846724114,117896794.43527637],[21.0,21.0,1352.0]],"n":1421}}}
|
||||
@@ -145,7 +145,7 @@
|
||||
{"case":"gen0126","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-3258.65091463762,68189.85306260228,-649753.9892763351],[-11164.012244321504,74275.40376665478,-1816786.282182102],[-5298.4287181065865,69778.57846991169,-750893.8682100736],[-3073.2313864340404,71385.80987513739,-248202.41141117713],[-14436.239899745959,78476.06975189662,-1825458.4549643844],[-224.17414968503084,16893.739726718635,-27024.002180827843]],"n":1325}}}
|
||||
{"case":"gen0127","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4091.3800232218146,4123.801424377272,519080.54308657296],[3097.719943750792,3118.5585971199853,499187.8492736018],[1099.63069125881,1150.0560130813326,90576.27490641987],[41.62808767020694,112.63023352334045,6971.032135355286],[84.92051970878695,129.34192086424505,7050.0344951328125],[70.62383419976234,91.8966879205904,9081.277100650426],[75.73215656827895,94.11996028908891,10956.164130484012],[80.30319352549556,118.54159807417653,9656.372196145665],[61.113763874245834,84.9840859714507,8140.131229488171],[68.7957747615228,90.23194521202919,8770.965904287566],[24.71590752271645,25.25306902342042,670.9343469597441]],"n":2666}}}
|
||||
{"case":"gen0128","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[242449.01156336817,7568154.9877282055,48678559.37093048],[-450228.915599977,6545288.721161246,-42853429.69001735],[184405.5530538224,6755229.746302572,49381811.01770187],[-304780.77408625616,5167645.687398806,-35013569.50355116],[-863520.22076359,4688643.805290802,-116558258.12693693],[-201078.64053051965,7303075.707724521,17588380.033115104],[37624.21992445721,1599362.8691660487,-10987372.221760392]],"n":1599}}}
|
||||
{"case":"gen0129","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.63976284660818,124.04902555033924,9241.865537430436],[72.87409687597408,123.0284602115044,11996.768428577752],[76.33216704648676,129.65873360799998,13458.199727563551],[74.82039789226722,126.69985386662022,12138.820969060222],[63.41983749120248,162.4028139827906,10472.599731648847],[51.37867047834507,99.7946025130683,6267.54795721676],[39.939815717380355,98.86182119841138,5465.39719155495],[48.454410929963615,97.29352290891248,7333.280171621876],[79.53430110238209,128.72948935451444,8429.276403567786],[42.005211350274635,94.44534757833377,5508.770169067439],[35.30048950797656,92.3527242817222,4297.3251974763025],[35.2450930471247,60.8710041891344,2305.2296798907646]],"n":2991}}}
|
||||
{"case":"gen0129","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.63976284660818,124.04902555033924,9241.865537430436],[72.87409687597408,123.0284602115044,11996.768428577752],[75.33216704648676,128.65873360799998,13213.199727563551],[74.82039789226722,126.69985386662022,12138.820969060222],[71.96546476667227,169.85718670732084,12092.786587689447],[50.8330432028753,100.34022978853807,6318.381000419634],[40.48544299285013,98.3161939229416,5645.563217068064],[47.90878365449383,97.83915018438226,7381.18895527637],[87.5343011023821,136.72948935451439,9063.99345413535],[42.005211350274635,94.44534757833377,5592.780591767988],[34.80255057894209,91.85478535268773,4239.45793280136],[35.74303197615916,61.36894311816887,2376.715743843083]],"n":2993}}}
|
||||
{"case":"gen0130","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[76.35954309617944,292.59891145817005,8567.327683090436],[79.47353707251747,292.4951088813454,6375.232538355077],[40.03831369535538,279.320470750304,7105.737591777539],[77.951169384225,247.46083438022538,14196.276663175653]],"n":1002}}}
|
||||
{"case":"gen0131","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[55.246443016105474,104.58870364644538,6605.695978346919],[48.17655915930073,102.66412918327808,7793.961224060633],[55.430687842212876,114.20458521459956,8382.695360980228],[46.24644301610546,97.58870364644535,3485.2961984407684],[22.53664529478982,79.28946090813231,3340.8993515449756],[52.22307061712412,79.8548111674182,8255.50955460603],[43.61979555361651,92.49355421479903,4076.318998338714],[22.27702249720272,77.63906236447085,2779.1799991341727],[38.54949937442443,78.04051763995035,5477.810941134654],[21.0,21.0,344.0]],"n":2397}}}
|
||||
{"case":"gen0132","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[117.22258826765969,132.45546274179753,15095.808148908813],[127.89465398367743,143.12752845781534,18703.677093974613],[1144.3084649742943,1165.5413394484322,175649.60228107136],[100.16853210385585,115.40140657799374,13300.625959420073],[104.83650818085982,120.06938265499772,14380.376641931565],[87.630256327123,100.23522033834924,13317.528280280052],[92.50330202287712,107.87933982685607,12441.946644057798],[97.96207556077435,111.52444751855764,11679.553944766343],[21.0,21.0,596.0]],"n":2153}}}
|
||||
@@ -153,7 +153,7 @@
|
||||
{"case":"gen0134","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.84475321646326,220.04943832941044,10317.807900338774],[1071.90022653047,1211.6563564760247,105455.6040623365],[1076.6770520702946,1162.0906621132308,20644.074636598456],[22.366619183646545,106.94681742339107,1870.4287026085265],[110.32073690312305,208.28168361455312,13046.464451055055],[42.852531048763424,129.73563378713055,3708.1521104961657],[50.110396625372836,133.5253657938103,8477.367310997284],[81.18344785577723,146.27310613138727,11261.260652089542]],"n":2019}}}
|
||||
{"case":"gen0135","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-3475.3892843424965,26133.355779827223,-613989.1778458097],[-3487.0882421418514,26153.212803231607,-602123.8225582958],[827.8687366922506,7445.212505864656,89421.70787544896],[-4441.393451682036,28459.457892129147,-655609.5595860027],[-1127.8902325719455,8749.54338530457,-98504.74385505698],[-1900.2504202723612,27712.510883090803,-203932.75777258887],[-3906.982658990171,15829.884768506774,-260566.99027019233],[0.0,0.0,0.0]],"n":1843}}}
|
||||
{"case":"gen0136","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.35614744565524,125.43427579603195,8343.563670293855],[60.09459564897421,120.17272399935091,9452.295224644085],[61.23142042139193,158.6168393216431,7786.103009370307],[73.16864070643692,112.06793893374021,9661.396234564098],[40.872011226146746,97.43626024947136,5181.472001319749],[31.624838215751453,98.40385391822703,4638.30341601392],[74.16406655579559,110.84859810394792,10301.910781684986],[31.866226169850513,93.38425538882018,4553.748917267992],[30.003306117922374,94.88152691704569,3784.9938311726555],[25.028298419100008,29.48101455188616,906.0999499999871]],"n":2418}}}
|
||||
{"case":"gen0137","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2065.1966127230685,2421.4136354159077,95113.23175264584],[104.30085358360616,363.9556037809253,10078.01553199858],[1121.0823055102512,1378.6454081077657,95032.10813608633],[101.2808378734711,338.2248570136508,13604.75005275545],[118.94418327438177,353.89619861292715,15652.856743591925],[36.28803368939735,297.7912699418136,3341.5201610737213]],"n":1505}}}
|
||||
{"case":"gen0137","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2065.1966127230685,2421.4136354159077,95113.23175264584],[104.30085358360616,363.9556037809253,10078.01553199858],[1114.7792122285398,1415.000462170124,93470.69652942657],[96.63466280552214,374.9229928622466,13768.28049426762],[120.67281180350277,352.16757008380614,16384.22952157374],[24.599393664801323,333.53187074705636,1152.3760002764254]],"n":1506}}}
|
||||
{"case":"gen0138","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.22262929167553,128.33694575751355,9981.000939675308],[65.00621265335987,124.12052911919797,7443.87485331105],[63.79677257787623,116.91108904371437,9305.667584534298],[53.775530182382866,76.81363567099557,7872.588461740247],[54.87568856166371,107.99000502750184,8521.296153848616],[21.0,21.0,1268.0]],"n":1417}}}
|
||||
{"case":"gen0139","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1104.4136885883916,1253.8002655329644,107881.05956964704],[18.756731190138396,125.69346362710155,808.2898638039354]],"n":438}}}
|
||||
{"case":"gen0140","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-1866.1227105280682,57431.2339866315,-168506.3770906245],[10559.22850409983,75854.144363084,1179057.0509323229],[1154.0986658690174,65560.6644033373,315667.19365482026],[6167.55800970671,68804.2650650323,-14241.785410242155],[-614.1781152166275,40410.89735554943,-36929.31863224964],[1161.0986658690174,65567.6644033373,317538.3909865585],[5396.536958828434,61375.65590534012,588517.4105835222],[-3529.188739064983,53401.87978539503,22512.50906629274],[995.3987154499798,54250.61356079703,-262729.7412510461],[-630.1342941100088,55206.18767268846,36013.61030304241],[1585.0190137740717,52265.4210075975,-109832.19256742815]],"n":2794}}}
|
||||
@@ -161,7 +161,7 @@
|
||||
{"case":"gen0142","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.01346487964918,285.2199487277276,8958.65918324517],[62.99615398598717,189.26395242276598,6826.104366513701],[101.13713390492686,231.46901706627182,13148.478483538644],[82.14715094830724,222.33476962358623,9547.510128111944],[87.68042663616373,204.7213116045362,12784.525660303003],[112.10818899080174,262.43662272983784,18638.951641371594],[64.6158726526481,190.0819125499679,10173.862867891115],[82.61285024612583,212.3806400336522,12681.032317759798],[85.8283321439191,234.48645619695566,10527.104614367125],[63.32573755744559,195.25807571781567,6600.461838424882],[96.68869108017896,229.95101545114684,10081.192202604394],[18.326177665426187,27.392140361767716,200.6530047260234]],"n":2832}}}
|
||||
{"case":"gen0143","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[111.3075609540347,139.62610147222657,14026.642225281319],[109.79929335389166,136.22238440781126,15260.924469130498],[114.00515905779605,147.48052537957955,15192.780577618807],[114.30756095403466,142.6261014722266,11419.023742449262],[88.1582742930117,115.93152816356164,11860.566893920532],[79.54374750294437,97.1771622341696,9958.335037376874],[103.38038863599111,130.3921080162961,12423.861427555797],[86.93082147663205,114.45595340675392,11294.572676034772],[79.05196112982355,104.24031893936387,9266.571389277142],[21.0,21.0,344.0]],"n":2397}}}
|
||||
{"case":"gen0144","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.46062823788107,137.96736728644555,9893.88960201992],[86.7288797730336,141.82968409882818,14249.814296306056],[1074.356241892445,1129.8968088345744,84838.61922599016],[54.68325867108637,109.784062996881,7097.846190413008],[78.42558780460121,96.62338311204525,12693.117400656138],[45.69724918122488,106.9385321094685,6721.343054715999],[53.112637178925375,102.51272551860494,8068.613596747688],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0145","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[17.170971819502686,127.74141886444973,2879.5135963358],[21.591296628459148,164.56575482886421,4589.212324195143],[13.677841814099391,86.76965825401982,-712.1655096969139],[5.5912966284591405,118.56575482886419,2321.282565235264],[42.352592730134546,78.95803371167352,5102.99217449687],[-19.759017559423512,104.49652587484827,-1675.9302781176223],[32.67556327184749,57.88085349636215,3856.148610165128]],"n":1750}}}
|
||||
{"case":"gen0145","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[17.170971819502686,127.74141886444973,2879.5135963358],[20.591296628459148,163.56575482886421,4424.212324195145],[13.677841814099391,86.76965825401982,-712.1655096969139],[5.5912966284591405,118.56575482886419,2321.282565235264],[42.352592730134546,78.95803371167352,5102.99217449687],[-19.759017559423512,104.49652587484827,-1675.9302781176223],[32.67556327184749,57.88085349636215,3856.148610165128]],"n":1750}}}
|
||||
{"case":"gen0146","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.2396082851277,101.76063051036454,4429.522294139527],[49.40366490264301,96.88385110915691,4919.027987154452],[31.150203756579067,58.325893375816726,1978.2430545411012]],"n":683}}}
|
||||
{"case":"gen0147","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.47557742012422,118.33987479239832,9373.07224676628],[87.5265753750754,138.51877285338162,14846.085067049524],[97.22918723357216,150.22138471187833,17018.592622862077],["NaN","NaN","NaN"],[56.41170475153162,106.45176358996034,8287.090994705055],["NaN","NaN","NaN"],["NaN","NaN","NaN"],[65.26362652037999,109.28838122837192,6128.9721664614535],[50.99490072622767,107.20545174376511,6353.596163095476],["NaN","NaN","NaN"],[43.5707963267949,43.5707963267949,2075.570796326795]],"n":2726}}}
|
||||
{"case":"gen0148","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.96395442807384,129.13413116529065,5514.786694893654],[56.13049135926539,141.6106248133852,9846.216719848406],[33.23116549509431,116.12145825029819,2916.151042950649],[27.92730355578712,112.92776087930513,3534.83716499385],[65.63280578053599,98.12435005837779,12149.130574444738],[17.251352010475692,100.29088241989575,2831.7180002622363],[25.14234161289632,101.60995717982799,4716.892605818176],[21.0,21.0,890.0]],"n":1911}}}
|
||||
@@ -169,7 +169,7 @@
|
||||
{"case":"gen0150","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4476.269195527655,60359.66065362278,1027421.7055264392],[48598.96757821366,74421.77240090404,6542280.982046046],[2170.6816547070257,81327.26157028123,311574.2690482446],[-2012.2623960123299,68477.2043427014,-633877.1365405527],[30552.96301505746,71431.31017126818,5251335.205514944],[27517.97572093652,83337.19887189289,3132321.632034096],[-1368.3792293757203,65287.74814886153,-59719.80487741434]],"n":1745}}}
|
||||
{"case":"gen0151","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[62.17408513666523,114.9106882459193,6608.7404771743695],[66.3835286826404,119.123559483529,11146.826945262233],[43.70434364438743,126.97171161981693,5023.61011163921],[59.351973819782565,109.8515006975294,7960.984285541869],[30.685882714784505,86.1953366248794,3678.879702963655],[39.382692025771604,92.12773252981836,6668.016376571016],[52.54620303838316,89.01442094418756,6191.703179795248],[24.87026101660199,80.38129693822053,3775.4626520881147],[27.559943125703004,58.64251895760204,3019.7204473371667]],"n":2255}}}
|
||||
{"case":"gen0152","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[87.63940308157193,204.33927370960217,6300.945620437449],[42.01282308066528,160.24920407071775,3298.694889324698],[12.68893044499222,52.66136169190203,592.9657016449869]],"n":599}}}
|
||||
{"case":"gen0153","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.26931214932118,176.03128493335925,11780.766752512587],[94.66956806172645,202.27350681238994,11777.223784295727],[67.06929473426412,166.83126751830218,7643.596260048817],[82.58938102706729,152.99200686084305,10830.323009566708],[54.600579887837554,130.74466341174667,7687.070771548677],[76.41114942950846,170.43166452341327,10886.845219065573],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen0153","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.26931214932117,176.03128493335925,11780.766752512587],[92.8386780345716,200.4426167852351,11385.47980959794],[67.0692947342641,166.83126751830218,7643.596260048817],[79.22184140723517,151.62446724101096,10303.869550176332],[53.600579887837554,129.74466341174667,7619.070771548677],[73.04360980967634,169.06412490358116,10131.399065526617],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen0154","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.98628241535242,131.6234756527979,5344.11551570978],[31.316113890624816,69.0657040427992,2939.297725515014],[19.876846222563717,27.35280760944096,692.224782471102]],"n":597}}}
|
||||
{"case":"gen0155","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[56.0445147647415,116.13109321193089,6841.202869588553],[59.43405725268954,128.08884017984695,9835.39995241718],[59.636733357870924,164.07325530930922,9022.744703493958],[24.729945311378444,100.3016591250878,4140.075041603355],[33.61945126206673,97.35344665582944,5350.761078681207],[22.312274245326822,98.42752499965763,3265.8180293324913],[50.86166536543233,93.0825235269504,6502.927506088685],[24.300884243568806,91.79153235324138,4220.844527687304],[22.546639046494803,98.30398967170849,2315.508716688542],[20.086577903508683,26.118615099124064,601.3408508636195]],"n":2410}}}
|
||||
{"case":"gen0156","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[90.02484876075441,157.1282999154571,8386.386883008036],[24.576921937377076,50.220664411030654,1314.5771258354064]],"n":435}}}
|
||||
@@ -180,10 +180,10 @@
|
||||
{"case":"gen0161","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.318139834952134,300.42723893127345,4845.236175104391],[45.53820416797083,276.8483281498172,4899.210774185928],[29.70496276067206,265.15686010167434,3902.2388635391435],[23.14683800019691,215.00438373120963,2956.6916220609664],[25.46504610347023,217.033038682221,3253.586278452119],[33.159321131496974,225.570655189285,1889.4270211769265],[13.506220271373305,72.10191290914742,372.99113067918455]],"n":1625}}}
|
||||
{"case":"gen0162","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.45906646783051,92.16714510447092,7734.030071707306],[42.808134065021136,81.51621270162374,5778.672481306166],[41.459066467830525,88.16714510447092,3594.70260344935],[49.97271097845024,77.1114300695191,8760.996587457328],[37.321778110470234,72.46049977422881,4808.366286688036],[34.4590669329539,73.16714299690449,6060.307813977082],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0163","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[86.47481245207901,122.22235942270775,6976.7770380326],[79.27015160512292,115.06629692059926,7926.9375951814445],[74.7266181751431,115.38530754952248,7059.080708181679],[91.38808267471045,125.25159345963482,11968.706919946944],[35.1727989512874,57.08007648429447,2724.8431549777733]],"n":1170}}}
|
||||
{"case":"gen0164","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.99874277291248,94.7502187111267,7383.669715599302],[59.00207726796979,94.75689675417965,9580.023010306822],[38.99937623032548,102.75207986837884,6022.577838232294],[31.99897999809563,71.74964695529005,3483.510456946656],[28.003442246338214,61.758261732548064,3706.2825063135797],[46.99915739438645,75.49933237217608,6345.346048719329],[20.924454590085247,58.91308528081771,3122.6302076826464],[29.075465311825525,47.342642131343666,3362.070330886865]],"n":2005}}}
|
||||
{"case":"gen0164","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.99874277291248,94.7502187111267,7383.669715599302],[58.001668838362846,93.75608180144715,9414.942080146016],[37.99887623032548,103.75157986837884,5956.544838232294],[31.99897999809563,71.74964695529005,3483.510456946656],[27.00253381673126,62.75694677981555,3470.1501501787075],[46.99915739438645,75.49933237217608,6345.346048719329],[20.92404616047829,58.912270328085214,3122.588486764108],[28.074965311825522,48.342142131343664,3315.0468308868653]],"n":2005}}}
|
||||
{"case":"gen0165","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[46.51946809557959,184.76384551541565,1365.9685623966627],[41.97097424180187,161.5681308507513,4197.535928560698],[19.7005518755661,108.49578204751421,2050.101075316516]],"n":690}}}
|
||||
{"case":"gen0166","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[38.19138090822485,151.59854746295636,4788.801555948618],[48.25726278753596,166.35140419976426,8271.011879795236],[32.69459335064855,148.05466609600367,4083.898788644982],[21.526697986448365,139.67860193922226,4228.043898088318],[18.918898367400654,135.0023710484597,4805.340126269585],[46.6494134828945,96.50337769717322,4747.381136322327],[6.904308131912063,133.2480687309191,1674.0809404785994],[27.804281277462543,122.47257823521271,4140.4421769410965],[21.0,21.0,512.0]],"n":2149}}}
|
||||
{"case":"gen0167","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.0816895837112,144.20502846984118,12093.442035681555],[96.13637362193973,149.31120852361138,15491.43663802022],[106.59617537254556,160.1100653870552,17914.330831422507],[103.8301553419309,155.9878249050887,14688.384564168904],[75.25054474764903,130.53103688232102,9859.812999671349],[63.97784215943513,121.36936172329062,9063.058503931221],[69.99292815316511,98.44236596675259,8977.195045622102],[98.99681594647923,139.69299901296802,10381.129413126417],[64.64401164534661,122.6317751416682,8202.837605252298],[64.6042289832311,123.41045644840786,7492.03136453479],[44.57183077979646,44.57183077979646,2164.190738243429]],"n":2728}}}
|
||||
{"case":"gen0167","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.0816895837112,144.20502846984118,12093.442035681555],[96.13637362193973,149.31120852361138,15491.43663802022],[105.59617537254556,159.1100653870552,17669.330831422507],[103.8301553419309,155.9878249050887,14688.384564168904],[75.25054474764903,130.53103688232102,9859.812999671349],[63.97784215943513,121.36936172329062,9063.058503931221],[69.99292815316511,98.44236596675259,8977.195045622102],[98.99681594647923,139.69299901296802,10381.129413126417],[64.64401164534661,122.6317751416682,8202.837605252298],[64.6042289832311,123.41045644840786,7492.03136453479],[44.57183077979646,44.57183077979646,2164.190738243429]],"n":2728}}}
|
||||
{"case":"gen0168","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[7.928650584753274,156.78843513372215,1060.5929122488503],[4.706595601779114,153.56876861434384,3166.297454756199],[27.112279501469704,177.97442170542902,7585.4571714903905],[11.833681082939085,181.39424854129265,-792.5889464036741],[-14.686533319439372,134.17500304254634,-1586.307542609296],[-24.396308461871442,147.1924024412969,-923.8009331233283],[42.485069834283195,155.9646640463334,5300.860527529822],[-25.29753196805762,125.12415149555964,-2476.054584227919],[-34.38239262170586,134.87041094237105,-4143.384858683072],[18.56253069201767,98.28715619502677,1887.8906352097597]],"n":2490}}}
|
||||
{"case":"gen0169","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.43836594225512,102.78410833241318,8529.038321929576],[53.43836607132991,120.78410892986432,5593.1615864800315],[31.33936582973664,76.68510799266271,3006.7109507152477],[41.05991074344843,63.29040590672736,4730.746694536356],[36.43836599325508,81.7841083834132,4370.791798499001],[23.479455264362993,34.59470273238643,1595.236214928304]],"n":1434}}}
|
||||
{"case":"gen0170","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1130.142432896496,1371.8795403226047,19714.22427894014],[3139.855037450603,3355.313020085378,283754.76098835707],[94.42728143244578,311.6431304123259,13284.042883720076],[28.260158599555446,197.5709955506221,1669.660343433139]],"n":934}}}
|
||||
@@ -231,7 +231,7 @@
|
||||
{"case":"gen0212","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.69752425690601,275.55867264069656,7678.9350885273225],[50.246834073911955,248.00257760356666,4010.3285810692746],[35.013192841469866,108.50131429074315,1329.4327505610995]],"n":615}}}
|
||||
{"case":"gen0213","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[42.0123195803861,335.68621752438037,2584.721429821571],[101.82656644790396,340.9868724537631,12177.528361021275],[114.76394014683189,322.71131445309845,13846.559342107317],[54.63458078421243,360.5552477202922,9035.982678117185],[100.8596489521045,341.3519788167814,13699.280607738934],[1146.648137713921,1373.4435430811195,232823.87563138452],[80.73964886173994,334.46771880168535,10839.655027460194],[114.26359409798121,378.2624446709497,12645.32540043399],[1082.7110181778944,1312.804151711627,43636.331878946556],[101.31379246559953,325.3012970776418,11118.307761382086],[89.05242902863513,258.79891423530916,8628.75514213876]],"n":2747}}}
|
||||
{"case":"gen0214","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-1184.5149321060342,14476.018201608676,-203614.34145736537],[-351.88708973081634,14368.605805070929,-245817.45738580212],[-1422.1228346161568,16817.51145455817,-289485.54447882564],[-1239.8742146795391,8398.187924129554,-310143.1573722442],[-245.56785483371652,11400.464667749768,-187443.3710176354]],"n":1264}}}
|
||||
{"case":"gen0215","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4148.242735000799,4158.605809463373,526198.2400177831],[3158.9580613678863,3169.3211358304598,511610.11595228134],[3169.0117982458864,3179.3748727084594,503400.5520383006],[145.14275009991331,167.50582456248645,14899.886757195593],[141.3470945846246,152.46437579000997,18798.067940648656],[103.43482154346032,109.58933110903007,12331.687390586005],[126.82040052739653,147.4567121752296,17239.12279245173],[128.21961710265293,138.58269156522596,17549.937686910074],[115.948315214286,124.58379433740814,13471.200724726186],[21.0,21.0,533.0]],"n":2406}}}
|
||||
{"case":"gen0215","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4148.242735000799,4158.605809463373,526198.2400177831],[3158.9580613678863,3169.3211358304598,511610.11595228134],[2167.332238217674,2177.695312680247,342176.057681545],[144.14275009991331,166.50582456248645,14788.02950729551],[141.13933862032835,152.25661982571376,18735.289876858802],[103.96301747954504,110.11752704511481,12435.65040806555],[126.82040052739653,147.4567121752296,17365.94319297912],[124.07472495999417,134.43779942256717,16680.132786219237],[119.41364732873325,128.0491264518554,13590.61437205492],[21.0,21.0,554.0]],"n":2407}}}
|
||||
{"case":"gen0216","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4081.0237321284267,4147.977856967489,514543.53912250337],[3090.8319290817453,3160.4828971528186,503643.35493871133],[27.12919500858102,183.5009155990999,2859.305070032192],[65.43052454672254,138.4299088103474,7063.017879107584],[48.85426281770509,118.09755702078918,5833.694693779154],[70.49619828311069,120.420430765083,11909.224221055887],[50.2493175806311,122.43398654972498,6972.797213427114],[39.88416392851982,111.42724681871698,5850.7859602828485],[22.94782628601089,37.60607635612807,1040.9326846282931]],"n":2173}}}
|
||||
{"case":"gen0217","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[98.17228758518391,121.31384895082988,11340.429306541397],[98.18932487857144,120.93592875791643,14627.55208037506],[67.75729304262491,102.8077103729707,8330.795708163525],[72.46031995309984,96.10063220047675,9508.014428678032],[71.02994153320533,89.8095784935887,9969.637832325003],[63.85169589518865,89.39233256176502,8771.44559126159],[68.9962090225235,89.35386471597151,9373.905026793063],[21.0,21.0,806.0]],"n":1907}}}
|
||||
{"case":"gen0218","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[13.400709465319537,135.33181818143154,1398.1501147563818],[12.998197446680614,134.93274844323346,4324.0137760716525],[34.76767473659705,156.714737260466,9582.113884481954],[44.92212759750668,145.56697792969098,9996.624439298752],[12.18822380324289,153.40781267969328,58.020840558255145],[-7.912483899135342,116.21001189368624,-904.8412836230716],[-21.50445739238269,111.42748373356562,-1800.7238188562378],[50.45193046434881,149.39692483896366,11250.682902679897],[-18.14253080095908,107.44474724052296,-1695.7184226883764],[-22.62369819053736,112.48677299420957,-2789.147671474009],[-19.780795424033055,126.09269580730277,-3570.916272167114],[23.064131810176917,24.696247328576327,244.04432137955354]],"n":2904}}}
|
||||
@@ -258,14 +258,14 @@
|
||||
{"case":"gen0239","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[28.644504188197303,292.4104248749771,333.45936601575363],[23.03858619745477,257.67376825960844,2226.5223156737266],[3.356054990695331,260.6812568776816,-2787.572109017651],[27.233313121043874,254.59243585654727,4142.640953559551],[27.35173076149798,248.28134599109976,5178.941870443242],[-2.93814759724592,44.432988588355364,77.41370389564875]],"n":1332}}}
|
||||
{"case":"gen0240","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[48.40533002138903,91.34833588063354,6200.545546216008],[55.752938539469525,98.69762170133097,7729.563703678826],[38.40427961611789,87.34728547536238,2789.916096965905],[49.60318617057894,78.2328047840182,8851.854198197707],[37.95514816999266,72.58345411181868,4762.34035159053],[30.401437626646832,73.34575615750461,6013.627207823783],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0241","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[51.02038231539431,83.3350235728361,4579.997562621957],[38.78446547956346,72.28305511287371,5651.64872510203],[35.59903596689514,74.0639780148665,6171.499789382644],[30.027847579080063,61.80796051326339,4364.734994425018],[13.514403031697203,29.657472845480616,885.0485278576256]],"n":1163}}}
|
||||
{"case":"gen0242","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[100.06486461259303,164.20984556102997,11234.972686517121],[96.56424307170008,163.8437583158729,12825.542125982092],[103.63380834894609,181.38088460409244,12871.481760552539],[74.76853694107706,141.00320742000457,9358.054879464693],[74.303253131749,141.0951741521406,10444.169843805397],[84.76118179464544,121.46068266056571,13019.50261268568],[64.9707123081774,132.9667695752792,9575.5392691698],[65.94177110405495,126.95790160966745,8692.488347744693],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen0242","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[100.06486461259303,164.20984556102997,11234.972686517121],[96.56424307170008,163.8437583158729,12825.542125982092],[106.04615311002159,183.79322936516795,13414.052658933022],[75.35619218000154,141.59086265892907,9433.41107164469],[74.303253131749,141.0951741521406,10518.473096937143],[87.60499177058527,124.3044926365056,13497.083851799835],[66.1269023322375,134.12295959933928,9707.433591751924],[65.94177110405495,126.95790160966745,8824.371889952803],[21.0,21.0,722.0]],"n":2159}}}
|
||||
{"case":"gen0243","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-1973351.516442371,2927361.763177467,-253520063.72970343],[-1659944.525697839,2967960.470166385,-208136598.67412034],[-972924.5630162599,2093959.3018368736,-71071624.58719468],[-2003495.388714444,2862719.043808015,-262687357.34014624],[-1664607.08523553,3132511.3508256217,-192418255.2029394],[-1858943.809718834,2806946.5841991096,-203613842.20434967],[-1490254.300602971,2344435.0711701564,-249593685.80642116],[-1675585.0248820367,2999517.3921263665,-194321690.904195],[-1072649.3560210485,1548673.0595693565,-61663459.79467461]],"n":2250}}}
|
||||
{"case":"gen0244","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[73.56596073323504,154.07484142227074,6309.866168548203],[69.02676184165827,127.1942948900654,10409.532859432236],[74.55557913969872,175.1824046623269,11914.06723758677],[67.82070806689786,153.13800971925707,8487.735515555854],[62.67009499690967,127.36361996162121,9800.937575594882],[41.418443633828524,80.28937186255925,3690.7479067347645]],"n":1417}}}
|
||||
{"case":"gen0245","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.68998772125212,130.37304591307586,10271.08984264031],[85.2788027118426,163.96692240882254,13004.033674453165],[43.67064364137575,88.12538308111371,6257.66962874908],[70.98772865535722,119.6745829760481,10264.02890116617],[56.05970910450339,84.28676248030011,8303.750779628153],[53.64358228204522,105.54670580564404,7616.199395289109],[45.95508091018439,58.966497771217675,4176.617716729304]],"n":1748}}}
|
||||
{"case":"gen0246","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[62.82783295395048,147.5691724442309,7990.864035935296],[54.08939734250691,132.53485426136277,8321.142925978604],[52.759318642284285,130.0251144559815,6298.963903484914],[16.965322765596355,108.84328771832166,2605.453522421567],[32.240226077649254,98.02744397056539,3906.7906944121105]],"n":1242}}}
|
||||
{"case":"gen0247","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-321291.6057510884,7133674.491214774,-116472978.6952433],[-236795.44467281533,3959137.052975796,5765555.168739315],[-17024.167160831537,4567758.618620669,-42546568.381025925],[41919.8877216338,116330.06741712053,390512.5688234013]],"n":785}}}
|
||||
{"case":"gen0248","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-1221215.6213860284,1886744.512919289,-153188713.83680922],[-1372482.8720202066,1735559.5158222949,-164604221.29313686],[-1296640.4445871364,1811351.7531024767,-181541953.811565],[-1211148.764000694,1896901.6555339543,-149735884.3000932],[-442038.9731951817,593971.7699320723,4354512.331908463],[-1459525.987051625,1993772.5742034786,-180450162.20674792],[-1209879.0682874038,1552824.8926053117,-137476181.24552956],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0249","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[879996.0417008691,2576127.4739201954,111876281.63724086],[429874.2228749507,3386349.530298056,43313683.8486783],[698148.0016406856,2624518.2968912655,71863507.48745713],[956560.6471859032,3682803.091577755,129857580.19705573],[-28473.61114257983,995454.754831002,21906141.62885632],[882075.2508953977,2919511.277320468,123590488.95977056],[336836.1849701545,2952006.8981875163,35043907.7194474],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0249","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[879996.0417008691,2576127.4739201954,111876281.63724086],[429873.2228749507,3386348.530298056,43313432.8486783],[698154.0016406856,2624524.2968912655,72336902.31512372],[956559.6471859032,3682802.091577755,130813883.84424168],[-119355.4398528474,904572.9261207344,-1467811.439123735],[972963.8109025545,3010399.8373276247,125524905.27187806],[336836.453673265,2952007.166890627,35717580.62679389],[21.0,21.0,974.0]],"n":1915}}}
|
||||
{"case":"gen0250","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[31.62844418481919,85.1444421169773,1295.2422510988345],[17.01561030917979,75.15734982136364,1289.0650077987295],[-0.6128330078840121,4.012908572141664,-3.418408986242617]],"n":523}}}
|
||||
{"case":"gen0251","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-17596.393080637386,40108.96368387155,-2250417.092745623],[-19892.869901218644,37814.340502452826,-2037556.3141800654],[-23149.75810803728,34565.55527558766,-2809975.672869569],[-22591.393080637383,35113.96368387156,-3145651.439305636],[-24471.43115222666,39646.824651673676,-3178148.3532179445],[-13958.850524161024,18103.474698104295,-1033247.0101094913],[-16484.032153856206,28404.816622333274,-2686659.256589289],[-26134.725093561792,37984.05806927729,-3384767.775590349],[-18417.154228448966,26467.151623197078,-1949570.919155898],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen0252","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4045.8338634090287,4100.35649973746,512910.8515194061],[1039.795874850835,1130.1167157963741,81365.1744941995],[15.422763972983237,83.11651392764047,1118.3824174839513],[18.126350746390415,77.20386213126524,2572.74832996557],[36.13373522789902,85.65460640618927,5156.15396756034],[11.26331417170514,72.17464901321809,2773.94949117636],[17.224991256134974,51.73692535223083,2694.2547113509263]],"n":1754}}}
|
||||
@@ -273,7 +273,7 @@
|
||||
{"case":"gen0254","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.48624893219004,89.20707328543924,8222.552673520318],[79.48625012003113,104.20708233026427,12807.690530804553],[81.7191521555855,109.2070862773622,13768.172077126244],["NaN","NaN","NaN"],[49.9333968196937,72.65423033547827,7835.00919118214],["NaN","NaN","NaN"],[89.84448334035663,97.41809295467472,13880.154116096433],[36.65921378417178,60.26016428176331,4932.682782734595],["NaN","NaN","NaN"],[55.331933306300144,76.06536372848585,5949.462901346721],[21.0,21.0,92.0]],"n":2641}}}
|
||||
{"case":"gen0255","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[50.19868265681189,293.1418947109465,3213.3050631163537],[54.90828814208228,296.32463693799656,5849.7746737462785],[15.56902656446481,274.30024370253557,-303.31913807755615],[65.83947585188638,305.66725223383827,7329.465754748113],[18.684221585137376,168.37012632438945,905.5133671882792]],"n":1170}}}
|
||||
{"case":"gen0256","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1067.1413927883566,1284.0500608519696,12353.093522292385],[24.777671329207138,216.49442732925553,1937.6352377142045],[62.38739638816366,249.29606445177754,9751.508385830253],[63.60338388155697,259.52054791347786,11434.595436094036],[44.51730063355757,225.54263585185237,3004.58354811798],[49.46230171575895,203.2796465619084,5681.340170252099]],"n":1526}}}
|
||||
{"case":"gen0257","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4126.341290829081,4148.440023197768,526837.2311887897],[2120.4948682204936,2146.5936005891817,263322.2518164522],[102.34506042371288,130.4437927924017,10622.136341700485],[91.89800141627096,110.63048966206352,11603.83142136561],[88.87259897824028,109.60508722403283,12965.349172888142],[87.51802476163405,113.61675713032287,11922.723945659425],[21.0,21.0,1121.0]],"n":1666}}}
|
||||
{"case":"gen0257","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4126.341290829081,4148.440023197768,526837.2311887897],[1119.4938615390442,1145.592593907734,95185.63964614402],[101.34434457523186,129.4430769439207,10467.304714874785],[92.89800141627096,111.63048966206351,11696.72942278188],[88.35987957377164,109.09236781956419,12922.454629915734],[88.02988290113827,114.1286152698271,12010.738029975142],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0258","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4052.9273891944545,4095.1327232947137,514178.36175929406],[3046.9277730724125,3089.135046064308,497889.28904393036],[1074.7027217241366,1122.9092805476282,91473.07618428869],[20.31364864143259,62.51972145302911,2729.340543559665],[28.927761836865372,71.13528025808222,6000.138213540766],[61.93388703944935,82.00208631021532,8413.096687678057],[12.01499321145854,59.80900526599526,2096.859178109215],[29.710398901837554,66.32997565674117,5278.939695426858],[21.0,21.0,785.0]],"n":2162}}}
|
||||
{"case":"gen0259","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[9704.93509733141,19744.432781743737,1239032.381476495],[11727.737115120035,24574.745526811337,1627062.4858145374],[8688.073304251291,21855.031528916585,1047475.0492682543],[3392.3780999586916,9737.439284841323,547046.7220649039],[9126.346301036152,22702.36863549737,1292100.7340112093],[5887.520187083819,17575.589946054897,709902.0087420759],[2913.315258090777,7024.653321650511,583369.2694100629],[7123.338970963724,20027.478394826074,1047903.9063268055],[7743.278080323833,20860.069583708082,956472.9852538673],[658.3122787003227,2131.196566208905,12021.272113426485]],"n":2485}}}
|
||||
{"case":"gen0260","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2232281.468917312,2938891.5830890974,327062737.1922691],[3400973.853964717,3808210.262385265,437071468.61378217],["NaN","NaN","NaN"],[2811606.770399441,3316827.0621593827,391492714.4785675],["NaN","NaN","NaN"],[799552.4326397704,1214603.3663052549,172266823.37316516],[2875333.232015594,3347892.22932907,412835372.50165355],["NaN","NaN","NaN"],[21.0,21.0,680.0]],"n":2157}}}
|
||||
@@ -291,7 +291,7 @@
|
||||
{"case":"gen0272","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.71817281044324,123.44144830697033,6798.695388196973],[59.886929379970226,165.6632393031204,5575.180244063362],[45.72979344874376,98.54991382762789,3145.059153661758],[59.96603936016726,105.75734943938534,9757.427526122745],[29.469839407463958,82.28995978634802,4108.183558301731],[54.22599340144709,100.01730348066519,9001.709060399291],[0.0,0.0,0.0]],"n":1593}}}
|
||||
{"case":"gen0273","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.74236656351477,162.60420311145975,4694.227597174535],[40.929903929341954,158.91666978358418,6809.090476147912],[69.27331824637258,187.6403045961109,12152.7154857772],[53.31594417977167,149.5849695977986,6499.488128874815],[38.10277606293149,132.05129284769902,5038.519066437377]],"n":1268}}}
|
||||
{"case":"gen0274","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.45805623080838,135.43505033432893,8821.631173018857],[73.16369495229073,183.99274505867393,11253.87903269857],[48.202811480958104,137.97937055319227,7268.072668371124],[46.566089111591275,126.54308321511179,4150.143589983152],[68.89704333485307,157.75862844302546,10436.82563471119],[45.20758777590623,100.91907684456206,6707.969452745043],[45.98413403276058,136.34986243260818,7446.403946845919],[25.276359685050267,48.03772142823971,1391.0872271086478]],"n":1926}}}
|
||||
{"case":"gen0275","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[137.33699507622092,154.4756338781297,16674.95459137847],[144.27030465279717,164.46989518636352,20419.111417951815],[125.30293283502253,146.17906325038004,14469.826362718537],[121.18125650974565,139.64335542986328,15565.505685655566],[92.93318401364519,97.9695237324903,13221.328133131568],[111.08632658242432,128.9709577025209,15339.931799582459],[103.62737087794414,121.08096909332279,13484.200825419275],[21.0,21.0,848.0]],"n":1909}}}
|
||||
{"case":"gen0275","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[137.33699507622092,154.4756338781297,16674.95459137847],[143.27030465279717,163.46989518636352,20170.111417951815],[125.30293283502253,146.17906325038004,14469.826362718537],[121.18125650974565,139.64335542986328,15565.505685655566],[91.16873384167425,98.20507356051937,13117.225572985282],[111.08632658242432,128.9709577025209,15339.931799582459],[101.86292070597321,121.31651892135186,13118.95963982129],[21.0,21.0,848.0]],"n":1909}}}
|
||||
{"case":"gen0276","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[20.42381168309922,162.33376666791244,1093.6778745573874],[30.40264633208079,164.21983750011108,3000.0702793852347],[-11.329621749072096,144.6884917713225,-851.542216823862],[36.535274316247154,89.83602685805181,1952.4697669976133],[4.1104549445916305,146.02810728880405,1683.0058296379189],[21.0,21.0,1436.0]],"n":1425}}}
|
||||
{"case":"gen0277","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[36.45315975321697,83.23835919747049,4485.454426457572],[35.45315892488143,82.23835837195774,6269.464620032169],[47.45315779414035,126.23835724105056,7196.011202226814],[46.8845068142454,84.074639780734,6395.095813702821],[12.55315985411882,59.33835929936865,1506.4607680124172],[3.898190541674568,57.01216443404972,983.0914004727243],[55.53799154131365,90.83199149272721,7408.137090532861],[4.899674095474567,53.58100655480315,1162.9563233622832],[1.4731189775882734,57.240900326525434,142.64170544919662],[19.352726709749476,23.636436848989053,632.3964556514603]],"n":2412}}}
|
||||
{"case":"gen0278","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[20114.037841251327,23639.36824915227,2487124.1373236645],[18844.071843132977,22366.398406658926,2350157.776801331],[19122.233386328953,22648.89593256287,2427285.504132198],[15707.885660460854,19241.21806836145,1977522.5917833918],[17233.092047578844,21145.59348931771,2183062.62326629],[16760.568058068096,20287.002445961774,2186786.863128567],[5346.338172929624,6531.115659521421,748619.0602295732],[15638.8059603641,19165.871854039204,2044768.4175812257],[16892.77394111661,20806.049276389687,2098446.6975703225],[11694.365851360606,14438.393316451651,1033319.0152768433],[21.0,21.0,239.0]],"n":2648}}}
|
||||
@@ -305,10 +305,10 @@
|
||||
{"case":"gen0286","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.18579950379555,89.34800111543638,6844.108730428322],[75.1858027555291,105.34800922422944,13209.079520064855],[34.63586573454114,68.79806831759376,3760.9262380410673],[34.18580570860313,62.348014220044206,4144.180417217294],[62.17866520515074,79.56606467103548,9459.194211910255],[28.678476690111093,58.34590974722372,4156.910073259],[36.69312497633293,65.3500999995614,5976.142821102281],[21.0,21.0,848.0]],"n":1909}}}
|
||||
{"case":"gen0287","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[42.408450961849944,86.89060181640507,5401.452015238408],[63.40386917716,107.89452302922956,12867.764354011213],[11.403353195861948,115.8940070479315,2601.813667623378],[31.187161776466407,87.40850158242557,2507.936613599115],[22.97582246658253,67.72634731363407,2992.8688185633237],[44.28397292003509,85.94535832280393,8644.923542681168],[17.214805062470326,62.08852958236872,2485.544362104716],[14.873437900975407,62.40653687796695,2408.4598632207862],[18.192326677658563,29.58853056197072,966.6753166014828]],"n":2174}}}
|
||||
{"case":"gen0288","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1133.7511280166236,1301.5393343253827,17364.408901065413],[87.90129039102874,255.4464660631014,12660.04899940926],[70.72135447789775,297.5993851638284,8868.568870279829],[55.86732213209061,294.25822108520566,5715.79330168752],[80.86575544419122,272.46810479939444,9025.033126410497],[71.63538633687659,234.8213245244279,6333.362561757727],[88.34717907672798,280.77537931498523,11211.890837252291],[30.753833352676917,136.90464278971734,948.0815360372357]],"n":1944}}}
|
||||
{"case":"gen0289","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-9246.476150541233,29393.34136464405,-1138881.715067954],[-8854.126582602219,42057.13611212149,-792873.0173850667],[-9507.423539092608,25492.038689018045,-668210.2753872395],[-10464.61967328031,28181.197841904974,-1545867.1369712802],[-9274.196024297842,47461.781794847026,-1228952.4500139833],[-3342.8196604461273,10079.666476692304,1881.167425141699],[-10576.619656482928,31619.689170884227,-1178463.4540335373],[-12000.88905885604,45248.27607359341,-1546696.4113986732],[-1498.6284547553462,4329.238761888114,-26285.26745941673]],"n":2242}}}
|
||||
{"case":"gen0289","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-9246.476150541233,29393.34136464405,-1138881.715067954],[-8854.126582602219,42057.13611212149,-792873.0173850667],[-9508.423539092608,25491.038689018045,-668287.2753872395],[-10458.61967328031,28187.197841904974,-1556518.7566445598],[-9272.353104633035,47461.93887518222,-1238009.015684427],[-3257.9275063588034,8408.77432260498,182829.02313853608],[-11456.354730235054,32499.424244636357,-1201376.1634940077],[-11999.528844427477,45246.91585916485,-1570347.7409429299],[-2300.9886691839088,3531.598976316676,-53314.758048670956]],"n":2244}}}
|
||||
{"case":"gen0290","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[41.30864154531459,117.76645538086116,4239.782060785773],[73.20856024828682,143.12309671116535,11366.891399575623],[38.65140967066342,116.55430456704664,6168.472941877307],[1064.9127766575039,1137.9375291016236,252528.76951479932],[46.156579164103164,122.9521737749308,7039.962844939362],[43.93709158467591,121.93800122284692,7179.686499531671],[40.46303765784336,115.17002238023694,5625.422218644405],[47.829201566710886,120.45737394894961,6981.373122662797],[38.44310308890076,109.44971772551833,5287.534000153682],[2.208584361983796,15.828497545724268,40.645783625277076]],"n":2347}}}
|
||||
{"case":"gen0291","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4991.218545862895,23336.381636492024,528591.7323184134],[6372.122782412474,24597.678007870414,725257.5569393584],[865.8746088280514,6996.046805164038,18806.90801016209],[10192.989274953728,27107.923014957003,636084.0574447128],[7679.247307587933,26499.95868532711,1063608.782832104],[8104.9051590106,18172.981881568412,1032445.7020281546],[5097.593344029062,25499.943199963087,798039.5151177638],[9038.450719363733,28308.822126628664,1566502.8622268988],[1987.6182894929395,3517.5581880911795,52327.6769903751]],"n":2164}}}
|
||||
{"case":"gen0292","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4034.12206548456,4112.05281127102,508900.587126625],[3042.270496407947,3122.2017000676,495805.5455022094],[-18.026369702504574,149.90506205512648,-2119.8225546921794],[19.474288060162745,97.40457653251761,1253.734110849835],[2.324098350269291,82.25530200992519,433.83219642507464],[44.046732356770754,96.00124885224668,10914.57292009485],[-8.3791527386359,79.49665654885726,-455.8078534412168],[-5.737292681892747,76.23831314874279,-439.9624920886987],[15.586520284256935,29.574198531459736,829.4832349294288]],"n":2167}}}
|
||||
{"case":"gen0292","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4034.12206548456,4112.05281127102,508900.587126625],[3042.270283757668,3122.201487417321,495805.49148918036],[-18.026239717671757,149.90450662787367,-2119.8120762734475],[19.474288060162745,97.40457653251761,1253.734110849835],[2.324015684822835,82.25453393239312,433.8421909663156],[44.046732356770754,96.00124885224668,10914.57292009485],[-8.379235404082353,79.49588847132522,-455.8100933860512],[-5.737292681892747,76.23831314874279,-439.9624920886987],[15.586520284256935,29.574198531459736,829.4832349294288]],"n":2167}}}
|
||||
{"case":"gen0293","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[98.6193336461672,130.0042648067125,13088.859999602717],[102.33642358669694,139.72135474724217,11412.25810964449],[90.43192636876256,120.24867564172308,13024.503404740106],[55.10353433637136,73.13335994413791,8129.324826690272],[85.43563008390528,115.25237935686579,11968.15696019697],[21.0,21.0,1394.0]],"n":1423}}}
|
||||
{"case":"gen0294","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-56030.881398686746,8017748.38000652,-33018598.197979044],[-826853.6060309471,7288717.889497029,-180134447.12541264],[-84506.6583350489,8546458.510701364,-50210147.40393443],[336892.2048973457,7782162.35073452,18829671.648200095],[-1704957.8579069003,9461769.57435433,-136219878.68480843],[-339934.5172228765,7270004.400164804,-123860501.34245989],[-1621366.6530754338,9834414.089512635,-258221109.78499866],[101098.32129491497,4893563.791202129,29910194.81819656]],"n":1931}}}
|
||||
{"case":"gen0295","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4041.7263195658957,4130.983229530355,505933.8543207926],[1043.4825356969332,1180.9535836751445,80359.0709249709],[-11.78923424430579,133.48401410169387,-2027.6835752699265],[7.8135778273670455,109.0704877918274,-1348.1200668789138],[31.930099630405635,137.31342991571665,3986.8045954158847],[21.80163093492897,89.32257593288418,2936.995450630544],[7.9505332777697975,124.07143520501418,2081.735010477866],[10.994736392045839,33.99312971661763,898.1975961482545]],"n":1924}}}
|
||||
@@ -323,7 +323,7 @@
|
||||
{"case":"gen0304","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.99021917935052,213.10673861019154,6551.899056792588],[52.57913193786868,164.4312737548647,5293.387001976774],[-0.21977272674969228,2.19923093166493,-1.915762807816256]],"n":517}}}
|
||||
{"case":"gen0305","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[37.77506363821718,88.05174960477727,4165.567470154821],[29.774512623047816,84.0511987024314,4281.629187926492],[20.77476333821718,77.05144930477724,791.9139368104322],[38.516508980346394,72.03429971247137,8141.351460825254],[21.518043103996455,67.03684222212883,2028.1102744861496],[17.773279214567125,64.04895679511982,4462.231560851354],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0306","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[149.19352779276625,255.8633293841266,17911.632601529323],[104.76147425580982,267.0388159432144,9398.877881038927],[96.23351749299084,264.25555272602685,14821.109558178052],[93.2069903916705,258.35837450275335,12893.482986669376]],"n":996}}}
|
||||
{"case":"gen0307","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4075.0543485301964,4137.403045311424,516416.10443345807],[3066.5034373903723,3128.8528409077835,495331.29522725334],[2066.8488909445077,2159.198523111861,237831.0612984669],[36.69957662547708,159.04910451477258,6318.80296645437],[59.07975044663464,121.42844722786138,5507.609072716486],[37.85890689312388,108.33754081850095,5208.363363399882],[76.73506487842405,115.36632707149928,13162.915371963541],[34.38690795510709,99.67086774681971,4724.260684562668],[37.63193343671371,108.51832282860055,4525.571140005952],[35.74831740321575,60.64908362421989,2932.452558871785]],"n":2491}}}
|
||||
{"case":"gen0307","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4075.0543485301964,4137.403045311424,516416.10443345807],[3066.5034373903723,3128.8528409077835,495331.29522725334],[64.85017574842823,157.19957926583967,7655.858509653042],[25.699576625477075,148.04910451477258,3560.2021197053245],[70.07950738465193,132.42820416587867,5641.706592804163],[38.73009620116652,107.46917859232032,5507.7917399321395],[75.86540343628347,116.23598851363988,13315.444574648318],[34.386778539723984,99.67070368750598,4793.007233305245],[37.70461328594522,108.44827741196247,4619.2449856389],[35.677051773286685,60.72034925414896,3003.8779280482872]],"n":2493}}}
|
||||
{"case":"gen0308","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[137.30074895231334,185.63800824967836,16379.942126488802],[144.73461699596885,193.7671036740212,20198.49150370004],[113.73231728913001,174.50371615586192,13199.752881683817],[124.24277120377768,172.415898210404,16277.181672984818],[92.47321455118782,120.73638247205115,13536.564909596704],[107.65272035443984,160.81823648003856,15123.350673827392],[105.63923643354568,149.41824618130522,13819.534734918565],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0309","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.01462823057803,98.97615463121656,3867.6358800665976],[45.00986068101454,74.51350765342421,5153.111070866828],[30.834270612821303,72.638813115187,3210.0397681483532],[43.81561663358125,72.09686415983562,4176.653676986917]],"n":1012}}}
|
||||
{"case":"gen0310","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.60763447958265,85.29179811487165,5733.5039157513065],[50.60849011190521,81.29265374719414,8546.64559313767],[1065.607998313313,1100.292161948602,255865.27206211488],[13.955856982727978,104.64002061801698,2269.73910156693],[48.607634179582654,85.2917978148716,3029.5420091561145],[23.260683160418182,53.94484679570713,3345.2020206250513],[31.954687555298893,64.63885119058783,5800.917151159663],[50.65145603811989,78.97191231742566,4892.3356032262],[20.358217110007587,53.07328518195341,2893.858325739149],[18.18522121553953,52.733732039941636,2279.9325474518278],[19.97717738203157,24.445633112244707,429.25801915063454]],"n":2657}}}
|
||||
@@ -354,7 +354,7 @@
|
||||
{"case":"gen0335","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[486742.2472333242,1881856.6760397316,71037943.50894445],[797986.0341056844,1926253.521314056,122445857.81066813],[511789.62028179405,1648478.1447474174,74326360.9413241],[351791.98702587915,1746912.4158322865,39390293.13132525],[1070830.3614032515,2325151.8633719203,146090278.11615083],[219775.1041472716,851368.2544243691,18192860.73481917],[379033.8929469561,1499530.9878944783,65255261.59235485],[712360.6734704132,1988900.0040595923,107068030.67556585],[551126.656582866,1435125.3120983383,43788182.93519837],[21.0,21.0,470.0]],"n":2403}}}
|
||||
{"case":"gen0336","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.42598435995654,104.2672169241522,8551.407951592202],[67.42803048671207,105.26831598248482,11590.884156812477],[45.072216765702116,80.91394572438391,5716.587540249799],[32.448956144873904,72.289169879797,4288.622002766761],[67.77290398921761,83.05291946781891,9871.254478896673],[32.012305786167246,67.50028957375505,4183.828020094343],[42.835275741978066,75.02930640884476,6487.059716383736],[21.0,21.0,764.0]],"n":1905}}}
|
||||
{"case":"gen0337","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.98755167366974,294.49929840551454,9169.761512703448],[82.08167048390716,305.8214287390835,10758.50939208752],[47.584956255672424,206.25630286863807,4312.564713800343]],"n":690}}}
|
||||
{"case":"gen0338","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1075.1151552137885,1104.3842596577733,93769.46865405352],[67.61577146115754,91.27323009729592,7903.902773454579],[82.59629160012,122.41211506156236,9716.27957336935],[33.992632214351104,56.84308142738405,2693.399178818596]],"n":952}}}
|
||||
{"case":"gen0338","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1075.1151552137885,1104.3842596577733,93769.46865405352],[67.61577146115754,91.27323009729592,7903.902773454579],[81.2134712286909,124.41163092959114,9967.852881853474],[30.61078010686447,56.84356555935527,2418.721419747352]],"n":953}}}
|
||||
{"case":"gen0339","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[58.614732840162986,269.1077154360568,1972.908838667272],[55.35913742746441,169.24012884689475,5882.890561514593],[53.39438657612658,213.26548780850385,5371.539480750237],[48.96069230853679,210.67317247064992,8017.99024204405],[23.63998358293365,94.28638457302047,2127.09339591412]],"n":1178}}}
|
||||
{"case":"gen0340","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[16801.312997343543,26728.963216706947,2128821.7868130254],[11065.813725351607,21487.223622770485,785043.2542056226],[16327.274861223785,27865.5560874462,1941034.3933132794],[17686.26418906254,32476.063531761356,2053084.8295332429],[11636.589060556578,17576.507586354786,2003256.9402678579],[16292.214927571511,31082.014270270327,1823799.7950754305],[0.0,0.0,0.0]],"n":1593}}}
|
||||
{"case":"gen0341","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4028.974504687088,4150.0894322014265,503674.3862517988],[3023.433128760454,3164.126610511259,484408.3764602662],[0.750560958979408,204.53174016736878,-1192.1222926154678],[17.200631445363836,148.66634879003166,1228.3211807529037],[-0.4444607883348546,133.72283621698455,281.76603851945276],[-5.977110742737718,137.8040684656518,2051.9300214566747],[31.19608268848607,127.07878332802073,2258.5399454673684],[-11.87601892061342,127.78907017909404,-1041.9989342530184],[0.1633276161103261,127.13190418497565,2085.7226125217467],[0.0,0.0,0.0]],"n":2331}}}
|
||||
@@ -364,7 +364,7 @@
|
||||
{"case":"gen0345","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.12070479135758,111.06005960671982,8979.352030200764],[77.53030716883609,116.66131640764117,12711.631253343321],[55.01655841278244,94.38723264595592,6451.869514922659],[48.78666536382345,81.19867961952346,6123.090530557672],[46.505282655515856,73.44548663639269,6830.8074130091845],[49.15488508447031,88.60544764909987,6608.006897513485],[37.32077472727953,73.95387035797403,5101.911025856143],[34.050357148197655,52.102299086329346,2471.872550459556]],"n":1990}}}
|
||||
{"case":"gen0346","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1106.4177802209829,1132.664428935755,269027.3271961115],[-3141.4817481230552,7553.9409213806985,-216667.0604447948],[-8695.995079698852,10943.806991011583,-2396029.1580282785],[80.91352913188604,114.4076323366395,11610.609808202335],[-13982.792682511088,16309.353383499267,-1520056.6273267015],[24.7143238053756,43.82817033688877,1590.7008827079815]],"n":1416}}}
|
||||
{"case":"gen0347","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4056.607481576103,4085.1253880801746,514493.6851916172],[3049.6084843355516,3080.1263889947654,497940.15865283523],[56.66755117720106,99.18531884534947,7719.04239106214],[23.60641402758561,52.12445775332654,2991.8713056831925],[30.609483371663135,63.12738803087535,5809.668569561244],[59.60393910880634,81.10991001343652,7851.6990914209355],[17.907889769167117,48.63014860688409,2729.1102806574017],[29.633630936158266,59.94718073056872,5052.870734679222],[21.0,21.0,722.0]],"n":2159}}}
|
||||
{"case":"gen0348","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[114.08099758982226,178.1487347399094,13592.562457548765],[126.60890959248567,202.05495680173968,17900.13075364785],[82.65631724273915,160.62043940268745,10147.490770279508],[101.49413809282692,175.04380029221977,13670.53652894163],[70.65596461514026,103.63605259643381,10913.97159448064],[88.02400862108107,162.65266683232326,12306.20941724442],[49.95732236211376,68.71367812583439,4175.070514633956]],"n":1746}}}
|
||||
{"case":"gen0348","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[114.08099758982226,178.1487347399094,13592.562457548765],[124.60890959248567,200.05495680173968,17487.13075364785],[82.65631724273915,160.62043940268745,10147.490770279508],[101.49413809282692,175.04380029221977,13670.53652894163],[70.65596461514026,103.63605259643381,10913.97159448064],[88.02400862108107,162.65266683232326,12306.20941724442],[49.95732236211376,68.71367812583439,4175.070514633956]],"n":1746}}}
|
||||
{"case":"gen0349","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[722.6362145760102,24192.055750037798,22182.951638440994],[-1.5396935577309703,35670.14925745893,295971.5603511826],["NaN","NaN","NaN"],[64.69190015042531,13290.241299975462,-117248.02079982222],[849.0328005446602,38838.937030241774,266219.66165709886],["NaN","NaN","NaN"],[1520.836315596086,9530.429221330032,287698.2924296076],[-2825.010908548847,32454.11385540801,-398148.47584927443],["NaN","NaN","NaN"],[-156.34420448288574,3806.3049856426833,8851.977140659616]],"n":2484}}}
|
||||
{"case":"gen0350","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4071.0446298768898,4121.856244738855,509204.125310364],[3082.861342259107,3147.945194477246,489232.1202418088],[2080.7173305046904,2151.8011827228293,241653.33770982243],[66.01724073712109,116.82885559908404,6126.993573610715],[50.00106152915059,119.43028326118711,7217.943574046842],[59.86539564699003,97.32345679449833,8653.668502450542],[67.01782713071734,110.18040378546645,7149.412502540382],[45.54656513733354,116.02342983941689,6085.436051740082],[53.670665706722374,103.73012209139758,6695.62889815718],[21.0,21.0,323.0]],"n":2396}}}
|
||||
{"case":"gen0351","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[741.9823099949765,80013.9995627481,-187398.97741070425],[2777.4591551399853,71842.29904728498,-90551.12466749082],[-11180.927041665793,85388.50621540692,-1203086.98013953],[5352.416814472184,74701.25646770853,121903.4664444487],[-9888.116411609586,47469.62337161671,-1014766.405900586]],"n":1172}}}
|
||||
@@ -388,9 +388,9 @@
|
||||
{"case":"gen0369","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-267845.8260231498,986629.9541155124,-35710697.18036803],[-432156.97453866206,971909.2400795447,-53131492.38572284],[-364975.6930904864,1039078.0442612364,-58899250.5913729],[-101631.98229398625,266679.87913680007,-24679052.506234553],[-364979.453999761,1039072.5219852038,-54519541.35864824],[21.0,21.0,1352.0]],"n":1421}}}
|
||||
{"case":"gen0370","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1046.9095269974503,1216.9585972611512,93801.44385559917],[1060.9819047862068,1167.7751694852516,98724.8908084734],[52.59494850775976,154.8838040788177,7191.511701250267],[24.094700667149397,167.51575469406345,4672.119604318237],[1057.584254197164,1212.5775336456434,256949.16562273365],[1052.837446658372,1157.5374408238656,80759.86646861104],[90.27360788597458,170.59312998722928,9050.630690885522],[24.267745680237496,155.20725484511806,3709.8648399142244],[1088.2990381219054,1184.2254200143882,154757.0927635422],[51.51004819856783,135.7745289717269,8088.48024274664],[24.29616934739853,159.8104505945957,3762.2048794560765],[11.366437464809072,55.568331249931504,520.9078391325486]],"n":2927}}}
|
||||
{"case":"gen0371","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.05154793835139,97.31052161594125,7400.740523076202],[76.05524238096969,113.32046250088383,11978.74301738065],[83.05149596184737,124.31723324447671,12999.720360654372],[30.397940146381757,97.65791283128759,4468.438854273908],[64.04990624868205,101.30887992627193,3781.8198340503204],[53.27167218794827,90.53776774230164,7637.3035581812155],[43.83591509979988,83.10026495690187,6983.233965351939],[63.57847898701104,90.14132571433154,6695.007893880968],[40.27071176634328,79.47665615632869,6197.956489041647],[37.92506293689082,80.85521777059469,4208.16075559491],[20.732308856127744,25.82278199416325,501.72835422694106]],"n":2660}}}
|
||||
{"case":"gen0372","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.594400864469684,157.44191182915807,7140.201148408214],[77.19859562237585,205.65638619159685,13561.9742552001],[92.6011045107848,232.74103705460223,15151.129908038762],[45.59860111779526,157.12826748531563,5245.4406715602245],[44.80242457018662,172.53260959934585,6151.061541681664],[48.16416335735697,172.6766838772715,7378.623005119876],[72.25318408918437,118.76377628473642,9264.141908594615],[29.307933325681336,146.62226892157048,4234.035284164218],[42.27572394851753,182.96982718033246,4883.823980746948],[41.488445080004276,55.58609584578053,2671.2155496329888]],"n":2484}}}
|
||||
{"case":"gen0372","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.594400864469684,157.44191182915807,7140.201148408214],[77.19859562237585,205.65638619159685,13561.9742552001],[84.407784482075,222.21128594532613,13699.38154895118],[50.59860111779526,162.12826748531566,5590.45030074579],[46.51810929004029,172.8169248794922,6380.7949392542405],[47.4484786375033,173.39236859712517,7426.071483757377],[78.2531840891844,124.76377628473642,10269.29920744271],[27.677334044206535,144.99166964009567,3872.914713728007],[43.90632322999232,184.60042646180725,4970.678449674011],[41.488445080004276,55.58609584578053,2754.1924397929974]],"n":2486}}}
|
||||
{"case":"gen0373","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1051.6211791071014,1175.0840145503528,98046.39009291916],[62.28253522044806,145.52833295139587,3309.964076057661],[26.13241624214195,120.33063612404437,3361.121144215488],[31.65550055084213,150.6893390323515,2906.3244023242783],[50.691757978380345,147.04274979687375,8252.438366313203],[20.3912558890218,121.68513155117407,3880.744631172147],[32.279948382091106,117.18379954031231,6402.719084967431],[39.74309322320404,144.0799887802037,4060.4987586375305],[15.480484087352256,113.91507023804363,2555.6851862258377],[24.35835594003438,90.96810426483329,2104.53849904683]],"n":2500}}}
|
||||
{"case":"gen0374","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4076.2048770475067,4142.798042763691,509545.10955881275],[3077.053686340035,3140.883304214994,490827.35420163395],[3079.6795461636657,3146.89504229613,487815.2254656278],[39.52575080478719,150.34169878956908,5907.052925578726],[74.23445225718176,152.8276179733655,8003.512582720247],[55.87101194250836,120.6218124312085,7351.839204999595],[38.90090040807475,110.13828366025962,6251.078839180942],[89.68657873762312,144.82507789487772,17410.31215806206],[39.504327689532154,107.92148732676324,6068.777946818614],[39.30191349707289,111.8820021229015,5117.693251518992],[35.613700612762294,106.47896140300237,3746.1260343186223],[21.887462583589496,24.946586594373212,343.37305316523344]],"n":2909}}}
|
||||
{"case":"gen0374","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4076.2048770475067,4142.798042763691,509545.10955881275],[3077.053686340035,3140.883304214994,490827.35420163395],[3079.502631077594,3146.7181272100584,487770.6428639378],[39.7317473934761,150.70122581388165,5912.903878621037],[74.23445225718176,152.8276179733655,8003.512582720247],[55.694096856437284,120.44489734513743,7307.256603309684],[39.10689699676366,110.49781068457216,6256.929792223253],[89.68657873762312,144.82507789487772,17410.31215806206],[39.504327689532154,107.92148732676324,6068.777946818614],[39.33099499969073,112.06461406114298,5128.236030764229],[35.613700612762294,106.47896140300237,3746.1260343186223],[21.887462583589496,24.946586594373212,343.37305316523344]],"n":2909}}}
|
||||
{"case":"gen0375","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[24.512759648484195,131.42844733873568,3069.13812064728],[23.66217811078835,153.5169883152822,6199.681431250374],[27.312544430242852,194.81372880615083,4939.373112744998],[1051.1581046176466,1131.9242146952424,166765.906543704],[1.2472187454741706,117.41787843732041,9.181484578695517],[-10.715295207768213,138.5606323390905,-595.516935135225],[39.849719822049295,117.47653891078167,5882.307917706387],[-8.54958106118906,112.36896744970184,-313.282509606373],[-15.580583530587239,136.4195471849044,-1903.619192086638],[15.876743686415594,31.2793930805318,620.6676269992591]],"n":2414}}}
|
||||
{"case":"gen0376","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-8.979801824176901,165.06104484092285,-2317.255329003748],[-20.273310717118747,162.7382476072741,-932.8026003834229],[-13.896072239120006,195.54197010619026,-1645.7271083301298],[-25.92433397530601,155.6923124208034,-3585.179027547936],[-48.579286236182256,148.26777719084572,-6116.607478938222],[-28.540171726764193,133.93842553085182,-1080.5195645191316],[-5.293771356653438,135.32491351663438,-3568.1088315488887],[-55.15499259494,143.56044416767398,-7150.050979578959],[-18.85580556909303,122.03902460465666,-355.3880042990859],[0.0,0.0,0.0]],"n":2321}}}
|
||||
{"case":"gen0377","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[59.55457851109875,129.42291891108636,4773.510903955875],[1051.8960895903315,1124.9872889694093,260941.9093557713],[53.47859549803929,162.7089602421631,8024.5258404053875],[31.510443818339724,106.78545517226807,2857.839212258287]],"n":1024}}}
|
||||
@@ -401,7 +401,7 @@
|
||||
{"case":"gen0382","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[906.3975584199756,24767.169448480356,121740.30905093181],[6154.434663974608,33420.96220059715,788711.2458083731],[6861.609503518448,34380.01295499595,842434.438833371],["NaN","NaN","NaN"],[-2.602441580024423,23864.169448480356,-115434.70555503396],["NaN","NaN","NaN"],[3815.94921458392,28277.350654287337,407427.2008637715],[1581.9574884945678,11706.754971358796,250563.3300060129],[2554.0421834102744,31333.34835667558,502181.07025185373],["NaN","NaN","NaN"],[2086.7634073960808,14315.982404958133,29182.723400181185],[0.0,0.0,0.0]],"n":2821}}}
|
||||
{"case":"gen0383","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1055.256391735115,1203.574340399803,180822.2910299487],[4082.969348832251,4199.56723176195,351585.18760781374],[21.684951650491314,153.21795367832877,630.9262321998792],[39.32810841378372,171.404622942509,5589.541721854527],[-2.5162767220192155,41.07344258836272,-151.67153261304793]],"n":1089}}}
|
||||
{"case":"gen0384","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.76963589448407,99.00083653007492,9381.810753418005],[97.76964115759,116.00083439138879,15917.962941217029],[49.23975977251192,95.39389022343872,6715.107765470249],[57.76963519448405,88.0008358300749,5534.276555434567],[64.76964038430333,83.00083525472054,9747.962651333903],[51.23975681916073,75.39389222106398,7391.477358314785],[47.103923758903136,66.14034644675357,6643.616625528273],[25.255595000725524,30.527432416979916,1364.9940535928545]],"n":1926}}}
|
||||
{"case":"gen0385","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.73089053410827,143.49451251042964,12048.296246705351],[124.47403913681102,172.195620956309,19859.55854616815],[52.15275339152457,160.55501526329698,5819.095140862841],[68.6351998274057,138.39882180372697,7070.159579616928],[92.87342679840384,142.72387049530343,11256.393178349666],[69.66777123193832,116.9841266825392,9452.123021416679],[73.2551765652065,128.51923962770715,10872.696504028878],[77.07627739341368,120.61673807445798,10128.061381015377],[0.0,0.0,0.0]],"n":2085}}}
|
||||
{"case":"gen0385","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.73089053410827,143.49451251042964,12048.296246705351],[120.47403913681102,168.195620956309,18843.55854616815],[51.62696283962427,160.02922471139667,5817.517769207139],[72.32072344765494,142.08434542397623,7217.377140856977],[86.71056465904508,136.68040821945996,10964.509268629547],[71.71194071474486,120.90889630183045,10011.837198545369],[69.43044008250659,124.69450314500726,10320.307213476344],[74.49890573771282,120.03936641875713,9949.732992216792],[0.0,0.0,0.0]],"n":2087}}}
|
||||
{"case":"gen0386","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[90.87986590794272,374.7604173628403,9187.10061610425],[84.57538417493393,362.75807110270864,9146.126800184567],[58.051460192534314,383.9647254260928,9708.9378345073],[83.5002355146317,359.9440552166653,9854.928864774301],[18.839152601586054,221.47970568645619,1473.6054422093184]],"n":1165}}}
|
||||
{"case":"gen0387","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[23.037294481538275,121.32817033948342,2879.3897991679196],[33.208011751978255,131.5007883883866,8697.642009837731],[1019.7224454055819,1130.0139548563482,75850.9586893392],[-0.17611003977481243,100.16421185921979,69.50125964651704],[53.642462286400296,96.3587535074028,9987.526887468206],[-11.78314061126224,97.5448518629287,-823.4629056851047],[7.356494509756526,94.61215452991902,2918.8381899440747],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0388","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[190469.13980321365,6762542.718986884,102582222.68100373],[-1062026.8199020666,7036371.612813866,-165012742.0438053],[698411.4452774787,7408037.948091214,205786563.76149938],[-583912.0579663828,6802988.361227167,-143284712.54748055],[-1264032.110261635,6594609.025841223,-141985078.97565877],[-867896.6475594336,6485369.327213034,-34160941.026038475],[431591.37769249757,6894688.694811639,-19838452.49324344],[-1399713.7122445842,7276815.196494829,-128115454.26559551],[867027.3201147417,6040022.606775425,163715885.45071623],[526691.9002009222,5113465.045682315,5122465.519163806],[-1596248.707008421,5980193.882098387,-141664596.38090575]],"n":2764}}}
|
||||
@@ -421,12 +421,12 @@
|
||||
{"case":"gen0402","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-9627.898363937305,14435.845642393248,-1259788.6280444637],[-9668.897363937303,14408.846642393248,-1406762.2368202577],[-2905.913273910296,4867.897700062275,-156844.0527455209],[-9644.898363937305,14424.845642393248,-1271351.4004646763],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
{"case":"gen0403","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[58.513261812880415,146.19384216051532,8091.609175451456],[77.82418279208903,162.4785361289756,13936.000939536254],[45.93157232892005,131.44830465022488,6228.36126010485],[49.538212132180696,142.1768451058729,5301.318212877592],[29.296748130401106,123.01816582442939,4745.520239156526],[53.58170090493002,117.96986264735405,7313.687112701551],[30.477159582494046,115.1589790835131,4505.494493462296],[36.82482752515075,111.75116178866483,5752.759859213958],[0.0,0.0,0.0]],"n":2075}}}
|
||||
{"case":"gen0404","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.261771177298115,275.135439064642,4986.940518668583],[39.89441142008397,261.67108669902035,3354.577786126774],[1085.9519703006704,1296.8134495427917,111794.74783935654],[74.15181792335788,257.7773056697969,7054.3582984008735],[14.505153051713496,108.32702516137269,199.72287968514954]],"n":1121}}}
|
||||
{"case":"gen0405","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1624373.215036659,2545780.6699787667,195534184.8424055],[1642112.93708372,2563139.1980316844,211499561.74495506],[1325044.2856735396,2245882.179219517,154411568.917704],[1654400.215036659,2575807.6699787667,206090284.41455907],[1713586.3492573465,2736855.815578897,224392100.9566751],[713145.5631206023,1224771.702976294,47688123.43921508],[1327366.6736527765,2044060.781267054,200176464.68729678],[1631991.8268971886,2655333.727985324,207324188.69003448],[1121902.3972884766,1838175.4497054126,98564055.25913742],[21.0,21.0,302.0]],"n":2395}}}
|
||||
{"case":"gen0405","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1624373.215036659,2545780.6699787667,195534184.8424055],[1642112.93708372,2563139.1980316844,211499561.74495506],[1324880.7711125484,2245857.2780255647,154379534.2536286],[1624377.215036659,2545784.6699787667,200027004.6295956],[1743453.8346963555,2766861.9143849453,226118234.92322427],[713145.5631206023,1224771.702976294,48401269.002335705],[1327372.6736527765,2044066.781267054,202831407.26417863],[1631829.2876422377,2655309.802097413,210561907.82847974],[1121902.4219824353,1838175.4743993715,100807860.07840838],[21.0,21.0,344.0]],"n":2397}}}
|
||||
{"case":"gen0406","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-524993.4620789493,7876557.15221034,-156379971.98071226],[-295284.0029862692,7583739.87584706,63335879.6054599],[-1600628.1980570955,5576535.335937732,-132714839.3259185]],"n":683}}}
|
||||
{"case":"gen0407","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1904546.5755833779,1970619.8174961992,261744646.17947045],[2237971.9386020335,2238004.381487766,276928813.7126835],[1762947.485089463,1829026.727002285,219850258.382895],[2439725.583554411,2439758.4276359333,321661044.8782222],[475443.9744069938,475459.72083978186,33647501.17531662],[2107524.980954783,2173599.521231349,290221540.6194854],[1790778.6131948472,1790809.7577168357,192628363.89804837],[21.0,21.0,806.0]],"n":1907}}}
|
||||
{"case":"gen0408","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.853897272544124,199.33383379440696,4311.1802882419515],[40.29716900658641,188.68577624471288,4559.547998034209],[-2.4029667542984035,5.198180938646601,-11.13079998636306]],"n":523}}}
|
||||
{"case":"gen0409","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.486655641175496,90.70252764786476,6761.721014386363],[52.49168746501697,97.70784694680185,8919.23101419098],[31.004954025655262,101.8160115144568,4105.597358145295],[24.491099709367923,67.70663959348693,3514.3389816700947],[42.65839178728537,71.46897428864088,8177.243528372899],[24.182497749764234,69.425163486474,2719.4634518871976],[17.447921645637386,62.802171453744585,2028.5406350041978],[40.69520396518958,48.53017704397091,3502.821564737462]],"n":1992}}}
|
||||
{"case":"gen0410","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.92104124275729,103.03881702819363,6719.066655073978],[51.38398764427366,101.50106977180754,6866.426427878433],[42.476975907023444,98.59475169245981,3659.2939841747043],[50.31877610851771,83.73013116221159,8891.086954255916],[40.96761848533899,81.73127840297963,5310.586788494613],[33.17810156448331,81.9435724859729,6279.312319214738],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen0410","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.92104124275729,103.03881702819363,6719.066655073978],[49.38390842722343,99.50104010175036,6445.411299831336],[42.476975907023444,98.59475169245981,3659.2939841747034],[50.318696891467496,83.73010149215442,8891.085293107353],[39.96761848533898,80.73127840297963,5242.586788494613],[33.17802234743311,81.94354281591572,6279.305271306761],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen0411","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4069.908356162664,4088.412691765544,516141.418818079],[3070.909361509077,3089.411699097928,496657.73877973243],[1064.3184209252543,1090.8207571746054,82804.40889435941],[43.90835761585432,62.412693218735576,4500.690111266136],[34.996372677686175,56.58325342597321,4896.393690363952],[72.0445175223783,86.21396293849557,10097.212410688677],[30.63974956624997,49.14008523306798,4090.6907759982428],[34.998100813885216,56.58374054385039,4355.470755484632],[43.73030374358778,43.731902602412966,2803.659167341102]],"n":2231}}}
|
||||
{"case":"gen0412","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.92658447629022,129.0308550971997,9712.160609835855],[85.40395798664686,142.77257796312563,12802.47526257247],[39.28473710311474,101.40015500420783,4396.895363982874],[60.85869547736029,118.15753232947003,8014.277633469345],[50.013289765503174,95.67385978406848,7321.999751382107],[49.820253804066816,110.5953938089768,8006.129868994935],[33.443824385683094,64.66757491203876,3630.341417613684]],"n":1750}}}
|
||||
{"case":"gen0413","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[83.81638213276413,115.80664469815788,10700.944132470606],[89.80725758428001,122.68285629262878,14116.01050236545],[103.96040069060497,136.8324790713111,17341.284586559002],[91.84285855306513,124.31513746295659,12274.284764374783],[66.77264637055116,99.84758876966737,8526.312063751257],[59.02622253619125,93.46584996745607,8241.851839916091],[66.08519804767458,85.73368063090635,8603.919664968387],[85.13020281274963,111.17797077689204,8787.2371970983],[58.83441752507479,93.23821701096193,7369.924063999979],[59.33735220513885,96.61388734130293,7007.385503184079],[42.1,42.1,2074.1]],"n":2726}}}
|
||||
@@ -512,7 +512,7 @@
|
||||
{"case":"gen0493","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.45402939578067,149.85431386737753,8103.4660281665865],[70.29387936130348,155.4151911107471,9657.265160481491],[63.186678128329746,146.43674651876708,7408.890782623742],[61.12659071906125,150.52687519065802,5075.646816214178],[44.77449876481251,138.89754731973724,5321.235575352806],[66.9514890735349,126.00519685210925,11993.70321453428],[32.31966225126366,119.30821220923777,5218.2292900947195],[38.88653577061046,133.07538366929427,4029.642127239118],[32.70480573781876,69.15101993327727,2832.973974655653]],"n":2240}}}
|
||||
{"case":"gen0494","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[115.04379289735748,136.84961623119847,14602.030520481607],[106.04604775178015,157.85187108562116,12037.762705149304],[91.08971017436933,142.89553350821035,10191.310340844788],[87.39285874775734,121.1986820815983,9763.295705266204],[72.57422926958087,99.11144482547485,6585.966222341893],[100.48779275747403,122.293616091315,11313.829241659096],[63.779234402441965,78.31644995833597,6193.857133927388]],"n":1765}}}
|
||||
{"case":"gen0495","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.73042791870796,137.70213441386636,12667.542911466742],[101.40182561392662,140.69024497657335,13711.536676305837],[99.13952316789346,170.79005874499038,11924.12463130849],[95.12623152133226,144.58837246541913,12117.764004125089],[78.12657078431093,118.37503552847414,10316.442116550255],[74.92011801430704,119.86725473918621,9985.88749288062],[84.90090905676672,121.62375879670604,11582.57230590392],[72.38689980404028,112.04335547190024,10116.02416465523],[70.8078990689715,117.78751708274449,8867.00164627596],[26.84988545987524,32.06961696784842,805.9966815919847]],"n":2414}}}
|
||||
{"case":"gen0496","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.12288651700467,164.22639150690705,10260.468728285794],[86.54411291643692,178.8609688071303,14515.819157085256],[86.7843014030602,191.03898484599483,13692.220765235234],[1073.366478671718,1159.2706772638314,85478.87376892721],[60.42773334337181,157.06461558525166,7701.211324591047],[60.38278013619561,122.4658279657998,10350.552203835092],[51.928618302621,148.10454218108487,7649.488964208017],[44.00479410261531,134.19452733082406,6535.458457836927],[20.7241753056634,38.934100696285384,921.6590737635056]],"n":2168}}}
|
||||
{"case":"gen0496","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.12288651700467,164.22639150690705,10260.468728285794],[86.13619419162522,178.45305008231858,14411.799882258274],[87.13737664845557,189.2853846409947,13705.941822029945],[1077.366478671718,1163.2706772638314,85911.51267297969],[56.33329623989701,152.86350303138133,7034.814525475649],[61.663321970784324,123.7463698003885,10463.753693625995],[55.56098165347167,149.44134446488624,7755.170683988405],[40.35757510175842,132.73619394662094,6300.852353316057],[21.364052017606113,39.573977408228096,963.7936189193751]],"n":2170}}}
|
||||
{"case":"gen0497","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[121.36114124588975,361.84525149470903,18458.607637598972],[44.256903544807315,321.86033854474726,5182.439363930062],[106.44918697750991,341.64798951512455,13222.353549849586],[73.04235277912636,307.926362672964,10199.099013993204],[77.11452146822486,331.84769873886347,14081.65946232917],[67.50491609107621,305.3421911241533,9243.68155214043],[56.84736264674488,311.1945977597661,3464.8808122259134],[91.86699281457736,359.8009517541268,9205.78498441812],[121.21333972454573,319.2196381192086,17027.582629879547],[47.52883373790773,329.0051294847966,9531.872212043618],[95.96072702925744,330.48322641966445,12429.015171232535],[26.657319255169106,86.08112684182376,1885.2239613316592]],"n":2896}}}
|
||||
{"case":"gen0498","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[45.91902404683696,90.66517125130903,6633.945593301938],[52.91902326211642,103.66517035728486,10904.431227007755],[37.32207579170856,112.2374581653742,5147.412100246921],[26.919023867884064,83.66517110018115,1518.9738473129687],[30.91902371211642,79.66517080728488,5965.269378773019],[46.32207605484985,73.23745851199061,6033.47336937595],[9.961907267503292,61.86081927386518,1681.8234832433448],[44.90313185479068,71.58113171875354,7670.4448315329355],[0.0,0.0,0.0]],"n":2085}}}
|
||||
{"case":"gen0499","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.2474258496063,230.91660692779587,5037.379914778518],[42.23282298411063,221.64901952067555,4890.345939285887],[0.27265117187278953,6.019666630248247,-3.596606368504848]],"n":523}}}
|
||||
@@ -521,7 +521,7 @@
|
||||
{"case":"gen0502","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[5.894025716213518,168.04017411105164,1223.928681805528],[3.3379960720168285,164.4563870819969,2500.741824435181],[21.63552195555569,209.08818722704322,3189.496186640723],[1049.457213311735,1197.265485986735,173445.79987445954],[-28.45482100394701,148.69337059805997,-3745.118267135241],[-24.054664161891928,153.25965373434735,-3068.589387889347],[42.0309874204088,139.80402291951143,6523.846117378963],[-33.060379868716126,147.17620524299312,-2979.5289550324346],[-30.588933035366413,141.94136841153812,-3834.421639394568],[15.319537262257716,38.70967733930234,796.1700759864284]],"n":2422}}}
|
||||
{"case":"gen0503","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[110.63971144955616,157.37988529881656,13676.23473782403],[125.42612135109988,177.61659126387977,16439.10410315875],[1104.104479540163,1166.2510884291232,95339.94682577341],[114.08443516796787,152.27490508074771,16539.991136600365],[57.276624237660656,97.07416612128769,8509.193160583938],[107.4261213510999,145.61659126387974,15209.81048690147],[0.0,0.0,0.0]],"n":1599}}}
|
||||
{"case":"gen0504","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[79.18470042693474,110.67813514368754,10227.59368286944],[86.50591755930918,121.63983555223234,13369.296652954094],[86.61235368274583,144.5840894389017,10985.152699322647],[54.370886110695025,88.9189119445187,7073.760133317533],[66.00785115374774,112.85816272611963,9919.166107333982],[62.50840887345789,95.43596932292859,8918.909313581127],[49.08716838586028,86.07000586281187,6843.036695127295],[44.62301215631318,76.04232808303853,4054.73770882578]],"n":2000}}}
|
||||
{"case":"gen0505","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[98.68827198875108,149.28436500423476,13235.387579844886],[113.64660351740868,167.61860519760677,16430.699910865882],[72.9537795654977,129.39699384045755,8663.407016008267],[84.687044610518,139.732242913441,10852.77076829894],[76.16955205120398,112.67420536494438,10423.185637883304],[74.35795242265324,131.87593329852714,10623.331400004832],[52.976606964726805,81.31423518656396,4945.92171596751]],"n":1748}}}
|
||||
{"case":"gen0505","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[98.68827198875108,149.28436500423476,13235.387579844886],[111.56073991162698,165.37986285130128,16046.431886471952],[75.9537795654977,132.39699384045753,9009.694136789205],[80.02111462130402,136.9134341837032,10158.367028976967],[79.56917387303314,116.07382718677356,10688.78677380696],[72.82365153171962,132.1887536670697,10251.867559807119],[52.976606964726805,81.31423518656396,5051.874929896963]],"n":1750}}}
|
||||
{"case":"gen0506","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1079.3078634634512,1194.8349307453523,9520.051744563549],[2093.7148834909203,2222.224893024024,333907.34468110034],[1060.96434050845,1212.2150787681942,77706.2280158419],[91.9078498080476,216.72745469501234,15437.904534195102],[46.99879987949185,166.53704561278278,9523.94149825495],[55.88284117791563,178.3973200190874,8649.804745038287],[9.083134792156505,32.57273574475342,222.00196544351442]],"n":1574}}}
|
||||
{"case":"gen0507","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[55.39495313679638,96.16139235830235,6705.391652590581],[51.39489417156315,92.16783897915492,10041.496295134157],[57.39502166827982,100.1699156602878,9983.231356847959],[1040.7428619793882,1115.5150271132106,78161.26267429892],[49.39510057645543,90.16153979796135,3868.6124255907553],[15.222231388262987,63.41873292653098,2575.282616868592],[36.56650384589638,69.9178411072281,6346.680372887058],[49.00905680070052,87.16884418340751,4128.0996709116835],[20.018960126742318,62.100146905379994,2417.008818654836],[17.605810672976745,60.97987711114624,1923.899467470355],[23.899074061594042,24.598311483185512,400.9750894229998]],"n":2655}}}
|
||||
{"case":"gen0508","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-3207.8780368740663,78241.73356779713,-389434.06949359074],[-4123.198900107756,77327.5739868883,-625888.9885626556],[913.8544506007025,915.0157329255604,951.841246129249]],"n":523}}}
|
||||
@@ -530,12 +530,12 @@
|
||||
{"case":"gen0511","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1076.156886896912,1324.119512591171,13424.7562092656],[29.273576256156257,269.97381442964246,1342.640228669468],[3.1474273222605187,170.44271145801147,-905.9996294959185]],"n":688}}}
|
||||
{"case":"gen0512","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.18132465672485,92.61444456871939,9867.801513649225],[92.18132655007048,109.614446462065,14894.713694907268],[63.53039099821903,78.96351091021353,7758.057986504149],[51.50992991748396,68.28585136488513,6677.0172520163605],[77.41424088539362,83.21581265398517,11503.41690909234],[48.148374179174965,64.92429562657614,6759.05134700285],[56.21427471806735,72.30459309465522,8112.050271914335],[21.0,21.0,806.0]],"n":1907}}}
|
||||
{"case":"gen0513","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.44923142348101,224.10379789736956,3739.8933195678374],[51.041458799355794,202.5842027317236,5984.399072459149],[39.23534598280051,206.07185360205625,3748.941600238511],[56.354418565801666,200.08652312872607,8552.432754506895],[16.601100108239482,113.28972937839146,1291.430005468025]],"n":1172}}}
|
||||
{"case":"gen0514","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.61923242806261,119.7572468141713,7069.086344610602],[51.81360042155761,114.45201030536467,7807.526227628701],[58.6047910545452,117.81440351109606,7627.652421962422],[36.11486790395097,96.39222367392782,4594.6067223491245],[34.148706550277964,93.71937312065894,6277.596406546891],[61.122181437073124,86.46952293131602,7595.512189169827],[26.161150481930665,88.31645672624042,3539.5998093622857],[35.1753778461356,85.87622455316308,5269.717186626409],[21.0,21.0,470.0]],"n":2147}}}
|
||||
{"case":"gen0514","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.61923242806261,119.7572468141713,7069.086344610602],[51.81360042155761,114.45201030536467,7807.526227628701],[65.60479105454525,124.81440351109606,8868.847557273113],[36.689483476720426,95.81760810115836,4778.397792454825],[32.211869701073724,94.93176741699362,6012.844037985182],[68.12218143707314,93.46952293131602,8710.75655204375],[26.29398837941813,86.92854350620907,3626.8218255391857],[33.68031867221336,87.90191649675965,5078.99046506339],[21.0,21.0,512.0]],"n":2149}}}
|
||||
{"case":"gen0515","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19.957130634435437,140.3191430380532,2458.7126445879676],[1066.4762222997952,1147.643827329453,12084.188952822169],[9.774274815187626,67.42865493558665,800.1415460883685],[9.402536328293415,52.494177106956315,-137.0943030781712]],"n":929}}}
|
||||
{"case":"gen0516","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-3779.2040511044306,67561.86929601779,-604996.2300813796],[-4788.812020970297,62249.82500103762,-421632.67432383576],[-5866.453254438406,74059.91764677297,-823258.2736718705],[1.4506678617305304,3.36247780899474,5.10375025182794]],"n":773}}}
|
||||
{"case":"gen0517","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.08380774190053,183.04257747829516,8147.556813421721],[91.18861359107706,183.22994061026137,8804.022105494832],[73.95069802778822,174.19167745784847,7357.341263842067],[102.40400586329649,188.4765949586075,14379.35197051138],[59.36702828784536,143.15291679894582,7001.999665058288]],"n":1253}}}
|
||||
{"case":"gen0518","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1079.4140050973733,1226.863969730719,104394.57496245408],[1057.2887578240054,1239.5423453129285,91125.64450030173],[69.3303941946477,197.97275388266416,9695.257598897915],[32.47805094063565,193.99852033955676,4786.200762409663],[19.24743554472964,202.669378936552,4107.164467213577],[31.363445353337376,242.99009840016345,5719.516142503527],[75.8067712024325,176.24637643097572,11683.722242865375],[26.685881682210216,182.2443435532373,5576.856627775896],[30.67410096230971,193.68595071839633,5254.561711927745],[3.281415767157127,15.50405102740554,49.84543107801285]],"n":2333}}}
|
||||
{"case":"gen0519","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.42867753938349,105.56323022005219,4665.782031011935],[36.58771529093315,98.60513962565214,5797.1261348515745],[55.304719876138435,124.648128799152,7045.344136866823],[18.289511747065138,81.19114942160974,2345.051819914082],[20.745345264592245,83.20454084778025,4816.087501839807],[58.765086537595224,89.98422341832435,7220.240345620996],[9.20840619657727,73.73127959392008,1675.879855415526],[22.234592434460488,77.78982839254172,4206.727804586248],[21.0,21.0,596.0]],"n":2153}}}
|
||||
{"case":"gen0519","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.42867753938349,105.56323022005219,4665.782031011935],[36.58771529093315,98.60513962565214,5797.1261348515745],[53.38122109297014,122.28285876751472,6811.175222724046],[18.36601296389685,80.82587938997244,2362.538511635662],[20.745345264592245,83.20454084778025,4816.087501839807],[57.765086537595224,88.98422341832436,7079.240345620996],[9.284907413408968,73.3660095622828,1691.3010142826502],[22.234592434460488,77.78982839254172,4206.727804586248],[21.0,21.0,596.0]],"n":2153}}}
|
||||
{"case":"gen0520","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8084.280309710468,60893.20565622742,1046171.2806540718],[17152.75114087437,78875.12494791311,1560515.0465412955],[13242.509736087033,70978.58123536369,2714560.8435840607],[0.14445057521793436,0.14445057521793436,0.14445057521793436]],"n":769}}}
|
||||
{"case":"gen0521","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1154.7125547166368,1346.1992442438407,28049.015726561363],[29.47910592863554,200.08055105888118,1540.9445268598977]],"n":449}}}
|
||||
{"case":"gen0522","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4096.990432781841,4161.499242811855,519103.9729927708],[1086.108012396522,1184.206509495599,86550.13779024534],[70.35626143106337,147.31378234471222,7941.655349318768],[66.07332415985748,133.27439949166921,8410.965904616334],[68.32536471810963,123.77994895508643,8980.224755588359],[65.85795560602662,136.20886229764164,9029.310099507456],[45.03511196822643,85.78857572903254,4719.382458007829]],"n":1756}}}
|
||||
@@ -566,7 +566,7 @@
|
||||
{"case":"gen0547","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[99.7869941028379,173.4239276328515,4903.953859287718],[18.631779794344567,41.6329513903544,857.2506151160053]],"n":356}}}
|
||||
{"case":"gen0548","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[105.83092382899004,376.72136127383754,8989.191476261523],[63.48309342962115,328.9368922417422,7961.066836114978],[53.67660136232777,286.1466445496797,3350.0912763899782]],"n":760}}}
|
||||
{"case":"gen0549","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2111.5293294071867,2353.5085578368953,103762.0724668912],[2131.173554022158,2353.1222104488315,347619.3281619168],[74.42676721769078,303.8338476959442,9323.046880466376],[87.79671044789521,325.43587330813034,12391.391523415015],[-6.73421632450583,169.31699962037212,-519.4591809486064]],"n":1173}}}
|
||||
{"case":"gen0550","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.88853192514235,111.77544971755347,7822.953648532312],[74.5020030250451,126.39413509826922,13009.210104782072],[50.54638704660239,112.43460840921675,6597.225771086714],[38.6647133998585,88.55358654757447,5052.050712325333],[53.662603011901375,86.92445891745187,8969.937776306731],[44.446337869710455,93.86608060989425,5770.905736276728],[30.330269648775086,81.89311850309726,4314.0209488910195],[42.9664854818131,53.762943752942356,3628.806096605851]],"n":1994}}}
|
||||
{"case":"gen0550","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.88853192514235,111.77544971755347,7822.953648532312],[74.5020030250451,126.39413509826922,13009.210104782072],[50.547582790002615,112.43775950792185,6597.338442249216],[38.665909143258716,88.55673764627957,5052.07609421962],[53.662218222099945,86.92407412765044,8969.882751365127],[44.44753361311067,93.86923170859937,5771.109283937648],[30.330269648775086,81.89311850309726,4314.0209488910195],[42.96610069201167,53.762558963140926,3628.7922441729993]],"n":1994}}}
|
||||
{"case":"gen0551","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.1036925458691,70.76582010382909,3013.068979172942],[30.400864047992336,59.904892583876034,4166.001029406119],[44.409835176905226,105.3709579167398,6385.9211296436615],[62.076988072996215,99.89319012003276,9321.489288223516],[1069.1084338706592,1092.5386021097422,74609.71533393206],[26.90698466361006,60.24968995393912,4434.423245606879],[29.855582060091034,60.10893928011162,3658.754519269792],[3.5179268321836137,5.745934662751535,90.38829539069582]],"n":1828}}}
|
||||
{"case":"gen0552","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[382.24921783127775,580.04161339366,38165.484902785916],[12.950654238185244,290.8990303913018,2987.8074122752664],[351.91050380575314,562.683453284686,32419.274597783526],[17.54313468677423,282.6105042961113,2955.1915197615813]],"n":1014}}}
|
||||
{"case":"gen0553","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.37594410266097,97.2115880888912,7453.880399361127],[15.7250116998043,72.56065568603447,1548.8336567177346],[45.14104906290733,72.08626466311951,4755.570678536727],[41.37594410266097,86.21158808889123,6200.256343070947],[21.0,21.0,1520.0]],"n":1173}}}
|
||||
@@ -582,10 +582,10 @@
|
||||
{"case":"gen0563","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[110.57993427661658,145.54544262292072,13304.092847934815],[112.61966242286813,168.09094094169464,13289.429472182368],[84.16569384223456,131.8615275598831,9455.892532703703],[95.34275292563447,122.27468218714974,12299.741726603348],[76.31658367530463,112.35724794418515,9832.085188569188],[90.93660775672181,123.82001598139816,12537.684962427362],[57.787413119700275,74.41594768273092,5457.798102923749]],"n":1756}}}
|
||||
{"case":"gen0564","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.425762924637766,123.48877291008412,7339.495207736043],[59.599989045276935,99.3157561063315,7667.927225198758],[37.394254380689695,109.4572643661361,4674.844727656944],[51.73672983242441,83.59461336295172,7196.249601876877],[31.88392114633584,95.37633851657318,4802.469370695828],[40.14606209828681,56.57453824402323,4370.651642036312]],"n":1500}}}
|
||||
{"case":"gen0565","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[220.3081452635871,235.26228210602568,27422.932606235303],[226.95961701459998,241.91375385703859,30982.367484224123],[217.71308564092212,232.66722248336066,26908.79594479653],[203.62956711593978,224.58370395837838,24422.374806923788],[217.13458525185484,232.08912395231377,29523.197948137407],[203.8382225385443,224.79195752296258,28351.51224155163],[185.22987418527717,201.06646555691134,25128.22865611763],[213.78669251760542,229.84973908171295,28266.70126738938],[14.561043096384173,17.554391126332522,277.81961862279246]],"n":2079}}}
|
||||
{"case":"gen0566","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[116.21148241147137,138.64348404802948,14882.75548359073],[126.92060175480574,149.35260339136386,18879.32981982826],[129.30121406379703,151.7332157003553,17096.897980513902],[117.42107676790327,145.85307840446137,11983.091726552468],[104.75452614388853,128.45245973366042,14346.405157258301],[87.27293874998264,100.96167455447431,11013.58395852244],[103.6454437161548,125.866043427074,13399.426992981911],[94.52021454984688,121.38232395105678,12727.281661827594],[93.72065645287059,110.45661837156325,10862.437275532773],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen0566","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[116.21148241147137,138.64348404802948,14882.75548359073],[126.92060175480574,149.35260339136386,18879.32981982826],[127.30121406379703,149.7332157003553,16678.897980513906],[123.42107676790327,151.8530784044614,11655.512803320375],[106.75452614388853,130.45245973366042,14453.159683402193],[87.27293874998264,100.96167455447431,11100.856897272424],[110.6454437161548,132.86604342707398,14058.280107357125],[94.24295315092371,121.1050625521336,12845.065911404026],[93.99791785179376,110.73387977048642,11050.155849837434],[21.0,21.0,470.0]],"n":2403}}}
|
||||
{"case":"gen0567","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2099.312355010199,2126.2943670718005,186521.63343649943],[2480.574643889431,5246.664304812862,327265.8750682657],[1309.3481230840778,2789.095434267023,27593.06443881489],[1222.285453342419,2548.638660271004,108287.01051991193],[16.098700847188642,30.630396206031993,862.5273879813108]],"n":1162}}}
|
||||
{"case":"gen0568","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.72902787223986,120.30847795907937,6445.979686022119],[41.788712967709,134.62000893269612,3736.4458108427757],[11.949173804636374,50.691375274293655,234.80277664246515]],"n":696}}}
|
||||
{"case":"gen0569","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.15064831319771,105.90290071094536,7134.120695296632],[70.14511581403872,118.9049416146443,12343.832705343979],[39.49531091435475,98.24908182940555,3675.1358896040792],[38.148979234058125,86.9074573730229,4997.6277341466075],[68.39570681543701,90.64881122664319,12003.977531447737],[20.800103113093943,74.01965062097699,3607.4282946000576],[35.49223615090901,79.78476684137931,5689.837446418934],[21.0,21.0,848.0]],"n":1909}}}
|
||||
{"case":"gen0569","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.15064831319771,105.90290071094536,7134.120695296632],[70.14511481403872,118.9049406146443,12343.832449343978],[43.49531091435475,102.24908182940555,4094.23566106274],[38.148960165967495,86.90743830493224,5035.771980881374],[72.3952000202942,94.64830443150038,13085.239366480266],[20.800626976327404,74.02017448421044,3649.0293125527123],[35.49223615090901,79.78476684137931,5760.8219187207515],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0570","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-17468.238888011027,270464.05322628724,-5229126.931265193],[-18003.685547239296,199307.11738787094,2286251.7368431655],[-27342.58205912438,244790.50921435383,-2833406.5105134672],[-6909.4563965321395,77471.68561356324,-887644.630628579],[-2057.5153089615233,113055.55203414618,242125.3947732011],[-22562.1502562385,255774.79430883902,-3761279.1971485186],[-14989.38441389216,224434.40072770882,4150978.9703665],[-34924.63831144154,207772.96014335082,-1557832.0940617293],[-3695.6320029268886,76483.46048629461,-602328.9195242306],[-9121.334940811406,46143.42328905779,-836703.9956765565]],"n":2461}}}
|
||||
{"case":"gen0571","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[37.26738407996901,161.25285605823422,3981.7123085120675],[22.059839372723392,166.0868452566684,4298.833382886694],[42.84842917014064,184.96702349471113,7799.5805072124185],[12.596672399754892,154.66614735581518,1188.142185098026],[-3.1557783809719653,145.8914784252215,-1685.1781512267812],[5.203769987262186,137.21913498741057,2940.862967602868],[52.15668994073355,114.55553727964804,5951.407609386978],[-3.9059495594843523,137.37747794982795,-429.80740515633636],[-16.94646344726875,136.0264197318762,-1644.7047645299729],[37.532031370232176,53.004439929979505,2700.620811251989]],"n":2486}}}
|
||||
{"case":"gen0572","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[341699.84083694295,1009038.2812232411,34341289.36143058],[239981.49264595867,673051.2684409863,12597648.22718941],[335589.81889541895,1005107.6249557436,35110353.359326795],[126328.63153489585,342869.5194324097,2664603.526078137],[453176.53819356416,1194864.2234029304,61416683.20819065],[-3916.858187082394,140442.15056138986,33536.220316010906]],"n":1504}}}
|
||||
@@ -630,7 +630,7 @@
|
||||
{"case":"gen0611","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[92.27338680290471,133.61032498380143,10835.533530023657],[81.13152413902816,122.46846231992484,11101.124220574162],[63.70221508791544,117.03915326881214,6077.286012912971],[76.46736570956224,104.02532449682673,10541.234230923132],[60.09435248041256,99.6523112676771,9158.50890669656],[60.0771309544579,101.41406913535457,9457.73618090295],[21.0,21.0,1184.0]],"n":1669}}}
|
||||
{"case":"gen0612","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[71.58860606071289,97.62694186171196,8888.8768506269],[77.59408541889724,105.63412806454252,11982.37976675087],["inf","inf","inf"],[47.54311083512759,74.96299937826093,7178.715200011471],["inf","inf","inf"],[65.1529801356494,76.34487783382976,8725.495331251039],[41.822799249505415,69.24298248547963,6086.626643629688],["inf","inf","inf"],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen0613","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4086.7249869522025,4107.55390212916,518378.5855154719],[79.81668094809187,106.64394727861308,9699.78742288399],[68.92981494850889,89.7603789719065,11095.160034634315],[49.367917018879396,62.31088874453252,7185.513732814886],[66.03768345622547,86.86659863318486,10277.809572637816],[21.0,21.0,1394.0]],"n":1423}}}
|
||||
{"case":"gen0614","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.58454645088247,94.99060016692816,9740.176825322327],[79.58592968409368,96.99033098122761,13813.77384515931],[59.68593680813489,77.09032930746416,7939.389849365592],[58.58454645088247,81.99060016692817,5503.434976500364],[42.56762149796622,60.01166701508237,5855.491480833793],[67.17545088845534,84.73985264951982,9188.08816638492],[37.75309251941615,57.62958560885069,5432.344310581651],[51.41798865387299,66.35029777745774,7773.644138408279],[0.0,0.0,0.0]],"n":2083}}}
|
||||
{"case":"gen0614","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.58454645088247,94.99060016692816,9740.176825322327],[78.58592968409368,95.99033098122761,13562.77384515931],[59.68593680813489,77.09032930746416,7939.389849365592],[60.58454645088248,83.99060016692817,5106.0195229512465],[44.58665673664049,61.99263177640811,5904.9511586710405],[70.15641564978108,87.75888788819408,9690.772850866659],[38.75309251941614,58.62958560885069,5508.850495620484],[51.41798865387299,66.35029777745774,7876.4801157160255],[0.0,0.0,0.0]],"n":2085}}}
|
||||
{"case":"gen0615","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[132.53321830146356,209.43950411927366,17315.904425370667],[67.29110270594947,163.02270299480762,8843.507642621866],[111.02018515021643,210.50118076171702,16021.320482632042],[89.92659854524045,171.30942131081858,13110.868734452066],[88.33259116878659,172.8756626637925,11201.20141927331],[90.48199837622997,185.05101310065038,14041.756974857251],[77.48673602864461,164.93978092732925,9389.256770150718],[13.22220634936935,34.57604142541794,604.609504795354]],"n":1838}}}
|
||||
{"case":"gen0616","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.8110835215696,123.31324415345645,12378.813670389347],[45.73434281070358,104.23650344259045,4941.832459623034],[58.11307775375392,72.0114962224703,6431.199031438081],[58.81108352156961,113.31324415345642,9358.090962781836],[21.0,21.0,1604.0]],"n":1177}}}
|
||||
{"case":"gen0617","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[87.62205447335339,115.19731628499042,12980.23257778709],[45.972382375016295,85.54764418665343,5591.78369849869],[55.558322880996535,70.74933574421868,5599.373816206504],[68.62205447335339,102.1973162849904,10123.09616725429],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
@@ -649,7 +649,7 @@
|
||||
{"case":"gen0630","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.14438883951619,225.99396156669388,5114.645404843306],[23.709957269714117,228.78370166061245,3045.2844603006774],[21.37201714463787,212.22158987181552,1003.2714324556473],[35.136498684530494,167.48163300117594,7051.616564980166],[27.05160113583954,166.57912557241457,3234.896797982196],[10.456918670406232,194.1241012776543,3295.542998077726],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen0631","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.540202683512426,128.8112161088367,7607.939479324799],[92.08638933986066,170.53418545045332,15755.513881133218],[40.901924226684216,127.05118959688181,5296.722236973761],[34.514324263033444,122.78533768835766,2953.907377239133],[65.00525776445272,143.4530538750455,9619.121152550306],[50.71109993365615,88.79967582673278,7001.972879421965],[32.48181245192549,117.97920376499536,6530.408427061082],[59.67913490638173,102.81197723615225,8462.148673053864],[0.0,0.0,0.0]],"n":2083}}}
|
||||
{"case":"gen0632","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[48.76921386984876,118.77845239633703,7068.878972152883],[36.003699539467895,120.3364785343263,4791.277526138902],[14.118279720248637,96.1275182467369,752.1569294119547],[49.52739494991138,83.86744163937846,3017.2562944187525],[49.59216113321071,97.26135297023185,8744.792477205077],[0.0,0.0,0.0]],"n":1347}}}
|
||||
{"case":"gen0633","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[123.25423056874503,143.42497495645702,17057.593526155684],[137.05567362164342,157.41048102178976,20411.650762833706],[99.95118409434806,119.59396861680035,11758.642898400345],[109.58879368126097,127.27099793273477,14864.563205526536],[94.22277330486466,106.62389933272928,11769.269182045595],[87.76911034692537,110.30484465415705,12960.927787826324],[107.07800203687553,125.34244502479471,14383.703866632919],[51.27624417812602,55.00161867083344,3446.345464713756]],"n":1986}}}
|
||||
{"case":"gen0633","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[123.25423056874503,143.42497495645702,17057.593526155684],[136.05567362164342,156.41048102178976,20164.650762833706],[101.95118409434806,121.59396861680035,11986.601972574494],[111.24447407956458,128.92667833103837,14887.66186157182],[92.53533758354065,106.93646361140527,11745.994466216973],[91.76911034692536,114.30484465415704,13404.113293118518],[105.45449755574926,124.764122749782,14284.716149428206],[50.867993336231905,55.548185622825756,3548.012293331171]],"n":1988}}}
|
||||
{"case":"gen0634","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[68.72102674081901,106.98020395569901,8525.925264060626],[70.7738442692398,109.0281041703831,11665.66239894363],[68.66673994103546,103.83488533897504,8827.789612801478],[67.76961787823126,118.02879509311126,5437.241052262994],[44.69605719967181,83.97851199156963,6213.6444532662035],[79.89510026076421,113.2841771690945,13561.42445951681],[40.4761100596165,80.52784692430637,5971.377721760327],[37.402940559319255,79.4863424362063,5258.996249331666],[43.54964184710083,60.34521202063208,3694.5905004905503]],"n":2244}}}
|
||||
{"case":"gen0635","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3096.2224339088557,3240.7098780760025,277498.5818885826],[60.60381462550697,190.52959309085662,6317.168251568263],[1073.1946011971277,1228.9395001589426,268514.4227940268],[56.943824914098116,196.50304583197598,5599.1498477944415],[21.46623054842401,118.55121998587698,1990.756996329396]],"n":1193}}}
|
||||
{"case":"gen0636","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[36679.02373783061,36711.394743913144,4714100.569789482],[37305.43073347214,37343.80173955466,4473725.573781121],[24919.722703663145,24973.303374384843,2940173.535874955],[37111.37280368101,37155.74380976355,5183144.059054294],[38152.276652073815,38194.64765815634,4795565.917733811],[23619.62254865178,23651.203219373474,3025054.153397334],[41534.14525082903,41573.699318820356,5438075.7394152675],[31725.860671832237,31757.04861600597,3523142.0304603945],[0.0,0.0,0.0]],"n":2089}}}
|
||||
@@ -702,7 +702,7 @@
|
||||
{"case":"gen0683","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[26.392877700381554,249.20005411308307,-1050.3186979337893],[75.07168920716279,273.2459420728678,11509.302480553459],[49.20264648430468,237.11697764541657,7056.616288935575],[2071.230183674305,2197.4229352241214,176085.9057156938],[37.22316764810336,164.80217645359352,2804.9648469185313]],"n":1198}}}
|
||||
{"case":"gen0684","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.14652744725579,97.75966216274315,10831.976075002434],[97.14653445846595,114.75966683202608,16081.511757492284],[2098.7643526454794,2110.5064412665897,413656.14847917104],[54.73124163712756,102.6022869508621,8148.922179544457],[60.89743690560113,79.00876516268569,8682.475750296362],[69.01344866081897,80.25734217904741,9629.35864424136],[58.05782144492446,82.9383479591545,7408.402736852503],[62.19556644763531,80.24570049169442,8350.9018258984],[45.38873931098793,49.68434584174626,3434.916552107972]],"n":2245}}}
|
||||
{"case":"gen0685","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-634426.962719631,2687335.439554168,-88517402.68926892],[-287794.1053999325,3368735.939760355,-32935800.18892753],["NaN","NaN","NaN"],[-520052.31952326297,1684623.6283457053,-105631016.03184931],[-249856.54660152286,3687553.408964671,-27009983.862583116],["NaN","NaN","NaN"],[-383895.62806763634,2193151.104786968,-60902654.70166513],[-582611.6889766298,3400537.342971065,-66605509.17775436],["NaN","NaN","NaN"],[0.0,0.0,0.0]],"n":2324}}}
|
||||
{"case":"gen0686","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[91.28185436380365,118.83499254010603,11227.126435224218],[78.90969281218649,105.61571720552868,10246.564021924632],[65.28185436380369,104.83499254010606,7172.877894862328],[73.9098972044699,91.79985254612879,10660.647980929896],[56.27980037768486,87.49435430902106,8541.143191771407],[63.2624151165753,89.49095470320034,9717.302231977206],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0686","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[91.28185436380365,118.83499254010604,11227.126435224218],[77.90969281218649,104.61571720552868,10079.564021924634],[68.28185436380367,107.83499254010604,7282.159749226129],[73.9098972044699,91.79985254612878,10734.557878134367],[58.07553669978826,88.81260582440734,8592.41355730011],[64.46667879447187,91.17270318781411,9846.47433196951],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0687","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.83874498762609,110.16093503741581,8945.342478940178],[78.81893685678648,113.25836590687082,11533.090346278097],[53.000304299257586,117.89430139895441,7303.75179496275],[51.45292074578277,94.77511079557246,4887.098226682318],[65.6059368423125,102.12752094191113,9638.682156517201],[48.24636269324388,80.7554097263388,7212.247063669491],[45.00237127462761,83.68613733107165,6585.687075903689],[24.95387762621989,33.60483868606924,1261.6200107095124]],"n":1924}}}
|
||||
{"case":"gen0688","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[46.89655825664403,127.71923769536639,6446.639927811814],[52.50248134785305,143.12384710540204,6519.380427232219],[16.22426172993319,109.00585342059627,920.1196105268739],[45.17574377466874,84.48642665344325,4636.275424125544],[30.234105270823747,111.0156969614868,3539.338183807852],[35.46555205088785,62.77623492966235,4529.389188355144]],"n":1508}}}
|
||||
{"case":"gen0689","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-177.50086003485933,77581.43006938089,592400.348870167],[-9864.553670463218,76314.72264817293,-2089429.5930232648],[6081.623981581303,64512.40488014813,74048.7182471458],[-5099.070128385561,64057.9296981303,15037.27709140048],[-5097.659915335281,64053.80140717806,-21263.36531346158],[-9179.953425247986,48813.044608451295,-1247688.6304921396],[2341.0798776640395,28034.963575292397,49930.33802412242]],"n":1631}}}
|
||||
@@ -712,7 +712,7 @@
|
||||
{"case":"gen0693","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[33.39783117816493,208.7236177063454,3258.133564948746],[33.86803148518605,169.31018277405897,3452.3154487477423],[34.002719247125356,190.8065718384597,3850.033841189654],[27.108224262689124,180.531291961866,3101.684860640629],[11.91673211595657,73.91439113006031,532.3590049670637]],"n":1125}}}
|
||||
{"case":"gen0694","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[118.09291814524471,293.08419943076734,15665.71441412472],[94.06947150795702,237.5217816207935,10416.875561587354],[1123.0248714070406,1263.0259168015023,195100.99876154584],[110.1874791054783,260.7655688297581,10770.107027944161],[42.45633460104234,199.33769262206957,2253.970167302042],[66.29032721258073,166.8482012242498,4167.656351791428]],"n":1461}}}
|
||||
{"case":"gen0695","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[93.7579252880666,115.15030059227449,12153.868076660023],[99.56189794576,120.95427324996786,14275.666383018834],[58.92558241326449,130.05637418133378,7444.040610227979],[68.33225355207112,101.72462885627898,7947.159570327035],[79.5683398347033,93.82992337084187,10981.720811157738],[57.601664729335795,83.86324826547433,9033.989334798132],[70.64972934705654,92.04210465126438,10342.540998086664],[21.0,21.0,1079.0]],"n":1920}}}
|
||||
{"case":"gen0696","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.09765253584283,178.501667986465,11261.80597797708],[96.6730581389166,195.98272553798253,14388.858311836126],[89.19677075105346,187.15465914419536,12186.547679037181],[79.75785533319602,188.85290325159568,8666.715913028584],[73.78225700704319,171.61551141899818,10133.65034074543],[78.41977287937983,181.9415720507285,12028.836230616942],[66.8679508860106,152.18324736307807,8797.428674362285],[64.54800348776816,164.03659242453583,8588.75425255967],[77.61095474226669,167.69326005192787,10004.537582277691],[0.0,0.0,0.0]],"n":2329}}}
|
||||
{"case":"gen0696","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.09765253584283,178.501667986465,11261.80597797708],[96.6730581389166,195.98272553798253,14388.858311836128],[88.19677075105346,186.15465914419536,12109.547679037181],[84.75785533319602,193.85290325159568,9215.187708443178],[73.7822570070432,171.61551141899818,10207.432597752471],[78.41977287937983,181.9415720507285,12107.256003496317],[72.30118653929539,156.75001170979334,9409.586290866746],[64.42874627480657,163.62060965570143,8798.257845822918],[77.2969763019435,168.54247847404704,10158.86391489068],[0.0,0.0,0.0]],"n":2331}}}
|
||||
{"case":"gen0697","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[134.49026877636868,164.79613131333366,18563.789794567874],[181.6748223983941,211.98068493535908,26351.408462888146],["NaN","NaN","NaN"],[126.73648981069422,163.0423523476592,14627.766960636991],[159.22307306806047,191.52434909756528,22490.258034456652],["NaN","NaN","NaN"],[131.72731048602762,161.04939808135896,17891.837715946735],[133.53667394898642,165.84877317918998,19354.545219279185],["NaN","NaN","NaN"],[82.65586548907937,94.73510366435299,6955.002949252517]],"n":2484}}}
|
||||
{"case":"gen0698","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.52941676876698,171.95434281262155,9106.34550633778],[85.13558623254158,178.56669885156595,14180.702979080168],[1063.329904457398,1186.7308035697777,262370.96703232336],[58.33088208209033,159.75580812594492,4895.1499627988205],[51.07879402130458,148.50889952344082,6841.278638317097],[65.57188957078581,131.84078621590564,10041.168047704163],[37.81093927752942,145.01699893156095,5767.403984032181],[50.755077792620966,130.39253977728686,7690.71182188391],[0.0,0.0,0.0]],"n":2089}}}
|
||||
{"case":"gen0699","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[350272.17709498416,2327031.3919131653,31515643.507112976],[379462.61584071646,2400446.4133633096,31257096.18464165],[134520.3318500269,544189.4122854361,24988218.049487278],[353010.6907867936,2578778.1260680077,32816031.02520557],[136893.50809751177,795819.246370239,3440266.2846786412],[562235.2730304616,2655755.3944039117,82671587.35696903],[21927.35957216979,1222727.492188069,4872589.47686825],[0.0,0.0,0.0]],"n":1841}}}
|
||||
@@ -732,14 +732,14 @@
|
||||
{"case":"gen0713","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[62.59076343342151,86.40165942755557,7805.088477729114],[75.59076435192749,99.40165876987736,13069.362539582946],[61.40957532107452,83.28350600557614,7172.547555906655],[39.59076579107894,63.40166071267833,4977.907441261247],[55.060506944474476,70.93443655644064,8854.549383885971],[43.70735792905714,68.27048123072483,5494.803484755016],[32.883640066964226,56.84691556173817,4440.860437637068],[42.93959631650583,48.034987932148,3428.1904209029453]],"n":1990}}}
|
||||
{"case":"gen0714","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[58.22803094545403,198.53615217591982,7984.965617697628],[1069.0182633152294,1164.0580551573785,178076.46503007607],[62.56262562538901,165.47895188816744,6451.219193355501],[1094.7308044565884,1193.9836980498908,268260.6804428113],[59.365940178736324,175.71634740952533,7092.723381737383],[42.92987586194625,120.748688487238,3828.2116919019063]],"n":1519}}}
|
||||
{"case":"gen0715","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[963215.4500613913,2437338.9820098565,126282692.5446625],[1231646.444520288,3421614.00523069,148814635.18119884],[1083822.0851284487,2636996.989066497,102993445.11763956],[1523719.3217091511,3634647.5104299686,235064749.87682068],[151654.078004724,881655.2649081908,15408390.070937918],[1107604.5217216348,2825055.6098037446,156556293.27216685],[1062052.696930384,3008692.7015071395,129297178.9834089],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0716","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.85312674797963,110.37872932671836,12347.116782760682],[116.46146372314666,133.13150329428763,18340.35289549089],[80.19870399108267,128.86874356222364,9347.55966657762],[83.23180754899596,98.75741012773466,8128.780586998473],[80.60675318440369,95.2767927555446,10197.86726080511],[81.36338086337796,91.71542864160634,11220.38061558778],[71.75716109389164,88.13340111054501,9932.347270584196],[74.00850499136152,87.25588572712017,10028.011861340517],[0.0,0.0,0.0]],"n":2085}}}
|
||||
{"case":"gen0717","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[72.3606346527663,112.0533981923303,7894.599271845332],[82.79836778119437,132.96726449465248,12804.604934346313],[89.72978005603093,172.548728001925,13344.534308982658],[2072.821532467084,2095.082041750519,247323.2787492454],[67.19194013295346,106.8847036725175,5542.524005702292],[53.19208044124775,107.45973769242809,7976.269420643224],[46.211892831514604,94.25294025878613,6804.988760363798],[69.13195199970731,104.60033881292009,6953.487424771702],[46.21721711434964,98.2175845410563,6550.3300400341695],[40.91697587895738,97.09772765566031,4041.432170882244],[21.446391255924006,25.798353457317564,361.81067734340377]],"n":2654}}}
|
||||
{"case":"gen0718","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2711.6203132706532,25104.312505356764,317033.36344100285],[1015.4469542240691,32622.43531247131,152753.67166415942],[1422.795223157206,30174.795116044083,168859.34862074503],[4719.366359513044,32830.03548231986,813493.4919197845],[-2733.61850709692,17597.15449997449,-118499.49628393826],[2689.272857429442,18838.88994900515,445150.6485131277],[1252.8104374677516,33496.906397919425,265830.30875777156],[-1361.294735348139,4250.672762957852,-20917.469999612553]],"n":1992}}}
|
||||
{"case":"gen0716","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.85312674797963,110.37872932671836,12347.116782760682],[117.46146372314666,134.13150329428763,18591.35289549089],[79.41560534561857,128.08564491675952,9325.676335126396],[87.23180754899596,102.75741012773467,8275.460431282387],[76.87222153938502,93.54226111052596,9665.315117996031],[86.15764552010265,96.50969329833103,11896.520292375577],[71.42108814906425,87.79732816571759,9932.50602661495],[72.29836234916831,87.54574308492697,10092.231881855774],[0.0,0.0,0.0]],"n":2087}}}
|
||||
{"case":"gen0717","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[72.3606346527663,112.0533981923303,7894.599271845332],[82.79836778119437,132.96726449465248,12804.604934346313],[88.95873588020166,171.77768374965072,13219.143659730222],[2071.821532467084,2094.082041750519,247071.2787492454],[73.1919401329535,112.88470367251752,5393.715945835247],[55.280857715430606,107.82887199014169,8269.98102866143],[45.35207138150248,95.11276170879825,6850.340831745302],[75.13195199970728,110.6003388129201,7455.634705927894],[46.645867679069774,98.24684554823261,6725.423702577849],[41.288446665841136,96.72625686877657,4270.111834498208],[20.875225728490868,26.369518984750698,403.67673353898533]],"n":2656}}}
|
||||
{"case":"gen0718","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2711.6203132706532,25104.312505356764,317033.36344100285],[1015.4469542240691,32622.43531247131,152753.67166415942],[1425.795223157206,30177.795116044083,170094.6260467197],[4718.366359513044,32829.03548231986,817955.8582792974],[-2732.61850709692,17598.15449997449,-121232.11479103519],[2693.338886245123,18842.823920189472,450834.4610977599],[1252.2680666856102,33496.44409147706,268213.4089698551],[-1360.818393381678,4251.2010982158945,-23639.609124988157]],"n":1994}}}
|
||||
{"case":"gen0719","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1060.1580144164284,1231.9849885381805,8266.683898934883],[22.536395123683665,170.74047861087902,3560.7478196417946],[35.09061262998714,214.26683524993172,5090.870596680678],[53.312381199484705,175.45681716341244,7250.854096815234],[10.357526721716498,177.97771121849522,1907.1610151400394]],"n":1258}}}
|
||||
{"case":"gen0720","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.96626008959335,131.89212847933598,9083.137675088145],[17.733734049780395,99.659602439523,1741.1172265045088],[47.25716861688992,72.6661137085756,5374.3427033889675],[38.96626008959332,118.89212847933597,6861.165431411624],[21.0,21.0,1604.0]],"n":1177}}}
|
||||
{"case":"gen0721","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[42.18088150455552,123.6355595844547,5115.479564941747],[41.00578203594914,122.45717076696569,8562.486902128627],[40.82732634394909,154.27707040052434,7577.02610062168],[30.942162434030177,127.61003339672219,4906.8046048884535],[18.90212891393327,100.3557105442049,2173.7234438875466],[7.257315817496799,98.3644962490698,1732.5661217339516],[57.067627611410536,119.88035553892843,12311.824233316467],[6.619344076053705,91.9274596028175,1906.5607856149468],[3.247085779969105,94.3612672506864,597.3783092654651],[22.288309816663876,88.78391710640017,3667.3534097086126],[0.0,0.0,0.0]],"n":2575}}}
|
||||
{"case":"gen0721","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[42.18088150455552,123.6355595844547,5115.479564941747],[41.00578203594914,122.45717076696569,8562.486902128627],[39.82722139134247,153.27714818952234,7416.007864079031],[37.634238383697664,134.30210934638967,6139.6152915689745],[18.21005296426579,99.6636345945374,1935.9334968518126],[7.949286814557663,99.05664998773523,1661.6779559027275],[65.0645498924208,126.40284323154212,14260.058015779441],[5.9303458453759665,92.71289596053633,1919.1586945998986],[3.2469808273624836,94.36134503968437,603.857602766169],[22.288309816663876,88.78391710640017,3711.93002934194],[0.0,0.0,0.0]],"n":2577}}}
|
||||
{"case":"gen0722","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2533438.7076940825,2585354.0075807953,324872624.8019778],[2671929.152175018,3031090.028061896,341044847.55907124],[2391536.5440333555,2676204.4695371697,310079875.0091431],[2607283.2981222267,2801517.9806351624,337205163.34152883],[2449010.7370861475,2785564.8022253052,273925261.8062686],[1228515.0223266245,1228529.2246150177,252378821.9424929],[2538221.1725479546,2749755.298711133,332240131.6094412],[2169341.3039227864,2488595.4432070972,211927395.48775995],[21.0,21.0,638.0]],"n":2155}}}
|
||||
{"case":"gen0723","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[45.480691264599486,113.83025262174716,4572.79296258959],[56.490681981260295,124.8314894949428,9586.775501987999],[1041.0931924064025,1162.2887398013468,171272.73710972545],[47.213160001492355,114.4114008867336,3310.045365672314],[17.244960932351567,87.70220898337904,2148.1216241960474],[70.27513514869152,112.57105488724169,11523.055866627967],[11.822814399980015,86.87777751657715,2037.163610518438],[25.319905344390396,95.15972126580401,4948.089417629509],[26.62811975090372,34.05422948571756,987.1164819264137]],"n":2165}}}
|
||||
{"case":"gen0723","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[45.480691264599486,113.83025262174716,4572.79296258959],[55.490681981260295,123.8314894949428,9335.775501987999],[1041.0931924064025,1162.2887398013468,171272.73710972545],[52.213160001492355,119.4114008867336,3819.6969125647393],[17.072217205038452,87.52946525606593,2120.97144720893],[73.80922225745981,116.10514199600998,12423.368994537957],[13.46147101852482,88.51643413512194,2064.0865525554877],[25.624626372269365,92.11683755167388,5078.716334854188],[26.323398723024752,37.09711319984768,1038.3941980293375]],"n":2167}}}
|
||||
{"case":"gen0724","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1133.8643737232392,1365.8195099155478,188419.78413008395],[3169.840823120931,3386.630095519168,297840.1923538186],[63.98571652572465,243.2146964702353,5957.530263133145]],"n":702}}}
|
||||
{"case":"gen0725","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.67731135501059,107.69814900956145,7496.369703337896],[65.00380730395277,91.72108266466469,7516.109419740027],[9.326494097911137,11.02293550613424,105.3886612875428]],"n":530}}}
|
||||
{"case":"gen0726","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4075.624659847875,4106.267326191219,509349.70630499994],[1069.0005894524952,1101.6067476195226,88522.1306889807],[61.53541924641235,98.17808558975265,6015.026017913043],[62.22670894555697,84.6178181671044,9622.66254244373],[52.82918266769893,79.96869534775088,7211.444545346692],[51.28356493000676,83.17782781484253,7916.947031281294],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
@@ -767,7 +767,7 @@
|
||||
{"case":"gen0748","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[21922.031190837926,22008.251081738366,2943162.345622753],[29798.049228945416,31462.2991677252,3582421.100626368],[25230.996086580355,26026.368500735414,2794978.605782743],[30383.263524804162,31354.360940329323,4239627.282711269],[8207.977508238764,8980.6005128205,297254.1706119802],[25495.0217613005,25689.46090788596,3586821.0239001485],[26160.05865848284,27716.0893415776,3095437.1687229234],[21.0,21.0,974.0]],"n":1915}}}
|
||||
{"case":"gen0749","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[10677.16241985778,88428.99797041666,1166887.325116025],[5794.340411645234,84316.62278625922,297636.6326607973],[-23.472176414817113,90534.2706944627,-135260.47184256767],[2512.7226055529345,94290.54719681936,315010.6506312784],[-7934.846025363856,85279.89387571666,-1091979.654510673],[13912.320117180858,65263.95632797172,2208426.5130513445],[5038.991474378993,92115.81635795295,119313.28982078575],[10739.978681684364,77203.09063367314,1361503.0227600331],[-2688.4445263745342,8634.063869462116,-2452.636092183199]],"n":2075}}}
|
||||
{"case":"gen0750","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.38808402767943,204.60879533032713,9937.775139296567],[85.35641741926923,279.89154447135564,14816.831929304724],[75.61333448811229,195.37789541837495,9523.753021676996],[67.01197717295754,233.41708791851084,8718.356796943239],[14.883541773552846,67.72128470430665,1001.2549382012726]],"n":1093}}}
|
||||
{"case":"gen0751","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1871148.3847984462,2670517.0625139526,226632506.29217392],[2056357.1908096161,3186649.046603788,227486508.62950674],[1763869.4613727152,2478430.2472731792,203286636.66043913],[2152423.653500759,3155417.6715723677,280537956.01877594],[1126828.8975980459,1787015.1871309474,75218838.0681096],[2274877.3707248326,3230937.8137475364,309842730.43176335],[1563186.5110347243,2451912.7097066543,172594282.3795428],[0.0,0.0,0.0]],"n":1836}}}
|
||||
{"case":"gen0751","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1871148.3847984462,2670517.0625139526,226632506.29217392],[2030323.3745005133,3109708.8952056607,222962796.24893367],[1763869.4613727152,2478430.2472731792,203286636.66043913],[2126389.837191656,3078477.5201742398,277992813.6776947],[1126828.8975980459,1787015.1871309474,75218838.0681096],[2248843.55441573,3153997.6623494094,305527288.5816631],[1563186.5110347243,2451912.7097066543,172594282.3795428],[0.0,0.0,0.0]],"n":1836}}}
|
||||
{"case":"gen0752","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.503597159093175,103.69646597486302,4213.566947146253],[50.501160494261605,112.69988236025199,7610.840184036466],[24.913018656962173,85.10718815055883,2058.3332906098417],[28.501522549626706,90.69894373779024,4998.244518227102],[50.86899943770054,70.9319883651304,6693.389984208744],[6.632789754361526,73.25096880293603,1377.768770659559],[38.07966144172693,53.72319318657254,3854.869891141515]],"n":1748}}}
|
||||
{"case":"gen0753","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[68.51787865138604,115.39044216374151,8351.594860258401],[52.729361370763854,102.27658380344583,7175.07539042232],[41.24252243469349,100.11508594704895,4920.054758792877],[58.14868941248605,87.19004187000712,9421.622449737828],[33.510615851717965,82.0992943035439,4973.925246528823],[45.98748869824465,85.31272621629525,7438.661208072914],[21.0,21.0,1016.0]],"n":1661}}}
|
||||
{"case":"gen0754","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[33.92592553720432,164.03081662847595,3097.597066655516],[36.59477566957061,166.6757590253522,6880.496777530202],[-4.340866531644124,166.73241383532294,-1497.2337930479734],[3.809673361120013,145.9145644523916,-1649.4780873311593],[20.214612672623232,147.31908295989612,4136.329333364493],[13.733403083797407,116.47298561158134,1732.4476437103606],[-6.176910943620086,137.23394920769442,132.2414643931436],[15.467797810489587,40.52671650993562,1024.531417445877]],"n":1926}}}
|
||||
@@ -788,7 +788,7 @@
|
||||
{"case":"gen0769","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1071.4988791642293,1161.829354443842,259984.93841520447],[2063.101385050548,2160.8553749565117,255843.43568039496],[18.63176467382815,109.1083618326506,1927.1737941911188],[42.01447127376995,125.23344265186255,4028.9256351264416],[-1.8241928651366708,28.41954082952085,-13.496295344769361]],"n":1092}}}
|
||||
{"case":"gen0770","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[36.60271988749324,158.16093996092562,5006.767431135373],[47.70489266764549,170.7982725476243,9793.309051120918],[28.473650012319183,155.76624711806375,3880.9186425662947],[22.63471569847492,143.86090706229766,3590.1415178812545],[31.5739429444327,112.2807276171113,7604.203971316552],[32.37698180480881,134.01252875078055,2998.6868022693106],[7.367267734852091,130.94801975464415,1597.7705094824216],[36.04204848999564,59.34453799379925,3447.6339375814696]],"n":1992}}}
|
||||
{"case":"gen0771","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[88.84391165084962,549.688101731709,11528.997971275172],[167.16923464232693,591.1704466508214,24513.90074313396],[306.2575698868169,473.7326840373594,25353.454025734656],[153.72922787492212,415.6272698153205,19718.780454566717],[99.78214945617528,382.6780366239517,16459.27959001002],[109.35757070677502,356.4945944415239,5777.966899084792],[194.4566025108184,461.36461056976253,12683.640843158042],[23.696911618698376,180.14013804626265,2685.0452510653954]],"n":1921}}}
|
||||
{"case":"gen0772","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.30487572157382,160.2803067097211,4277.050058401945],[40.1595399867582,162.95170865436054,8085.757924388625],[22.801363644415122,138.81995208006134,3021.9440217419206],[12.698790209559668,131.66405706853513,1935.7621902134206],[29.743501320922327,111.0314578352875,7245.724977658875],[18.421164366781706,118.98702013642249,1529.3659395251468],[2.8171087585682946,121.41697073561791,900.8645992476104],[33.095493654693165,60.6973755637525,3364.058482417534]],"n":1992}}}
|
||||
{"case":"gen0772","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.30487572157382,160.2803067097211,4277.050058401945],[39.1595399867582,161.95170865436054,7836.757924388625],[25.101363644415112,141.11995208006135,3400.7988780373103],[10.4810335201663,131.44630037914177,1220.9085556907544],[31.39002010197045,112.67797661633561,7277.114997760846],[21.722210886233132,120.62234677474532,1867.2734647441168],[0.5351070437525183,122.80068890193073,555.0063605877514],[33.805210941712275,61.40709285077161,3431.116990065436]],"n":1994}}}
|
||||
{"case":"gen0773","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-943976.9076645379,2408241.3607210894,-130835968.1921834],[-1134601.4626906337,3896570.9262511437,-122812996.100171],[-1136848.4211378545,2494459.0172223216,-149651884.16220224],[-848801.9583715413,3903295.2605956886,-79570589.19660132],[-708295.8504706754,968775.6111765835,-14918883.469138108],[-1038548.4565347997,2872686.6884938064,-141270162.58249748],[-1187424.9033164151,3284730.6089823833,-130793439.7449984],[21.0,21.0,953.0]],"n":1914}}}
|
||||
{"case":"gen0774","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.20414990514548,105.06412456592199,8714.165749703228],[73.92065745111182,111.78057351875793,12736.103714087372],[50.837380536534425,94.69715019546541,5456.931729838866],[41.74267724304074,80.42696296266836,5166.473661208448],[75.59555149666448,93.39126121385993,12417.71853642705],[33.273534510411224,75.2829897181647,4655.052958609407],[44.085148335263476,77.79537885408706,6708.696887974066],[21.0,21.0,848.0]],"n":1909}}}
|
||||
{"case":"gen0775","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3060.6980119782506,3091.1783361879466,265352.1924591329],[20.69901202825125,63.17933623794329,1855.3405850058118],[48.89967056630226,65.0597791629875,5764.944168982106],[40.69801197825124,77.1783361879433,6327.894471683337],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
@@ -807,7 +807,7 @@
|
||||
{"case":"gen0788","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.181925740898386,325.7270487091175,5378.57291035901],[56.29174864772456,350.4307392875226,4285.39925032045],[132.83525318970806,396.13429569992724,21724.058153395512],[41.33334848453443,318.93008366264536,7976.850053250399],[55.726195297260475,342.92551217164436,6204.395781760977],[115.65235089593008,402.2181067528787,22759.82932460846],[97.45496100916664,345.456401971532,12004.368852315016],[48.9513289108753,341.4315923529968,5418.701561714247],[57.71610559913586,298.9657424533285,8579.486773677634]],"n":2262}}}
|
||||
{"case":"gen0789","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[41.917131358196656,125.08768263597082,6030.357696758089],[31.625977463755515,120.79652874152977,3480.340814226097],[29.461123132998445,112.57208228210567,5480.519529902249],[40.84106808994279,74.62417731120121,5257.806345152134],[21.732212480134727,104.84317162924194,5131.611864597182],[21.0,21.0,1268.0]],"n":1417}}}
|
||||
{"case":"gen0790","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[21.436466331350907,122.65589766574404,1089.746361906914],[27.768905489469894,68.8303007531994,2641.203979340522],[72.44171102355612,130.52852279355233,6629.879000967724],[45.831324307674564,77.48093259709657,2840.686016013894],[16.522918732530197,31.208723650612782,227.95558462792192]],"n":1114}}}
|
||||
{"case":"gen0791","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.781313846581476,114.36488684719018,7991.570933381975],[65.11503799964524,122.40912482750048,9805.390387409245],[67.77283411896913,132.22512626005403,9655.87379591489],[70.47570754355088,123.05928054415959,5106.2416547006505],[39.552296307897784,100.90496065356719,5555.704770653992],[53.085794786825666,88.79000296012126,8300.049524803631],[69.99806618835026,115.89253101643556,7180.223096024269],[34.98177273812658,96.37423936552742,4736.507294897559],[43.48558974378584,91.83910380787344,5869.399858507119],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen0791","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.781313846581476,114.36488684719018,7991.570933381975],[65.11503799964524,122.40912482750048,9805.390387409245],[65.51288194353378,129.7715659541011,9193.015990403554],[70.47570754355088,123.05928054415959,5106.2416547006505],[39.29234413246248,100.45140034761425,5528.462804405527],[53.085794786825666,88.79000296012126,8300.049524803631],[68.99806618835026,114.89253101643556,7129.223096024269],[34.721820562691285,95.92067905957448,4694.9679590001515],[43.48558974378584,91.83910380787344,5869.399858507119],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen0792","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[79.81332103123765,171.58246908459165,11270.005475832453],[52.17555831971267,175.7342606539117,6704.771342497788],[20.31448668831124,147.0910825216068,3912.2947463934975],[44.847682693060406,112.80922122953314,5245.907055865266],[39.813321031237635,137.58246908459165,4744.886051797874],[29.22435604597205,60.70884920417921,1953.2506778471752]],"n":1438}}}
|
||||
{"case":"gen0793","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.0103984946922,90.11400306361926,10449.471845285505],[84.01486907344123,101.11784864624435,14204.828991840004],[60.67025177579098,125.03791163339201,8924.456511774846],[57.35810002625699,70.46171789482574,6643.069401355473],[50.018486972803125,67.12146654560624,6143.709265820402],[67.68726361277635,76.4225966276201,10734.092699809853],[45.736291310394655,60.363973101430325,6343.970578989977],[43.76391615279968,61.10163015824325,5897.780912416883],[24.192521586040648,26.8013830885343,879.1071549325208]],"n":2164}}}
|
||||
{"case":"gen0794","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.84890324315508,249.68746567962276,9255.988909273225],[88.42289648752059,284.9774532650813,15911.276818609931],[43.45624509395486,233.3953483887357,5465.882109033988],[61.133357812853355,271.5966028918174,9214.525903233465],[66.39150314568472,126.05773047922078,11768.656151735951],[37.915381662779524,232.906939826062,5948.908960026525],[51.823025802244004,227.22458685299003,7581.30101368378],[21.0,21.0,806.0]],"n":1907}}}
|
||||
@@ -842,7 +842,7 @@
|
||||
{"case":"gen0823","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.74157210444568,116.59848508863558,12033.754602231707],[97.3071899411348,117.28276348869605,12509.408361371368],[69.74325205141146,96.60016503560132,7884.265182797069],[82.75882518938693,94.89548128332926,11428.388714303492],[58.69395467559863,79.38840213380608,8990.145626396228],[74.15818841569941,92.45731003562423,11089.215014976202],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0824","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[39.756461284898776,335.5145336426552,1398.7123717435084],[68.27404097706052,276.57486601210167,9868.575739213611],[5.75448860418743,334.02440539070557,-1167.7703410151717],[41.34177585384594,281.27316353798483,4800.7527316249],[54.19816472624128,272.30616512266727,5186.094302267602],[44.00658714839376,317.18798450759607,2289.3067755184074],[51.82239859548524,258.389921282464,6274.82728868195]],"n":1792}}}
|
||||
{"case":"gen0825","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.75120193879173,146.56865236953627,5914.350354821584],[45.322740567361016,76.5634259623995,5276.209246672826],[40.37355006869836,80.95364212563436,3394.0729137727717],[34.40902797333067,87.34367176049534,2987.9860576834926],[-376249.0578278514,1198161.330799631,-33823720.44963144],[42.29412112944951,67.13490983386164,6234.520435546154],[-376306.1850916338,1198085.4785742967,-32328002.104819745]],"n":1691}}}
|
||||
{"case":"gen0826","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[15.64477857868886,234.4891728438855,623.2872058744265],[82.13482874658966,200.1634869555557,10733.12725715419],[1093.6356204642395,1201.6935613903647,96131.06474988924],[42.72631167929403,211.20115761778092,7218.256185597421],[2093.6428852541853,2218.793864279566,421810.8181201249],[28.55828312812814,190.80146259068871,3119.264848861707],[22.157956788334072,105.68278749895471,1520.709516146778]],"n":1687}}}
|
||||
{"case":"gen0826","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[15.64477857868886,234.4891728438855,623.2872058744265],[82.13482874658966,200.1634869555557,10733.12725715419],[1093.6356204642395,1201.6935613903647,96131.06474988924],[42.72631167929403,211.20115761778092,7218.256185597421],[2098.0109376336036,2218.793864279566,421914.8496249551],[38.57665323966081,190.2625945950775,3923.5067275865717],[22.696824783945278,106.2216554945659,1543.4063409307234]],"n":1688}}}
|
||||
{"case":"gen0827","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4077.765907330051,4098.749412417092,510233.07573994115],[67.560317469943,100.5438225569828,8043.363731949482],[68.2139438954225,89.19744898246226,10802.44119422229],[47.120423521495724,66.11492521717565,6857.616570456416],[63.2139428954225,84.19744798246228,9995.87387747722],[21.0,21.0,1310.0]],"n":1419}}}
|
||||
{"case":"gen0828","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[51.79722239682591,94.93495270872245,5639.046900496668],[66.86481692386373,129.62330395585997,9591.31741034256],[41.10906520130586,117.93318307267324,5133.008140856632],[14.6215652074529,61.31442116398015,1274.4664949712924],[58.833406292448025,79.21264969039721,4852.9914077361045],[35.89722411631074,79.03495454625619,6329.146982519107],[21.0,21.0,1541.0]],"n":1686}}}
|
||||
{"case":"gen0829","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.79515291284462,107.60778923197728,5435.661282680153],[1050.3803294249824,1103.6521707668335,6917.738419402847],[32.26288488252468,68.57571923097629,3587.556877648854],[31.582524172656427,84.05283669260697,3551.6170565677453],[57.96149716794619,107.2833210682844,9222.584500078474],[31.28113684175137,106.76043901571852,3684.263016454894],[31.26288449985127,67.57571874617281,3271.0311080625456]],"n":1787}}}
|
||||
@@ -870,7 +870,7 @@
|
||||
{"case":"gen0851","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.43914247793595,211.9770977103234,8258.742323926854],[65.36285525758359,237.25203433445196,9400.006425730295],[38.89663425364579,164.2509826277942,5503.810242613753],[41.958055231191615,184.8322027618745,5377.89432447239],[56.99080941794467,150.69638163657393,10271.641445077566],[33.94138101608014,191.78320775715696,4865.141229749276],[41.921675979716184,168.50269510045197,6751.773842278416],[0.0,0.0,0.0]],"n":1835}}}
|
||||
{"case":"gen0852","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[165.18496157389984,186.6936319466993,22680.716611875087],[179.01113669676448,226.57672385225766,20452.349212679375],[131.99632740394014,165.5049977767396,15731.993799810103],[177.3969761629605,194.96256331845365,23369.834256249807],[92.91577297542119,120.5692476297229,15317.73519666138],[168.72256978465592,186.28815694014912,21267.744948672585],[0.0,0.0,0.0]],"n":1593}}}
|
||||
{"case":"gen0853","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.00641885242396,142.1459712090469,7503.545193149013],[66.09851045384198,170.78170584185548,8389.988399083695],[40.15750880422548,102.3320360244639,2972.1829883190767],[55.91626474631917,130.68095245015576,8145.4175736276375],[47.06504477999652,84.19305454802725,6576.537435843754],[35.291950916334194,117.02771174008097,5792.541932244788],[39.71677785421396,57.79222218651147,4064.2981008632796]],"n":1750}}}
|
||||
{"case":"gen0854","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2030786.8222335717,2030809.0720879114,277742353.9739276],[3124271.924213204,3124294.174067545,403724992.7393724],[2275216.328307282,2275246.578161623,249084213.38038608],[2468631.1757946652,2468653.425649005,339759660.7915911],[2907199.1292982413,2907223.3791525825,310221100.2456829],[823263.3618591612,823277.5539658202,175282767.50033686],[2532086.3742853766,2532108.7987002954,368708727.2932905],[2519687.8153130435,2519711.1151182596,243096029.3436497],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen0854","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2030786.8222335717,2030809.0720879114,277742353.9739276],[3124271.924213204,3124294.174067545,403724992.7393724],[2275220.618026036,2275250.8678803765,249584072.31404674],[2468631.8860759116,2468654.1359302513,342228292.6776671],[2907199.1292982413,2907223.3791525825,313128299.37498116],[823269.2578878424,823282.6745059558,176930031.1207467],[2532086.478256696,2532109.67816016,373772899.75808823],[2519687.8153130435,2519711.1151182596,248135404.97427574],[21.0,21.0,722.0]],"n":2159}}}
|
||||
{"case":"gen0855","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.64239457734493,212.38908755367316,6537.653975281114],[36.316067458737635,204.64462129592692,4042.0011342947564],[34.77966548544164,136.43652218563238,2381.9585483788587]],"n":685}}}
|
||||
{"case":"gen0856","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[98.34363585427984,132.34722205170962,12876.810776065355],[1129.4649701293908,1171.2915271607958,265849.3207595648],[75.36675595684689,121.37034215427666,7885.871320002095],[98.93874169947252,126.44100322603366,12116.455456673524],[68.44779515805533,103.56607430002065,10652.296564035765],[86.21121882273613,124.59878740476178,12829.0232027044],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0857","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4044.0635406609813,4087.2394139889,513066.60042009957],[2067.055136204911,2110.7005717185693,247791.27400703542],[27.72852132003223,111.96376555581351,3857.1373020118076],[10.313283122979541,54.899347728772426,1284.677717740461],[32.678994773909444,76.42229194911944,5990.4781728427915],[28.227333357878255,69.05045150435201,3044.9853816386058],[13.02976717670829,60.4059830594409,2603.2627530126565],[22.928706784376786,48.09916010129665,2915.5933811402506]],"n":2004}}}
|
||||
@@ -878,7 +878,7 @@
|
||||
{"case":"gen0859","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[97.02631110864749,376.4450514607004,8252.256912166495],[100.93533111265677,440.92360989720476,10589.14653749764],[93.87683452765259,361.7159663342198,11529.037483432152],[108.60030299836248,423.13512877946306,14442.09826593159],[-9.142013883087007,107.91734267167334,40.816869063028705]],"n":1088}}}
|
||||
{"case":"gen0860","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3.843219953502507,171.3613612117361,1089.8738340929385],[18.683762520976398,158.79743448916028,3494.951287325457],[-21.39144959383725,152.12669166439636,-5054.779403829215],[37.48476497749975,98.54160096159167,5264.795536854104],[-9.472896953593123,158.04524430464053,-1229.9885425478033],[19.864180300971146,74.92101628506298,4107.009425467677]],"n":1508}}}
|
||||
{"case":"gen0861","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4321.329874037022,21598.41921046812,344194.0680660733],[4273.8602420036505,21669.836957937197,354240.0032699719],[4229.508851947998,21817.26032688641,335691.02111041045],[1509.406785073538,7076.661804879038,250583.50761479203],[4223.536133445225,21811.287608383638,284961.04480261845],[21.0,21.0,1352.0]],"n":1421}}}
|
||||
{"case":"gen0862","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-38.8128621053609,240.21612275315312,-4337.010449813579],[-47.40135415080126,273.3920248636171,-3931.9450224524344],[-47.88958188254206,290.53630023607315,-6212.772982387768],[-65.1110618457454,216.97406226273222,-9996.039920604988],[-59.476220382959525,274.9483539501407,-7452.0243257897055],[-73.44192568725387,215.93304747726862,-5879.017026718473],[-30.011441456363766,180.90967782480945,-7522.3744952773095],[-83.20534337384474,264.9558399389626,-11239.632754138907],[-50.645277211238806,190.92287318152185,-4030.7763774511095],[0.0,0.0,0.0]],"n":2325}}}
|
||||
{"case":"gen0862","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-38.8128621053609,240.21612275315312,-4337.010449813579],[-47.40135415080126,273.3920248636171,-3931.9450224524344],[-48.88958188254206,289.53630023607315,-6289.772982387768],[-59.11106184574542,222.97406226273216,-9464.850706845655],[-53.57719236656779,269.04932593374895,-5995.450345959989],[-83.41875198540373,219.75427721190218,-6203.0258773276055],[-24.01144145636376,186.90967782480945,-7182.264248459474],[-71.9786700867414,253.7291666518592,-8515.460760830323],[-65.94974878010031,200.071748186867,-4283.188593729421],[0.0,0.0,0.0]],"n":2327}}}
|
||||
{"case":"gen0863","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[64.97123300394394,199.0739575622241,8720.32067314035],[61.5641388697325,219.76259002051364,6692.798273767114],[33.630678621884336,177.01355507321938,3676.1015195799882],[53.83570916010334,185.0956354110691,7749.50264648729],[44.75037841669541,142.3389627175855,7061.823702229697],[38.43502726750158,169.69495351846737,7148.810685261562],[0.0,0.0,0.0]],"n":1587}}}
|
||||
{"case":"gen0864","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.61889343518737,98.54703883129244,5458.069352271479],[59.97290616335218,144.59217716733005,7704.846433237268],[13.110794921985606,119.38732436477964,2864.0965031937762],[13.550097900797239,75.12985925021324,1280.0350286818132],[38.787396107574835,87.40590600169094,4032.6906981239154],[20.994749548861964,75.92327549989788,2812.067359369568],[18.878747166346034,37.188002113404096,1459.343388846389]],"n":1684}}}
|
||||
{"case":"gen0865","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4055.3870601030826,4112.360067482135,514061.95544403855],[3048.613486386628,3109.4426870428806,497796.37252769404],[1054.6416118680258,1143.2091618984118,257344.1975360062],[1052.8323990919976,1122.48878189663,83710.92608669914],[22.334194510899607,79.99057731097506,3388.601703817691],[12.673851627245979,81.46903244561459,2232.015595911707],[60.5441380921182,108.57594034981585,7695.824519859381],[15.386895915010141,75.29720378761805,2292.7672041645774],[14.288659178157284,83.00790798673943,1609.6704425223184],[21.123511369202202,24.6381385291567,655.1962269851729]],"n":2412}}}
|
||||
@@ -895,7 +895,7 @@
|
||||
{"case":"gen0876","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4044.981340385409,4087.5219713726565,512885.0576592077],[3051.976491973837,3096.52873192155,504442.19176143955],[1034.628059115033,1083.1751517240589,83064.65090994565],[9.980029423283785,52.528342570137205,1322.0900539132026],[37.64961138693325,68.01724777862805,7759.774268433242],[32.14993086055303,68.33370152566035,3075.2050590630406],[2.3426829454509734,48.1641809499384,730.1415871927397],[38.11340368329774,49.203540769580556,3590.2696204190756]],"n":1996}}}
|
||||
{"case":"gen0877","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.81285214326419,302.47844387087713,2608.6147814897176],[55.33978284433925,354.5791739428229,3752.2188178706074],[39.11683856590525,397.3804583842243,8540.674830572281],[74.93647488718062,278.50795838520895,14102.487320700766],[-7.602920217356704,388.2508462409183,123.13230827876393],[49.74665168284184,304.00204154767835,4959.803272100501],[72.94483703964151,347.8359275693565,6523.198308630666],[26.769667126017072,391.77024273144224,6365.919260381456],[17.487570398705916,178.568981883269,605.2453351844276]],"n":2239}}}
|
||||
{"case":"gen0878","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[23.87616079014161,181.332172775241,3144.285224498376],[22.238705293612597,188.6475195688863,2246.021258949946],[1009.8819776224985,1178.2727026899038,79830.5930988491],[12.4140225021009,171.04185237631762,2280.542696154946],[8.36038137081029,124.53209083927291,915.528537132463],[0.8603015453013754,159.48813141951808,2150.437771217469],[0.0,0.0,0.0]],"n":1593}}}
|
||||
{"case":"gen0879","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4112.349033667703,4131.423514890826,525651.7872648],[2106.8271534557634,2129.9016346788844,262224.0141778549],[87.53704119734111,112.61152242046067,9320.091147867213],[82.32221694180556,99.03853775721863,11180.387264740755],[75.90011834135987,94.61643915677294,11029.81629803186],[76.45417401425104,99.52865523737061,10879.435490090022],[21.0,21.0,1121.0]],"n":1666}}}
|
||||
{"case":"gen0879","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4112.349033667703,4131.423514890826,525651.7872648],[2106.8038234972096,2129.8783047203306,262226.3468629474],[87.51371123878732,112.58819246190689,9322.283853208366],[82.32221694180556,99.03853775721863,11180.387264740755],[75.64010422374986,94.35642503916293,10964.466353794078],[76.6908581733072,99.76533939642678,10884.800557768918],[21.0,21.0,1121.0]],"n":1666}}}
|
||||
{"case":"gen0880","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.54568874439798,163.41077468779235,6199.084340761082],[45.446032549345446,131.4474549168353,6055.0707213930955],[89.31945345961785,175.68739451275204,10638.535205021959],[76.91486832969414,154.99969743843175,8546.15220760937],[73.24414232625202,159.31049018348136,12576.4245749564],[43.53717969281123,137.21810508162864,7108.37077074128],[72.37474052716148,154.51357123047217,8760.866358867932],[20.342211059921663,66.8303699555229,1188.1570063242425]],"n":1936}}}
|
||||
{"case":"gen0881","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.88689097325533,248.97962536860013,10754.103910555354],[14.02771030560955,253.43320784870446,5002.708903360639],[21.009718862496054,211.4967634520877,1865.064744160094],[2093.471576418262,2262.0735544214695,110727.61247032457],[91.9544847559713,233.52605341420647,11860.933153886324],[37.45110842380764,181.37006452424833,2800.769109816947],[48.27681769333827,193.7190374300157,3639.934563059526],[10.00428991252493,129.5782635698983,398.1396383494525]],"n":1941}}}
|
||||
{"case":"gen0882","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[9.909222291075244,190.86170160012642,789.0945753258266],[6.039183663282856,186.9801478853483,4121.536566754385],[16.759389167193504,203.7034240791217,2627.008400858099],[-7.844069290824891,173.10457165589762,-639.535091515105],[-21.580051611271465,159.35886548421877,357.0469474246984],[48.70827735048574,127.61936497713894,4999.449619738103],[-25.591592090416064,158.0925092443761,-2321.7701895123255],[-12.596113968027616,147.01502766986638,746.9113630889601],[21.0,21.0,596.0]],"n":2153}}}
|
||||
@@ -917,7 +917,7 @@
|
||||
{"case":"gen0898","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4056.0616077729596,4089.232983745292,511523.52681208646],[1050.0616120544826,1115.2329866469981,84968.96788065943],[1057.723471962904,1091.8377229560795,84256.43531638698],[37.06161305448205,72.2329876469972,6291.650715458248],[45.03627123858185,68.09339690764811,6130.380705195947],[16.502185444304786,51.96453868703729,2630.079995750509],[40.24662833449669,51.01277500839079,4152.950873023955]],"n":1754}}}
|
||||
{"case":"gen0899","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1127.1417895068662,1210.6474150225135,17108.977656325027],[66.96765074963204,220.81793233206227,4489.035865010179],[69.63279239578034,194.05788343106522,8182.044818899011],[64.09397813214595,234.2331858392241,8678.941327530094],[72.83141650242369,219.3339822368016,5667.907130677874],[63.000500023751464,197.7121894364879,7564.2200039290465],[31.840557620864725,108.54624582314922,2149.0149720240547]],"n":1671}}}
|
||||
{"case":"gen0900","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.71789864795208,127.00327830995357,8906.364006095837],[82.67081465249353,125.07608486532448,13176.861100789578],[85.98841270030675,131.98283209512806,13769.595629816333],[81.99743320524661,141.33291323419297,8471.679573102121],[56.93963044475299,105.29480029063907,7342.401108711405],[53.50919721166568,96.26999720146435,7425.6474673154735],[99.57588086153397,129.9510998167513,16969.520983066137],[42.24564776948817,91.39306400088827,5988.519762813495],[52.69589805174133,98.72015844933757,6930.018821593971],[53.64365937056066,88.67231107928296,6532.385636320334],[21.0,21.0,302.0]],"n":2651}}}
|
||||
{"case":"gen0901","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[41.715554106842276,139.59885589028346,4531.156752410413],[24.504097252926645,124.59948709447288,3189.0568161245233],[15.71555410684226,125.59885589028347,624.6927959013854],[37.31546884993401,104.7224948423032,7761.71965877724],[21.640914878590717,101.90119753076229,2801.158679232882],[11.888676297999746,108.91872142163862,3944.229722831775],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0901","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[41.715554106842276,139.59885589028346,4531.156752410413],[24.504097252926645,124.59948709447288,3189.0568161245233],[18.71555410684227,128.5988558902835,182.40835000822813],[39.31546884993401,106.7224948423032,7801.035127627174],[28.173587237544428,105.36852517180859,3638.139073395692],[10.35600393904605,110.45139378059231,3966.174066694481],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0902","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[73.17489052679565,237.20503236234794,8420.799222122956],[73.53429412017665,303.79568879891406,10188.112534581225],[47.835092211236656,267.06065438610216,9805.422445703338],[68.33302220603936,264.79068603849726,9911.553428440839],[39.7960256936283,303.8504224911536,5753.13723681812],[72.40067016929102,254.95750637429234,10017.151496646635],[61.25643390783643,276.78425748412747,7806.576576707721],[38.79453786554368,307.3650782026385,3776.843283321737],[65.89400780235282,297.8806326934696,8828.219444261447],[57.236462418784605,285.19939940636186,4962.014469801278],[77.06482238729286,241.09210835716195,7796.1429459326455],[64.70052140987933,268.1695771395858,7473.213728977996],[46.29943995310389,266.6934302633255,5966.115573921107],[88.93135987032605,247.2263578977231,10562.39564774993],[59.97707152327164,260.76175948000315,7398.616119467748],[53.62949245371695,317.4304343780457,8278.83405607198],[76.87694906532542,229.0850007231178,11063.4058050919],[48.6047234313848,267.3390087893303,5685.420967688782],[20.463955358188638,121.90143374376896,494.386140267366]],"n":4716}}}
|
||||
{"case":"gen0903","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[50.82093301367182,136.64204508058302,6223.862213944558],[21.810917447589045,40.323898813250516,1875.2014253396735]],"n":441}}}
|
||||
{"case":"gen0904","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.98365641107627,141.75155513451529,8061.988445970715],[67.27615663956328,147.1450570225128,11954.809786284713],[40.97654363112742,115.45347812841551,4924.963555745693],[39.142578362195295,121.38017844027065,5328.748328099724],[64.38919011316086,94.96898783594179,10686.346960530072],[30.445485908103464,106.18853421090488,4654.18466308364],[39.47743063854114,106.08021721597727,6541.067156864523],[21.0,21.0,848.0]],"n":1909}}}
|
||||
@@ -937,7 +937,7 @@
|
||||
{"case":"gen0918","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-610723.6867253558,7722799.023155553,-143010998.56149226],[261504.29543188986,6457892.540502411,28209281.41288502],[1073567.4422361862,7091193.809758573,135107077.50715563],[622764.9703532339,4589176.3421042105,70940128.46750927],[418916.2436638156,4681747.581196641,48429739.12148748],[160860.77759665175,6468858.991450646,-39854396.14736228],[467074.158478087,5453729.372273636,32323619.329662435]],"n":1780}}}
|
||||
{"case":"gen0919","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.1017712113237,136.28892644512152,7662.947297010325],[70.10713779474285,144.5491058073217,11236.010346111796],[1071.2488924780337,1150.5072922074187,167787.16094892134],[37.69857990690112,112.66108756548857,4760.146658058891],[44.3770805843135,115.1149406676906,7427.454798243961],[44.974260408680834,106.6233995523159,5641.908649872136],[29.842635667939053,106.4080974053076,3962.9903130026505],[33.630091012870245,71.58645881016734,3179.85652173529]],"n":1992}}}
|
||||
{"case":"gen0920","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[62.80824383159822,169.44511473100042,5522.602188601258],[33.890635778109875,176.13862041789372,3848.280747575335],[47.44273584874823,154.44044368373235,6906.142917068409],[39.91141060310698,160.46348477407255,4618.9035687910255],[38.02376202431046,163.40241649057558,6015.968121571215],[-0.5429540072188566,17.801242711347758,-8.182588857784292]],"n":1323}}}
|
||||
{"case":"gen0921","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.53357074645758,120.97278573478187,9997.159743683527],[99.53402059886116,134.974868587373,16029.867592499375],[46.40463000764823,82.03022899980189,6087.972333313789],[66.53402059886113,101.97486858737307,9513.95918919479],[60.469995817886925,84.28252314726578,8860.148766358147],[48.295541030085595,85.90862856781901,6744.568299659981],[45.173113758536886,54.81394587209014,4084.45922296875]],"n":1748}}}
|
||||
{"case":"gen0921","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.53357074645758,120.97278573478187,9997.159743683527],[84.63031820905337,120.07089403086735,12332.525208881692],[55.48619505566404,102.92568221068623,6270.313792545847],[56.41536195685783,92.14100323239678,7084.632632902599],[64.76651711809826,100.10786715662067,8974.678742258468],[48.29546605468499,85.90828142572046,6744.561956695197],[39.35105139214549,60.80577166856731,3964.121064950107]],"n":1748}}}
|
||||
{"case":"gen0922","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.68570074761092,205.66769230610308,5455.200170329486],[43.24425497055923,146.6427918213614,3616.759959912581],[1058.5345053336755,1131.9694054153297,194930.2139742801],[2083.4740792259463,2157.1240864957153,122429.65697110473],[39.19755457328384,120.48931963392523,4599.980301814228],[9.656344304988197,42.394394250775555,-22.900746786836656]],"n":1366}}}
|
||||
{"case":"gen0923","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[853707.2708663889,3562963.0105660874,97522867.12652193],[1224258.9702086165,3739514.836649438,138088838.86536756],[1383573.9644028158,3823417.435687408,161453891.54786143],[926314.0302086163,3441629.896649438,100292233.56700407],[809700.2708663889,3518968.0105660874,95504583.68412459],[1679459.7134199587,4194715.57986078,248304901.4087933],[730383.3657085593,3191752.3784663603,92627405.4824324],[760745.3903367955,2882326.3881941587,126778881.13621072],[1140322.2510524455,3698690.4458118994,166212117.18830433],[1295394.8836712155,4021867.3830871526,154579962.8914431],[23183.88493445014,302654.8118000168,93586.61865249762]],"n":2665}}}
|
||||
{"case":"gen0924","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[982335.4695860713,1872824.7182552794,149665042.70979673],[1753263.784790926,2945516.6390028507,211197708.03606993],[1086878.3891212016,2016513.0063575916,93023728.4039203],[1648759.8652557964,2801875.3509005383,202235483.4063615],[195968.1930518723,431357.4426612186,39862448.67432264],[1642083.5643715675,2827084.571902853,248600009.04240853],[246266.82800446966,511209.0556743298,9629678.532867428]],"n":1752}}}
|
||||
@@ -963,10 +963,10 @@
|
||||
{"case":"gen0944","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[42.26660536081518,89.2534092321202,5257.075001697098],[42.31758316858648,89.304626660667,6984.297018209083],[1079.6313790812806,1137.2886289172332,171606.33265853763],[17.349809608047632,65.66642227882339,2092.5910483160765],[28.364525730946642,75.35161198035476,5626.996086221256],[56.24910815755919,86.11983592701318,7288.860308356596],[12.045830031443899,61.06415315076367,2088.163423453576],[26.792589681809666,69.53975744421935,4930.5774325999],[21.0,21.0,722.0]],"n":2159}}}
|
||||
{"case":"gen0945","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.30414270804639,155.56501763251575,5509.915298841085],[46.44722807310123,151.796626443582,8645.530010915263],[50.322623677937,159.89916223986276,9925.360128612032],[39.7597051239632,148.15628100565795,4818.6136798496045],[20.581355575732275,134.63319777952435,2873.3393082731427],[10.212931407486213,120.39133875700283,2040.4757511034531],[78.05175337958943,120.71393912019283,13309.4678111393],[13.176031084312504,123.37293064499283,1696.090025212486],[13.00509240201761,120.8806461629408,1214.722082832218],[23.16176841394673,109.0632535625365,3274.980333420693],[21.0,21.0,134.0]],"n":2643}}}
|
||||
{"case":"gen0946","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[58.38248471904709,285.68517118496857,7150.6073663696325],[64.32100599495404,283.1190747176802,7034.905017296654],[76.74346829705699,261.1562616543007,12607.371202014101],[60.852853297566945,215.19849145693368,5951.465100545445],[62.13983414497671,269.4936731452289,5811.851142841309],[103.54070854903993,248.35990566829423,16197.72636232664],[64.49348995755298,227.6192056754739,10958.580282249566],[75.12693919845053,280.66847449056905,11519.082697033042],[1112.9739561981937,1265.2909496591496,126406.91481690292],[64.52038153291274,223.55728928919683,6345.508470537956],[116.73493655471724,247.6806485443105,15778.544929923513],[24.52979204730308,87.49752558153139,897.2390111479644]],"n":2906}}}
|
||||
{"case":"gen0947","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.76443070443808,100.52517917050875,7132.337243111747],[68.120326636161,110.87787209905906,8779.66475782024],[44.76396447468831,93.52471294075896,4317.091322188995],[60.513283197917616,89.01816098255571,9724.407895001694],[37.41328684165834,74.58725108118216,4932.012391293696],[48.21522625479449,88.30688826597938,7650.816968244486],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen0947","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.76443070443808,100.52517917050875,7132.337243111747],[68.11992928134846,110.87770353018743,8779.584456065535],[48.763964474688315,97.52471294075896,4417.855286663683],[60.51288584310508,89.01799241368407,9784.908029408227],[41.86040200368048,76.36802521960935,5451.5102408430785],[47.767713737959795,90.52594555868055,7747.645794668378],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0948","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.56389722725709,268.0664542630282,7858.809651238979],[104.57268379904576,265.0490712794222,14104.193868388107],[81.75338924512519,266.40060214485106,8496.139707267086],[100.64536373494795,267.0266709545012,13757.650385255132],[56.556263739474915,161.33162720892673,4693.687904560551]],"n":1170}}}
|
||||
{"case":"gen0949","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-8589.156481813578,97724.20112588751,-1715472.1787767536],[671.4395516851132,59317.26148118512,190786.27006767088],[-7920.072689296514,103650.14384872446,-1993125.6438137484],[-30265.459796106225,140963.405152371,-3187605.0622175825],[-19485.065323018673,109713.27112986808,-4415559.06670205],[4441.843417339851,62614.74639449909,598028.2751345308],[-9095.115790116903,100729.18544861104,-976117.7424824161],[-17107.38124596148,104040.14470078208,-2342752.2119726865],[3909.8579874659213,75316.05309660382,899007.6809549106],[-13193.372409401689,51127.634902587684,-407386.62259282346]],"n":2356}}}
|
||||
{"case":"gen0950","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[108.58873043294618,157.33912516502966,13667.836729048215],[114.20999035277615,161.57825193366676,17033.040431579484],[105.49002103097146,155.01185296199455,12297.09704141322],[102.91370792951125,157.6641026615947,10631.280682093056],[90.83305084594222,141.87097371176546,12153.508582278879],[90.7350038024442,111.6987248465853,13505.343852574333],[78.4095247544378,128.61589511048393,10843.078649597892],[87.21822009209545,138.63790083823855,11576.589032296473],[47.35835779518289,54.48928103055583,3281.504773742614]],"n":2240}}}
|
||||
{"case":"gen0950","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[108.58873043294618,157.33912516502966,13667.836729048215],[114.20999035277615,161.57825193366673,17033.040431579484],[105.49002103097146,155.01185296199455,12297.09704141322],[108.99369122092033,163.74408595300375,10547.750095914695],[91.75306755453317,142.79099042035642,12245.261649833417],[94.90030100083925,114.86648989273762,14874.578298690658],[79.7331071623811,132.93700967067,11004.043629998772],[87.21822009209545,138.63790083823855,11751.025472480664],[45.847237401521255,54.978160636894195,3333.910118310454]],"n":2242}}}
|
||||
{"case":"gen0951","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1087.5038578064,1298.4800720188646,93724.13258740304],[38.58943712695625,243.26397401243798,2704.5813982560826],[62.961985150842004,267.62665504941367,5926.809292068434],[45.37491189534491,284.855367630837,4615.2037557712065],[26.17683522017257,113.97388954305318,1592.1865407212774]],"n":1171}}}
|
||||
{"case":"gen0952","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.75711423705256,120.78523891115168,6871.769784091204],[71.40108447446828,130.19277656551884,11519.303886460497],[2066.0200834311386,2126.6643056728867,412107.48696532677],[44.682644205138814,107.58962206295591,5848.846057515925],[30.578287791266675,91.67714970667429,4624.941122936961],[58.217429589057254,105.38152462855412,10644.644051271316],[27.134869805691633,84.72537725437947,4340.637247789647],[25.45635587347549,92.25407105860191,3438.7832888206503],[18.72564925049334,26.78244341457797,625.7633996331945]],"n":2156}}}
|
||||
{"case":"gen0953","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[51.05882529340699,112.9746231413993,5685.974028343596],[53.81246959300864,110.47072514428454,8599.143540367215],[1077.890640153407,1144.26571123754,171558.5979067219],[23.933973027073325,84.07377858166026,3168.294564573005],[35.438208401183,94.09646395245886,6150.092298491039],[58.761030452617504,89.39430665353476,7738.877712059948],[18.20557871290519,80.48713999078444,3307.484578517515],[32.60715946905683,85.11300939958863,5302.505183630934],[21.0,21.0,722.0]],"n":2159}}}
|
||||
@@ -975,24 +975,24 @@
|
||||
{"case":"gen0956","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[93.41677437355231,164.8011859140642,11611.423748410327],[90.24727636931713,161.6203643295156,14001.234003393974],[92.79961338491597,170.168926818343,10786.087824781607],[84.41677437355224,155.80118591406426,8886.424228578626],[63.91103492241556,143.71839803235724,8840.707872230267],[80.46705534961912,109.21024085940822,12134.607084394473],[60.034881294081124,132.4667172530066,8176.916153506306],[61.93118764841361,141.7369995993914,7848.4094641338825],[44.63938237942693,51.561315599652886,3033.763334879235]],"n":2236}}}
|
||||
{"case":"gen0957","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[66.31415040424464,363.2630968289239,8295.367387263199],[76.27164772433049,363.44733294396656,15192.326861893063],[58.761884435725385,328.6586334495829,7464.549515976765],[56.48527186981409,369.4697572295241,6970.268409517857],[97.66018321392708,344.0679581996157,12602.808176393326],[36.776318062437426,352.01564805587844,5918.515550128651],[83.44234625952096,291.2138080271194,7548.997490904725]],"n":1752}}}
|
||||
{"case":"gen0958","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[107.74145882355603,153.51914370523167,12519.052048922034],[111.76273615077423,157.53163032975624,14559.754079225133],[84.74111517217158,136.5188000538472,9268.44183853806],[88.50757845398306,119.02054085258322,11545.533129154226],[72.55135906964784,109.07091449526825,11232.29082451005],[80.69833799302168,126.46942984767699,11532.829087703823],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen0959","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[133.46279239946753,152.47807454593416,16464.93688178577],[170.60650389988118,190.1045959656129,23540.421092267938],[163.49766543642167,182.99575750215345,21773.222327236042],[129.2024328759774,154.21771502244394,12715.05134499313],[163.47644267199726,180.9745347377291,22334.104114988935],[95.253015109559,108.91840982004685,11900.694184033484],[117.57370285605286,137.7589347467945,15986.02929966379],[132.0805269648242,150.69477892890978,18606.3860573365],[119.23154031396827,136.61082538182723,12873.487960046208],[21.0,21.0,554.0]],"n":2407}}}
|
||||
{"case":"gen0959","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[133.46279239946753,152.47807454593416,16464.93688178577],[170.60650389988118,190.1045959656129,23540.421092267934],[162.49120339556796,181.98929546129972,21604.945414853708],[134.20243287597742,159.21771502244394,12912.253777869108],[158.00773415284735,177.50582621857916,21159.264760239243],[97.7060468437934,111.37144155428125,11998.400230877278],[121.51753302561474,141.70276491635636,16294.930089773628],[132.42204053800737,151.03629250209292,18688.7818370212],[116.9305197863077,136.30980485416666,13007.926287854294],[21.0,21.0,596.0]],"n":2409}}}
|
||||
{"case":"gen0960","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-43.23155526303372,226.59557106422915,-7359.533267859784],[-39.822369991442955,230.0346900382535,-844.9737633971508],[6.005100539663384,155.96077984983262,3487.556433627534],[-60.175332198126796,215.65179412913608,-12961.617784250502],[-60.529527205950004,209.32753282374645,-6819.678174213279],[26.70398936896997,122.64303884445383,1479.0421048673613],[-89.47472815046777,207.95638697791122,-10698.274591532172],[-14.751941107885223,137.5620806452112,3329.1354747979676],[0.0,0.0,0.0]],"n":2089}}}
|
||||
{"case":"gen0961","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[73.75641642852122,95.77580682312953,9925.608987222415],[104.75737107330758,128.78230411749126,17552.85308847899],[56.105282343652966,84.1251346257259,6292.452960349944],[62.76116438867215,84.7868906553523,8552.60709664668],[76.60038805149391,91.94077728959407,12304.926045517226],[45.23438581242598,68.7967781805421,6900.930409978544],[59.27876251115109,81.760693581827,8344.790062950768],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0962","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[3692.144508606237,16921.62039703822,771678.1239643539],[14400.322608217866,20315.68579082595,1951011.2597858277],["NaN","NaN","NaN"],[10843.342337779197,19402.43178216664,1884984.4991845437],["NaN","NaN","NaN"],[474.8798631633078,8604.691743828878,115253.5198177476],[9395.573881885273,17141.08696120388,1602038.9945021155],["NaN","NaN","NaN"],[21.0,21.0,596.0]],"n":2153}}}
|
||||
{"case":"gen0963","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-178885.86775786747,7711071.730500166,27260765.351660952],[582619.2654165535,8331247.979329606,159854639.0358831],[-569335.7646117855,7220322.963354647,16987474.461239323],[-168190.3352350949,5976868.300771304,-63266538.58476342],[148491.82548958506,8956708.49717465,16715046.934633452],[1323609.5899612408,5428859.617991282,82705634.88625456]],"n":1494}}}
|
||||
{"case":"gen0964","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[59.89459730558976,94.6972093531478,4402.022796002081],[39.309252602164875,73.52426245594562,5242.540011750547],[63.223774724070196,99.66692223761908,5147.0737315831275],[44.85668631067187,75.9285253482387,6740.26301054925],[61.94895326001404,87.23675203610063,10246.141888885668],[6.621724309359297,13.007354250114316,337.5811024006236]],"n":1330}}}
|
||||
{"case":"gen0965","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4535.912578887529,14388.085201734593,514341.3019972985],[4648.928592490515,14500.475754581108,526943.3048677614],[4484.897248875504,7775.538225548183,522088.5208498615],[4905.185535339535,14757.066440324767,617056.3841983409],[2653.7645511642245,9220.12932589129,186315.21845086987],[3978.978893863689,11645.69988816338,666864.8014366826],[3562.7344737981716,12320.259159210775,406332.4836310811],[21.0,21.0,953.0]],"n":1914}}}
|
||||
{"case":"gen0965","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4535.912578887529,14388.085201734593,514341.3019972985],[4647.676813201294,14499.51569315372,526725.6396330755],[4483.897248875504,7774.538225548183,521836.5208498615],[4809.683756050314,14661.856378897379,596170.6544989945],[2752.7645511642245,9321.12932589129,188887.98300203407],[3982.8449674339486,11649.66320102092,675090.1326216412],[3561.3666209386915,12321.085784925846,413185.44822667335],[21.0,21.0,995.0]],"n":1916}}}
|
||||
{"case":"gen0966","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[129.15579522796287,176.38949658987488,17085.232311458654],[200.12335418002056,237.51658957017736,29164.940170438724],[114.04554914175985,212.9148544276798,13284.425930202595],[105.47537592633309,164.70907728824508,13474.683301943973],[175.18267662311965,216.67076984364766,23392.84516704975],[87.66257743243881,126.07280724616666,11688.465537297117],[132.28952532885182,178.90470496907048,20052.596609893735],[140.47218133444562,174.5477520575936,16289.592025353519],[0.0,0.0,0.0]],"n":2083}}}
|
||||
{"case":"gen0967","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[134.01418076565417,140.35614795280887,17270.5480339659],[154.7014772452584,160.84914268557512,21737.19432446831],[96.26640520950579,114.60837239666046,11282.257309272081],[127.61029729869833,133.75796273901506,17060.29456107337],[85.06899683495077,101.30801175181426,11635.083559294693],[114.95031147496027,122.66930634663991,16945.8786946605],[68.9767548882965,71.55759954066355,6232.732581335087]],"n":1752}}}
|
||||
{"case":"gen0968","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.1932132789445,108.60189231508942,5156.228113477348],[49.064898658104084,118.24530501192642,8575.07807687669],[32.30355730799367,114.61968659029353,4774.0775084573],[40.66220067678454,106.0708797129294,1786.1622712834796],[25.334179956122124,92.1085539228296,4921.831739541227],[60.92754560818414,84.57291616483786,8085.504405353139],[9.381973267577369,79.51184075973299,1899.2490452573209],[40.037866820657925,78.44568177470089,6894.618969759076],[0.0,0.0,0.0]],"n":2079}}}
|
||||
{"case":"gen0969","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4052.9965069157315,4100.180912994876,513951.27067008615],[3057.5032328261013,3123.702949392889,492023.09708907356],[2055.7449150584603,2128.5076916235525,242958.48499385966],[40.99650691573082,88.18091299487757,2575.615460464381],[17.488853261975382,89.17678909454736,2457.225705363796],[49.54828459492288,88.2772897047389,8036.888734907812],[47.9859850849313,90.32906450099777,3786.247381123338],[17.70216634244482,86.35194007577698,2189.8203368137733],[33.64549334541353,86.2539872175496,5146.999971676043],[21.0,21.0,365.0]],"n":2398}}}
|
||||
{"case":"gen0970","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[89.9373911287544,140.15649132006348,11540.05193081975],[116.95356104947741,179.40649041586636,17093.307972734423],[112.04080181027797,181.20683969668463,14250.253523922738],[80.9373911287544,131.1564913200635,8802.536786990551],[91.56047487961997,159.15558112479528,12802.328856546224],[85.89304240420924,109.260371943743,13069.8505310133],[60.018516837039165,113.02126394427745,8638.345781450565],[88.40066846223867,158.09804707221056,11317.576054593354],[45.61412952207349,53.85388553517899,3086.522109366791]],"n":2236}}}
|
||||
{"case":"gen0970","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[89.9373911287544,140.15649132006348,11540.05193081975],[116.95356104947741,179.40649041586636,17093.307972734423],[110.78459976740369,179.95063765381033,14015.343965499142],[87.93739112875441,138.1564913200635,8925.474178119306],[91.39534096871112,158.99044721388643,12785.623797116647],[89.23940971284087,114.60673925237461,14304.714854417432],[60.72158650362778,113.72433361086608,8759.085884791233],[87.70749909012665,157.40487770009855,11240.200337347305],[44.19160195849692,54.431357971602424,3119.65016770905]],"n":2238}}}
|
||||
{"case":"gen0971","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.684247748818514,108.90935947258103,5856.080336762454],[47.34697657133765,105.16241180114471,7473.615578596817],[67.79110590660432,153.6358037431225,11233.489493555277],[37.91103241497925,83.4529654442431,4023.156534691656],[20.174610933860375,82.81379497387717,2116.738681570194],[35.63779625036109,86.77579570351398,5976.380364612009],[57.90674216193182,91.79661869399564,7029.742450359645],[16.248100077094048,76.35288257559982,2104.5731609436016],[20.19160017744409,78.37372357850644,2433.4423243962624],[40.37699718273076,47.519331273532416,2732.965276260448]],"n":2486}}}
|
||||
{"case":"gen0972","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[91.83326078618344,116.12644984993676,13388.805007368395],[56.27749002393433,74.18070800475797,7882.364714480355],[51.21863228029034,92.75005626986837,6664.850483402145],[44.93048904070576,66.66806095476544,5663.650406336519],[46.846341898292806,89.58576867001148,7135.387120162226],[45.377004350252186,62.732447316478556,7663.54479803284],[1071.4688884284687,1112.0932197048558,149786.39141930925],[39.5258858087652,66.89664868400696,5538.159223291587],[14.711857585950916,24.515850983667747,1107.1323184803007]],"n":2175}}}
|
||||
{"case":"gen0973","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-1215.7312131571534,64635.427970159515,18609.09444297076],[-2660.202127186487,62484.36091486044,-111064.1030611645],[123.07490287836082,13829.00066567072,-35469.523765240745]],"n":609}}}
|
||||
{"case":"gen0974","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[283689.4436520487,5539287.113612516,103385258.25606638],[556690.186448968,7055479.097377298,57359542.95030382],[462386.1950023416,5717987.93575994,144620520.54565835],[67215.40830378792,4768205.08718262,-17315602.71084579]],"n":933}}}
|
||||
{"case":"gen0975","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[79.3992451743312,111.63552938679211,9159.612350993499],[79.52277786428179,111.75906207674267,12669.893273037018],[97.33322242972017,129.56950664218107,14954.623745450295],[71.60136975000641,109.83765396246721,8057.014396562516],[56.09028837251243,88.32657258497332,7421.175959760662],[56.80065455734261,89.03693876980356,8925.914225941546],[80.22243841528358,98.94624825878535,11127.875822734475],[45.00945526655582,80.13391529371795,6312.727521089035],[46.32238027488826,82.44872022838663,5622.835913376279],[45.9658586136608,47.95467335606094,2795.463662303836]],"n":2486}}}
|
||||
{"case":"gen0976","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.50462819126137,161.01583824992358,4575.235301161614],[44.33329861580107,177.10661997743784,4759.404727762567],[42.11524378057545,129.30682485792232,3379.1104445995275],[39.217771139220375,160.02677473912348,4225.907813453876],[42.44269761856236,138.0532088842794,4559.212237039627],[25.34707588662174,140.19141759749155,2583.1753439879894],[22.434527375467145,116.41550609926956,972.5287581737693]],"n":1711}}}
|
||||
{"case":"gen0976","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.50462819126137,161.01583824992358,4575.235301161614],[44.33329861580107,177.10661997743784,4759.404727762567],[42.393488070346464,132.20933955437584,3286.6254113565005],[40.12120039685074,159.12334548149312,4497.306903804099],[40.617835063206705,140.52247310241773,4522.261678953398],[24.82530230428429,140.56220905107057,2382.4295772705777],[23.295100690186427,117.83265172986295,1042.6877622179695]],"n":1714}}}
|
||||
{"case":"gen0977","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1076.102944957928,1158.4842319868064,9692.540681747441],[58.43844900921332,138.12437009578025,8419.930467684711],[81.57813983783815,174.53788187743888,12763.947961780454],[57.66448556124538,139.1750779963302,8197.850238531453],[61.667578178637,133.85922705608394,10353.057260905489],[78.97808189957635,159.87185861371975,11040.182627976788],[47.925169452199114,125.55460467781295,7521.219207538686],[65.9928018008058,124.26352159837826,7674.627856628721]],"n":1998}}}
|
||||
{"case":"gen0978","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[86.87239729290762,146.9864802963631,11541.593298945652],[102.57088019080241,147.83844261092335,15265.004231233279],[48.391376467703914,132.2112818094938,6556.4885443078165],[79.57088019080246,124.8384426109234,12197.82379773349],[50.787120794097106,91.67805963108283,7388.718703516576],[57.04050286587598,114.73890783649973,8333.787212841511],[52.75310919867606,58.36029431599171,4464.215065227963]],"n":1750}}}
|
||||
{"case":"gen0979","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[38.09335066078349,83.67029302968872,4873.770803403399],[41.09235011083141,86.66929400163008,8308.848258757786],[55.393899531890796,115.77852889388281,9141.963884629033],[34.441377069392196,86.01848887577209,3374.7808706168194],[15.09404926130387,60.67099264480473,2114.850907450643],[24.090608584779268,69.66772167631376,5446.94172595404],[41.35027814654862,81.51681801262899,3776.171698241147],[6.897070417965313,55.05613405439702,1212.0873982600515],[26.68127857425119,65.89435962361188,4794.746442245861],[0.0,0.0,0.0]],"n":2329}}}
|
||||
@@ -1010,7 +1010,7 @@
|
||||
{"case":"gen0991","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[0.5810162450946341,284.3694717878165,4040.463142864678],[7.2088681260326215,302.4343945713683,152.71415668185716],[4.8013339816371,66.52728686376169,193.65953575511793]],"n":826}}}
|
||||
{"case":"gen0992","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.62772328863537,120.15150551744209,10363.465267033567],[59.433462204692674,119.83543405256911,6897.6679880491465],[69.77413727542519,114.19452796985756,9931.018383521408],[49.71611739680326,73.91903570234501,7298.0443670266795],[61.442098030081894,105.86248872451428,9066.299132991258],[21.0,21.0,1226.0]],"n":1415}}}
|
||||
{"case":"gen0993","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.04222221150167,124.81675100454592,4547.362299619516],[64.43109325747942,98.44798537662716,5814.903026652425],[69.55327452909232,112.87881100058702,5505.21494922693],[74.29289056743993,107.89013571053124,10094.9572258163],[31.35187123691354,45.02278482604271,2592.3162777169637]],"n":1172}}}
|
||||
{"case":"gen0994","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.97479056730327,133.81702516167792,8449.469336865572],[75.30709052329921,149.43298272288794,12102.996151648025],[39.92840020817829,117.67634160849522,4059.1373196962413],[48.862007559371825,120.88333108164679,6212.500139029754],[64.42888177391892,101.09719270242057,11059.763277878828],[35.493815655600436,105.3931973622813,5850.186348029819],[39.00081947587344,107.0695645631559,5998.489990068381],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen0994","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.97479056730327,133.81702516167792,8449.469336865572],[69.30709052329921,143.43298272288794,10578.996151648025],[43.9136156245586,120.37910205307381,4492.57225898114],[43.106564554165544,113.326749947812,5817.019523228241],[68.80838797360795,103.99538205893641,11984.689778497714],[30.45775036685893,99.26069769616022,4501.625888458366],[40.660947959097705,106.543672451958,6424.17764401513],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen0995","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.12266720048044,355.9919097780406,4448.140450097045],[39.723353310059345,359.2948269192291,5859.509202469382],[52.08308988320478,353.9338239142107,6114.310742595563],[28.962923210507206,337.5529053662312,4943.116886966889]],"n":1005}}}
|
||||
{"case":"gen0996","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[90.62457935135018,145.4204605984352,12585.89485426488],[1109.610951880478,1176.9523356140216,175887.33194321158],[91.99663273609357,147.33801646963687,14131.824696068286],[48.30371251058559,78.38717209746119,7102.651259405946],[84.51667092456704,139.85805465811032,12976.486419583796],[21.0,21.0,1310.0]],"n":1419}}}
|
||||
{"case":"gen0997","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1857659.4090890482,1935596.5657146617,227815963.99723452],[1682325.9677934807,2008401.291705592,195883667.22601703],[1770078.497718454,2018272.1630085562,210637609.3671639],[1997112.892614743,2199116.824606416,264440405.69789857],[936711.530848375,1092517.8968737498,72337159.8403851],[1536879.9109082506,1562885.8383183186,258597993.02913052],[1734585.1157457982,2045270.6500803558,231469228.6502428],[275520.6570240488,296664.86481579934,5288958.78205303]],"n":1994}}}
|
||||
@@ -1032,9 +1032,9 @@
|
||||
{"case":"gen1013","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19.85067388492307,257.7272346977349,-549.301862568085],[96.86815929476303,324.21054119007806,13127.641186456862],[28.437526480510293,263.9318951980773,3967.1454808510857],[16.062298351339145,249.7778070059155,3333.582460036679],[45.66612572528148,264.16729255968653,10495.120276070924],[6.508627193069366,226.99393093634063,1825.3271371783019],[-1.607556045400549,235.25781579802702,1379.9461387526596],[47.97488704025702,260.90718048956415,9069.619514812533],[-6.000152448123756,42.59012802439128,-102.10948496865251]],"n":2087}}}
|
||||
{"case":"gen1014","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[396361.87535458064,2046718.516361691,44727367.97091803],[345536.88867234474,2414660.0295365714,26022934.581392843],[405540.70247059164,2474652.604200767,37149896.6089695],[89074.10287923095,499622.3564515706,14057857.865199052],[405535.7450714683,2474646.26636867,32283413.10092928],[21.0,21.0,1352.0]],"n":1421}}}
|
||||
{"case":"gen1015","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[134.99229010369095,167.69717128592194,18604.025618943022],[149.91013729084924,186.2028589905559,19370.003213041324],[141.0336870467221,205.2326170534346,17028.500819869583],[124.76210224183689,157.72284859082146,15407.436462886844],[134.95167377501915,168.64141937028174,19290.248278447292],[114.67083336842788,154.27453744799897,15398.458269754643],[104.38109653331294,126.0579031926235,14139.743175072681],[125.23661352801649,159.1273511580939,17656.839064845153],[114.86291296981956,153.9834074926598,14327.227714615892],[28.432987804934015,33.06357372897315,694.3985243514109]],"n":2408}}}
|
||||
{"case":"gen1016","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4009.109743200773,4171.566787133139,512498.05197367736],[3007.341338952778,3179.1190445594716,497068.9301607769],[2.3216489144460573,154.09047720422112,792.0226053663104],[-28.431899200798256,142.8099387407588,-3912.3741387871264],[43.6401681624795,110.32838380506885,9168.774743821165],[-35.483788533512126,144.85455151408738,-4172.344407226293],[14.110150044742209,110.92594688901318,5634.864234730288],[0.0,0.0,0.0]],"n":1835}}}
|
||||
{"case":"gen1016","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4009.109743200773,4171.566787133139,512498.05197367736],[1005.1095931741703,1175.423166445997,82593.23560893183],[3.719026310378292,154.02257434105394,831.231498462251],[-28.039653661617894,141.7391998693077,-3929.633285819888],[42.829877488785854,109.5180931313752,8998.434498798739],[-33.90125970590842,144.9729480068256,-4112.567263670672],[14.110150044742209,110.92594688901318,5663.084534819773],[0.0,0.0,0.0]],"n":1837}}}
|
||||
{"case":"gen1017","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[89.24162696732768,253.70747873238375,8713.731835400113],[63.91063244510696,263.86386165378036,9649.734338022245],[60.143678230642834,255.79564281387354,11047.823125549054],[1100.4012566273925,1275.5446599622637,159490.56944902323],[93.87523231508986,239.5832686077542,12664.876407337892],[47.61176927765509,266.0995509807559,6697.553262960658],[1060.0459855511328,1261.5162275320345,207085.5570062652],[113.82388386699765,250.10801140781368,12836.926149940591],[69.69448060947876,267.11644973749435,7649.371120808839],[52.06872912384209,239.5805525253184,6519.457469611875],[2.838813043870002,4.230619987832519,12.842314578804515]],"n":2568}}}
|
||||
{"case":"gen1018","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[11352.519568120855,18997.219207236834,1494303.4728076234],[16405.922184107523,20385.077680052764,2110367.0474746027],[8836.557387882845,10353.501416058603,671527.5882573514],[6372.517130997272,12208.218170366808,1045464.4857629152],[17334.043776513445,21916.960317675897,2303190.5209072726],[7680.272214393531,9266.689810644286,627339.5088662868],[9035.658523572067,15475.600300967379,1487279.1701763205],[15417.052761076426,20002.769908705304,2043233.8853645436],[6933.121837255748,7912.498089014305,408508.99250680767],[0.0,0.0,0.0]],"n":2321}}}
|
||||
{"case":"gen1018","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[11352.519568120855,18997.219207236834,1494303.4728076234],[16405.922184107523,20385.077680052764,2110367.0474746027],[8834.234834905872,10350.549939791252,671282.5753733815],[6375.517130997272,12211.218170366808,1051957.0018939127],[17331.96363045438,21914.25124832645,2320076.5601658104],[7681.029807475623,9267.447403726379,635020.5386737623],[9039.658523572067,15479.600300967379,1505616.4862234644],[15415.284349876207,19999.264290661198,2074021.1337107997],[6932.567695478992,7913.0522307910605,422374.68203954236],[0.0,0.0,0.0]],"n":2323}}}
|
||||
{"case":"gen1019","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4085.8886325388703,4127.676039160958,517875.08391116146],[3074.3536857934587,3116.141468640297,502673.0581988823],[2078.0135849494277,2149.801329728848,247396.95330170752],[25.61096919273454,129.39862732046203,2972.578204600608],[77.44957648371113,125.2369831057985,6077.8296112017615],[48.415933215746676,94.81026953275816,6462.259447335726],[79.19991262364303,110.45176983786953,14081.224492103753],[42.31101980332379,89.65204958646174,6194.124884340133],[44.040244072181885,88.64996205834954,5909.98370504532],[36.05046019362685,55.53330457764663,3187.452260270331]],"n":2499}}}
|
||||
{"case":"gen1020","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[137.11913820934433,258.3161092618996,18034.169788466235],[182.5386670646957,293.195932438317,26793.419957219296],[191.609223324571,304.26648869819235,28016.843772816203],["NaN","NaN","NaN"],[169.92096493202138,291.0102401127695,24163.53522493268],["NaN","NaN","NaN"],[114.09743298270888,155.03257532259764,18898.19052933636],[98.80284727745435,228.15406821407012,14582.919897733082],["NaN","NaN","NaN"],[124.9116316198959,211.35610634681666,11520.004756640536],[21.0,21.0,218.0]],"n":2647}}}
|
||||
{"case":"gen1021","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-363193.4448870234,6356155.046932423,32375570.13665154],[-101424.66852330744,8184640.636544083,-117138234.84271242],[-335976.5523335954,6762059.215281229,-138211677.14695626],[655849.7906946562,6752511.660334859,-43614468.46713499],[1074801.0060498957,8127526.820760996,63339429.206703626],[-236241.69569918793,7067135.915647241,7211861.496438026],[-653969.099415949,1664119.4228008655,-19468756.27581869]],"n":1590}}}
|
||||
@@ -1054,7 +1054,7 @@
|
||||
{"case":"gen1035","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4105.075512127401,4122.640005395002,514673.3818796177],[3123.9192352434948,3141.6070961634196,494943.52818667726],[1107.8491119441244,1137.4752891543224,89049.0442698083],[91.280158645053,108.9063357388163,12985.809507197095],[92.37539996153704,110.06326099789707,13031.042018540458],[70.68412868336789,90.36421498890459,10141.11787705492],[85.31875456827092,104.03681188757848,11696.027765104734],[88.47404628378823,103.22421060288036,10691.277012179748],[21.0,21.0,617.0]],"n":2154}}}
|
||||
{"case":"gen1036","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1078.5046018081691,1166.3652078929304,13270.361949346165],[49.16025052594476,126.7936643050292,6048.820545089579],[38.451714438149885,124.1746081682238,5639.83229818183],[17.046987849999105,68.8614318684878,1564.6960354824926]],"n":911}}}
|
||||
{"case":"gen1037","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-16.89579460800073,353.8112856307693,-6871.27681926588],[-7.338104128026458,340.06788832116507,-1888.0832043024056],[25.42707638061181,320.7620827368871,3326.2438116881767],[-32.1366505601611,364.99238933175263,-3571.3895039180406],[1.6424422133349053,330.32811045522294,1305.952444696892],[14.998042987285423,54.62430470982592,327.04900616636655]],"n":1323}}}
|
||||
{"case":"gen1038","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.11160235185139,131.18664628782417,11980.231223355482],[108.93278095820428,141.54232158813073,14000.732826855172],[68.84095717707982,117.91600111305257,8609.062615635306],[95.36709815300208,117.10679190628636,12054.189685459203],[58.130972286019826,96.33616934535047,9181.068995835225],[88.08135368923683,120.69089431916326,12854.752430429588],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen1038","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.11160235185139,131.18664628782417,11980.231223355482],[105.25158760461021,137.8611282345366,13368.523995911362],[70.84095717707982,119.91600111305257,8224.903572812389],[92.68590479940798,114.42559855269226,12141.469629425803],[60.47353123259798,98.67872829192862,9203.23046554913],[85.05760138906457,117.66714201899099,12701.616936760633],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen1039","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[55.97371552253288,93.40640654492834,3074.4985944283785],[59.91611928696995,98.74964536019765,6978.520943667355],[45.90606537978076,78.05492425656642,6747.671283993894],[15.930445152729089,24.758298513672674,891.5608616538137]],"n":845}}}
|
||||
{"case":"gen1040","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[6281.0142818140275,72492.20164074507,1052549.916104551],[3548.5516334771723,70358.73130770966,173869.19848530082],[-172.0349060195133,64671.32383616996,243371.77748930026],[10451.493970588452,74796.00595967258,1904749.2522249434],[-3977.229158627281,66251.70766498888,122267.32211673653],[-3012.062303268776,14624.847857928493,-72691.41174044953]],"n":1325}}}
|
||||
{"case":"gen1041","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[37.44382652358979,125.52690890417833,5217.390771401761],[39.46778638695015,137.41581043437927,6629.787298092919],[37.53408236464225,160.70152815265322,4229.626560501698],[21.82427696022719,111.12864029759041,2049.569454567876],[13.4179854727847,117.73265099239178,1569.5528447137372],[20.75727895816057,105.2105813310886,4402.176654714967],[29.108802812632245,101.17630757564585,2192.2122229905626],[8.319604169441709,112.85411058744009,797.255984985893],[22.827564676697026,97.32621678699985,3821.804426700003],[0.0,0.0,0.0]],"n":2323}}}
|
||||
@@ -1063,7 +1063,7 @@
|
||||
{"case":"gen1044","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[149.40485614499792,186.93547405039465,19694.74404746169],[146.35708228829142,198.51827780770617,20203.281765972846],[157.3642104004233,208.3328726698829,18742.684144539217],[135.46096235927163,182.91457269300167,17661.99012942145],[122.61460989485843,170.16810179294873,16709.19860798998],[102.11239056109868,120.57231288462225,16276.558021333767],[119.15666449688187,170.96456718831195,15735.313886148817],[116.39280294362297,157.69222691113526,13607.687860380516],[21.0,21.0,554.0]],"n":2151}}}
|
||||
{"case":"gen1045","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[17.42202897012705,172.74008614085866,1310.962376705939],[18.600040027035394,173.1938378545129,1833.5371928653478],[-0.5739593767249431,164.78964828038175,1891.0136721139224],[28.97602838203924,86.50281274387245,2453.1614596953905],[0.3619207520055028,157.0329763321882,1862.7138254296483],[21.0,21.0,1394.0]],"n":1423}}}
|
||||
{"case":"gen1046","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[48.499639603585756,93.75499213093266,6676.7790495997615],[35.14870703688336,86.4040593391963,3841.0214702951093],[35.1996397674315,80.45499251981224,6182.478281069198],[39.84894805699644,60.93406511604413,5125.521764636511],[28.499638847748862,73.75499160012961,5759.282964255742],[21.0,21.0,1268.0]],"n":1417}}}
|
||||
{"case":"gen1047","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[116.96996926927834,139.38563384436574,15349.172727029663],[158.4359306830749,175.32250925870852,23822.09864374915],[145.47115841241666,171.97162135124057,17040.44878871043],[103.96782906257661,124.43205152020973,13942.85271203311],[141.66863293107406,157.56717304662567,18685.84046098771],[84.81531031985338,101.4355329232517,13158.792424080038],[99.85167131012575,119.6654130619408,14090.734518254705],[130.3250047872881,144.3761719027733,15231.959507439233],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen1047","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[116.96996926927834,139.38563384436574,15349.172727029663],[158.4359306830749,175.32250925870852,23822.09864374915],[150.52271198267255,177.02933362217394,17939.391151965854],[104.91161103669658,125.37583349432973,14047.764323069809],[138.39575254465518,156.30045136088424,18088.416026258623],[91.35108680911607,106.89975643398895,14388.896629942063],[99.31589482086304,120.2011895512035,14289.770781574454],[127.0521244008692,143.1094502170319,14835.33845901412],[21.0,21.0,722.0]],"n":2159}}}
|
||||
{"case":"gen1048","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1063764.5618422215,7501640.967156958,106347256.15229781],[364204.5855655005,5564976.113150279,34404358.92515218],[817720.7576001567,6463010.273817706,82043597.99613152],[455856.80012700823,7611849.304834015,47443189.64286806],[881565.0283603896,7500298.820117284,114082988.56002168],[537578.6877395625,6334146.76103845,68754168.39740708]],"n":1514}}}
|
||||
{"case":"gen1049","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4075.133292858456,4087.9041733580734,516661.7567476697],[3067.133294881579,3079.904175048512,500212.446626724],[3095.4342934249607,3114.2051735708374,502122.33433183155],[65.13329240845724,83.9041729080752,4428.2228474513595],[40.1332979966896,52.90417820798158,4898.09162838339],[57.75552682253982,68.26944688385296,8943.417317994186],[62.65952034864845,78.2503524026767,7028.204747751605],[35.21918186988372,49.041630030434284,4675.102009872511],[45.44441501015551,58.08681556779981,6457.598460180721],[21.0,21.0,575.0]],"n":2408}}}
|
||||
{"case":"gen1050","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[123.67498807925344,294.0281035376028,12144.273026798925],[98.80834390000545,359.27412837752956,13155.262401084354],[108.97185936449712,321.90156301519494,14657.879067697548],[100.9821698099625,315.04380292345996,12847.837064219199],[94.46586567095433,352.1328185715406,12529.965895671674],[32.31814859053774,77.521030907679,1018.0135659090608]],"n":1330}}}
|
||||
@@ -1093,7 +1093,7 @@
|
||||
{"case":"gen1074","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[727857.8103692792,1324865.0869227587,86958776.68626949],[773397.7172228232,1370445.3263416095,94908745.31157538],[681926.0179783622,1270863.4280818345,84357789.03175026],[822899.5923598541,1486267.5965023995,107147466.06463681],[443208.203927541,766822.7565377263,43596743.16903463],[790250.892174477,1453599.911924983,102279765.0394995],[607429.6805597103,1130047.569176359,64806101.06468157],[0.0,0.0,0.0]],"n":1828}}}
|
||||
{"case":"gen1075","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.82807413763561,333.0985740421243,11290.126474720113],[123.05898130838966,376.71514041668,18908.025751784055],[1176.3804604374695,1408.637150590349,183540.69635899898],[42.89945207815344,338.1610517279366,7507.571043376478],[45.08567461003658,347.737524719384,5868.5881254678225],[88.99246338321687,356.6702852306582,13200.39646593129],[96.82046925417515,343.93001895926795,9514.148044716105],[81.45366247017509,301.17095251333984,5717.427393049465],[97.06994314557497,335.1203947010227,12329.217080227012],[100.87282549073758,285.09710418563486,9460.541860721489]],"n":2508}}}
|
||||
{"case":"gen1076","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.1627047097637,145.49256788482774,5173.96560363456],[1048.01137241413,1161.8765471559777,259574.13390615385],[67.71222957657572,155.26355349716476,7657.143203449371],[1056.360076418118,1164.3143156247488,261806.70080875614],[57.025498253774984,147.43006548762185,6835.108995169311],[47.365087790785616,151.31932699741634,7522.968689531671],[64.99693713294043,149.05632052918955,5041.959308649271],[31.853942860111314,145.93522892363498,5222.033028441229],[66.65736336396965,150.71674676021874,5554.2349809979505],[0.7420445649024692,79.68347338311757,-244.34530757292333]],"n":2463}}}
|
||||
{"case":"gen1077","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[97.71620405660515,127.12312890650206,12460.116197918185],[100.72067339123227,127.86658016790821,15132.684313764965],[109.85124965270249,136.74593219902053,16110.998891563297],[92.84943937454968,122.00011550948157,11561.41565871234],[75.57635436514045,105.747724230956,10349.101123205433],[72.13592787549946,99.19641841385372,9620.315644372145],[94.38563938699714,113.90316488707103,12113.099468193332],[70.69559591328887,102.7801528565556,9294.206772453028],[72.41824390790067,99.396667864338,8767.329761518049],[49.29825767332932,61.51992637042463,3173.311875236407]],"n":2480}}}
|
||||
{"case":"gen1077","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[97.71620405660515,127.12312890650206,12460.116197918185],[100.72067339123227,127.86658016790821,15132.684313764965],[108.85124965270249,135.74593219902053,15953.998891563297],[92.84943937454968,122.00011550948157,11561.41565871234],[75.57635436514045,105.747724230956,10349.101123205433],[72.13592787549946,99.19641841385372,9620.315644372145],[94.38563938699714,113.90316488707103,12113.099468193332],[70.69559591328887,102.7801528565556,9294.206772453028],[72.41824390790067,99.396667864338,8767.329761518049],[49.29825767332932,61.51992637042463,3173.311875236407]],"n":2480}}}
|
||||
{"case":"gen1078","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4128.063311168435,4145.69506606068,520661.91504107247],[107.15183197564612,138.7835868678919,12687.790088353713],[111.08088299643916,130.71263788868492,15270.719332223189],[62.75872070201831,80.6359723327669,9871.781565708385],[105.08088199643916,124.71263688868493,13951.748746265917],[21.0,21.0,1310.0]],"n":1419}}}
|
||||
{"case":"gen1079","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.58292196767958,95.82180957862958,6600.563515581969],[53.33821966155785,96.37889214032313,9166.020080849696],[65.12360371745734,119.60613349190939,7313.096601111368],[30.85019063990049,73.0890782508504,3731.7973329989964],[36.39837291066943,79.1267554789421,6373.694566899057],[55.666521388713385,82.4641831163047,7353.3453613684505],[22.195544693851932,67.00674386251805,3417.8903356122337],[34.75285714231415,74.19089562892937,5440.7658968857],[21.0,21.0,638.0]],"n":2155}}}
|
||||
{"case":"gen1080","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.73882563777092,95.61669819902032,8941.718588804828],[69.74200414892898,89.61819071745802,10136.799713250404],[91.79814449140363,117.67592831619399,10873.080834389773],[51.74103609840467,71.61819876798229,6935.36686672265],[54.73815254192682,74.61496026566857,8725.16088872841],[63.932820693941984,78.47872473112687,8629.089347597896],[44.81505574132347,65.42895620966866,6060.16490891429],[53.03822557651005,70.25879266701062,7218.185187703975],[21.0,21.0,596.0]],"n":2153}}}
|
||||
@@ -1107,7 +1107,7 @@
|
||||
{"case":"gen1088","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[123.58968639762352,395.4504815085047,16534.221645177004],[3100.826559016909,3402.615849092131,262810.00399458717],[1064.922211184008,1374.8958050971319,87595.29060505591],[1118.0780361753236,1340.1679606031962,87382.92757009222],[83.20089928029061,342.56121737217137,13019.17531402158],[41.71557993785041,203.29020210706665,4279.53619898564]],"n":1420}}}
|
||||
{"case":"gen1089","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.80611816855554,110.49222325652485,4593.297561404412],[61.6584143674945,148.68434076443452,13161.567385861894],[17.657492258163725,100.24136762668961,1502.8512738857642],[20.945150594780475,110.43845669684742,3479.9446252579196],[60.882892881016325,95.52638502773198,10180.096240634482],[2.6189215987068697,81.95879979943203,464.5556317176215],[32.335255388353666,106.70740867245202,5729.847631990331],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen1090","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.66367044518443,180.71158193253953,8574.818432008047],[39.67702226769006,154.4638261006246,3439.1990605701067],[17.012738040913522,150.06064952826864,763.8086366475836],[29.177365620768086,119.83717598014098,2051.3044010397452],[33.8926194146285,161.03226093567136,5317.569935976972],[0.0,0.0,0.0]],"n":1349}}}
|
||||
{"case":"gen1091","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[71.98276090476811,109.7652576978305,9528.821176736397],[81.82435346032858,119.95738928927541,13705.88952668827],[68.02341493465863,111.9459729089614,8772.25364778745],[48.281554891949476,86.27452953965594,6003.756987459813],[59.221949955361126,84.69040351321684,9106.769460416592],[60.360404438647805,94.0965399328945,7458.480572422287],[40.6815604380266,78.81459626697345,5703.041363626242],[42.70046443641157,52.74682573522728,3510.833920544052]],"n":1992}}}
|
||||
{"case":"gen1091","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[71.98276090476811,109.7652576978305,9528.821176736397],[80.82435346032858,118.95738928927541,13456.88952668827],[66.02341493465863,109.9459729089614,8529.253647787446],[48.281554891949476,86.27452953965594,6003.756987459813],[59.221949955361126,84.69040351321684,9106.769460416592],[59.360404438647805,93.0965399328945,7398.480572422287],[40.6815604380266,78.81459626697345,5703.041363626242],[42.70046443641157,52.74682573522728,3510.833920544052]],"n":1992}}}
|
||||
{"case":"gen1092","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.82681120943683,153.5307607543661,9776.635739612097],[97.11253326994287,158.3675959967596,14288.06500584338],[107.25101226781504,200.24429974902202,15996.373961033814],[69.77232980063077,118.89760673161065,7632.060455362688],[75.19436465028,139.09471517915054,10580.385500895929],[67.07260979970225,130.06589728090927,10359.49764571725],[83.89911030504626,115.3398617196043,11858.133570589782],[54.85839500439826,123.58670287707719,8316.608084491292],[62.71423680999494,132.70755163465094,7697.049639453643],[46.56756213117356,52.42410296033803,3005.882220308824]],"n":2490}}}
|
||||
{"case":"gen1093","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[71.42067765196208,102.00526077382709,9167.58460381161],[81.82661361239175,104.10509409784682,13348.92757746101],[1072.140024376259,1115.955906619322,82543.34136961075],[54.51489603077632,79.56207739503465,7232.6410498707655],[70.23848608443922,88.74174595332312,10007.134326189902],[44.03181100883569,77.1554545371595,6356.777750912514],[58.5936504011471,78.3330704801433,8688.808703216024],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen1094","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.83633531827637,198.3175920980932,4835.752783004273],[3106.2223187800746,3210.5885540028926,216729.55327357198],[35.21207852889545,181.74844312980107,1923.8551179279043],[62.23385509049595,151.4759795865137,5839.661176688133],[62.20578221199411,131.93433393216165,7539.277032143093],[11.874466641064464,15.72273220995576,49.01361476543913]],"n":1290}}}
|
||||
@@ -1123,11 +1123,11 @@
|
||||
{"case":"gen1104","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.17221027798,163.81917389789675,10592.54943552536],[96.77313782080235,212.1339339460588,15670.947986754813],[60.46999028276359,185.8059101978214,8681.066152245476],[52.57414925987625,113.69771986690866,6981.849671228418],[69.26608978014251,169.82945569484082,10431.639551633356],[45.23459923199698,158.06150417556304,7233.714778145284],[57.22281168918991,118.01691506655229,8015.919047097278],[58.8394738152307,174.8656653831746,8907.078277653554],[26.226851242965886,88.66735690289461,2870.362314819214]],"n":2249}}}
|
||||
{"case":"gen1105","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[64.06071933011944,148.37988841577604,8376.687931768678],[72.88985411515885,172.3656497423568,12960.535283605157],[53.524092155504455,161.6317485512488,6759.315770077982],[46.038162727633434,135.4095406604708,6144.546322202364],[53.848520918601906,121.84978761912706,8941.623195721548],[50.76597218113639,123.36725168507695,6682.618315901045],[35.11789102388768,134.53278549428063,5364.9471107829395],[41.32164411445524,65.10043485297632,3606.2139460010944]],"n":1994}}}
|
||||
{"case":"gen1106","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.17672368347201,187.31736654330942,2109.059391292559],[33.178465889864846,195.13409058419936,3323.982822696682],[27.547970972614138,165.34280641216083,2222.2218948771597]],"n":766}}}
|
||||
{"case":"gen1107","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[113.20223030917509,138.0278776151801,13534.163519339641],[111.45401909712427,136.27966640312926,15022.037431889516],[105.92790052660406,138.75354783260897,12083.925531646066],[114.55544404062637,139.38109134663128,11033.3924351125],[91.51496499213235,116.34061229813734,12529.86600693304],[95.50341768958806,111.77863345825637,15404.784767761292],[80.03555568594419,106.39143060462037,11136.37941487505],[86.89709043740511,113.17081608338934,11661.001816024125],[44.443798483366116,51.740708299384,3220.087936162107]],"n":2240}}}
|
||||
{"case":"gen1107","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[113.20223030917509,138.0278776151801,13534.163519339641],[111.45401909712427,136.27966640312926,15022.037431889516],[108.8572578920706,141.6829051980755,12319.117038042223],[116.53309268451947,141.35873999052438,11533.670487949134],[91.46667371370579,116.29232101971077,12528.062137264542],[97.1516070166882,111.4268227853565,15490.4906127705],[81.98965685949733,108.34553177817351,11579.700720201137],[86.87234662931853,113.14607227530274,11659.963249957009],[46.09198781046626,51.388897626484145,3262.9408586667105]],"n":2240}}}
|
||||
{"case":"gen1108","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1097.9956850227106,1124.9102485999663,183063.80001333027],[79.6375107938196,111.51469038017761,10903.55629107973],[65.60085544336104,103.33287982634867,7590.572760917221],[64.47930727378282,101.55603035820627,6205.681154076515],[37.34750032371501,58.02607846429865,5119.484312877329],[66.36640672594581,102.93932061859951,9496.690666547232],[38.424563948264705,69.61294047573779,5575.591512370477],[40.35207418295182,64.38253313025884,5309.012206230442],[4.20329530973154,7.807159841700272,97.53939745980207]],"n":2076}}}
|
||||
{"case":"gen1109","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-618769.0304703588,1804410.1143734606,-88496751.2813384],[-648704.3322303749,1775106.5363123699,-81934255.5601507],[-578743.2158570441,1845172.939968855,-65478534.11301619],[-615871.9774857564,2734269.7867609733,-89042023.84838054],[-667396.1619452512,1811537.6729356574,-101325898.99599823],[-614123.1392174722,2079126.0897422647,-82946576.92346822],[-250299.42915526585,1587307.2987043145,8518192.02901765],[-774223.069632859,1918244.6937141325,-76926599.52245228],[-774197.1397010674,1918992.9632295812,-98027432.05889626],[-501394.6815920151,1874843.3184522933,-56459123.498532765],[0.0,0.0,0.0]],"n":2579}}}
|
||||
{"case":"gen1109","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-618769.0304703588,1804410.1143734606,-88496751.2813384],[-648704.3322303749,1775106.5363123699,-81934255.5601507],[-578744.2158570441,1845171.939968855,-65478697.1130162],[-615871.9774857564,2734269.7867609733,-89042023.84838054],[-667396.1619452512,1811537.6729356574,-101325898.99599823],[-614123.1392174722,2079126.0897422647,-82946576.92346822],[-250299.42915526585,1587307.2987043145,8518192.02901765],[-774223.069632859,1918244.6937141325,-76926599.52245228],[-774197.1397010674,1918992.9632295812,-98027432.05889626],[-501394.6815920151,1874843.3184522933,-56459123.498532765],[0.0,0.0,0.0]],"n":2579}}}
|
||||
{"case":"gen1110","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4048.1705627006695,4163.680553334905,514323.00867189246],[3053.1256688325648,3181.3907009705727,494626.1256357831],[1044.1216444283662,1186.6709445465629,79999.54491957922],[1028.1304911143877,1140.8695579756543,77849.68873537763],[19.81747391295798,150.28195058495123,2591.9801809823084],[17.48870393662399,145.0217860712764,2593.1046489551313],[45.655329996362006,119.8910666462134,5912.789964123588],[7.819792506618584,142.20859090799345,1353.3161250727787],[16.574231152619742,143.5551547756907,1043.7285233469904],[16.212613945402595,28.442140715010957,451.7692623149732]],"n":2404}}}
|
||||
{"case":"gen1111","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8.098552014763516,127.8539377296259,803.3492998047641],[25.60494887899292,145.35814430472843,5630.362214262712],[-6.658226694291085,125.67385637981079,-1294.7851466078994],[-13.005144715090381,106.74914585520875,-1459.0479126223172],[12.39912124611057,104.8112819791961,5076.7111406487875],[-25.236992779825073,107.30914387539082,-2682.5325597776673],[-3.168907863120328,104.37023398150087,1791.9837571154758],[0.0,0.0,0.0]],"n":1831}}}
|
||||
{"case":"gen1111","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8.098552014763516,127.8539377296259,803.3492998047641],[24.604948984820318,144.35814368062333,5465.362236155881],[-2.658226694291088,129.6738563798108,-567.2494752596141],[-14.005144609262995,105.74914523110365,-1729.0530354442383],[16.39912124611065,108.81128197919601,5846.6012666778215],[-22.898292502159144,106.97044325223183,-2387.112066387797],[-4.507608034958945,105.7089339805548,1783.4587355198328],[0.0,0.0,0.0]],"n":1833}}}
|
||||
{"case":"gen1112","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1842658.246603229,2258591.4535536156,248927903.2197643],[2175671.706630928,2696129.0318502965,252237465.94235966],[1708392.076677395,2124435.35526069,196886061.98837203],[2222188.6504757353,2721762.3840466836,261379011.91745594],[1036738.8613607121,1293149.7262802208,167328936.18482286],[2202184.2954598446,2701757.6809651307,234892756.2878733],[0.0,0.0,0.0]],"n":1597}}}
|
||||
{"case":"gen1113","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.156575297772946,86.6606568205582,8184.8520788899505],[73.15657751039124,98.66065897897835,12211.70377158739],[70.15658178031865,93.66066453138029,10930.49577424404],[40.50563851440522,100.00972073779177,6466.796492554784],[39.156579622428005,62.660661252470796,5224.009269750643],[47.15657249172193,72.66065385101005,7984.502857310847],[44.72304625571863,69.20648070411778,5751.04949925209],[34.0419742004978,58.41759414166417,4531.1981121107565],[34.949389590506996,48.93319226609556,3434.816302210782]],"n":2249}}}
|
||||
{"case":"gen1114","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4060.71066068165,4116.534545335691,508011.40492727485],[3071.3961341295003,3125.9900849351684,487610.43818293576],[2079.503798550227,2163.8956323642865,241933.30049914154],[1067.965377941547,1117.3179603607286,78058.7573887645],[42.910752542735025,102.54559566310162,5711.405322788564],[54.697276609583014,104.45819605840343,8718.472261087742],[66.44415740591235,104.09075110760335,8644.374733163348],[32.95597339592348,91.57104793470326,4517.723140474826],[43.65641602027079,101.47022301642629,5554.8929241351025],[44.51686027175849,49.189730023499855,2822.5769596311648]],"n":2487}}}
|
||||
@@ -1139,7 +1139,7 @@
|
||||
{"case":"gen1120","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1077.5566369430078,1240.5844520222201,98612.99381899009],[2068.359342809395,2315.3532558729157,373549.2221415769],[20.28305168007375,213.3844898566338,-357.2835015397827],[39.348971790345786,211.11922589975208,5200.772442410502]],"n":1023}}}
|
||||
{"case":"gen1121","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.77758781260795,107.73573704596913,6352.727812642903],[61.712001574780246,140.5534016145971,10173.109668687079],["NaN","NaN","NaN"],[1063.9040314354927,1105.5621018022944,76201.80366257246],[25.29999404878251,100.7505229932206,4779.619221171139],["NaN","NaN","NaN"],[72.13374972535466,95.64085579834584,11261.645456524653],[11.661886359213609,86.250988103533,2336.8558117999955],["NaN","NaN","NaN"],[50.61722128978338,84.60267325384123,5859.627814062582]],"n":2560}}}
|
||||
{"case":"gen1122","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1130.3624609986766,1165.0431862070375,192773.60323558317],[-2923287.0492647076,2925509.131295631,-50262826.64468763],[88.78080921353768,120.1518745168766,7915.390981329595],[-2924306.515296305,2924485.9858833705,-278538991.5473894],[15.64123591486985,25.1537502325932,956.8943063676523]],"n":1091}}}
|
||||
{"case":"gen1123","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.07643072153526,142.42476239127654,12914.573750821566],[107.77812475039894,159.33416646082992,15942.792012240165],[99.27650619610792,165.6472006697286,13334.488228325856],[73.7423232936156,127.61711803816031,8651.995779605395],[90.28753285170058,145.760776118354,12413.677608340751],[73.86911117203084,122.98148825143603,10394.250135843693],[73.66330480942003,116.68231868618,9278.825722335498],[81.5470683115481,137.137747486216,10873.966855034489],[70.89791553995838,113.56329034232361,8355.9396378213],[0.0,0.0,0.0]],"n":2323}}}
|
||||
{"case":"gen1123","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.07643072153526,142.42476239127654,12914.573750821566],[107.77812475039894,159.33416646082992,15942.792012240165],[98.80937832827148,165.18007280189215,13295.716615295432],[75.7423232936156,129.61711803816033,8573.596705100446],[92.48216318720984,146.63189004717185,12634.929747082691],[73.20735296868514,123.64324645478173,10467.457488812383],[77.66330480942003,120.68231868617997,9691.80161281219],[81.01543932431676,135.34065387588004,10991.292170604122],[70.9624166593533,114.89325608482314,8498.497203451558],[0.0,0.0,0.0]],"n":2325}}}
|
||||
{"case":"gen1124","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-645814.07915935,7363093.357831477,-124709150.90322599],[-797680.1748812327,7528884.539660814,-216619155.79674527],[1869735.1014851246,7202766.592440859,343832639.91512734],[-411710.28056535916,6596718.393146888,-212245165.93919116],[1091776.1127436403,4335281.728755934,141464761.01807576]],"n":1190}}}
|
||||
{"case":"gen1125","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4061.7987293897,4159.658706789217,515278.3009927105],[3082.447643461498,3171.2683272911863,498484.0688978625],[3089.542814785929,3181.6351976114665,496412.22564589704],[1075.0705257426052,1178.7427664509273,78651.23967769813],[50.42826871433247,139.3481268123089,7480.314074661924],[41.688381819969166,143.04009100098622,6398.546864979509],[91.29127497878731,128.63735721495556,15337.68104633924],[23.858014389075457,122.36978144231588,3900.387084081505],[47.15370780287464,145.77987470035717,6644.4170602500235],[44.533676555894715,115.8906554464147,5903.46450811244],[21.0,21.0,239.0]],"n":2648}}}
|
||||
{"case":"gen1126","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[79.81763206675663,92.1615628251683,9413.160012085491],[77.91863693571291,90.26256812013676,10670.071515014542],[55.81763186675661,80.1615626251683,5476.336250419076],[71.87842459531039,80.10771203384247,10421.032796107078],[48.9806401607134,69.20992727973645,7137.877076600684],[58.81641630235358,71.16034738027433,9051.262988870025],[21.0,21.0,1184.0]],"n":1669}}}
|
||||
@@ -1149,8 +1149,8 @@
|
||||
{"case":"gen1130","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[55.43207390320229,96.40614226076993,6177.844815699385],[47.432206193785326,90.40627171732528,7605.735375124642],[74.27938710892437,121.80405877718195,9431.853223962744],[24.64456509478211,67.06802757938983,3197.7867845726705],[32.431649477689895,75.40571515653774,6154.203885995277],[60.14663403755856,85.92389902229817,7714.826076180452],[20.856284911995896,62.30426980917358,2744.620474585384],[32.065025928452904,70.44593209235613,5293.183578988686],[21.0,21.0,722.0]],"n":2159}}}
|
||||
{"case":"gen1131","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[7271.812073355943,69205.80508411446,1754773.8639955153],[-1845.6205941776807,68197.77205246167,-40233.5261659252],[6446.73362278657,47804.84447090319,289791.3142479952]],"n":683}}}
|
||||
{"case":"gen1132","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1970305.907897942,3552195.5781676667,247435140.16979972],[1721528.1844822634,3303226.6682526097,206684346.77394763],[1845662.1794417263,3427557.8497114507,228608416.50725666],[1230674.823393671,2285158.744537606,83515575.54420564],[1561448.424937561,2791840.1326241554,269843968.4071455],[1515010.3483216688,2920998.2320487364,178677804.07725313],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen1133","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1332163.0788705195,3603174.6674335185,168677540.57628357],[1154580.7312865665,4125101.846401285,161655917.16081682],[1054342.1381754419,3460355.8388122646,143488071.5294774],[1446304.2175350268,4215577.363022065,204339635.39973444],[782117.4303061262,3534827.611404698,98437069.10233328],[1149758.4375184341,2761766.006058207,178177474.82364988],[1132801.861274364,4169577.4383404846,155654712.87669033],[446674.5965226135,1862312.6177706292,24588263.09024868]],"n":1992}}}
|
||||
{"case":"gen1134","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4034.4817445229537,4152.420857454097,508097.0964498659],[2020.1179082637664,2140.2233529675445,244309.6892401682],[12.924985696552387,142.882579935768,47.32081457372669],[29.686080558150728,110.42304369400362,6933.457306940389],[18.252022493892667,117.3731250213318,1890.1097440401002],[4.711471223889486,116.28492607151881,2674.910359578625],[21.0,21.0,1037.0]],"n":1662}}}
|
||||
{"case":"gen1133","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1332163.0788705195,3603174.6674335185,168677540.57628357],[1154581.7312865665,4125102.846401285,161656166.16081682],[994346.0381754418,3400359.7388122645,133819445.30652782],[1446304.3175350267,4215577.463022064,205785939.71726945],[782117.4303061262,3534827.611404698,99219186.53263941],[1089762.9997909572,2701769.443785684,176787290.45746326],[1132801.204270325,4169576.987756927,157920292.1248672],[446674.69125412946,1862313.6306267097,25481611.918963145]],"n":1994}}}
|
||||
{"case":"gen1134","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4034.4817445229537,4152.420857454097,508097.0964498659],[1019.1061804411315,1139.1931438368415,80148.3127383345],[11.48174452295293,141.42085745409807,-306.1003140520038],[31.11759390911598,111.85455704496889,6964.574900849504],[18.153519955098982,117.26846204651464,1884.1314480037886],[4.798245940048957,116.35937991563131,2682.3931469325535],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen1135","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-179202.44539009823,2077048.1954980022,-85285010.15556878],[-579555.7851248257,2929578.325462286,19023603.05058698],[-399250.6284462127,853681.1969568413,-39678860.07270857],[-2062057.5986480226,3796372.1652752114,-244119822.79505563],[33574.51377383647,1379557.7478910617,-86480433.35049778],[0.6629148633879636,0.6629148633879636,0.6629148633879636]],"n":1281}}}
|
||||
{"case":"gen1136","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4128.54511976279,69513.72653119886,1387204.542171376],[1490.5465855139705,75127.14059082449,354652.92735316494],[6725.279875964964,71509.73065734441,289763.5043925678],[6824.81325987959,72915.24686157684,651850.655235996],[8546.462745817773,45126.717360989984,715008.9584168268]],"n":1170}}}
|
||||
{"case":"gen1137","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[141.55641451768335,298.47136225313847,15211.494026526874],[102.66252656104075,285.77147031499845,10348.754421565358],[53.24736364718821,347.9540924073071,8487.450115776484],[102.78261621858636,332.40738308628437,17228.442017860358],[113.37878596012929,258.69700021385967,14630.523808219985],[38.59514592666377,346.8583506892986,5725.917098607657],[22.52770252538171,178.44360770381337,1298.8530039784323]],"n":1658}}}
|
||||
@@ -1174,10 +1174,10 @@
|
||||
{"case":"gen1155","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[94.07532223748842,151.1845321721179,11860.144147795756],[99.57856125658702,156.68097125094792,14910.608822942511],[69.39813730158384,138.50904722128055,8792.685316409981],[80.57504468400828,137.67915466343624,11520.220868286862],[64.66489509161855,95.70214839569537,9877.53581397242],[65.63722831786039,128.01774571448155,9108.032037808056],[48.66508911731181,62.42508501166171,4212.414627713675]],"n":1748}}}
|
||||
{"case":"gen1156","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.157616776698866,109.96367186149604,6353.521104335782],[45.75751269111058,109.35627004872686,7030.337010354666],[69.8794115220358,131.93732322739038,7921.505488938739],[27.98328414381965,85.80419219307564,3737.9659873177857],[31.148950722021905,92.9847117357367,6173.669317154094],[51.681097107609666,79.72948642826165,6510.753350033261],[19.0660397898759,79.18593471865593,2909.8141095451006],[31.976915366404135,86.8080354387551,5294.725014448285],[21.0,21.0,638.0]],"n":2155}}}
|
||||
{"case":"gen1157","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.8188944092014,102.95478545094747,7404.765258545737],[50.84976541720947,106.0497356961737,5784.725735527481],[50.80498979858264,94.00496007754687,7965.790519317565],[45.09249680279807,70.78310073764067,6194.499608213229],[44.803989798582634,88.00396007754686,7368.140641734575],[21.0,21.0,1310.0]],"n":1419}}}
|
||||
{"case":"gen1158","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-15247.37760981204,30822.090573162604,-2016054.7982488098],[-16604.068873797292,33265.282775096326,-1905636.7204368794],[-17495.58158034096,32278.822981904686,-1943499.8992387105],[-16193.127140837794,29876.34104213685,-2375315.3477797625],[-17230.456276246452,38147.08895436098,-2292658.532358755],[-11085.729064551739,16397.786221935596,-819348.1931369259],[-11019.90642503048,24494.32769836155,-1803941.9833304738],[-19511.40283510174,35314.94580945172,-2523716.6763037834],[-13994.239940345664,24595.705991778264,-1506411.419538238],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen1158","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-15247.37760981204,30822.090573162604,-2016054.7982488098],[-16604.068873797292,33265.282775096326,-1905636.7204368794],[-17451.897722031,32085.13912359472,-1933850.793195714],[-16189.127140837794,29880.34104213685,-2391980.4749205997],[-17385.77241793649,37756.40509605102,-2350087.010548352],[-10909.729064551739,16571.786221935592,-820929.9222014776],[-11014.90642503048,24499.32769836155,-1825668.5594616926],[-19467.7951905915,35121.185737342035,-2554326.478626816],[-14019.163726545949,24570.782205577983,-1521044.8232051297],[21.0,21.0,470.0]],"n":2403}}}
|
||||
{"case":"gen1159","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4094.621527991289,4126.09201185604,520720.37255934015],[3126.3547450941683,3151.8142890523422,510494.251788741],[1111.8889823090433,1187.7484693832353,90640.99242167996],[1083.0071294493353,1095.2774995461239,88825.57235329486],[86.19180301346051,112.32166018444053,12570.1782574337],[101.57809540592476,129.03763936409882,14546.210987166256],[74.26783763552572,89.73113319228072,10332.03330226614],[80.71872366897152,106.551879371862,11364.989343546416],[97.79046656422354,122.35378653052027,11966.110506635729],[21.0,21.0,659.0]],"n":2412}}}
|
||||
{"case":"gen1160","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[85.94126574429849,170.61159142696317,10989.515743576765],[91.29842431739428,176.77009140140925,13420.32070702248],[108.32929199755915,195.61655006639717,17442.721314961917],["NaN","NaN","NaN"],[76.60050833181064,165.54950750878035,10428.538288322172],["NaN","NaN","NaN"],[86.40328918589213,128.6093987198917,12155.303662718634],[56.517123215181655,149.4774813338782,8257.606792470055],["NaN","NaN","NaN"],[42.0102254768267,52.652364287685884,2668.4228023533306]],"n":2484}}}
|
||||
{"case":"gen1161","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.9689337943477,94.61539037640972,7776.875123325958],[89.96882048285423,116.61723809713848,13705.945305535564],[49.97921281572182,78.40985703172609,4602.080707366919],[53.96911187860971,78.61720265419032,8358.915514234202],[50.990278717967996,71.20543740661827,7205.917157492334],[35.67280981424482,61.213642564124456,5120.405770424557],[43.28523616211875,50.60797971517372,4205.971786371396]],"n":1752}}}
|
||||
{"case":"gen1161","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[69.9689337943477,94.61539037640972,7776.875123325958],[86.96882048285423,113.61723809713848,13019.945305535564],[42.9688322389965,79.61561565976217,4683.594215115148],[40.9707905286354,65.61888130426249,5152.341125335414],[53.97816391141863,82.40946180478386,7265.535259525276],[35.67280981424482,61.213642564124456,5120.405770424557],[33.274855585393425,48.81373834320982,3705.8884451706]],"n":1752}}}
|
||||
{"case":"gen1162","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[7.650417126215976,359.98884603945953,-1594.555930427266],[1053.6844509875066,1371.3114262806182,253378.80468552708],[95.05324374477915,343.9782627940054,14832.949643253978],[3072.501735489698,3276.5847981365646,467071.54573138687],[118.33076998345771,254.41266963996276,17383.789104586114],[30.655762981036887,265.595370757738,3457.8133737870244],[47.688486209844555,315.946056885487,3851.7259254851097]],"n":1755}}}
|
||||
{"case":"gen1163","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[74.5082006052872,141.02308885947093,8998.25838923768],[79.53217244502603,153.80445500387836,13209.832387574448],[80.64034452243223,159.34359343154398,13225.676140044048],[33.21023192346603,198.06831258387473,4388.3728724579205],[83.74098121990299,154.841667575643,9166.304098873006],[50.07950568343017,125.76599014072633,6709.971082213433],[49.704326443399836,131.4534732329402,6672.427403424406],[96.79075614266542,148.2192761465847,17228.432374849737],[39.59004027437551,108.47857446404787,5534.568546929533],[48.7979221125243,133.00968848267576,6244.605633509929],[47.09297224494271,128.2141685650205,5658.419051184455],[21.029563808331453,22.68214942663846,280.28056592030646]],"n":2906}}}
|
||||
{"case":"gen1164","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[50.4378008406651,113.04359389192693,6682.846134498121],[61.99547377116125,133.224287061868,10300.437615104565],[28.675291558567636,97.28108460982942,3698.2513994864444],[35.262093998297246,104.28323387585571,6025.7179268762175],[50.675663375331254,75.57702824104192,7046.084923721526],[15.324474225472295,90.58724316228317,2623.519033216352],[40.09912517514262,54.60587350209161,3886.804010309036]],"n":1746}}}
|
||||
@@ -1217,7 +1217,7 @@
|
||||
{"case":"gen1198","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2124.5844178859024,2263.5548157364674,277364.72564113245],[104.88312653909921,259.09329453116413,13376.941266647751],[59.19924050461635,197.4673100029019,7638.636695807205],[90.99991739008678,245.44034568949996,11095.522190538795],[-0.04981468645966447,1.2591473049589756,0.5548516227899911]],"n":1026}}}
|
||||
{"case":"gen1199","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[74.89909491233331,125.32127522464114,9230.847737962207],[1076.8743606005658,1110.4908294420393,174255.91344445714],[59.25331342751442,115.67549373982226,6233.403036077067],[58.43505035304111,81.24328477377784,8302.68208652708],[51.809059174749734,103.48884515749579,6684.281896379574],[40.52934598510842,56.07997473540692,4425.026774221684]],"n":1503}}}
|
||||
{"case":"gen1200","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4066.3382667264104,4112.882405314756,519799.83959160023],[29.056462018354665,138.33349381239665,1627.9564555186607],[1053.6432574644432,1104.72100626478,87944.13130847932],[42.25856037356235,91.53619554420784,7052.642688795434],[31.602000287468325,67.16563960162776,4262.0909711045115],[37.05646201835467,86.33349381239663,6519.503184158106],[0.0,0.0,0.0]],"n":1595}}}
|
||||
{"case":"gen1201","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[144.44270089401448,160.20443869490265,17756.873358319866],[151.44750169585714,169.2092394967453,19986.507918776228],[144.59512638448632,190.35686418537455,17964.58116864706],[129.3276420949361,151.0893798958241,14989.71457022383],[133.63472029969904,151.3964581005872,17245.275359023675],[123.9225085597861,139.6842463606743,16450.054602301596],[115.30189253784465,133.19813755890752,16620.740266725832],[119.16968406388332,138.16995825133398,15664.224078128722],[116.54837518037267,129.68315677422765,13272.437708969303],[0.0,0.0,0.0]],"n":2329}}}
|
||||
{"case":"gen1201","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[144.44270089401448,160.20443869490265,17756.873358319866],[151.44750169585714,169.2092394967453,19986.507918776228],[142.25588081889327,188.0176186197815,17692.39360159259],[134.32764209493604,156.0893798958241,15575.077291050842],[132.25940503278107,148.02114283366924,17305.066380258675],[124.9225085597861,140.6842463606743,16574.977110861382],[119.64445259752898,137.54069761859188,17043.624545104474],[119.7780832012629,135.53982100215111,16181.264629448928],[116.22210071639076,130.59541869680822,13504.58891667278],[0.0,0.0,0.0]],"n":2331}}}
|
||||
{"case":"gen1202","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.25733761608562,91.37777764459732,9678.724577324834],[92.25733759697168,106.37777796460853,12011.467236979883],[65.25734488414832,79.3777823158031,7559.239597251132],[48.257337616217626,68.37777764472929,5041.327303948335],[44.25733762717251,58.37777798822889,5549.209903920341],[50.257338238075356,70.37777826658701,6190.982072923573],[32.25733700505079,46.37777736610716,3821.8645159799307]],"n":1767}}}
|
||||
{"case":"gen1203","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[64.05742435883107,186.87177540152933,4577.12404293928],[55.519899045968025,165.11791822035516,6387.932420570383],[37.94695453603383,169.33553710416138,4598.574976227757],[30.391887224955862,102.2518442322874,1779.3193941419308]],"n":933}}}
|
||||
{"case":"gen1204","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.32375258518204,83.25299322495685,6501.87279789809],[67.32253183834081,97.25847667621164,12259.619647333267],[81.66955105568432,117.60492671854038,10132.19957155611],[29.324578688570607,59.255113135564,3585.8223857737617],[48.32118351692634,78.25930225140863,7804.341537236311],[68.98580592336208,85.218285832307,9338.977346265088],[21.11527234354895,53.46207289554941,3162.505785602656],[47.33384131809022,74.59936079035249,6579.411203372569],[21.0,21.0,680.0]],"n":2157}}}
|
||||
@@ -1236,7 +1236,7 @@
|
||||
{"case":"gen1217","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.3423783384705,297.75665832837296,7132.836893025062],[52.03291954221848,330.0074668553747,5299.177302349503],[73.93676961530696,309.74166109022815,7504.453920472549],[70.91399122691689,297.3282712168194,8003.160374818027],[100.06889932081947,324.15947747934604,13414.566687043689],[78.86860507511656,282.1084445326408,8390.354573260227]],"n":1535}}}
|
||||
{"case":"gen1218","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[113.1963456844112,279.7184577298339,11604.245051426169],[71.98533026617804,255.24476015124003,7528.804434043899],[92.79386046223838,307.54803864610926,11207.725153763651],[80.8388340402392,269.1039687539907,8359.706213719921],[32.06948400264397,97.83719373277397,1151.5239106940123]],"n":1115}}}
|
||||
{"case":"gen1219","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.00440483823134,262.4367474549352,7629.746570941995],[29.532582273121584,249.05214732996242,1488.4938101893053],[37.958707053520484,272.2517693368891,2868.343213937178],[5.128168779373734,59.806630038939836,121.35904294094982]],"n":845}}}
|
||||
{"case":"gen1220","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[39.142954290918155,140.69451747694998,4941.784477721897],[26.221362223512855,135.6176310338471,4857.6422648137195],[58.49499455942356,154.21192624004618,9076.173916454081],[1042.7626366455486,1151.5722054283713,74987.93452405617],[10.021798595150203,121.68857507182138,1310.5062185831011],[15.23770917571898,109.94087952193509,3989.2745884794717],[39.6467966410575,124.76377362936131,3264.125603648121],[-0.08684523648827247,112.8736738267921,105.4746189748879],[19.72210877983027,101.71434241029293,3535.8434799213205],[0.0,0.0,0.0]],"n":2323}}}
|
||||
{"case":"gen1220","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[39.142954290918155,140.69451747694998,4941.784477721897],[26.221362223512855,135.6176310338471,4857.6422648137195],[57.49499455942356,153.21192624004618,8918.173916454081],[1042.7626366455486,1151.5722054283713,74987.93452405617],[10.021798595150203,121.68857507182138,1310.5062185831011],[15.23770917571898,109.94087952193509,3989.2745884794717],[39.6467966410575,124.76377362936131,3264.125603648121],[-0.08684523648827247,112.8736738267921,105.4746189748879],[19.72210877983027,101.71434241029293,3535.8434799213205],[0.0,0.0,0.0]],"n":2323}}}
|
||||
{"case":"gen1221","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4038.8067225867117,4146.810091377876,516458.46811188874],[1027.8515870056754,1171.6156767952657,85002.5220393894],[1060.0523740052843,1128.6203157876223,173031.15073801603],[-1.4614294931854248,111.94948409307959,-420.99262572936914],[47.02514397008079,88.89808131129277,7656.785870190269],[-2.9939104138655717,115.99704194235024,-359.1690024238369],[33.31186719476728,96.17270754836754,7155.444870335631],[0.0,0.0,0.0]],"n":1839}}}
|
||||
{"case":"gen1222","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19737.481775995166,19753.912925573513,2449802.0914860694],[20364.01774266185,20382.448892240194,2441163.1934591923],[17907.804730659922,17930.23588023827,2180536.6491990844],[23777.458522083303,23795.88967166165,3145516.2049922817],[5117.181488978519,5128.658538837971,661201.0995213313],[20362.08307023863,20379.601637585438,2781998.417787484],[16371.43040589412,16388.774137704022,1711083.928740977],[21.0,21.0,806.0]],"n":1907}}}
|
||||
{"case":"gen1223","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.13903937182959,91.14031307280769,4484.311962224209],[42.91181364118826,86.60239724336991,6850.968656075626],[40.636199846319855,85.93836373618447,6426.73430777603],[32.623974264725945,73.89659413254411,5512.21151604028],[18.8363668345198,36.95108787929508,1543.2661922192005]],"n":1163}}}
|
||||
@@ -1249,7 +1249,7 @@
|
||||
{"case":"gen1230","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[149.49254812210867,313.285581813525,13655.921057936643],[67.85329324724293,303.6811805839041,3686.5502298446477],[126.33393587617292,351.19207895081246,18699.831694722434],[23.66067697576719,89.4450852040853,1078.6997700536642]],"n":829}}}
|
||||
{"case":"gen1231","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[9342.592137986288,19703.16637109009,1217968.5067957807],[13159.98677627157,24660.213250186665,1627901.640999257],[8184.187755497721,19155.376517034212,834433.1473820084],[12175.67356636674,23168.054599921263,1606643.631413533],[10469.42155750182,21938.455759398883,1101046.1841190527],[4602.294216095732,8961.80389727674,956855.1253134543],[11406.391897367304,22523.910543810078,1741995.6590743042],[8634.774926870792,18828.763858886214,873494.1189604425],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen1232","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[55.09421474937713,100.20976151197728,2539.750398885999],[27.574568350235307,62.049448891880985,3179.952359691447],[21.98729405780569,81.10263300977704,1774.809041405526],[31.82923161630725,74.6635154483446,4650.062353008886],[25.33885233518199,57.89230628936902,4287.589759505289],[-0.020354753048548835,5.578761661933084,57.32672281263494]],"n":1336}}}
|
||||
{"case":"gen1233","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.11653321016047,129.90644196046736,10104.625094275898],[89.55433222726768,129.72452734784835,13073.937800514785],[83.8990261206767,152.36599113578922,10217.928208441372],[67.78187696364573,124.882475021108,7792.1519766520705],[63.583095050052755,105.54616396586309,8545.981488980586],[73.29180555549792,111.75877057061035,10952.44341357498],[58.955696180307676,108.46199820527497,7899.87330930602],[58.86392962473738,100.07996779253536,7953.674729558082],[69.37941092972316,103.8434807323667,9122.141805339576],[0.0,0.0,0.0]],"n":2331}}}
|
||||
{"case":"gen1233","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.11653321016047,129.90644196046736,10104.625094275898],[89.55433222726768,129.72452734784835,13073.937800514785],[83.41511454314016,152.33029800706012,10161.496905699743],[72.21108063685084,129.31167869431312,8024.662393938979],[64.66997979931112,107.08126716392891,8512.603770370133],[73.29180555549792,111.75877057061035,11025.735219130478],[64.95569618030767,114.46199820527497,8453.665638810617],[58.38001804720085,100.04427466380626,7984.968768258667],[69.37941092972316,103.8434807323667,9260.900627199026],[0.0,0.0,0.0]],"n":2333}}}
|
||||
{"case":"gen1234","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[514.7198652334789,1544.0624452215027,10033.453564043717],[518.8681988822714,1529.019103802794,64492.45738913831],[12.652387260602172,29.312480844654406,117.77035587963034]],"n":609}}}
|
||||
{"case":"gen1235","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[27.389903742916523,142.74647873694764,3906.2394409731473],[33.7582850863072,156.0670709611232,8030.786299222293],[29.262606665730065,161.2539889136176,4139.574910044114],[9.293580851096,126.96755947205544,1482.5791795191428],[10.556984605772334,133.6382383562311,3872.9102627198463],[51.117653642722,112.50731955279954,5947.923856564775],[-1.7745004529019932,118.61813057259135,550.2740591655844],[14.115535880028293,122.76819510490986,3555.90158298949],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen1236","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[56.25606680979464,157.59057103930246,7693.52882281534],[49.97125504258176,144.3040335409637,7612.375760668993],[1021.717997688409,1129.052501917914,1175.2464095043583],[50.73176064116272,88.79974185403087,3254.888670088648],[48.269422354425544,117.53594537106521,8806.529747054687],[0.0,0.0,0.0]],"n":1353}}}
|
||||
@@ -1259,13 +1259,13 @@
|
||||
{"case":"gen1240","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1140.978385436806,1392.8105814292599,24653.689137660964],[80.5282875553201,392.9636328873953,6712.719330138183],[129.1305946021024,394.7827064422326,17152.49283829481],[-31.70163112628104,501.2062333517089,-2035.7592643610471],[121.0736315748988,364.6475776322922,11105.072806566246],[73.4384482702751,318.1953730106834,4178.299465161882]],"n":1519}}}
|
||||
{"case":"gen1241","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[86.59796084462974,244.40832204955805,6708.109407893769],[45.01698212178044,210.93194452517153,7460.0386069288115],[58.599940412214806,227.78240099510774,5499.304042081808],[45.63709349980749,200.93577463400956,6714.58300639994]],"n":1010}}}
|
||||
{"case":"gen1242","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.97104960354803,171.52666701837438,3253.655284480886],[54.37447145575629,122.72214499415482,5314.390248989294],[41.477192759904405,123.11525393397173,6159.175738197638],[54.57301746145104,112.11132279553433,7628.895527355285],[40.50743174213496,119.53578599309765,5459.8023345014335],[17.128006331294216,58.204059215876704,1422.9511817111784]],"n":1427}}}
|
||||
{"case":"gen1243","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.730228701944746,119.5907509311977,7886.269139910254],[79.63310520802227,135.46821931038392,14194.324455793238],[89.86924599844312,147.4679322759547,15058.954966985906],[1055.4279255837791,1141.0222192907383,254681.08474464164],[62.3774239793978,126.23794620865071,4009.9211655351924],[55.84588876929706,113.91080275576412,8450.594636017582],[42.64675720040588,98.13237126011263,7206.181383799943],[62.64859686279358,111.4329812147465,7072.432693181751],[38.73321406098107,99.77827142216366,6199.800458984739],[37.72994515003278,100.13444084764093,3887.3977068819277],[23.108614856403072,28.285727721086154,572.9671728927209]],"n":2662}}}
|
||||
{"case":"gen1243","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.730228701944746,119.5907509311977,7886.269139910254],[79.63310520802227,135.46821931038392,14194.324455793238],[88.52039193184922,146.11907820936085,14821.823477834703],[1055.4279255837791,1141.0222192907383,254681.08474464164],[67.37742397939782,131.2379462086507,3634.2985895145907],[57.971548021566804,115.43628943690048,8556.258958583638],[42.34667091483919,98.43245754567933,7248.528054714781],[68.85174571619326,117.63613006814617,7459.588841249946],[38.857128622451384,98.27255867066191,6109.561804765379],[38.22845470186582,102.26257771244602,3964.6694299421465],[23.108614856403072,28.285727721086154,619.1844026055271]],"n":2664}}}
|
||||
{"case":"gen1244","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[111.93605450002816,155.55144738741902,14635.756078744296],[145.34245378838446,184.47357482461993,20469.89055454292],["NaN","NaN","NaN"],[91.22183145861898,146.83722434600986,11115.878169739208],["NaN","NaN","NaN"],[101.71591928725478,138.12155918745646,15455.34266658515],[79.06916805803968,123.03300173605247,12822.277737574826],["NaN","NaN","NaN"],[65.53465201975017,81.7886257885969,5823.585536137105]],"n":2240}}}
|
||||
{"case":"gen1245","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[58.312024842798095,285.1635609136124,8151.63964841228],[29.020420511440193,167.94680824447806,1409.952405315147]],"n":661}}}
|
||||
{"case":"gen1246","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[53.94162760800898,246.4926621197067,4642.67944070529],[41.6322736513983,237.3826252833627,4942.235464775162],[-0.5525618227219098,7.133171029767132,-8.190376156149174]],"n":523}}}
|
||||
{"case":"gen1247","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.14499238496819,130.82951673344115,6203.9510674568955],[50.85902506951857,133.45838334653314,9926.012128504293],[43.14631484430644,121.83083919277935,6058.714929029552],[20.626288242880193,99.48114473427003,2954.0034316697456],[40.54924276515807,94.8352601878898,8148.185384674351],[37.456451926521275,98.44396034355057,3640.866712320327],[11.125758714671527,93.98108225579065,1599.7384377747396],[40.60002272554427,55.95590732142496,3502.393510332233]],"n":1992}}}
|
||||
{"case":"gen1247","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.14499238496819,130.82951673344115,6203.9510674568955],[50.85902506951857,133.45838334653314,9926.012128504293],[42.14631484430644,120.83083919277934,5980.714929029552],[20.626288242880193,99.48114473427003,2954.0034316697456],[40.54924276515807,94.8352601878898,8148.185384674351],[37.456451926521275,98.44396034355057,3640.866712320327],[11.125758714671527,93.98108225579065,1599.7384377747396],[40.60002272554427,55.95590732142496,3502.393510332233]],"n":1992}}}
|
||||
{"case":"gen1248","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[36.94840498297864,109.87488632755706,4547.783591193905],[37.742896200113265,109.62815996987277,5799.674968295238],[20.83895095364381,105.76543229822227,503.5174304211628],[42.62411869426016,90.59101193971723,8014.59745188408],[27.58731491552373,88.33512134209502,3440.7420968596834],[16.252111781255028,88.39767994471919,4402.795525678589],[21.0,21.0,1142.0]],"n":1667}}}
|
||||
{"case":"gen1249","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4081.383449506372,4113.187357998892,517992.77352748986],[3092.7585575813173,3129.057489345889,510641.94316947385],[59.19768239746966,97.75009787160502,6476.976058545794],[58.26093054478449,93.87587798391687,7503.2491466594765],[77.13402504123196,95.16781917912868,12180.6311248317],[44.961571032680624,81.53614574457785,6738.131820063218],[56.219873564044036,87.99764143644451,8110.287528116187],[21.0,21.0,869.0]],"n":1910}}}
|
||||
{"case":"gen1249","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4081.383449506372,4113.187357998892,517992.77352748986],[2090.8168928344066,2127.115824598978,260157.87699426466],[59.475131629798014,97.77804477672797,6521.842904958036],[58.50422519468705,92.05464997764406,7721.680938976096],[76.22651487674698,96.07532934361367,12256.857639708449],[44.56080114745585,81.05220841695127,6686.439286460711],[55.956427934686374,87.56786092228324,8158.5762252573895],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen1250","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.413596341544036,220.6761813764205,5915.265014841123],[75.22637272645287,194.4105788195168,10684.855006086796],[53.99733252661034,210.2178636376095,10237.30721515946],[29.079938105328928,182.9390769592197,5157.745851882679],[42.32052042443204,176.77924377654202,6668.520047976878],[30.87595488015377,154.27436267008898,3920.8095485604617],[22.178480815113332,149.0760319846389,3308.052734952808],[4.591613096987081,32.50603412081334,200.14689908139516]],"n":1869}}}
|
||||
{"case":"gen1251","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.4886583566486,145.33110951808348,8036.462534423967],[54.04557577049079,123.16419224270004,3416.3681309852223],[42.75861568732566,105.08551285891971,5504.599472532064],[1.9823915172682176,22.76879968250126,112.76486968639898]],"n":840}}}
|
||||
{"case":"gen1252","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[19.880063398947545,129.9199129170113,2781.383224604698],[41.04793901652976,175.48788841863995,9751.794892468835],["NaN","NaN","NaN"],[15.880063398947518,125.91991291701127,-145.2142618674751],["NaN","NaN","NaN"],["NaN","NaN","NaN"],[36.81902592650219,120.55248194287526,1415.8066867809853],[-4.41751482088125,141.99621162823195,-91.24690133947468],["NaN","NaN","NaN"],[21.0,21.0,344.0]],"n":2397}}}
|
||||
@@ -1273,8 +1273,8 @@
|
||||
{"case":"gen1254","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.80079760335414,91.1737596225974,6316.590046187473],[43.73980128655769,91.85643739110785,6835.462279360493],[54.56474499786593,107.46193088915673,6858.347159635761],[34.20650867172019,77.57947069096343,3666.800610943849],[15.928734450970431,69.41609814595068,2666.5224109011597],[47.307145151878416,81.94018948469844,6581.706739543933],[19.591196618916147,64.92013818200545,2758.0409822587853],[16.31915503682479,68.2026855104077,1939.97821919429],[27.432951712060245,45.198205485578406,1111.5064304992054]],"n":2230}}}
|
||||
{"case":"gen1255","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.18058693792614,239.65614562399782,8394.107043424081],[52.51746354989555,221.57739860906705,4864.261637479969],[54.52262256141317,222.71140367627595,8152.894116644513],[51.501866111859634,213.84857874224,4471.011035734566]],"n":1014}}}
|
||||
{"case":"gen1256","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[6767.490177618158,66936.14233013039,686558.0202290675],[16062.468608714615,67413.5285430169,3765695.3837221805],[25000.258210415745,59468.850770330355,3628444.775378825],[19886.282349176377,62201.76869828088,3755322.33751094],[7907.860984755347,18868.767217321776,515084.5770848223]],"n":1106}}}
|
||||
{"case":"gen1257","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[588923.8314895218,1151595.4162983405,58548815.19503614],[626218.5038873715,1166563.6305649567,57905181.07573485],[588908.8314895218,1151586.4162983405,63796099.220941134],[417647.4252585228,772722.6310076113,20562488.932467166],[612253.0894343989,989660.753314721,109186054.41980772],[394524.93773747835,934870.0644150642,49541518.16039953],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen1258","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.72864678193302,109.4597652569122,10970.571565011354],[90.71070248243683,120.08817417146854,13482.423433393453],[84.97980580261482,122.66861312676296,11053.597796935064],[83.728646781933,110.4597652569122,8150.3751365881],[64.38127678477031,97.04553717099465,9002.749917799603],[70.47920584996928,87.03684464693106,9539.417947305772],[69.6073999180158,96.83085739876358,8419.124071690605],[61.95098985424233,92.42600230740354,8151.225817620153],[66.38087585767559,90.63542358193186,8132.606499001792],[21.0,21.0,386.0]],"n":2399}}}
|
||||
{"case":"gen1257","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[588923.8314895218,1151595.4162983405,58548815.19503614],[626217.5038873715,1166562.6305649567,57905014.07573485],[588908.8314895218,1151586.4162983405,63796099.220941134],[417647.4252585228,772722.6310076113,20562488.932467166],[612253.0894343989,989660.753314721,109186054.41980772],[394524.93773747835,934870.0644150642,49541518.16039953],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen1258","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.72864678193302,109.4597652569122,10970.571565011354],[90.71070248243683,120.08817417146854,13482.423433393453],[82.97980580261482,120.66861312676296,10639.597796935064],[91.6205513407812,118.35166981576043,8261.32325499402],[63.87300672443638,96.53726711066071,8908.833356143692],[69.49106998875732,88.04870878571913,9398.719338781133],[76.6073999180158,103.83085739876361,9000.988735313374],[61.95098985424233,92.42600230740354,8275.12779732864],[64.77637449497792,91.03092221923418,7965.3264958926775],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen1259","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.103551363393635,90.45391720053262,4323.9856411382525],[47.104675844017876,99.45376826736566,8990.275421046166],[22.104208799747635,80.45441546016275,2233.498425795394],[7.106457989951844,61.45773623908674,732.8442341054434],[66.36937174872318,89.15048202525851,10882.37248456673],[3.3529717566473396,58.542195303152404,758.7136818802092],[21.853911120550382,65.36430501125602,4567.575695830017],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen1260","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4064.8506834868545,4149.076957927005,515215.83822709584],[3062.9300064013555,3155.1141526552965,492600.9873447341],[2063.475993404066,2164.8832278310506,241821.31026538112],[22.232396525598077,167.00399627952154,3946.7587593863745],[48.07037778759406,132.29665222774545,4283.003902073094],[31.170428893945807,131.97036875367905,4891.249000588839],[34.7225192439105,118.5224543458432,5783.181759307454],[53.254416089532526,123.1368522247802,5223.079707725474],[27.866974233761653,130.03518275965206,3993.241891468913],[21.587397139061526,124.36290066307507,1730.1217458544297],[23.31796095250751,23.31796216917327,304.3179672206023]],"n":2651}}}
|
||||
{"case":"gen1261","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[68.56841308375222,148.07245270646192,7421.576261386227],[65.86010066739568,153.58492054201415,10330.955620874876],[71.253766040664,164.8285356955525,9602.835573678763],[43.02157024615152,126.31981613897301,5681.934643534323],[39.499530073728366,128.23613828704333,7015.609732179746],[72.94948693335232,111.42767293539808,10219.576650424984],[30.427486263996247,117.26076129413943,4657.046413981796],[39.81644793568289,117.77645212716304,6001.353992875316],[21.0,21.0,680.0]],"n":2157}}}
|
||||
@@ -1284,10 +1284,10 @@
|
||||
{"case":"gen1265","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[18.138216779044804,116.98837888192712,1646.1436656140522],[23.210431144357457,124.64812283241771,7740.194148491531],[1014.1153623478842,1127.828034312493,79183.47642847552],[-13.321531991535778,89.25364983483617,-1543.2078077422366],[50.956660777980275,97.05672796120471,9858.809291847614],[-21.002436780815064,89.37595518231653,-2335.9130711870866],[-0.6489152957826754,89.26054653202841,2254.8213495722653],[21.0,21.0,1016.0]],"n":1917}}}
|
||||
{"case":"gen1266","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[56.281728618714524,116.1630715134398,7487.695895851746],[83.36994016609302,144.74112304298907,14510.285874689744],[27.363920457774437,79.69707571448055,3107.5889102169444],[44.207155799162145,103.21502869463893,6972.071535469429],[49.66619405319614,82.06168343891919,6861.2582917506115],[21.04099180460222,87.48402576465013,3449.754462186817],[41.86389039913815,54.36639520555014,3998.7069754692293]],"n":1748}}}
|
||||
{"case":"gen1267","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[59.531700763774694,85.22669267139344,7359.991963858236],[91.53170838124163,117.22670028886034,16263.675688585958],[44.8807700631399,82.57576197075865,5025.336920932988],[48.531709818765165,74.22670172638404,7051.461597901534],[69.19296856006952,89.75796586260891,10713.456075467322],[32.176989877257405,59.182948453803554,4395.4428836981115],[54.886418942197494,79.2704441808888,8039.531101368863],[21.0,21.0,932.0]],"n":1913}}}
|
||||
{"case":"gen1268","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[56.60722655743173,96.65222713926251,7359.539793094816],[46.908784697230104,103.58920917512654,5310.675769688319],[30.60722680726537,82.65222746111975,2895.030711641437],[53.12949939799489,78.46971163694312,6630.094764482492],[30.784888781217994,71.61907020752086,3770.014955271842],[41.60162332528258,54.15265479178232,4452.820106402385]],"n":1504}}}
|
||||
{"case":"gen1268","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[56.60722655743173,96.65222713926251,7359.539793094816],[45.90878444739646,102.58920885326918,5229.675741688914],[33.60722655743173,85.6522271392625,2973.637887214769],[47.12949939799489,72.46971163694312,5149.224263880486],[39.78720430321175,79.83220488504251,3847.5744283442227],[41.599307553455155,54.93951979240337,4535.626446894899]],"n":1506}}}
|
||||
{"case":"gen1269","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[43.196169731553326,298.1248792384221,3075.5109827904776],[47.826707302125,312.42136486684666,4373.708688030974],[7.594260467452994,293.6503741493195,1487.2820349116073],[65.95370083907498,307.590469468454,7261.621711540202],[15.52987404430853,314.92222053296365,1630.4500408757965],[5.3947280063589345,51.69645690460501,159.5747624310445]],"n":1334}}}
|
||||
{"case":"gen1270","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[32.861533945412084,124.95058840282417,2026.1967270275836],[81.96136013323562,107.81726893873042,9993.917078659768],[47.20999264018107,122.74605187224637,5506.236487387592],[1066.524072626334,1131.4289510220265,263059.0610484169],[45.6536820620684,88.59150552292566,4804.689171280822],[56.30592409939684,77.9937258522559,7022.946074087255],[2.899918712978417,15.138571538356404,14.137335179178415]],"n":1607}}}
|
||||
{"case":"gen1271","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1154220.352296892,3606454.9521005535,139789639.86366636],[1401546.5905163216,4170347.819710444,150630770.0069649],[1103738.792639762,3334301.5914891683,110786619.81992698],[1359303.6748722573,4261666.996257,162006875.550833],[1287919.9176910392,3955200.7377176825,124074307.47204871],[1043072.984268266,2601761.7439284297,186988654.94881755],[1412510.4749278696,4483621.803747182,209508603.39832592],[493019.01832038024,1927052.3943984436,32113718.15796078]],"n":2003}}}
|
||||
{"case":"gen1271","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1154220.352296892,3606454.9521005535,139789639.86366636],[1400541.5905163216,4169342.819710444,150375494.0069649],[1087341.6584501227,3292524.3550076587,112245033.4952711],[1342902.4726502553,4219885.691743128,162162649.85268185],[1287920.9176910392,3955201.7377176825,125362228.38973972],[1013066.984268266,2571755.7439284297,180470245.9330858],[1426115.8067220494,4471847.033249491,211336257.86406353],[493019.01832038024,1927052.3943984436,32606737.176281158]],"n":2004}}}
|
||||
{"case":"gen1272","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[62.71020620546645,186.11943464702088,4465.394035816714],[34.48029791044843,168.34561875801165,4404.4552170490515],[42.612586057077316,190.8043571997786,6320.1739610896975],[43.664356887608314,162.4754661413848,5297.9984589760825],[31.05838285205436,171.85201294969443,4633.492914186054],[11.152888606300628,87.75319217949267,1076.2614284859947]],"n":1408}}}
|
||||
{"case":"gen1273","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[76.05076162458496,110.32883635782943,8843.702936107376],[43.68882830627648,86.63822338384311,5128.022727052542],[9.079137058456395,39.13548797315943,-47.80040083288483]],"n":679}}}
|
||||
{"case":"gen1274","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[-933217.5563732259,2531014.024173253,-130833439.1302941],[-912789.4908947863,2514463.684196765,-123598581.98454829],[-639909.708450441,1674214.312595745,-122959902.85618025],[-906907.3317647586,2529590.4446905255,-124239435.0993642],[-240972.35428705858,1068715.5554958761,-51109484.01890455],[-992587.5369200025,2687765.5332921566,-111226298.31199157],[-313216.14900813834,447364.66849823843,-7731528.547372613]],"n":1746}}}
|
||||
@@ -1322,7 +1322,7 @@
|
||||
{"case":"gen1303","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[37.84279078679523,297.2287112939163,2555.237075361422],[5.913763351222173,303.451206659223,-1670.0311681363369],[17.720538583711605,272.51984513855126,165.9642431443856],[-3.1011880360085864,26.22424409964082,59.09739163453419]],"n":796}}}
|
||||
{"case":"gen1304","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[22067.035762535317,28003.717644661047,2842655.317393152],[26055.248849856096,32432.441985934696,3317818.444970566],[27316.971322000994,33727.63365013036,3589539.0304674623],[15385.865212955428,21416.942363785332,1965417.631264845],[25250.010595507134,32243.471593916365,3401839.295988324],[22509.674597780395,28921.746479959238,3021051.781959087],[6134.733168427329,8197.534137134095,820205.1873340514],[19469.152944132296,26169.643485192926,2730763.214998037],[22685.550429034673,29085.12994802483,2865923.297977461],[16435.141168004408,21420.954209045096,1266479.3207205245],[21.0,21.0,113.0]],"n":2642}}}
|
||||
{"case":"gen1305","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[103.74854516563181,359.174379054344,8420.50431175954],[113.27068546182939,339.5897492981249,13762.886451516804],[29.63321624386197,285.12973956554725,4737.227145775591],[70.77928967585116,334.07915651766524,6173.7010585303],[62.43668125390445,289.4438087210627,10319.135618465158],[52.29574921390778,339.83501269879514,2751.953976798727],[39.805895355221345,167.49936364252005,3663.59509986751]],"n":1690}}}
|
||||
{"case":"gen1306","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4069.3504010411225,4086.790097086513,520575.24316092423],[3070.354429668355,3091.7938584742346,504397.6590632366],[1055.6533695086237,1079.0930321538583,83590.16430530393],[32.35377744015507,51.79338097451421,3912.349825501214],[53.23291734711208,66.8590987196983,8678.917358228566],[47.026838712855756,66.77183149173831,5363.672224606667],[27.421253827397376,47.64644664800747,3629.696984791586],[42.55766870208096,47.46665420886009,3486.097642444333]],"n":1992}}}
|
||||
{"case":"gen1306","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4069.3504010411225,4086.790097086513,520575.24316092423],[3070.354429668355,3091.7938584742346,504397.6590632366],[1055.653362218589,1079.0930865375026,83590.16472472152],[32.35373744015507,51.79334097451421,3912.339785501214],[53.232950057077716,66.85919310334255,8678.918226459215],[47.026838712855756,66.77183149173831,5363.672224606667],[27.42124653736301,47.646501031651695,3629.6969668071147],[42.55766870208096,47.46665420886009,3486.097642444333]],"n":1992}}}
|
||||
{"case":"gen1307","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1028.010068231304,1254.8375494633422,3537.5663521136644],[54.67124713849375,229.89025764714825,6751.930570244522],[1040.96269369322,1274.590122564014,167130.58182666378],[49.196532666809105,252.58134909612104,8437.143221657607],[1039.1292421706728,1231.3712865029377,76978.51755859761],[44.130823279032164,228.24178074743463,6800.242710982777],[6.3469730996116045,109.35654580431648,211.18797889664762]],"n":1675}}}
|
||||
{"case":"gen1308","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4074.4209613695666,4134.020316294861,516725.67890133767],[60.895934061337535,120.45964760650276,7237.48111629449],[42.748923099039324,114.3482780243378,4535.357945780846],[58.62932536942167,90.48389655114339,7218.188166977316],[44.4968938689792,106.15100771952774,6190.685855678279],[41.721352711281,59.52116496775267,4599.381367717768]],"n":1504}}}
|
||||
{"case":"gen1309","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.08095520308119,325.6435184988924,3257.299629098704],[42.13407369384505,338.35157945964863,6439.932130969399],[2043.619675380754,2373.1914437333103,239776.83769897494],[104.49788956336472,379.2156725110757,13574.636675615218],[83.19180189912431,327.2383289168951,11137.267442676111],[33.05269207613831,377.10733446462,2920.5031293249526],[27.956002009340324,465.23257735831635,4270.087772775594],[9.723462683246987,489.9061574035377,4548.766874982469],[-0.4445967613193522,172.99720927642764,-1283.5204009022157]],"n":2168}}}
|
||||
@@ -1338,7 +1338,7 @@
|
||||
{"case":"gen1319","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1103.534684980835,1237.2239980542793,181648.25764990682],[66.1526353057711,190.9882502367367,10720.666066091455],[23.399490312689178,130.33708401812618,647.0738604851081]],"n":673}}}
|
||||
{"case":"gen1320","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.592104538268686,147.49937065383835,4435.069924557265],[39.36848359335107,142.26872024809757,7361.916197033643],[23.599031023409744,142.08845276681217,2667.539382550731],[41.592104538268686,144.49937065383835,1598.9150556150296],[11.707855549486274,122.49789012126986,2057.205321589538],[39.47013718074087,101.77046317339322,7349.603469521058],[38.543648168183516,128.23987722491498,3482.7231921169614],[4.621914621604798,120.1762240184749,733.6818242625309],[25.60453679069297,108.351625017097,4018.0924571904843],[21.0,21.0,386.0]],"n":2399}}}
|
||||
{"case":"gen1321","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[63.17597994222455,150.78240375153334,2858.7014406537833],[65.51352916024089,171.3676612880002,9905.312822454194],[65.89333096247087,166.54256290908617,7922.859595870345],[42.9290963371149,132.246273738606,7289.044803059581],[71.25981944469568,178.07255481602692,10870.810232786182],[14.418797283243439,42.16937676902627,893.0986585799163]],"n":1332}}}
|
||||
{"case":"gen1322","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[130.3973795456893,136.30621925413703,15466.330089652047],[129.9687779009227,137.87761760937047,18287.81740370231],[100.03446495464769,133.97369142694617,12851.598276319612],[101.05646975219894,118.96530946064668,12449.457441315253],[108.22584977163146,114.13468948007923,14047.224770432626],[90.18174211825577,106.12096859055426,11687.787557547119],[101.04724266257026,107.60776951077548,13282.52219153082],[100.70064763635924,106.65705457464139,13457.560390071338],[30.104730783245955,31.37508964980332,1012.457145437738]],"n":2164}}}
|
||||
{"case":"gen1322","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[130.3973795456893,136.30621925413703,15466.330089652047],[129.96861020683878,137.87744991528655,18287.77464171091],[110.33489258369126,146.24373229213904,14553.726417637175],[101.05646975219894,118.96530946064668,12449.457441315253],[108.2250176613079,114.13385736975567,14047.06288615607],[90.18123898309726,106.12046545539575,11687.758375707926],[101.04724266257026,107.60776951077548,13282.52219153082],[100.69931239087714,106.6557193291593,13457.384727867073],[30.104730783245955,31.37508964980332,1012.457145437738]],"n":2164}}}
|
||||
{"case":"gen1323","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.340572613726856,206.35791381366067,2486.978578614544],[61.53402051440412,207.34002867189534,7009.81406876327],[47.760052397046266,200.71436271982554,4039.754242724775],[55.36223364912992,207.67016576576106,8229.501143654627],[42.58761099169363,165.13727767554988,5726.231677258375]],"n":1246}}}
|
||||
{"case":"gen1324","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[111.19961963601801,149.30419503841472,16105.665433985585],[168.36581411061496,183.8177066679262,25743.201870968467],[147.1318756096009,168.471491737336,24092.97442802675],[104.00364230390936,152.22049413588235,11770.562343833572],[140.66236330678888,158.940807823201,20166.52254713872],[117.53951151659311,136.9795230087057,16048.609412096928],[104.35184842468622,127.90716760811216,16303.62361151864],[86.68592897609412,122.04804071587928,13490.011475808727],[136.62833313661628,152.18062105830498,17271.962325330474],[93.68109674261044,112.21886908453605,10009.733508360765],[21.0,21.0,176.0]],"n":2645}}}
|
||||
{"case":"gen1325","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[92.37942297159778,356.0411947842685,17111.967545999254],[88.11312236549412,362.38194237103437,15147.12830130664],[24.05114247594386,337.4315507991073,6159.692155955225],[93.4943305134165,306.36563194941374,9341.176301667721]],"n":1231}}}
|
||||
@@ -1348,7 +1348,7 @@
|
||||
{"case":"gen1329","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[51.824509133927485,133.6423980188908,6445.459923811113],[40.84635466673773,155.07442986774265,5802.957468469408],[54.93529103448616,108.85418519843975,5755.1392512444145],[18.410202456879706,105.74590142139283,2810.357481740377],[60.28231535648832,89.63698251431492,10069.045641315839],[13.055499807441247,105.70544957263358,1792.2527840163327],[47.36802238128489,91.32733290238593,8152.843690797435],[0.0,0.0,0.0]],"n":1839}}}
|
||||
{"case":"gen1330","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[808178.2113986178,8100232.877174163,151449841.14085042],[315416.78772569646,7421044.598155809,94420725.74277821],[452846.50386096764,6199107.122189808,34244422.34999837],[-177426.21880491465,7722239.8598567275,-35097132.52922511],[770814.7173258706,6814772.514254601,12058621.593761079],[817299.5555243614,7596391.347666976,99090961.61555097],[369855.8702788788,6149592.468159267,89477777.27419752],[2169907.6148858317,5762919.873861554,287811790.76855695],[540965.1752527071,7505665.869405791,131564995.23467448],[1443586.9360470728,5431743.173726176,207990509.3094403],[-142961.4369858476,331861.55408603005,-332841.1567210064]],"n":2580}}}
|
||||
{"case":"gen1331","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[71.12418066018688,121.91485102260702,8415.512343621633],[83.47867923398543,137.05798096616093,12547.32435407749],[47.02556190181751,129.03974427771786,4325.948021431101],[49.812910257719906,106.60358062014008,4460.269752298219],[61.56609539724882,114.62252874759525,9264.71388977796],[43.64329768039961,83.15516566746028,6175.46977055891],[43.96704987006782,101.53850270991722,6206.785440237756],[23.76865810450132,36.53244099035781,1363.6825269260169]],"n":1930}}}
|
||||
{"case":"gen1332","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[76.77467609426326,104.06886769498975,10062.11234784235],[94.45892328173797,117.73310356193313,15757.423343364742],[100.0795347322573,129.39106677243896,17479.907559850348],[85.73922962239128,116.41583342505352,11499.958693731201],[59.745975188861856,85.17026875963215,7928.896104880653],[66.80306519873552,96.10195002499918,8795.582087571289],[63.20504843551372,78.32876107769619,8676.641666243493],[73.70422223227293,101.54527355674068,7857.854009057495],[62.096543530326905,88.32211619548765,8379.458283610082],[57.920006563788114,87.94615173044528,6184.658045219394],[43.18149111132667,44.501151368655265,2245.89325440549]],"n":2730}}}
|
||||
{"case":"gen1332","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[76.77467609426326,104.06886769498975,10062.11234784235],[94.45892328173797,117.73310356193313,15757.423343364742],[96.99341352594855,126.30494556613023,16705.118894740357],[91.93355435230302,123.07763264677612,12719.47922510471],[59.026421057198704,84.91818913545805,7846.6909664122495],[62.630822854001714,91.92970766458716,8565.294740031786],[65.06626135981406,78.18997400199652,8825.462509196823],[80.64979846107678,108.49084978554455,8400.870835018715],[57.25917074508395,83.95221790728161,7583.640206631847],[60.07725860354701,87.34644374924247,6715.699324290309],[42.88545207591021,44.962072348974445,2331.5817175848592]],"n":2732}}}
|
||||
{"case":"gen1333","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.82059005637949,306.1584977097399,5102.127503592427],[91.66385813192998,306.9903829063964,11934.377517911242],[61.231345434322385,328.7725475809621,3544.579029018823],[89.91726497860854,290.61575500029136,10543.950081496523],[63.50178420601969,206.99065101202586,5503.756700589451]],"n":1170}}}
|
||||
{"case":"gen1334","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.53886182112718,145.09139969039543,9180.716145310591],[74.69102425589475,140.24248384842045,10953.788685320671],[90.70521084084879,158.63102352602712,14554.01750683205],[72.12377373212485,174.11197422723424,10981.757049403215],[48.685009419474355,117.50300059421967,6157.026648541762],[49.55390128694791,120.29197501313867,6897.306340903155],[84.48439438420898,131.89073274814945,15673.468481598307],[40.14893735094368,111.4568895244655,6249.491114764727],[44.77403818715879,115.11367422773925,5889.642151201636],[49.09979532872112,104.33308265159955,6307.994015818091],[0.0,0.0,0.0]],"n":2577}}}
|
||||
{"case":"gen1335","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[903203.5556130427,1892691.301273875,97264912.3166944],[983660.9539453968,1961837.7958769787,112562485.05319865],[837096.1019277598,1826583.8475885924,101154070.38682877],[1124883.3103369777,2240254.793117172,149737512.48735493],[257453.3041647339,478269.627032176,46316376.62559165],[952471.6759022716,2051900.889283885,134254901.5641268],[698942.2252612977,1567177.5994720988,74688470.495049],[21.0,21.0,806.0]],"n":1907}}}
|
||||
@@ -1383,7 +1383,7 @@
|
||||
{"case":"gen1364","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[74.07676433137023,152.36130137027715,9262.720441799642],[73.95102710909175,154.46423602400006,12176.647750839813],[1078.5330724141568,1163.5415417459544,255097.2798750255],[77.14573683049866,161.43027386940554,5690.6009808063645],[52.759145830929654,137.48952422219241,7375.1317120981075],[51.7901477669604,105.4960811089895,8386.765689515621],[75.04514954205115,139.33387314355792,8602.681522521798],[41.727300593876144,129.78641067076123,5925.353533965091],[46.274614305368615,116.64762939917551,6086.033309988356],[21.0,21.0,470.0]],"n":2403}}}
|
||||
{"case":"gen1365","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.23220938117876,89.6086783998455,5676.253601780507],[84.1268466268771,105.6644184993969,10328.777087308656],[43.96416607073034,80.75495486655437,6805.845165029572],[74.29875606266128,88.20796481364177,10227.417091568002],[23.52693459203368,39.78292008808112,1842.771056125314]],"n":1163}}}
|
||||
{"case":"gen1366","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.27772190875349,95.670355672843,8307.186617954427],[61.27772332811942,95.67035583856347,10767.409736084133],[76.05599147263855,124.44862464119797,9375.866086964059],[40.84851914167038,75.24115230871053,5430.161488560449],[43.277721328119426,79.6703538385635,7665.576030043187],[60.048109611316534,86.96605437032632,8112.740167481953],[31.173413061806755,67.33508978574116,4330.808207119299],[44.1088958178921,75.27875146614505,6532.770675505299],[21.0,21.0,680.0]],"n":2157}}}
|
||||
{"case":"gen1367","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4042.8018796482406,4093.232845923314,512581.1915106249],[3032.901385720839,3083.3346886381887,491911.5544525553],[2044.0949206871844,2128.5276005034543,240277.17833231878],[32.87196458028052,108.49260876366424,5776.304601327658],[7.900943818757377,58.33268897018371,1054.2881414604387],[0.15108314538207468,58.267954500233756,370.8773944612432],[55.38252160141961,100.70884301457438,11507.870743000123],[2.088507387939986,54.8145060761419,587.5914788863122],[1.235578495908996,57.611314880256,214.66237129589643],[19.110288773738713,61.916412831103365,3714.6128056338284],[0.0,0.0,0.0]],"n":2574}}}
|
||||
{"case":"gen1367","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4042.8018796482406,4093.232845923314,512581.1915106249],[3032.901385720839,3083.3346886381887,491911.5544525553],[1043.0948724144773,1127.527292592074,80136.04062400149],[32.87196515692674,108.492608187018,5809.176714106015],[7.900868128154841,58.332614432873626,1062.170005682608],[0.727756210723548,57.6910754787167,519.2301413510431],[55.55714346625895,100.53422114973505,11755.75251723311],[1.3372392990074489,55.56577416507444,588.9287181853192],[0.5988762215352799,56.97435296721013,52.27895067012348],[19.746942775404396,62.553066832769055,3734.3597484092325],[0.0,0.0,0.0]],"n":2575}}}
|
||||
{"case":"gen1368","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4074.7349257750125,4129.852516167161,509356.7093103086],[3088.88732056623,3166.698023832619,493676.72890702105],[3088.9075617865396,3171.7698786009255,491403.25680485764],[65.10797560536801,144.35107218531212,8521.833106955346],[52.33175736222062,131.89044250545422,8337.323985120524],[45.80617549440122,121.92774405228461,6876.963612307758],[89.79375560966942,126.69208512269678,12881.748919337313],[33.02802564278286,113.06406650583966,5197.469112951431],[47.869411830651806,132.46112061473033,4979.8876437468125],[40.02537075964399,47.582156656281754,2634.9293367932423]],"n":2484}}}
|
||||
{"case":"gen1369","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[108.19944491778004,212.3108971225838,11452.803004651909],[83.43764661266334,178.31877134827872,8043.287720226784],[37.624655052683856,148.79076717888387,3130.42370993584]],"n":766}}}
|
||||
{"case":"gen1370","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[45.25664828813151,79.63119093710999,5535.395890627718],[45.256645486107146,79.63120208399752,7246.513740722333],[37.25664226286166,131.6311955855247,2683.940875548027],[43.067433097133865,110.51653889953647,7320.341132079236],[18.794925776397122,56.09491401484697,2438.8481031167457],[17.655124585756,53.05869185058489,2722.984015286569],[63.12168616866819,91.00903813792219,8806.356753969556],[14.192405780010903,52.52140281461167,2187.996599000386],[16.493615484210036,52.20881783313601,2267.031516311981],[18.912816409205234,25.07590362401853,753.868178136983]],"n":2418}}}
|
||||
@@ -1411,12 +1411,12 @@
|
||||
{"case":"gen1392","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[137.59977295225505,371.5202764773001,12834.703443535092],[33.87262657471817,329.90681602995875,3220.505165528907],[89.42591479393568,334.03748198886234,14124.432305438551],[92.3152526941816,354.9343473344999,9313.036123527581],[26.090991457582323,328.8676217407418,5929.271689136593],[1136.0573250037746,1348.7939242538566,234257.27038011112],[90.75355849577669,341.0442635814431,9499.90088411434],[22.87407011716596,321.51313691873276,1292.6758423922652],[1129.1292116041154,1322.2240510797149,208252.2715046075],[1082.0854075207367,1288.0987927751762,27133.367552351665],[3091.3515659060404,3342.91699827123,452260.71381103556],[68.77984990042643,317.9762018207146,9163.639497133268],[27.165390118133917,333.89217459177934,5796.991687807017],[65.07132507645375,159.88223286194693,5464.168160286796]],"n":3476}}}
|
||||
{"case":"gen1393","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[90.89456186279715,234.91908509704683,11059.581846317655],[2086.223624378318,2250.8804873696618,264197.5199935175],[39.48767541502977,225.73672729708844,3128.8061591370893],[28.150875178102357,175.90399808350588,3937.5025461564214]],"n":1006}}}
|
||||
{"case":"gen1394","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[60.454809458821096,239.5506360908273,5333.814890341119],[78.340052847807,230.70978393099108,11653.737926886726],[37.87366138337001,291.15255335216506,7896.214366771076],[37.219157066959795,261.65829682556034,6140.014069010694],[98.31233051856036,261.35473168560577,16198.344735092598],[94.05722186308212,307.7524252214754,10015.998669367838],[85.7604298164151,289.7777435950583,8047.615808258717],[62.604224394428975,236.59744656399366,6411.60710141368],[76.17477861661507,264.19921632529906,6856.390643200395],[1136.5680532645815,1291.3435871966249,43356.23518694525],[42.20640173764736,247.8523216942685,8418.360023402558],[20.021682133807197,103.21111003562676,577.749102010891]],"n":2913}}}
|
||||
{"case":"gen1395","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.383836002614075,84.69067085621732,6931.312878841172],[56.38699967100308,88.69383452460633,10228.90237177803],[63.387249832164606,125.69408468576786,10520.763509028187],[58.93773030830514,86.47562021070722,8616.49228058807],[30.386092756227832,62.69292760983111,4028.6268827533763],[23.229828391756215,59.78596841198272,3537.8859380939207],[66.65423379703573,92.8330487291983,12033.066063594493],[23.669296787762157,57.33520661160504,3311.344712423437],[22.46705819227188,59.47861948879035,2695.7833582872504],[38.46490908688456,61.817712330949355,5296.107274185171],[0.0,0.0,0.0]],"n":2573}}}
|
||||
{"case":"gen1395","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[52.383836002614075,84.69067085621732,6931.312878841172],[56.38699967100308,88.69383452460633,10228.90237177803],[62.387249832164606,124.69408468576786,10359.763509028187],[64.2645450332682,91.80243493567033,9568.276722455772],[31.059278031264697,63.36611288486797,4059.6861607846377],[22.55664311671934,59.11278313694584,3388.107150801197],[74.74090786447196,98.09274521183583,13917.91978687227],[22.255807995362794,58.74869540400441,3356.566572357663],[22.854961674246347,58.50744800052522,2841.0882070243497],[38.0770056049101,62.78888381921448,5371.969651391846],[0.0,0.0,0.0]],"n":2575}}}
|
||||
{"case":"gen1396","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[72.08508156802922,88.24218295010343,9047.205858017025],[66.3900555350963,107.16145683356663,7186.5000774978225],[51.434146613753576,73.5912479958278,4948.575687334888],[59.044094893656506,70.42979554289167,6306.207849697624],[57.08508201064325,73.24218339271751,6325.205966664112],[47.69502604922464,53.0807266984598,4860.345969301652]],"n":1508}}}
|
||||
{"case":"gen1397","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[11714.10107935278,18130.35326183493,1278375.796467013],[5493.71093546245,9815.21239051536,542682.408221371],[6454.055923515861,11458.632801031705,484406.60674701014],[5455.237299727251,9019.663332122816,880191.7960589082],[3257.2647796255296,6123.0909297121225,77568.89679298215]],"n":1262}}}
|
||||
{"case":"gen1398","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.26430470432629,90.52113989334507,7531.782761270502],[60.26430630046802,89.52114348656319,9133.844911682238],[71.26430520682631,100.52114215365611,11158.051890213874],[68.61336981571169,97.87020524801983,7408.189221476138],[36.32791383040262,65.58475075989168,4827.942296415654],[30.742709128306398,61.857884085769335,4420.776305033749],[82.52526985384017,102.02982674468288,10757.832454959105],[30.19850037719511,61.06844892221911,4101.377076861058],[31.466693028500483,62.58186822496421,3907.778888293207],[30.497608005344084,44.672377084943285,2460.6574692367276]],"n":2484}}}
|
||||
{"case":"gen1399","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[2080.257106510852,2134.0875119708394,280296.4846804786],[56.230193854825764,86.1600947534861,5168.346070659341],[56.54885003609801,81.51860471717583,7738.952119932699],[51.554458685994064,88.67255175520498,8208.369872892425],[45.53748644621878,63.68081078020034,7269.072555259986],[17.67819877321614,40.06843074684135,1540.0393932750765]],"n":1419}}}
|
||||
{"case":"gen1400","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1474266.2977329788,1474290.9074600467,202016924.92376694],[1769269.5283981937,1769298.1381252613,218153768.36263746],[1560923.490184502,1560948.0999115699,227209026.01902512],[1138850.4584659648,1138868.864950677,88647447.62367569],[1160181.3715538802,1160200.5584903574,211175875.784642],[1544573.6370965862,1544597.466371889,181983677.34820086],[21.0,21.0,1016.0]],"n":1661}}}
|
||||
{"case":"gen1400","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1474266.2977329788,1474290.9074600467,202016924.92376694],[1720594.3359466707,1720622.9456737384,208436434.88095662],[1512249.2977329788,1512273.9074600467,217248486.57508668],[1088849.4584659648,1088869.864950677,81247299.62367569],[1133956.6407367056,1133975.8276731828,204706706.7373089],[1472123.1754622378,1472149.0047375406,170227983.23573673],[21.0,21.0,1016.0]],"n":1661}}}
|
||||
{"case":"gen1401","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[67.5430023947867,165.12971554439176,7757.52847733019],[71.25377499277973,176.5338459834102,11664.505589132996],[73.17908321725382,167.66526012862235,12255.352337756301],[62.23265053774003,162.61914132088137,6674.899829311197],[59.37243841101435,182.84774133245676,7480.421555975148],[68.2647952514613,159.23293972133024,7855.503682007501],[47.98839696308097,152.8820461977957,5954.212743489029]],"n":1766}}}
|
||||
{"case":"gen1402","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[38.55379850307755,371.8016187139923,-2351.591839753914],[83.38431038876055,373.05996197548046,10526.344833267276],[69.33502177532057,385.3652345633669,9452.433271964566],[31.343605813300357,367.6387025753627,5736.5592363421565],[53.491341221878095,360.9533401161417,6750.445603786132]],"n":1253}}}
|
||||
{"case":"gen1403","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[115.86737942412496,151.01831925197098,14631.737721469013],[130.85185890799238,166.00279873583855,18753.435687647838],[85.2200008255954,180.37094065344155,9567.760493872318],[88.42618407805503,135.57712390590106,10731.701270146237],[100.35920434962132,135.5101441774674,12925.535222141976],[85.47824278158762,120.91220266681836,11825.484340654732],[89.79890900571156,128.5573736734254,12184.61416732061],[89.10932404361974,124.2602638714658,11891.720556746372],[27.377961846796186,35.48741694954377,968.5933413948311]],"n":2166}}}
|
||||
@@ -1427,7 +1427,7 @@
|
||||
{"case":"gen1408","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[82.73414378266757,195.07305837886426,8574.518953464558],[1091.595966214771,1202.7426349390973,109619.25152899166],[1131.5396320330767,1234.9639799919346,26443.381417274595],[41.99011176055891,129.6496030922935,3842.8283241186546],[43.58296321716601,136.77178544128256,4515.239064015756]],"n":1275}}}
|
||||
{"case":"gen1409","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[8410.002489391016,73452.84374760605,1401069.6241837884],[16795.724344977032,72162.69720190352,3073452.5678753555],[-4352.341568120662,76433.1569951578,-1325419.432216836],[15366.643716294173,73612.05041470515,1872323.662079637],[-5015.870307786239,50568.34801227943,-1049914.2978198945]],"n":1196}}}
|
||||
{"case":"gen1410","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[5446.464747873041,68818.75309354313,741133.1561071879],[5224.824226215228,68249.6323881971,158557.36399942703],[8858.106083764405,82029.25592169575,1183762.6375414568],[9590.621940568173,65159.172077740855,1487688.2528851496],[1564.3049228740072,85816.78591666676,501867.29986339185],[5231.784083061304,68256.59224504318,163894.02136923373],[4773.178732407451,56101.59028914849,314285.9798227318]],"n":1710}}}
|
||||
{"case":"gen1411","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.23730622380738,120.66404929290857,8183.769292839588],[82.48574640672419,149.33981020635878,14515.407571936788],[62.942639343463455,149.1915171504924,7491.3231019876985],[72.4200087197597,127.84675178886084,5174.130505046452],[55.106419601052124,126.94647147848373,8731.729849854877],[56.83419775644698,102.0125386474771,8408.913016322998],[62.61941740647328,116.47807424153396,6788.5552146636965],[42.89492438682476,112.68694324914371,6404.44442335007],[55.19829884228421,115.99275898246752,6507.864079253507],[21.0,21.0,428.0]],"n":2401}}}
|
||||
{"case":"gen1411","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.23730622380738,120.66404929290857,8183.769292839588],[82.48574640672419,149.33981020635878,14515.407571936788],[60.94263934346341,147.19151715049242,7077.3231019876985],[77.96133665569641,133.38807972479756,4917.671793301946],[56.56509166511542,128.40514354254702,8788.294941519987],[56.83419775644698,102.0125386474771,8465.747214079445],[68.6194174064733,122.47807424153395,7301.442034918313],[42.054738500970096,111.84675736328904,6274.306499459072],[56.038484728138876,116.83294486832219,6619.10086282393],[21.0,21.0,470.0]],"n":2403}}}
|
||||
{"case":"gen1412","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[45.226678316993336,123.4666345726305,5900.352818592308],[36.2875825834273,116.94589253072309,4767.396920206644],[32.226678316993315,116.46663457263044,1881.8353984109654],[43.41509160480656,97.16410677050406,8097.473418310468],[34.28387249783289,96.6417603149732,4140.335728405698],[21.712120196849632,97.34320380104396,4812.223756700314],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen1413","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[119.42344790043093,186.84593625976996,15747.26890658729],[80.70251709671435,160.12500545605334,9961.549837067654],[70.88764068102287,97.51189501119758,6449.860088814626],[102.42344790043092,175.84593625976987,15028.822665749341],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
{"case":"gen1414","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4004.64734020005,4183.2477433142285,511487.85082165233],[3010.2855250845755,3193.93593762745,499241.8302286506],[-22.125432556577195,165.2439247316469,-4565.242230848562],[-26.935475442234914,155.7241912822119,-3168.111017368757],[45.227344601214924,114.00854284894024,11541.216750225081],[-43.54023895741321,154.8781809837434,-4610.21117143863],[-26.55105821809606,138.04829167217991,-862.9969196395123],[21.0,21.0,911.0]],"n":1912}}}
|
||||
@@ -1445,7 +1445,7 @@
|
||||
{"case":"gen1426","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[92.89460368936841,114.72263546069402,10921.03473734747],[65.64511433929552,100.72719976864232,7734.185336219779],[73.65315889236899,96.22713700390956,10580.891817430016],[53.80726707951689,75.08327767025274,8335.500823619146],[62.89460369078286,84.72263546122657,9710.98330425972],[21.0,21.0,1352.0]],"n":1421}}}
|
||||
{"case":"gen1427","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1152.4213461933834,1273.1278033046601,108286.97111055201],[1142.635902296191,1240.8420713163387,100463.8060736917],[104.17047616034895,245.43325912448526,14005.918610280238],[75.7924513765932,214.87676974372414,10891.622849834173],[117.23765783027284,238.70655565155803,16102.582840444626],[98.43220068790187,229.74374899021763,10468.267964428116],[106.4450253528899,227.18328619563997,15721.956855887316],[1122.2564082373692,1254.5962173951898,73329.9077206725],[97.45318599725617,219.96659762663347,14867.567719470433],[54.48906374257464,127.57668290172828,4528.91384841315]],"n":2439}}}
|
||||
{"case":"gen1428","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[57.05731602513115,85.84487408661855,7121.979713252941],[67.05731500470978,125.84487306691763,10582.949919176684],[33.720611271537265,58.91231664575924,4285.87625437697],[45.05731551658525,73.84487357869018,7345.6930057498],[51.03484096084846,66.63069364790803,6955.479116690954],[26.984801705666758,57.96994457941903,4068.8590056130947],[41.758284121607275,49.15655199712244,3936.6742979591327]],"n":1746}}}
|
||||
{"case":"gen1429","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.20357243955314,128.48501093496338,8652.989717659408],[80.4839944093501,137.00291953496372,12448.51754848486],[50.254520828396885,139.62271157786708,6372.710992711053],[65.4098981728589,123.69133666826916,5021.596536490876],[50.68587113970063,110.54715525235802,6798.15729714356],[72.05401663196025,107.58821450139125,10530.37056826351],[40.8259225563678,99.70499051172845,6399.729412178201],[48.18359189424605,99.55090431780125,7291.526388174862],[0.0,0.0,0.0]],"n":2081}}}
|
||||
{"case":"gen1429","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[70.20357243955314,128.48501093496338,8652.989717659408],[79.41241323910869,135.9313383647223,12183.407512413782],[49.9531285259412,139.64501346940474,6362.552045059559],[65.4098981728589,123.69133666826916,5021.596536490876],[50.31289766700353,110.49787597365427,6785.975252166605],[72.05401663196025,107.58821450139125,10530.37056826351],[40.452949083670696,99.65571123302469,6332.347293242076],[48.18359189424605,99.55090431780125,7291.526388174862],[0.0,0.0,0.0]],"n":2081}}}
|
||||
{"case":"gen1430","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4046.175748563722,4082.5022738050116,513380.9067637325],[62.51049854175691,86.72818236518792,8271.00304883138],[17.175748363721475,65.50227360501114,1498.3126759106099],[44.785249571378955,68.89409148309447,5824.273711758619],[17.17669995320245,53.50322519449213,1785.4468862220228],[39.724297680896974,51.83313959261249,4576.111113105902]],"n":1506}}}
|
||||
{"case":"gen1431","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[76.83688401048074,131.0711532122579,9656.56579652575],[70.31163951928659,124.14544823467818,10226.469231373047],[91.6184826326093,153.80269427358846,11001.296391852615],[54.9592917105213,108.7931004259129,7030.192075306214],[53.16436400718234,107.34857564816146,8423.797486420808],[71.96562260833329,100.25885832489936,10094.438266615496],[43.1586474902124,100.1548649883473,6085.1386066166215],[51.580670348075785,98.63761502766229,6986.124345755144],[21.0,21.0,554.0]],"n":2151}}}
|
||||
{"case":"gen1432","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4643251.179676847,9710044.736782553,510154021.5198571],[4850280.228725597,10502404.082527379,610340637.4574645],[3164146.398916879,6737088.937771083,543697940.2609702],[18.20220354517177,28.533102177662396,988.1338891835799]],"n":875}}}
|
||||
@@ -1464,20 +1464,20 @@
|
||||
{"case":"gen1445","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[40.89854595845463,357.96237930583067,713.2764904145256],[5.775364005347498,345.78776826682844,-1619.7212861295786],[60.648386756341985,344.0188801651784,4685.4057544211655],[31.91877474206661,317.4649676276134,888.6194345309258],[20.608029575349164,182.75879422243182,3228.3701681231337]],"n":1202}}}
|
||||
{"case":"gen1446","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1067805.4938653149,5869982.496182092,212367772.61359674],[-548021.3214528051,7283682.5537278615,-183272106.1837946],[1139120.0106443434,6312186.784260995,145306288.11014497],[866748.5251223617,7889402.496402484,36345208.74644639],[-394404.24409293,6313394.457162856,7234995.565402187],[1719991.1335923518,7748822.2846206315,164379739.0579918],[-412767.11430138885,7273469.525243254,11211119.227682577],[544226.1548329488,2902854.9355577193,59800801.34211313]],"n":1914}}}
|
||||
{"case":"gen1447","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[47.8389311818602,78.0817404871783,3727.5515454498163],[41.16223728521561,68.3560810683104,3790.8268987472525],[50.10014537192043,72.2183720201526,6675.464531097726],[3.8043849009249673,11.800025826609662,55.3912279001966]],"n":843}}}
|
||||
{"case":"gen1448","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[6313.294420191672,14486.919331987856,727664.3410580377],[7419.136731194312,16553.258971882875,728679.5439215548],[6466.943454747513,14989.11196765434,794413.223426356],[4796.090487462807,10886.171981255186,226810.39435355147],[6044.33149471971,12534.104038748545,1092817.451050296],[5199.703447490707,13322.180910161078,568017.5880905662],[21.0,21.0,1058.0]],"n":1663}}}
|
||||
{"case":"gen1448","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[6313.294420191672,14486.919331987856,727664.3410580377],[7238.487696638471,16030.066336216394,694910.4809707192],[6289.294420191672,14468.919331987856,765985.2097229331],[4797.090487462807,10887.171981255186,231607.48484101435],[5460.566338937902,11836.157682596522,956026.0997425418],[5606.819568716675,13500.934630646614,576423.1897409259],[21.0,21.0,1100.0]],"n":1665}}}
|
||||
{"case":"gen1449","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[26.05337558054246,135.1821901212839,2065.5593934166895],[30.84113394822693,139.98087560327048,8195.888370254705],[2030.0714478754646,2140.421670970417,412021.9976441539],[14.948267469968444,130.0770820107099,-1119.6577778877077],[-11.011682173678965,107.39928538041185,-974.8541481163315],[16.06205887076655,124.80587483336922,4038.07611492408],[-16.683316762005433,102.82296835901712,-1244.4477625560996],[-15.748278182707429,104.76921441839556,-1949.3039635398789],[-5.947912818824849,8.220013965437726,-112.50146610003057]],"n":2082}}}
|
||||
{"case":"gen1450","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[86.29562041883932,143.7829561008152,10279.576082644526],[92.48103332871716,154.1346773953562,12349.682859554556],[1078.3958866477637,1134.3586439945796,91002.02719571265],[69.113458390875,127.49508472930393,10245.068128991943],[49.855360675542784,87.6461090295955,7114.849556026789],[62.626413297942,120.5464040814021,9375.149051828554],[0.0,0.0,0.0]],"n":1595}}}
|
||||
{"case":"gen1451","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[65.24416035904885,188.5764130889322,4743.008738000095],[57.253982297369625,158.94965510150791,6265.829849854368],[43.59034530407522,155.88806920044095,5474.2975688600745],[1.133528857803814,2.0674794647642196,3.4883724461594197]],"n":773}}}
|
||||
{"case":"gen1452","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[68.43308635147545,302.6105936667405,8090.727261782193],[55.8567968501847,305.2418428608174,8051.016103360814],[54.84109645705818,292.1998231584174,7778.71672730857],[19.554515904718357,193.7347490712155,743.0968227505509]],"n":926}}}
|
||||
{"case":"gen1453","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.03830016529506,149.26236655101854,10563.604311139505],[117.69587728690573,155.40005759222552,16298.40446766587],[81.61318911695756,156.99798824391124,9306.503189389105],[83.13852310277885,138.10264644830048,13032.137735326787],[78.14833130387376,122.27318739062557,10560.453089398963],[61.09560882207424,110.7667916598743,8227.083385641388],[72.85797417439466,125.05235989730292,11769.230035622579],[75.11133702842426,115.49608437529783,8473.220607800624],[21.0,21.0,554.0]],"n":2151}}}
|
||||
{"case":"gen1453","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[75.03830016529506,149.26236655101857,10563.604311139505],[117.6958772869057,155.40005759222552,16298.40446766587],[86.92061791834415,162.3054170452979,10248.44515484354],[81.89952619772035,136.863649543242,12795.009481021209],[75.62046899645273,121.74532508320452,10065.673236454157],[64.70342067217976,114.37460350997985,8729.3286068021],[73.58043128381229,124.63628527755725,11733.634098649558],[72.01420885642156,115.53748793245846,8157.423745811172],[21.0,21.0,596.0]],"n":2153}}}
|
||||
{"case":"gen1454","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[64.16700181416874,227.0361478401846,6213.402261706605],[33.72394394704718,224.3847851710399,4495.759795519604],[1095.7042441395786,1219.4511262951085,256584.1394885924],[46.25622200358453,227.63186233722325,6774.986052997605],[52.15076070190393,215.84246390651032,11176.94930600259],[70.26899177244653,244.50077393842366,8015.335684092965],[26.82907869787003,183.35266164847363,5367.065647477482]],"n":1766}}}
|
||||
{"case":"gen1455","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[13314.724845536484,77927.89471974142,1227022.9370408994],[-2584.5174070005205,67979.13043097507,-808034.277788186],[3537.318377817514,75061.96057398891,738933.5520385492],[5821.663489159428,24274.51780215305,158707.6845600779]],"n":853}}}
|
||||
{"case":"gen1456","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.39168245784509,174.97993732960055,11045.430652991834],[105.55524074278667,171.04527739240123,15931.281828448093],[1115.1392696034786,1180.279603160414,261725.06057757235],[89.65484825290694,175.24310312466244,7462.939351408471],[86.74926727116998,158.231850483404,11684.03958377424],[72.8608352381642,109.74507870729067,10008.50157587125],[85.61170797235425,153.18829752147448,10498.703523092872],[71.84212638615472,145.75278464821463,10342.99803301702],[73.11111935585286,125.57895309778866,8975.569030209948],[21.0,21.0,470.0]],"n":2403}}}
|
||||
{"case":"gen1456","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[95.39168245784509,174.97993732960055,11045.430652991834],[105.55524074278667,171.04527739240123,15931.281828448093],[1115.1392696034786,1180.279603160414,261725.06057757235],[95.654848252907,181.24310312466244,7377.594199661378],[86.80034384110411,158.28292705333814,11527.915529518486],[71.33369878413414,110.2179422532606,9745.567190302434],[92.61170797235431,160.18829752147448,11134.626936085464],[71.23308461892373,145.14374288098364,10329.924111504597],[71.24410123898791,125.71193498092371,8649.707312932926],[21.0,21.0,512.0]],"n":2405}}}
|
||||
{"case":"gen1457","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[78.58773774026552,226.15210874097875,7562.050560470241],[145.74352570791964,282.37050216268204,19494.559211499178],[72.24846195670096,226.13871403317893,9302.795341428355],[123.51956641692841,265.9405626103822,17103.288902589076],[60.395624991282375,141.6050459154642,5130.9544455954265]],"n":1163}}}
|
||||
{"case":"gen1458","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[89.90433746942885,123.10495737675323,8787.524945164423],[64.21090058294544,107.57726744080574,7333.764453041596],[84.36237952560927,115.191347300091,11252.01230853704],[64.85010210083509,102.5410196101603,7686.2593829419275],[33.141587327238284,48.915897243918714,3031.585537980935]],"n":1163}}}
|
||||
{"case":"gen1459","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[80.93613259733608,244.6969283044107,6177.788844141645],[71.93377013361088,246.9751026539558,6797.701562012291],[39.7606382879008,153.69511890405562,2605.381645225472]],"n":683}}}
|
||||
{"case":"gen1460","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[77.24566226982579,205.6192632933446,6230.941412593698],[39.34402672080652,201.716990503965,2731.5674143557817],[66.87824419996306,199.98360742780991,5861.113887700721],[60.2876017062913,197.40699586013315,7025.219021148863],[-4.176150023201774,48.085169710238475,-127.50099972820833]],"n":1093}}}
|
||||
{"case":"gen1461","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.4361243091628,212.88849569008923,9801.584875312601],[64.64035257731305,240.14836544412245,9461.272784705923],[1052.2834132848086,1239.7914261516182,261648.73402692875],[51.777232296047664,244.9995436366262,4969.587295212225],[13.295468798335065,57.99090415070542,384.37628698237296]],"n":1113}}}
|
||||
{"case":"gen1461","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[81.4361243091628,212.88849569008923,9801.584875312601],[64.64035257731305,240.14836544412245,9461.272784705923],[49.2834132848087,236.7914261516182,5881.734026928742],[53.80041208903996,247.0599184133699,5024.994965121852],[13.318648591327356,58.05127892744912,399.533991324208]],"n":1114}}}
|
||||
{"case":"gen1462","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[98.66637695269601,126.93708485009888,14523.646057131027],[62.59535433200837,155.12766561141282,6153.885901232428],[59.131235940514465,94.44835736592378,7075.77970393584],[60.53924408124763,78.86317591551301,6593.974385369263],[66.19903844253399,100.46974633993688,8266.345396527453],[32.61844154037304,42.303614221508944,1991.199238879662]],"n":1434}}}
|
||||
{"case":"gen1463","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4054.57745680124,4091.4854275919874,518537.5858179992],[64.05312473043344,88.65843859093171,8109.23717147731],[21.57745780124087,64.48542859198828,1885.632840029374],[49.528179318776026,67.83083624902515,7010.387763539039],[16.583411671790824,53.491382462538226,2098.6181864523564],[39.5211734482251,51.82383037847423,4465.315112416389]],"n":1504}}}
|
||||
{"case":"gen1464","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[54.74345880527581,135.3506868246036,8516.086795895088],[13.213734268025435,105.8209622873532,600.8783005089946],[45.63553934488647,79.43727171334123,5200.05030205976],[34.7434588052758,121.35068682460357,5301.314061856067],[21.0,21.0,1562.0]],"n":1175}}}
|
||||
@@ -1503,7 +1503,7 @@
|
||||
{"case":"gen1484","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1121.3642235117834,1270.2102190360474,270635.3974042425],[64.67546180728861,229.16068056913699,5204.605111238984],[1088.0564336859206,1267.8578170580054,261477.50532826976],[126.76463961734103,283.96057404681096,13946.342621326203],[57.03345889143078,234.84485723946196,7763.52099985317]],"n":1278}}}
|
||||
{"case":"gen1485","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[773230.0741262244,8371059.773133811,62705104.48114102],[335290.6573468982,6464983.02098914,4015666.902633135],[773238.1718355633,8371069.9531034045,58842605.03998372],[287618.8775609752,6893401.636003197,-9442613.53836244],[922470.7683883903,8044210.072595999,43933164.04874056],[-458263.5591905384,5448272.175744713,-187958564.2818721],[-600576.8849103722,5208097.771186734,-43807558.09119369],[-1306103.2590951836,3939194.9979841197,-132050890.60193092],[-939045.0214984575,5906183.974921919,-169445637.7422909],[-328530.9842048117,590840.0467209492,-9753151.7993539]],"n":2360}}}
|
||||
{"case":"gen1486","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[160153.42933378438,8007521.21751143,-84243081.37582333],[440685.0563326817,8760768.967309814,-10498407.574453436],[958721.9653475073,8027202.757611329,97893208.57606542],[-138427.51337184221,8390697.071784321,81022558.25651358],[365201.5074376584,7426671.664662552,153755585.68578285],[-641232.4068551803,8519662.923689544,-64214085.89064168],[-58037.42267543523,6492735.405317669,-35177444.15879889],[678288.6599525925,8506146.325978234,22993381.53733252],[-239210.69238768148,6883006.163299554,3818785.316548068],[-563263.1783355622,1924456.1563690244,-26256486.907830443]],"n":2367}}}
|
||||
{"case":"gen1487","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[68.59276322921812,115.93160476548805,8246.794544223034],[76.59270630888531,123.93178797976776,13656.749420967619],[40.72879528439685,132.2883432172356,4575.289448033837],[1058.5930131390155,1117.9316946480506,84290.32795066884],[40.375721267626425,87.71472292489244,5495.484705889129],[50.12476303310528,93.68407083133154,8915.7511409689],[32.54995900913685,85.89724232798008,4942.036388283846],[28.415928670489844,77.76175167053923,3836.8714021630594],[23.905099929812774,31.669450743642653,999.0294731941965]],"n":2168}}}
|
||||
{"case":"gen1487","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[68.59276322921812,115.93160476548805,8246.794544223034],[76.59270630888531,123.93178797976776,13656.749420967619],[40.72879540673405,132.2883431520466,4575.2894571003535],[1064.5930131390155,1123.9316946480506,84910.05623684173],[39.375721328795024,86.71472289229794,5278.860434656691],[55.94618900255177,99.50549680077802,10163.160952180302],[33.94567554404261,85.71370251163907,5064.548018434277],[27.69173608650191,77.97744585659225,3762.919982400273],[24.41215007061707,32.81587037188966,1048.1734580791522]],"n":2170}}}
|
||||
{"case":"gen1488","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[44.29649264366925,126.16423654330599,5818.959046948599],[45.202685037235916,140.4283673753097,7346.39780296083],[15.162019667310007,103.02976356694673,-328.5702880361237],[54.57630729340809,116.72676218545732,8779.801164535722],[25.181456023952492,86.92046734743458,3027.3607994411386],[25.604140644113926,115.8833281123178,5858.761830544958],[21.0,21.0,1184.0]],"n":1669}}}
|
||||
{"case":"gen1489","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[61.48021998688382,105.57848307409029,8406.113076681453],[60.5366629472812,110.63331433140897,10702.95797979774],[44.27732861273722,100.37559169994363,4751.548931943257],[32.98761448421862,81.1908631118631,3901.763423736078],[70.35848192258737,98.3196505951447,11552.75674102172],[29.087514147463242,76.77898162366814,4434.382799608095],[35.15481046887474,81.65825746400421,5984.258791838796],[21.0,21.0,890.0]],"n":1911}}}
|
||||
{"case":"gen1490","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[87.76276487078985,345.3171932586177,7438.911102390982],[49.45271432314582,290.8081365779898,8900.711375571636],[35.32632448904051,345.4484991785006,3397.0936943790566],[72.92396049135569,325.5752558693577,7895.489540040002],[52.193499096659124,295.44065712760056,9227.246794952734],[63.197521626014435,320.135491850155,10029.838088027549],[74.67817727630376,332.998212537318,6256.095024161258],[52.994386697990564,314.0916792007824,6621.624907916101],[46.165148262018135,299.7078160525298,7668.458826171017],[87.952056309054,329.34476925322747,10806.699316924556],[43.54216528747045,296.17115963444354,6978.834862873627],[45.59415752305107,229.39193008764812,4773.881846064687]],"n":3006}}}
|
||||
|
||||
@@ -882,7 +882,7 @@
|
||||
{"case":"gen0858","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[83.56196851444659,119.38108701016462,8149.222988035503],[67.05475735413509,104.33729834643627,7755.8993990123],[1.9294523191719057,3.392874815755047,3.6551518951380544]],"n":523}}}
|
||||
{"case":"gen0859","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1091.728060827818,1128.3301459288627,12912.998647943987],[77.40139734105439,114.00348244209904,6384.850807121354],[124.6042510284157,124.6042510284157,16801.070875542315],[7.9062403310362654,7.9062403310362654,448.90624033103626]],"n":838}}}
|
||||
{"case":"gen0860","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[60.87876274982954,82.22877006824616,7730.3373951883705]],"n":510}}}
|
||||
{"case":"gen0861","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[52.84234592321434,83.57369215275718,8441.226972124954],[41.088667020846096,73.65645064660943,5991.908775057806],[41.21518666626527,61.60083157437663,5689.798353498651]],"n":990}}}
|
||||
{"case":"gen0861","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[39.07546993030278,81.77426650157207,5056.75026717125],[52.985778220376936,85.46249248731431,6693.38594430769],[49.19661731715969,79.92796354670253,8184.738402180353],[7.0,7.0,350.0]],"n":1080}}}
|
||||
{"case":"gen0862","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[1085.6061257732092,1107.1478653011877,11842.582082341902],[74.29289449370194,95.83352255832978,6724.28742084008],[110.18078468250047,110.58536277608476,15021.434849347195],[7.361748073821014,7.361748073821014,448.361748073821]],"n":838}}}
|
||||
{"case":"gen0863","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"],[90.8869009936571,146.45384278769484,8838.337450236846],[82.64750976691562,141.90544562229496,6842.3137454710195]],"n":757}}}
|
||||
{"case":"gen0864","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[4044.5923946734424,4105.2484186206875,505943.20709011075],["-inf","inf","-inf"],["-inf","inf","-inf"],[15.755055204521941,81.39653012839246,2767.5991191259086],[11.598201743793327,77.93741348602248,2237.2590638710312],[5.077634874492773,33.986338858193214,284.052976659159]],"n":1412}}}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"blend.feature_executive": {
|
||||
"add_by_faces_extrusion": "tangency-labs/solidcore#3093: the base body is a closed solid or an open 9-face body depending on a few ulps of the extrusion surface's extended domain, with success either way",
|
||||
"attributes_add_by_faces_extrusion": "tangency-labs/solidcore#3093: the base body is a closed solid or an open 9-face body depending on a few ulps of the extrusion surface's extended domain, with success either way",
|
||||
"multi_feature_adv_extrusion": "tangency-labs/solidcore#3093: the base body is a closed solid or an open 9-face body depending on a few ulps of the extrusion surface's extended domain, with success either way",
|
||||
"multi_feature_extrusion": "tangency-labs/solidcore#3093: the base body is a closed solid or an open 9-face body depending on a few ulps of the extrusion surface's extended domain, with success either way",
|
||||
"multi_rebuild_0_extrusion": "tangency-labs/solidcore#3093: the base body is a closed solid or an open 9-face body depending on a few ulps of the extrusion surface's extended domain, with success either way",
|
||||
"single_face_fillet1019_6": "tangency-labs/solidcore#3093: success with a body that fails check_brep level 2 (11 lines, the first Bad Repeated CPts)"
|
||||
},
|
||||
"blend.fillet_executive_p3": {
|
||||
|
||||
+1
-1
@@ -350,7 +350,7 @@
|
||||
{"case":"adv_sec_b8_e2_m0_f0.0_c2_t0.0_0.0","deferred":null,"error":"rejected","ints":{},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m0_f0.0_c2_t0.5_0.0","deferred":null,"error":null,"ints":{"sector":[1,0,0]},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m0_f0.5_c1_t0.5_0.5","deferred":null,"error":null,"ints":{"sector":[1,0,0]},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m0_f1e-09_c2_t0.0_0.0","deferred":null,"error":"rejected","ints":{},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m0_f1e-09_c2_t0.0_0.0","deferred":null,"error":null,"ints":{"sector":[1,1,5]},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m1_f0.5_c1_t0.0_0.0","deferred":null,"error":null,"ints":{"sector":[1,0,0]},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m1_f0.5_c1_t1.0_0.0","deferred":null,"error":null,"ints":{"sector":[1,0,0]},"topology":null,"values":{}}
|
||||
{"case":"adv_sec_b8_e2_m2_f0.5_c2_t0.25_0.75","deferred":null,"error":null,"ints":{"sector":[1,0,0]},"topology":null,"values":{}}
|
||||
|
||||
+6
-6
@@ -11,6 +11,7 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::mass::enclosure::VolumeSign;
|
||||
use solidcore::topology::brep::validation;
|
||||
use solidcore::topology::Brep;
|
||||
|
||||
@@ -25,8 +26,8 @@ pub struct Soundness {
|
||||
pub check: Result<Vec<String>, String>,
|
||||
/// Whether the body is a manifold solid.
|
||||
pub manifold: Option<bool>,
|
||||
/// Its volume, when it is a manifold solid.
|
||||
pub volume: Option<f64>,
|
||||
/// Its volume's sign, when it is a manifold solid.
|
||||
pub volume: Option<VolumeSign>,
|
||||
/// Its face, edge and vertex counts.
|
||||
pub counts: [usize; 3],
|
||||
}
|
||||
@@ -35,7 +36,7 @@ impl Soundness {
|
||||
/// Whether the check is clean and a manifold body has a positive volume.
|
||||
pub fn sound(&self) -> bool {
|
||||
self.check.as_ref().is_ok_and(Vec::is_empty)
|
||||
&& (self.manifold != Some(true) || self.volume.is_some_and(|v| v > 0.0))
|
||||
&& (self.manifold != Some(true) || matches!(self.volume, Some(VolumeSign::Positive(_))))
|
||||
}
|
||||
|
||||
/// One line: `sound`, the check's line count, manifold, volume and counts.
|
||||
@@ -164,7 +165,7 @@ pub fn probe(runner: &dyn crate::UnitDiff) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{measure, start, take};
|
||||
use super::{measure, start, take, VolumeSign};
|
||||
use solidcore::kernel::Kernel;
|
||||
|
||||
fn cube() -> solidcore::topology::Brep {
|
||||
@@ -198,8 +199,7 @@ mod tests {
|
||||
assert!(s.sound(), "{}", s.line());
|
||||
assert_eq!(s.manifold, Some(true));
|
||||
assert!(
|
||||
s.volume
|
||||
.is_some_and(|v| (v - 1000.0).abs() <= 1e-6 * 1000.0),
|
||||
matches!(s.volume, Some(VolumeSign::Positive(e)) if e.lo <= 1000.0 && 1000.0 <= e.hi),
|
||||
"{}",
|
||||
s.line()
|
||||
);
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
publish = false
|
||||
rust-version.workspace = true
|
||||
# solidcore-record needs Rust 1.88 (edition 2024 let chains).
|
||||
rust-version = "1.88"
|
||||
repository = "https://github.com/tangency-labs/solidcore"
|
||||
keywords = ["cad", "brep", "nurbs", "geometry"]
|
||||
categories = ["data-structures", "science"]
|
||||
|
||||
+151
-7
@@ -21,6 +21,8 @@
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
#![allow(clippy::neg_cmp_op_on_partial_ord)] // NaN-refusing tests
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use solidcore::curve::curve::Curve as KernelCurve;
|
||||
use solidcore::curve::from_parameters::{
|
||||
self as curves, approx_tolerance, zone_tolerance, MAX_TOLERANCE,
|
||||
@@ -93,6 +95,86 @@ pub struct Order {
|
||||
pub vertices: Vec<VertexId>,
|
||||
}
|
||||
|
||||
/// A kernel edge solids touch along, as [`to_record`] writes it: one record edge per pair of
|
||||
/// faces it separates (SolidCraft's `history::Piece`). An edge with uses on three or more faces
|
||||
/// has one [`Piece`] per record edge [`to_record`] copied it into, each with the faces of that
|
||||
/// copy's own uses (two, for the sector pairs the radial order gives); none is merged or split
|
||||
/// here.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Piece {
|
||||
/// The kernel edge.
|
||||
pub kernel: EdgeId,
|
||||
/// One of its record edges.
|
||||
pub edge: u32,
|
||||
/// The record faces that record edge bounds, ascending.
|
||||
pub faces: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Order {
|
||||
/// Each kernel face's record face (the inverse of [`Order::faces`]).
|
||||
#[must_use]
|
||||
pub fn record_faces(&self) -> BTreeMap<FaceId, u32> {
|
||||
inverse(&self.faces)
|
||||
}
|
||||
|
||||
/// Each kernel edge's first record edge (the inverse of [`Order::edges`]; a kernel edge
|
||||
/// solids touch along has more, its [`Order::pieces`]).
|
||||
#[must_use]
|
||||
pub fn record_edges(&self) -> BTreeMap<EdgeId, u32> {
|
||||
inverse(&self.edges)
|
||||
}
|
||||
|
||||
/// Each kernel vertex's first record vertex (the inverse of [`Order::vertices`]).
|
||||
#[must_use]
|
||||
pub fn record_vertices(&self) -> BTreeMap<VertexId, u32> {
|
||||
inverse(&self.vertices)
|
||||
}
|
||||
|
||||
/// Every record edge of each kernel edge that has more than one, in record order, with the
|
||||
/// record faces it bounds (`record` is the record this order came with): the faces of that
|
||||
/// record edge's uses, sorted and without repeats.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`Unsupported::Invalid`] for a record whose edges are not this order's.
|
||||
pub fn pieces(&self, record: &r::Brep) -> R<Vec<Piece>> {
|
||||
if record.edges.len() != self.edges.len() {
|
||||
return Err(Unsupported::Invalid("a record not of this order"));
|
||||
}
|
||||
let mut count: BTreeMap<EdgeId, usize> = BTreeMap::new();
|
||||
for &e in &self.edges {
|
||||
*count.entry(e).or_default() += 1;
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for (i, (&kernel, e)) in self.edges.iter().zip(&record.edges).enumerate() {
|
||||
if count.get(&kernel).copied().unwrap_or(0) < 2 {
|
||||
continue;
|
||||
}
|
||||
let mut faces: Vec<u32> = e.uses.iter().map(|u| u.face).collect();
|
||||
faces.sort_unstable();
|
||||
faces.dedup();
|
||||
out.push(Piece {
|
||||
kernel,
|
||||
edge: u32::try_from(i).map_err(|_| Unsupported::Invalid("index"))?,
|
||||
faces,
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Each entity's first index in `list`. An index past `u32` is left out: unreachable, since
|
||||
/// every list is an [`Order`]'s, indexed by a record's `u32` indices.
|
||||
fn inverse<T: Copy + Ord>(list: &[T]) -> BTreeMap<T, u32> {
|
||||
let mut out = BTreeMap::new();
|
||||
for (i, &x) in list.iter().enumerate() {
|
||||
if let Ok(i) = u32::try_from(i) {
|
||||
out.entry(x).or_insert(i);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn int(n: usize) -> R<u64> {
|
||||
u64::try_from(n).map_err(|_| Unsupported::Invalid("count"))
|
||||
}
|
||||
@@ -231,8 +313,8 @@ fn build(
|
||||
}
|
||||
|
||||
/// The record as the kernel's B-rep data: edgeuses face by face, loop by loop, in loop order;
|
||||
/// edges and vertices in the record's order. Each edge and vertex keeps its own tolerance (at
|
||||
/// least the body's); the body and every face take the body's. The record has no face
|
||||
/// edges and vertices in the record's order. Each edge and vertex keeps its own tolerance; the
|
||||
/// body's and each face's are the least of theirs ([`own_tolerances`]). The record has no face
|
||||
/// tolerance: a face's own crosses as its edges' (`to_record` puts it into them).
|
||||
fn data(k: &Kernel, rec: &r::Brep, bare: &[(u32, u32, u32)]) -> R<BrepData> {
|
||||
let zone = zone_tolerance(rec.tolerance);
|
||||
@@ -318,7 +400,7 @@ fn data(k: &Kernel, rec: &r::Brep, bare: &[(u32, u32, u32)]) -> R<BrepData> {
|
||||
// Edges first (their kernel intervals are the trims' parameter), in the record's order.
|
||||
let mut intervals = Vec::new();
|
||||
for e in &rec.edges {
|
||||
let tolerance = e.tolerance.max(rec.tolerance);
|
||||
let tolerance = e.tolerance;
|
||||
if !(tolerance <= MAX_TOLERANCE) {
|
||||
return Err(Unsupported::NotYet(
|
||||
"an edge tolerance over the kernel's limit",
|
||||
@@ -475,9 +557,18 @@ fn data(k: &Kernel, rec: &r::Brep, bare: &[(u32, u32, u32)]) -> R<BrepData> {
|
||||
let interval = intervals
|
||||
.get(u.edge as usize)
|
||||
.ok_or(Unsupported::Invalid("edge"))?;
|
||||
let source_interval = rec
|
||||
.edges
|
||||
.get(u.edge as usize)
|
||||
.ok_or(Unsupported::Invalid("edge"))?
|
||||
.interval;
|
||||
ud.uv_curve = int(d.uv_curves.len())?;
|
||||
let trim =
|
||||
curves::uv_trim(&crate::curve2(&u.pcurve), interval, made.scale)?;
|
||||
let trim = curves::uv_trim_on_edge(
|
||||
&crate::curve2(&u.pcurve),
|
||||
source_interval,
|
||||
interval,
|
||||
made.scale,
|
||||
)?;
|
||||
d.uv_curves.push(Keyed {
|
||||
key: key(),
|
||||
object: trim,
|
||||
@@ -500,9 +591,9 @@ fn data(k: &Kernel, rec: &r::Brep, bare: &[(u32, u32, u32)]) -> R<BrepData> {
|
||||
}
|
||||
}
|
||||
|
||||
// Vertices, each with its own tolerance, never below the body's.
|
||||
// Vertices, each with its own tolerance.
|
||||
for v in &rec.vertices {
|
||||
let tolerance = v.tolerance.max(rec.tolerance);
|
||||
let tolerance = v.tolerance;
|
||||
if !(tolerance <= MAX_TOLERANCE) {
|
||||
return Err(Unsupported::NotYet(
|
||||
"a vertex tolerance over the kernel's limit",
|
||||
@@ -514,9 +605,62 @@ fn data(k: &Kernel, rec: &r::Brep, bare: &[(u32, u32, u32)]) -> R<BrepData> {
|
||||
vd.point = Vec3::new(v.point[0], v.point[1], v.point[2]);
|
||||
d.vertices.push(vd);
|
||||
}
|
||||
own_tolerances(&mut d, rec, zone)?;
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
/// The body's and each face's tolerance from the edges' and vertices' own, as SolidCraft's
|
||||
/// flat import took them (`own_tolerances`, after the capi's): the kernel's build raises every
|
||||
/// entity to the body's, so the body's is the least of the record's and its edges' and
|
||||
/// vertices', and a face's the least of its loops' edges' and vertices', at most the record's.
|
||||
/// Each entity keeps its own across the crossing, none raised to the body's.
|
||||
///
|
||||
/// This keeps invariant 5 of `docs/validity-and-tolerances.md` §10 (every local tolerance at
|
||||
/// least the body's) by the other route from raising each entity to the body's (8252ae3e93,
|
||||
/// "never below the body's"): the body comes down to the least of them instead, so no entity
|
||||
/// is below it and none is changed. A face's is at least the body's (both are minima, the
|
||||
/// body's over more) and at most its loops' edges' and vertices'.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`Unsupported::Invalid`] when the least zone is not positive and finite: a tolerance so
|
||||
/// small that its zone (half of it) underflows to zero passes the record's validation.
|
||||
fn own_tolerances(d: &mut BrepData, rec: &r::Brep, zone: f64) -> R<()> {
|
||||
let edge_zone = |e: u32| -> R<f64> { Ok(at(&d.edges, e)?.zone_tol_3d) };
|
||||
let vertex_zone = |v: u32| -> R<f64> { Ok(at(&d.vertices, v)?.zone_tol_3d) };
|
||||
let mut faces = Vec::with_capacity(rec.faces.len());
|
||||
for f in &rec.faces {
|
||||
let mut t = f64::INFINITY;
|
||||
for l in &f.loops {
|
||||
match l {
|
||||
r::Loop::Edges { edges } => {
|
||||
for oe in edges {
|
||||
t = t.min(edge_zone(oe.edge)?);
|
||||
}
|
||||
}
|
||||
r::Loop::Vertex { vertex } => t = t.min(vertex_zone(*vertex)?),
|
||||
}
|
||||
}
|
||||
faces.push(if t.is_finite() { t.min(zone) } else { zone });
|
||||
}
|
||||
let least = d
|
||||
.edges
|
||||
.iter()
|
||||
.map(|e| e.zone_tol_3d)
|
||||
.chain(d.vertices.iter().map(|v| v.zone_tol_3d))
|
||||
.fold(zone, f64::min);
|
||||
if !(least.is_finite() && least > 0.0) {
|
||||
return Err(Unsupported::Invalid(
|
||||
"a tolerance whose zone (half of it) is zero or not finite",
|
||||
));
|
||||
}
|
||||
d.zone_tol_3d = least;
|
||||
for (fd, t) in d.faces.iter_mut().zip(faces) {
|
||||
fd.zone_tol_3d = t;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The curves of a ball patch face's loops, for [`face_surface`] to certify on its sphere:
|
||||
/// each edge's NURBS form, or its curve; empty for any other face, whose label is not
|
||||
/// checked against its loops.
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//! B-rep data, with no arrays between. What is done here is index mapping and field copies:
|
||||
//!
|
||||
//! - **Geometry:** each edge's curve and each face's surface as the kernel writes its record
|
||||
//! ([`record_of`], [`record_face`]): a line by its two end points, a conic by its own frame and
|
||||
//! ([`record_of`], [`record_face`]): a line as the kernel holds it, a conic by its own frame and
|
||||
//! its NURBS form, proved on each other; an analytic face by its label and its NURBS form,
|
||||
//! proved (`lies_on`); a plane by its placement, its parameters its kernel ones times its
|
||||
//! scales. Nothing is sampled. A curve or surface with no NURBS form (an offset) is refused.
|
||||
|
||||
+76
-4
@@ -226,6 +226,29 @@ fn a_cube_record_builds_the_kernels_cube() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pcurve_support_may_extend_past_its_edge_interval() {
|
||||
let mut record = cube(1.0);
|
||||
let Curve::Line { start, end } = &record.edges[0].curve else {
|
||||
panic!("cube line")
|
||||
};
|
||||
let (start, end) = (*start, *end);
|
||||
record.edges[0].curve = Curve::Spline {
|
||||
knots: vec![0.0, 0.0, 1.0, 1.0],
|
||||
points: vec![start, end],
|
||||
};
|
||||
record.edges[0].interval = [0.0, f64::from_bits(1.0_f64.to_bits() - 2)];
|
||||
record.validate().unwrap();
|
||||
let original = record.faces.clone();
|
||||
let mut k = kernel();
|
||||
let (mut body, _) = to_kernel(&mut k, &record, true).unwrap();
|
||||
let (saved, _) = to_record(&mut k, &mut body).unwrap();
|
||||
assert_eq!(
|
||||
saved.faces.iter().map(|f| &f.loops).collect::<Vec<_>>(),
|
||||
original.iter().map(|f| &f.loops).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records_the_conversion_cannot_carry_are_refused() {
|
||||
let mut k = kernel();
|
||||
@@ -556,8 +579,8 @@ fn lumps_and_solids_touching_along_an_edge_go_round() {
|
||||
}
|
||||
|
||||
/// An analytic body's record: its curved faces are labels on their NURBS forms, its circles
|
||||
/// labels on theirs, its lines their two end points over [0, 1]; each pcurve on its edge's
|
||||
/// interval.
|
||||
/// labels on theirs, its lines as the kernel holds them (its point and unit vector, over the
|
||||
/// height); each pcurve on its edge's interval.
|
||||
#[test]
|
||||
fn analytic_records_carry_labels_on_their_forms() {
|
||||
let mut k = kernel();
|
||||
@@ -576,8 +599,9 @@ fn analytic_records_carry_labels_on_their_forms() {
|
||||
for e in &r.edges {
|
||||
match &e.curve {
|
||||
Curve::Line { start, end } => {
|
||||
assert_eq!(e.interval, [0.0, 1.0]);
|
||||
assert_eq!((start[2], end[2]), (0.0, 5.0));
|
||||
// as the kernel holds the seam: its point and unit vector, over its height
|
||||
assert_eq!(e.interval, [0.0, 5.0]);
|
||||
assert_eq!((start[2], end[2]), (0.0, 1.0));
|
||||
assert!(e.nurbs.is_none());
|
||||
}
|
||||
Curve::Circle { radius, .. } => {
|
||||
@@ -1146,3 +1170,51 @@ fn a_ball_patch_far_from_the_origin_reads_as_one() {
|
||||
assert!(read_ball(&mut k, &off, i).is_none(), "face {i}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A record whose body tolerance is above its edges' and vertices' builds with the body's
|
||||
/// lowered to the least of them (the flat import's `own_tolerances`): valid at Level 2, and
|
||||
/// every edge and vertex at least the body's (invariant 5 of `docs/validity-and-tolerances.md`
|
||||
/// §10), each at its own (the zone, half the record's), none raised.
|
||||
#[test]
|
||||
fn a_body_tolerance_above_its_entities_comes_down_to_theirs() {
|
||||
let mut k = kernel();
|
||||
let mut r = cube(10.0);
|
||||
r.tolerance = 1e-4;
|
||||
r.edges[3].tolerance = 4e-6;
|
||||
r.vertices[5].tolerance = 2e-6;
|
||||
r.validate().unwrap();
|
||||
let (mut b, _) = to_kernel(&mut k, &r, true).unwrap();
|
||||
valid(&mut k, &mut b);
|
||||
// the least is the other edges' 1e-6, as a zone: half of it
|
||||
assert_eq!(b.tolerance(), 5e-7);
|
||||
for &e in b.edge_list() {
|
||||
assert!(b.edge(e).unwrap().tolerance >= b.tolerance(), "edge {e:?}");
|
||||
}
|
||||
for &v in b.vertex_list() {
|
||||
assert!(
|
||||
b.vertex(v).unwrap().tolerance >= b.tolerance(),
|
||||
"vertex {v:?}"
|
||||
);
|
||||
}
|
||||
let mut own: Vec<f64> = b
|
||||
.edge_list()
|
||||
.iter()
|
||||
.map(|&e| b.edge(e).unwrap().tolerance)
|
||||
.collect();
|
||||
own.sort_by(f64::total_cmp);
|
||||
own.dedup();
|
||||
assert_eq!(own, vec![5e-7, 2e-6], "edges keep their own zones");
|
||||
}
|
||||
|
||||
/// A tolerance so small that its zone (half of it) underflows to zero passes the record's
|
||||
/// validation and is refused by the conversion, not dropped to the body's.
|
||||
#[test]
|
||||
fn a_tolerance_whose_zone_underflows_is_refused() {
|
||||
let mut k = kernel();
|
||||
let mut r = cube(1.0);
|
||||
r.vertices[0].tolerance = f64::from_bits(1);
|
||||
assert!(matches!(
|
||||
to_kernel(&mut k, &r, true),
|
||||
Err(Unsupported::Invalid(_))
|
||||
));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,602 @@
|
||||
//! Parity with SolidCraft's flat-array hop (#3137): every operand record of the rig corpus
|
||||
//! (`crates/solidcore/tests/kernel_cases/`) is what SolidCraft's flat export wrote (record →
|
||||
//! arrays → kernel → arrays → record), or, for a body SolidCraft made or read itself (not
|
||||
//! `trusted`), what it handed the kernel. Each goes to the kernel ([`to_kernel`]) and back
|
||||
//! ([`to_record`]); the record that comes back is compared with the one that went in, field by
|
||||
//! field, bit for bit. A difference is classed by where it is (indices dropped) and, for a
|
||||
//! kind, by what it became; a number within [`ROUNDING`] of its size (relative, no floor) is
|
||||
//! counted as rounding, with its count and the largest.
|
||||
//!
|
||||
//! On the whole corpus (1,333 bodies, at #3199 with kernel case 11991 (#3231) in): 1,259 come
|
||||
//! back bit for bit, 36 within rounding (the largest 4.9e-16 relative), and 38 differ, each in exactly the classes
|
||||
//! [`pinned`] lists for it.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing,
|
||||
clippy::print_stderr
|
||||
)] // test
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Read as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::Value;
|
||||
use solidcore::kernel::{Kernel, KernelOptions, UpstreamFixes};
|
||||
use solidcore_convert::brep::{to_kernel, to_kernel_body, to_record, Unsupported};
|
||||
use solidcore_record::brep::Brep as Record;
|
||||
|
||||
fn corpus() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../solidcore/tests/kernel_cases")
|
||||
}
|
||||
|
||||
fn entries(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(dir)
|
||||
.unwrap()
|
||||
.map(|e| e.unwrap().path())
|
||||
.collect();
|
||||
paths.sort();
|
||||
for p in paths {
|
||||
if p.is_dir() {
|
||||
entries(&p, out);
|
||||
} else if p.extension().is_some_and(|x| x == "json")
|
||||
&& !p.to_string_lossy().ends_with(".bodies.json")
|
||||
&& p.parent() != Some(corpus().as_path())
|
||||
{
|
||||
out.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Class {
|
||||
bodies: usize,
|
||||
places: usize,
|
||||
examples: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Tally {
|
||||
bodies: usize,
|
||||
identical: usize,
|
||||
rounding_only: usize,
|
||||
rounding_places: usize,
|
||||
rounding_largest: f64,
|
||||
refused_in: Vec<String>,
|
||||
/// Sheets with a face used on its reverse side (every difference follows from that).
|
||||
sheet_side: Vec<String>,
|
||||
refused_out: Vec<String>,
|
||||
classes: BTreeMap<String, Class>,
|
||||
/// Each differing body's classes (sheet-side bodies' too).
|
||||
per_body: BTreeMap<String, BTreeSet<String>>,
|
||||
/// Bodies whose `.tolerance` moved other than down to the least of the record's.
|
||||
tolerance_wrong: Vec<String>,
|
||||
/// Sheet-side bodies whose geometry differs as multisets (not only renumbered).
|
||||
sheet_geometry: Vec<String>,
|
||||
/// (body, face, the record's kind, the kind that came back, trusted)
|
||||
relabelled: Vec<(String, usize, String, String, bool)>,
|
||||
}
|
||||
|
||||
/// The relative size of a difference counted as rounding, not a class: a few ulps.
|
||||
const ROUNDING: f64 = 1e-15;
|
||||
|
||||
fn normal(path: &str) -> String {
|
||||
let mut out = String::new();
|
||||
let mut in_index = false;
|
||||
for c in path.chars() {
|
||||
match c {
|
||||
'[' => {
|
||||
in_index = true;
|
||||
out.push_str("[*]");
|
||||
}
|
||||
']' => in_index = false,
|
||||
_ if in_index => {}
|
||||
_ => out.push(c),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Every place `a` and `b` differ: (class, place); numbers within [`ROUNDING`] of their size
|
||||
/// (relative, no floor) counted apart, with the largest relative difference.
|
||||
fn walk(
|
||||
at: &str,
|
||||
a: &Value,
|
||||
b: &Value,
|
||||
diffs: &mut Vec<(String, String)>,
|
||||
rounding: &mut (usize, f64),
|
||||
) {
|
||||
if a == b {
|
||||
return;
|
||||
}
|
||||
if let (Some(x), Some(y)) = (a.as_f64(), b.as_f64()) {
|
||||
let rel = (x - y).abs() / x.abs().max(y.abs());
|
||||
if rel <= ROUNDING {
|
||||
rounding.0 += 1;
|
||||
rounding.1 = rounding.1.max(rel);
|
||||
return;
|
||||
}
|
||||
diffs.push((normal(at), format!("{at}: {x} -> {y}")));
|
||||
return;
|
||||
}
|
||||
match (a, b) {
|
||||
(Value::Object(x), Value::Object(y)) => {
|
||||
for (k, v) in x {
|
||||
walk(
|
||||
&format!("{at}.{k}"),
|
||||
v,
|
||||
y.get(k).unwrap_or(&Value::Null),
|
||||
diffs,
|
||||
rounding,
|
||||
);
|
||||
}
|
||||
for (k, w) in y {
|
||||
if !x.contains_key(k) {
|
||||
walk(&format!("{at}.{k}"), &Value::Null, w, diffs, rounding);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Value::Array(x), Value::Array(y)) if x.len() == y.len() => {
|
||||
for (i, (v, w)) in x.iter().zip(y).enumerate() {
|
||||
walk(&format!("{at}[{i}]"), v, w, diffs, rounding);
|
||||
}
|
||||
}
|
||||
(Value::Array(x), Value::Array(y)) => diffs.push((
|
||||
format!("{}: length", normal(at)),
|
||||
format!("{at}: {} -> {}", x.len(), y.len()),
|
||||
)),
|
||||
(Value::String(x), Value::String(y)) => {
|
||||
diffs.push((format!("{}: {x} -> {y}", normal(at)), at.to_owned()));
|
||||
}
|
||||
_ => {
|
||||
let short = |v: &Value| {
|
||||
let s = v.to_string();
|
||||
s.chars().take(60).collect::<String>()
|
||||
};
|
||||
diffs.push((normal(at), format!("{at}: {} -> {}", short(a), short(b))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A record's geometry as multisets, its numbering dropped: the vertex points, each edge's
|
||||
/// curve (a line's two ends unordered), each face's surface and NURBS form.
|
||||
fn geometry(r: &Value) -> [Vec<String>; 3] {
|
||||
let list = |key: &str, f: &dyn Fn(&Value) -> String| -> Vec<String> {
|
||||
let mut v: Vec<String> = r[key]
|
||||
.as_array()
|
||||
.map(|a| a.iter().map(f).collect())
|
||||
.unwrap_or_default();
|
||||
v.sort();
|
||||
v
|
||||
};
|
||||
let curve = |e: &Value| {
|
||||
let c = &e["curve"];
|
||||
if c["kind"] == "line" {
|
||||
let mut ends = [c["start"].to_string(), c["end"].to_string()];
|
||||
ends.sort();
|
||||
format!("line {ends:?}")
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
};
|
||||
[
|
||||
list("vertices", &|v| v["point"].to_string()),
|
||||
list("edges", &curve),
|
||||
list("faces", &|f| format!("{} {}", f["surface"], f["nurbs"])),
|
||||
]
|
||||
}
|
||||
|
||||
fn kind(face: &Value) -> String {
|
||||
face["surface"]["kind"].as_str().unwrap_or("?").to_owned()
|
||||
}
|
||||
|
||||
fn one(name: &str, record: &Record, trusted: bool, fixes: &UpstreamFixes, t: &mut Tally) {
|
||||
t.bodies += 1;
|
||||
// a sheet face used on its reverse side: the kernel keeps both uses of a sheet's face and
|
||||
// the record comes back with the `same` one, as SolidCraft's flat export wrote it
|
||||
// (a sheet's shell is listed by one region; a closed shell by the two it separates)
|
||||
let listed = |i: usize| {
|
||||
record
|
||||
.regions
|
||||
.iter()
|
||||
.filter(|r| r.shells.iter().any(|&s| s as usize == i))
|
||||
.count()
|
||||
};
|
||||
let sheet_side = record.shells.iter().enumerate().any(|(i, s)| match s {
|
||||
solidcore_record::brep::Shell::Faces { faces, .. } => {
|
||||
listed(i) == 1 && faces.iter().any(|f| !f.forward)
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
let mut k = Kernel::new(&KernelOptions::default());
|
||||
k.fixes = *fixes;
|
||||
let built = match to_kernel(&mut k, record, trusted) {
|
||||
Ok((b, _)) => Ok(b),
|
||||
Err(Unsupported::NotYet(w)) if w.contains("healer") => {
|
||||
to_kernel_body(&mut k, record, trusted)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
let mut b = match built {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
t.refused_in.push(format!("{name}: {e}"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let back = match to_record(&mut k, &mut b) {
|
||||
Ok((r, _)) => r,
|
||||
Err(e) => {
|
||||
t.refused_out.push(format!("{name}: {e}"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (a, z) = (
|
||||
serde_json::to_value(record).unwrap(),
|
||||
serde_json::to_value(&back).unwrap(),
|
||||
);
|
||||
// `PARITY_DUMP=<dir>` and `PARITY_ONLY=<part of a name>`: both records written there
|
||||
if let (Ok(dir), Ok(only)) = (std::env::var("PARITY_DUMP"), std::env::var("PARITY_ONLY")) {
|
||||
if name.contains(&only) {
|
||||
let stem = name.replace(['/', ':'], "_");
|
||||
std::fs::write(format!("{dir}/{stem}.in.json"), a.to_string()).unwrap();
|
||||
std::fs::write(format!("{dir}/{stem}.out.json"), z.to_string()).unwrap();
|
||||
}
|
||||
}
|
||||
let mut diffs = Vec::new();
|
||||
let mut rounding = (0, 0.0_f64);
|
||||
walk("", &a, &z, &mut diffs, &mut rounding);
|
||||
t.rounding_places += rounding.0;
|
||||
t.rounding_largest = t.rounding_largest.max(rounding.1);
|
||||
if let (Some(fa), Some(fz)) = (a["faces"].as_array(), z["faces"].as_array()) {
|
||||
for (i, (x, y)) in fa.iter().zip(fz).enumerate() {
|
||||
if kind(x) != kind(y) {
|
||||
t.relabelled
|
||||
.push((name.to_owned(), i, kind(x), kind(y), trusted));
|
||||
}
|
||||
}
|
||||
}
|
||||
if diffs.is_empty() {
|
||||
if rounding.0 == 0 {
|
||||
t.identical += 1;
|
||||
} else {
|
||||
t.rounding_only += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
let classes: BTreeSet<String> = diffs.iter().map(|(c, _)| c.clone()).collect();
|
||||
if classes.contains(".tolerance") {
|
||||
// lowered, to the least of the record's own (the flat import's `own_tolerances`)
|
||||
let least = record
|
||||
.edges
|
||||
.iter()
|
||||
.map(|e| e.tolerance)
|
||||
.chain(record.vertices.iter().map(|v| v.tolerance))
|
||||
.fold(record.tolerance, f64::min);
|
||||
if !(back.tolerance < record.tolerance && back.tolerance == least) {
|
||||
t.tolerance_wrong.push(format!(
|
||||
"{name}: {} -> {} (least {least})",
|
||||
record.tolerance, back.tolerance
|
||||
));
|
||||
}
|
||||
}
|
||||
t.per_body.insert(name.to_owned(), classes);
|
||||
if sheet_side && geometry(&a) != geometry(&z) {
|
||||
t.sheet_geometry.push(name.to_owned());
|
||||
}
|
||||
if sheet_side {
|
||||
t.sheet_side.push(name.to_owned());
|
||||
return;
|
||||
}
|
||||
let mut seen = BTreeMap::new();
|
||||
for (class, place) in diffs {
|
||||
seen.entry(class).or_insert((0, place)).0 += 1;
|
||||
}
|
||||
for (class, (n, place)) in seen {
|
||||
let tag = if trusted { "" } else { "(untrusted) " };
|
||||
let c = t.classes.entry(format!("{tag}{class}")).or_default();
|
||||
c.bodies += 1;
|
||||
c.places += n;
|
||||
if c.examples.len() < 3 {
|
||||
c.examples.push(format!("{name} {place}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fixes() -> UpstreamFixes {
|
||||
let fixes_json: Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(corpus().join("fixes.json")).unwrap())
|
||||
.unwrap();
|
||||
let mut fixes = UpstreamFixes::default();
|
||||
for flag in fixes_json.as_array().unwrap() {
|
||||
*fixes.flag_mut(flag.as_str().unwrap()).unwrap() = true;
|
||||
}
|
||||
fixes
|
||||
}
|
||||
|
||||
const LABEL: [&str; 6] = [
|
||||
".faces[*].nurbs",
|
||||
".faces[*].surface.frame",
|
||||
".faces[*].surface.homogeneous",
|
||||
".faces[*].surface.u_knots",
|
||||
".faces[*].surface.v_knots",
|
||||
"",
|
||||
];
|
||||
|
||||
/// Every body that does not come back bit for bit (or within [`ROUNDING`]), with exactly the
|
||||
/// classes it differs in: a body that newly differs, one that no longer does, and one whose
|
||||
/// classes change all fail (#3187 review). Each group says why its difference is intended.
|
||||
fn pinned() -> BTreeMap<String, BTreeSet<String>> {
|
||||
let set = |classes: &[&str]| -> BTreeSet<String> {
|
||||
classes
|
||||
.iter()
|
||||
.filter(|c| !c.is_empty())
|
||||
.map(|c| (*c).to_owned())
|
||||
.collect()
|
||||
};
|
||||
let label = |kind: &str, radii: &[&str]| {
|
||||
let mut c = set(&LABEL);
|
||||
c.insert(format!(".faces[*].surface.kind: {kind}"));
|
||||
c.extend(radii.iter().map(|r| format!(".faces[*].surface.{r}")));
|
||||
c
|
||||
};
|
||||
let groups: Vec<(BTreeSet<String>, &[&str])> = vec![
|
||||
// a record whose body tolerance is above its least entity's: the import lowers it to
|
||||
// that least, as the flat import's `own_tolerances` does (checked: only lowered, and
|
||||
// only to the least of the record's own)
|
||||
(
|
||||
set(&[".tolerance"]),
|
||||
&[
|
||||
"continuity/cov-patch-g1-caps-an-opened-tube-end.json:b3",
|
||||
"continuity/cov-patch-g2-caps-an-opened-tube-end.json:b3",
|
||||
"continuity/g1-patch-g2-at-a-corner-whose-rounds-clash.json:b13",
|
||||
"continuity/g1-patch-g3-at-a-corner-whose-rounds-clash.json:b13",
|
||||
"continuity/g2-patch-g2-on-a-hole-in-one-sheet.json:b11",
|
||||
"continuity/g3-patch-g3-on-a-hole-in-one-sheet.json:b11",
|
||||
"fillets/9-fillet-corner-r5-curvature-default-setback.json:b8",
|
||||
"fillets/cov-fillet-g2-pocket-corner.json:b18",
|
||||
"fillets/every-box-corner-three-edges-r3-curvature-setback-3-5.json:b8",
|
||||
"fillets/report-fillet-curvature-three-edges-at-bar-corner-solver-residues.json:b18",
|
||||
"fillets/report-fillet-curvature-three-edges-at-bar-corner.json:b18",
|
||||
"fillets/report-fillet-g2-corner-of-bar-running-into-cylinder.json:b18",
|
||||
"ladder/07-fillets/015-corner-g2-patch.json:b8",
|
||||
"ladder/07-fillets/019-concave-corner-g2-patch.json:b18",
|
||||
"surfaces/cov-blend-step-between-offset-sheets-g1.json:b1",
|
||||
"surfaces/cov-blend-step-between-offset-sheets-g2.json:b1",
|
||||
"surfaces/cov-blend-step-between-offset-sheets-g3.json:b1",
|
||||
"surfaces/cov-patch-g0-fills-a-240-deg-window-in-a-cylinder.json:b13",
|
||||
"surfaces/cov-patch-g0-fills-a-saddle-window-in-a-torus.json:b13",
|
||||
"surfaces/cov-patch-g1-fills-a-240-deg-window-in-a-cylinder.json:b13",
|
||||
"surfaces/cov-patch-g1-fills-a-saddle-window-in-a-torus.json:b13",
|
||||
"surfaces/cov-patch-open-box-rim-and-stitch-closes-it.json:b3",
|
||||
],
|
||||
),
|
||||
// a plane through three points: the kernel's plane holds no record of which kind it
|
||||
// came from, and the flat export never writes this kind
|
||||
(
|
||||
set(&[
|
||||
".faces[*].surface.kind: plane_through -> plane",
|
||||
".faces[*].surface.u_axis",
|
||||
".faces[*].surface.u_point",
|
||||
".faces[*].surface.v_axis",
|
||||
".faces[*].surface.v_point",
|
||||
]),
|
||||
&[
|
||||
"surfaces/cov-ruled-box-edge-along-the-top-normal.json:b2",
|
||||
"surfaces/cov-ruled-closed-top-chain-tangent-mitred-frame.json:b2",
|
||||
"surfaces/cov-ruled-cylinder-rim-tangent-to-the-top-is-a-flat-ring.json:b2",
|
||||
],
|
||||
),
|
||||
// a label its form is not proved on goes as the form alone (#2540): cylinders whose
|
||||
// frames are off their forms past the rounding `evaluates_as` derives (margins 1.23 to
|
||||
// 2.03, tests/issue_3185_label_margins.rs)
|
||||
(
|
||||
label("cylinder -> rational_spline", &["radius"]),
|
||||
&[
|
||||
"booleans/3456-deep-cut-through-drilled-taper-leaves-base.json:b94",
|
||||
"crashes/2243-step-export-flanged-enclosure.json:b43",
|
||||
"fillets/2903-original-rim-chamfer.json:b0",
|
||||
"fillets/2903-original-rim-round.json:b0",
|
||||
],
|
||||
),
|
||||
// a torus label whose frame is off its form's own first sweep row past the rounding
|
||||
// `evaluates_as` derives (margin 1.69, tests/issue_3185_label_margins.rs)
|
||||
(
|
||||
label(
|
||||
"torus -> rational_spline",
|
||||
&["major_radius", "minor_radius"],
|
||||
),
|
||||
&["sweeps/4280-circular-pattern-of-sweep-cut-groove-on-hub.json:b46"],
|
||||
),
|
||||
// an extruded circle the flat hop left a spline, proved a cylinder
|
||||
(
|
||||
label("rational_spline -> cylinder", &["radius"]),
|
||||
&[
|
||||
"tessellation/1615-crossed-lofted-tubes.json:b3",
|
||||
"tessellation/1615-crossed-lofted-tubes.json:b6",
|
||||
],
|
||||
),
|
||||
// a line end the export wrote as 3.1e-16 (a cos 90 deg residue) where the kernel's
|
||||
// line, point plus its vector, holds 0: an absolute 3e-16 on a coordinate of size 0
|
||||
(
|
||||
set(&[".edges[*].curve.end[*]"]),
|
||||
&["assemblies/cov-contact-check-touching-faces-do-not-overlap.json:b725"],
|
||||
),
|
||||
];
|
||||
let mut out = BTreeMap::new();
|
||||
for (classes, bodies) in groups {
|
||||
for b in bodies {
|
||||
out.insert((*b).to_owned(), classes.clone());
|
||||
}
|
||||
}
|
||||
// a sheet face used on its reverse side: the kernel keeps both uses of a sheet's face and
|
||||
// the record comes back with the `same` one, as the flat export's rule says; every
|
||||
// difference is the renumbering that follows (checked: the same vertex points, edge curves
|
||||
// and face surfaces, as multisets), and two of them also lower `.tolerance`
|
||||
let renumbered = [
|
||||
".edges[*].uses[*].position",
|
||||
".faces[*].loops[*].edges[*].edge",
|
||||
".shells[*].faces[*].forward",
|
||||
".vertices[*].point[*]",
|
||||
];
|
||||
let match_sheet = [
|
||||
".edges[*].curve.knots: length",
|
||||
".edges[*].curve.points: length",
|
||||
".edges[*].uses[*].upper",
|
||||
];
|
||||
let reverse = [
|
||||
".edges[*].curve.end[*]",
|
||||
".edges[*].curve.start[*]",
|
||||
".edges[*].vertices[*]",
|
||||
];
|
||||
let with = |extra: &[&[&str]]| {
|
||||
let mut c = set(&renumbered);
|
||||
for e in extra {
|
||||
c.extend(set(e));
|
||||
}
|
||||
c
|
||||
};
|
||||
for (b, classes) in [
|
||||
(
|
||||
"surfaces/cov-match-coplanar-sheet-onto-the-near-edge.json:b1",
|
||||
with(&[&match_sheet]),
|
||||
),
|
||||
(
|
||||
"surfaces/cov-match-raised-sheet-down-onto-the-edge-g1.json:b1",
|
||||
with(&[&match_sheet, &[".tolerance"]]),
|
||||
),
|
||||
(
|
||||
"surfaces/cov-match-raised-sheet-down-onto-the-edge-g3.json:b1",
|
||||
with(&[&match_sheet, &[".tolerance"]]),
|
||||
),
|
||||
(
|
||||
"surfaces/cov-reverse-normal-l-sheet-swaps-the-thicken-side.json:b2",
|
||||
with(&[&reverse, &[".edges[*].uses: length"]]),
|
||||
),
|
||||
(
|
||||
"surfaces/cov-reverse-normal-then-thicken-goes-down.json:b1",
|
||||
with(&[&reverse]),
|
||||
),
|
||||
] {
|
||||
out.insert(b.to_owned(), classes);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Every `stride`th entry; the report is written to `$PARITY_OUT` when set. No body is refused
|
||||
/// either way, and every difference is a named one.
|
||||
fn parity(stride: usize) {
|
||||
let mut files = Vec::new();
|
||||
entries(&corpus(), &mut files);
|
||||
let fixes = fixes();
|
||||
let mut t = Tally::default();
|
||||
let with_bodies = files.iter().filter_map(|f| {
|
||||
let e: Value = serde_json::from_str(&std::fs::read_to_string(f).unwrap()).unwrap();
|
||||
let bodies = e["bodies"].as_object()?.clone();
|
||||
(!bodies.is_empty()).then_some((f.clone(), bodies))
|
||||
});
|
||||
for (f, bodies) in with_bodies.step_by(stride) {
|
||||
let gz = f.with_extension("bodies.json.gz");
|
||||
let mut text = String::new();
|
||||
flate2::read::GzDecoder::new(std::fs::File::open(&gz).unwrap())
|
||||
.read_to_string(&mut text)
|
||||
.unwrap();
|
||||
let records: Value = serde_json::from_str(&text).unwrap();
|
||||
let short = f.strip_prefix(corpus()).unwrap_or(&f).display().to_string();
|
||||
for (id, how) in bodies {
|
||||
let record: Record = serde_json::from_value(records[&id].clone()).unwrap();
|
||||
one(
|
||||
&format!("{short}:{id}"),
|
||||
&record,
|
||||
how["trusted"].as_bool().unwrap_or(true),
|
||||
&fixes,
|
||||
&mut t,
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut report = String::new();
|
||||
let _ = writeln!(
|
||||
report,
|
||||
"bodies {} identical {} rounding-only {} (places {}, largest {:e}) differing {} refused in {} refused out {}",
|
||||
t.bodies,
|
||||
t.identical,
|
||||
t.rounding_only,
|
||||
t.rounding_places,
|
||||
t.rounding_largest,
|
||||
t.bodies - t.identical - t.rounding_only - t.refused_in.len() - t.refused_out.len(),
|
||||
t.refused_in.len(),
|
||||
t.refused_out.len()
|
||||
);
|
||||
for (class, c) in &t.classes {
|
||||
let _ = writeln!(
|
||||
report,
|
||||
"CLASS {class} | bodies {} places {}",
|
||||
c.bodies, c.places
|
||||
);
|
||||
for e in &c.examples {
|
||||
let _ = writeln!(report, " {e}");
|
||||
}
|
||||
}
|
||||
for n in &t.sheet_side {
|
||||
let _ = writeln!(report, "SHEET-SIDE {n}");
|
||||
}
|
||||
for (n, classes) in &t.per_body {
|
||||
let list: Vec<&str> = classes.iter().map(String::as_str).collect();
|
||||
let _ = writeln!(report, "BODY {n} | {}", list.join(" ; "));
|
||||
}
|
||||
for w in &t.tolerance_wrong {
|
||||
let _ = writeln!(report, "TOLERANCE-WRONG {w}");
|
||||
}
|
||||
for (n, f, a, b, trusted) in &t.relabelled {
|
||||
let _ = writeln!(report, "RELABEL {n} face {f}: {a} -> {b} trusted {trusted}");
|
||||
}
|
||||
for r in &t.refused_in {
|
||||
let _ = writeln!(report, "REFUSED-IN {r}");
|
||||
}
|
||||
for r in &t.refused_out {
|
||||
let _ = writeln!(report, "REFUSED-OUT {r}");
|
||||
}
|
||||
if let Ok(p) = std::env::var("PARITY_OUT") {
|
||||
std::fs::write(p, &report).unwrap();
|
||||
}
|
||||
eprintln!("{}", report.lines().next().unwrap_or(""));
|
||||
assert!(t.bodies > 0);
|
||||
assert!(t.refused_in.is_empty(), "{:#?}", t.refused_in);
|
||||
assert!(t.refused_out.is_empty(), "{:#?}", t.refused_out);
|
||||
assert!(t.tolerance_wrong.is_empty(), "{:#?}", t.tolerance_wrong);
|
||||
assert!(t.sheet_geometry.is_empty(), "{:#?}", t.sheet_geometry);
|
||||
let pinned = pinned();
|
||||
let seen: BTreeMap<&String, &BTreeSet<String>> = t.per_body.iter().collect();
|
||||
for (b, classes) in &seen {
|
||||
assert_eq!(pinned.get(*b), Some(*classes), "{b}: classes not as pinned");
|
||||
}
|
||||
if stride == 1 {
|
||||
let missing: Vec<&String> = pinned.keys().filter(|b| !seen.contains_key(b)).collect();
|
||||
assert!(
|
||||
missing.is_empty(),
|
||||
"pinned bodies that no longer differ: {missing:#?}"
|
||||
);
|
||||
// The corpus grows as cases are added, so the totals aren't pinned: every body that
|
||||
// differs is pinned above, and the rest must be identical or within rounding.
|
||||
assert_eq!(
|
||||
(t.bodies - t.identical - t.rounding_only, t.rounding_only),
|
||||
(pinned.len(), 36),
|
||||
"bodies that differ, bodies within rounding"
|
||||
);
|
||||
// the largest rounding difference, relative (#3137: 4.885e-16)
|
||||
assert!(t.rounding_largest <= 4.9e-16, "{:e}", t.rounding_largest);
|
||||
}
|
||||
}
|
||||
|
||||
/// The whole corpus (`PARITY_STRIDE` takes every nth entry; the run takes seconds in release).
|
||||
#[test]
|
||||
fn every_corpus_body_comes_back_as_the_flat_export_wrote_it() {
|
||||
parity(
|
||||
std::env::var("PARITY_STRIDE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(1),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
//! (#3185) An analytic label is kept on its NURBS form when the kernel's analytic surface,
|
||||
//! carrying the label's numbers, evaluates as the form within the rounding
|
||||
//! `AnalyticSupport::build` derives term by term (`surface::implicit`'s `evaluates_as`):
|
||||
//! [`AnalyticSupport::parametrisation_margin`] is the largest ratio of distance to bound over
|
||||
//! the form's Greville points, at most 1 where the label stands.
|
||||
//!
|
||||
//! - The corpus labels #3137's parity run refused under the old constant `gamma(16 (p + q +
|
||||
//! 2)) size`, each pinned to its margin as measured under the derived bound: the three tori
|
||||
//! of b6 and b85 (0.02 to 0.50) stand; b46's torus (1.69) and the four cylinders (b94 1.23,
|
||||
//! b43 1.40, the 2903 rims 1.52 and 2.03) do not: their frames are off their forms past
|
||||
//! the rounding at their size. Since #3199 the kernel's recognised sweep frame is the form's
|
||||
//! own (its end meridians), so a label is measured against the form, not against a fit
|
||||
//! that shared the label's error.
|
||||
//! - A torus the kernel makes at the groove's sizes stands on its own form, and the same label
|
||||
//! turned about its axis by four times what the bound allows is refused.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing,
|
||||
clippy::print_stderr,
|
||||
clippy::disallowed_methods
|
||||
)] // test (the turn's cos and sin)
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Read as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::Value;
|
||||
use solidcore::blend::fillet_section::station::as_b_spline;
|
||||
use solidcore::geom::axis2_placement::Axis2Placement;
|
||||
use solidcore::geom::extent2d::Extent2d;
|
||||
use solidcore::geom::vector3d::{Point3, Vec3};
|
||||
use solidcore::kernel::{Kernel, KernelOptions, UpstreamFixes};
|
||||
use solidcore::surface::from_parameters::from_parameters;
|
||||
use solidcore::surface::implicit::AnalyticSupport;
|
||||
use solidcore::surface::surface::{Surface, SurfaceKind};
|
||||
use solidcore::surface::torus::{Torus, TorusOptions};
|
||||
use solidcore_record::brep::Brep as Record;
|
||||
|
||||
fn corpus() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../solidcore/tests/kernel_cases")
|
||||
}
|
||||
|
||||
/// The corpus's kernel, its fixes on (as the parity run and SolidCraft run it).
|
||||
fn kernel() -> Kernel {
|
||||
let v: Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(corpus().join("fixes.json")).unwrap())
|
||||
.unwrap();
|
||||
let mut fixes = UpstreamFixes::default();
|
||||
for flag in v.as_array().unwrap() {
|
||||
*fixes.flag_mut(flag.as_str().unwrap()).unwrap() = true;
|
||||
}
|
||||
let mut k = Kernel::new(&KernelOptions::default());
|
||||
k.fixes = fixes;
|
||||
k
|
||||
}
|
||||
|
||||
fn body(case: &str, id: &str) -> Record {
|
||||
let gz = corpus().join(format!("{case}.bodies.json.gz"));
|
||||
let mut text = String::new();
|
||||
flate2::read::GzDecoder::new(std::fs::File::open(&gz).unwrap())
|
||||
.read_to_string(&mut text)
|
||||
.unwrap();
|
||||
let records: Value = serde_json::from_str(&text).unwrap();
|
||||
serde_json::from_value(records[id].clone()).unwrap()
|
||||
}
|
||||
|
||||
fn placement(f: &solidcore::eval::Frame) -> Axis2Placement {
|
||||
let v = |a: [f64; 3]| Vec3::new(a[0], a[1], a[2]);
|
||||
Axis2Placement::from_stored(
|
||||
Point3::new(f.origin[0], f.origin[1], f.origin[2]),
|
||||
v(f.x_axis),
|
||||
v(f.y_axis),
|
||||
)
|
||||
}
|
||||
|
||||
/// A corpus face's label as the kernel's support, and its NURBS form.
|
||||
fn label_and_form(
|
||||
k: &Kernel,
|
||||
case: &str,
|
||||
id: &str,
|
||||
face: usize,
|
||||
) -> (
|
||||
AnalyticSupport,
|
||||
solidcore::nurbs::b_spline_surface::BSplineSurface,
|
||||
) {
|
||||
let r = body(case, id);
|
||||
let f = &r.faces[face];
|
||||
let nurbs = solidcore_convert::surface(f.nurbs.as_ref().unwrap()).unwrap();
|
||||
let (s, _) = from_parameters(k, &nurbs, f.domain).unwrap();
|
||||
let SurfaceKind::BSpline(form) = s.kind() else {
|
||||
panic!("{case} {id} {face}: the form is not a B-spline")
|
||||
};
|
||||
let support = match solidcore_convert::surface(&f.surface).unwrap() {
|
||||
solidcore::eval::Surface::Torus {
|
||||
frame,
|
||||
major_radius,
|
||||
minor_radius,
|
||||
..
|
||||
} => AnalyticSupport::Torus {
|
||||
placement: placement(&frame),
|
||||
major_radius,
|
||||
minor_radius,
|
||||
},
|
||||
solidcore::eval::Surface::Cylinder { frame, radius } => AnalyticSupport::Cylinder {
|
||||
placement: placement(&frame),
|
||||
radius,
|
||||
},
|
||||
other => panic!("{case} {id} {face}: {other:?}"),
|
||||
};
|
||||
(support, form.copy())
|
||||
}
|
||||
|
||||
/// (case, body, face, the margin measured at #3185, whether the label stands)
|
||||
const CASES: &[(&str, &str, usize, f64, bool)] = &[
|
||||
(
|
||||
"sweeps/3897-sweep-ring-along-a-short-arc-joined-to-a-collar",
|
||||
"b6",
|
||||
1,
|
||||
0.0201,
|
||||
true,
|
||||
),
|
||||
(
|
||||
"sweeps/4280-circular-pattern-of-sweep-cut-groove-on-hub",
|
||||
"b46",
|
||||
1,
|
||||
1.6943,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"sweeps/4280-circular-pattern-of-sweep-cut-groove-on-hub",
|
||||
"b85",
|
||||
15,
|
||||
0.4995,
|
||||
true,
|
||||
),
|
||||
(
|
||||
"sweeps/4280-circular-pattern-of-sweep-cut-groove-on-hub",
|
||||
"b85",
|
||||
21,
|
||||
0.4245,
|
||||
true,
|
||||
),
|
||||
(
|
||||
"booleans/3456-deep-cut-through-drilled-taper-leaves-base",
|
||||
"b94",
|
||||
3,
|
||||
1.2339,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"crashes/2243-step-export-flanged-enclosure",
|
||||
"b43",
|
||||
1,
|
||||
1.4008,
|
||||
false,
|
||||
),
|
||||
("fillets/2903-original-rim-chamfer", "b0", 1, 1.5243, false),
|
||||
("fillets/2903-original-rim-chamfer", "b0", 3, 2.0275, false),
|
||||
("fillets/2903-original-rim-round", "b0", 1, 1.5243, false),
|
||||
("fillets/2903-original-rim-round", "b0", 3, 2.0275, false),
|
||||
];
|
||||
|
||||
/// Each corpus label's margin, pinned to 1 % of what #3185 measured, on its side of 1, and
|
||||
/// `build` agreeing with it.
|
||||
#[test]
|
||||
fn the_corpus_labels_stand_or_fall_by_the_derived_bound() {
|
||||
let k = kernel();
|
||||
let mut report = String::new();
|
||||
let mut wrong = Vec::new();
|
||||
for &(case, id, face, pinned, stands) in CASES {
|
||||
let (support, form) = label_and_form(&k, case, id, face);
|
||||
let m = support.parametrisation_margin(&k, &form).unwrap().unwrap();
|
||||
let _ = writeln!(report, "{case} {id} face {face}: margin {m:.4}");
|
||||
let built = support.build(&k, form).is_ok();
|
||||
if (m <= 1.0) != stands || built != stands || (m - pinned).abs() > 0.01 * pinned {
|
||||
wrong.push(format!(
|
||||
"{case} {id} face {face}: margin {m} (pinned {pinned}, stands {stands}), build {built}"
|
||||
));
|
||||
}
|
||||
}
|
||||
eprintln!("{report}");
|
||||
assert!(wrong.is_empty(), "{wrong:#?}");
|
||||
}
|
||||
|
||||
/// A torus at the groove's sizes (#3185's b46: centre (19.375, -33.558, 37.5), R 33.657, r 2,
|
||||
/// a 43.6 degree sweep of the whole tube), made by the kernel: its own label stands on its
|
||||
/// form; turned about its axis by four times the turn the bound allows (the margin is linear
|
||||
/// in a small turn: a turn of `t` reads `m(t)`, so `4 t / m(t)` reads 4), it is refused.
|
||||
#[test]
|
||||
fn a_torus_label_turned_past_its_bound_is_refused() {
|
||||
let k = kernel();
|
||||
let o = Point3::new(19.374_999_999_999_975, -33.558_484_396_647_01, 37.5);
|
||||
let x = Vec3::new(
|
||||
-0.427_099_277_807_218_7,
|
||||
0.739_757_649_038_078_7,
|
||||
-0.519_946_946_895_745_4,
|
||||
);
|
||||
let y = Vec3::new(
|
||||
0.259_973_473_447_872_36,
|
||||
-0.450_287_264_631_874_2,
|
||||
-0.854_198_555_614_438_5,
|
||||
);
|
||||
let domain = Extent2d::from_bounds(0.0, -180.0, 43.602_818_972_703_645, 180.0);
|
||||
let made = Torus::new(
|
||||
&k,
|
||||
o,
|
||||
x,
|
||||
y,
|
||||
domain,
|
||||
33.657_280_044_590_65,
|
||||
2.0,
|
||||
TorusOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let s = Surface::from(made);
|
||||
let form = as_b_spline(&k, &s).unwrap().unwrap().into_owned();
|
||||
let own = AnalyticSupport::of(&s).unwrap();
|
||||
let m0 = own.parametrisation_margin(&k, &form).unwrap().unwrap();
|
||||
assert!(m0 <= 1.0, "own label: {m0}");
|
||||
assert!(own.build(&k, form.copy()).is_ok());
|
||||
let turned = |t: f64| {
|
||||
let AnalyticSupport::Torus {
|
||||
placement,
|
||||
major_radius,
|
||||
minor_radius,
|
||||
} = own
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
let (c, sn) = (t.cos(), t.sin());
|
||||
let (px, py) = (placement.x_axis(), placement.y_axis());
|
||||
AnalyticSupport::Torus {
|
||||
placement: Axis2Placement::from_stored(
|
||||
placement.origin(),
|
||||
px * c + py * sn,
|
||||
py * c - px * sn,
|
||||
),
|
||||
major_radius,
|
||||
minor_radius,
|
||||
}
|
||||
};
|
||||
let probe = 1e-11;
|
||||
let m1 = turned(probe)
|
||||
.parametrisation_margin(&k, &form)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(m1 > 1.0, "a turn of {probe} rad reads {m1}");
|
||||
let four = 4.0 * probe / m1;
|
||||
let m4 = turned(four)
|
||||
.parametrisation_margin(&k, &form)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
eprintln!("own {m0:.4}; turn {probe:e}: {m1:.3}; turn {four:e}: {m4:.3}");
|
||||
assert!(m4 > 1.0, "a turn of {four:e} rad reads {m4}");
|
||||
assert!(turned(four).build(&k, form).is_err());
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//! [`Order`]'s inverse and its pieces (#3137): what SolidCraft's `history::Order` gave, read off
|
||||
//! the conversion's order. A body whose solids touch along an edge comes back with one record
|
||||
//! edge per pair of faces that edge separates: each is a piece of that kernel edge.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing
|
||||
)] // test
|
||||
|
||||
use solidcore::boolean::api_brep::boolean_union;
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::math::Point3;
|
||||
use solidcore::primitive::api_primitives::create_box;
|
||||
use solidcore_convert::brep::to_record;
|
||||
|
||||
/// Two unit boxes touching along one edge (the line x = y = 1): one body, its shared edge one
|
||||
/// kernel edge with four uses, two record edges.
|
||||
#[test]
|
||||
fn an_edge_solids_touch_along_is_two_pieces() {
|
||||
let mut k = Kernel::default();
|
||||
let a = create_box(&mut k, Point3::ZERO, 1.0, 1.0, 1.0).unwrap();
|
||||
let c = create_box(&mut k, Point3::new(1.0, 1.0, 0.0), 1.0, 1.0, 1.0).unwrap();
|
||||
let mut b = boolean_union(&mut k, a, c).unwrap();
|
||||
let (record, order) = to_record(&mut k, &mut b).unwrap();
|
||||
let pieces = order.pieces(&record).unwrap();
|
||||
assert_eq!(pieces.len(), 2, "{pieces:?}");
|
||||
assert_eq!(pieces[0].kernel, pieces[1].kernel);
|
||||
assert_ne!(pieces[0].edge, pieces[1].edge);
|
||||
for p in &pieces {
|
||||
assert_eq!(p.faces.len(), 2, "{p:?}");
|
||||
assert_eq!(order.edges[p.edge as usize], p.kernel);
|
||||
// the faces are those of that record edge's own uses
|
||||
let mut uses: Vec<u32> = record.edges[p.edge as usize]
|
||||
.uses
|
||||
.iter()
|
||||
.map(|u| u.face)
|
||||
.collect();
|
||||
uses.sort_unstable();
|
||||
uses.dedup();
|
||||
assert_eq!(p.faces, uses, "{p:?}");
|
||||
}
|
||||
// the two pieces bound four different faces, two of each box
|
||||
let mut all: Vec<u32> = pieces.iter().flat_map(|p| p.faces.clone()).collect();
|
||||
all.sort_unstable();
|
||||
all.dedup();
|
||||
assert_eq!(all.len(), 4);
|
||||
// the inverses name each entity's first record index
|
||||
for (i, f) in order.faces.iter().enumerate() {
|
||||
assert_eq!(order.record_faces()[f] as usize, i);
|
||||
}
|
||||
let first = order.record_edges()[&pieces[0].kernel];
|
||||
assert_eq!(first, pieces[0].edge.min(pieces[1].edge));
|
||||
for (i, v) in order.vertices.iter().enumerate() {
|
||||
assert!(order.record_vertices()[v] as usize <= i);
|
||||
}
|
||||
}
|
||||
|
||||
/// A body with no touching edge has no pieces.
|
||||
#[test]
|
||||
fn a_box_has_no_pieces() {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = create_box(&mut k, Point3::ZERO, 1.0, 1.0, 1.0).unwrap();
|
||||
let (record, order) = to_record(&mut k, &mut b).unwrap();
|
||||
assert!(order.pieces(&record).unwrap().is_empty());
|
||||
// one record edge per kernel edge here; in general there are at most as many kernel edges
|
||||
// as record edges (a touching edge is copied)
|
||||
assert!(order.record_edges().len() <= record.edges.len());
|
||||
assert_eq!(order.record_edges().len(), record.edges.len());
|
||||
}
|
||||
+2
-1
@@ -5,7 +5,8 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
publish = false
|
||||
rust-version.workspace = true
|
||||
# solidcore-record needs Rust 1.88 (edition 2024 let chains).
|
||||
rust-version = "1.88"
|
||||
repository = "https://github.com/tangency-labs/solidcore"
|
||||
keywords = ["cad", "brep", "nurbs", "modelling"]
|
||||
categories = ["science"]
|
||||
|
||||
+5
-7
@@ -824,13 +824,11 @@ fn overflow_first(
|
||||
}
|
||||
// a round mitred to one that goes first goes with it, through every mitre
|
||||
let place = |e: &EdgeId| edges.iter().position(|p| p == e);
|
||||
let pairs: Vec<(usize, usize)> = mitred
|
||||
// two by two (an odd last one is no pair); the chunk size 2 is not 0: cannot panic
|
||||
.chunks_exact(2)
|
||||
.filter_map(|pair| match pair {
|
||||
[a, b] => Some((place(a)?, place(b)?)),
|
||||
_ => None,
|
||||
})
|
||||
// two by two (an odd last one is no pair)
|
||||
let (mitred_pairs, _) = mitred.as_chunks::<2>();
|
||||
let pairs: Vec<(usize, usize)> = mitred_pairs
|
||||
.iter()
|
||||
.filter_map(|[a, b]| Some((place(a)?, place(b)?)))
|
||||
.collect();
|
||||
let mut grew = true;
|
||||
while grew {
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
//! Contact between bodies: whether two bodies meet (share material or touch within a
|
||||
//! tolerance) and the distance between their boundaries, each decided by the kernel's
|
||||
//! certified bracket ([`solidcore::query::contact`]). Ops passes the tolerance on as the
|
||||
//! caller gives it and reads the kernel's verdict; it computes no distance of its own.
|
||||
|
||||
use solidcore::geom::certified::Verdict;
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::query::contact::{self as c, Contact, Prepared};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
use solidcore::topology::Brep;
|
||||
use solidcore::Point3;
|
||||
|
||||
use crate::body::Body;
|
||||
use crate::session::{run_on, Answer};
|
||||
use crate::{Error, Result};
|
||||
|
||||
impl Answer for Contact {}
|
||||
impl Answer for Vec<Contact> {}
|
||||
|
||||
/// The bracket of the distance between two bodies' boundaries, at most the width asked.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Gap {
|
||||
/// No point of either boundary is nearer the other than this.
|
||||
pub lower: f64,
|
||||
/// The distance of the witness points, rounded up.
|
||||
pub upper: f64,
|
||||
/// A point on each body `upper` apart, when one is known (the first on this body).
|
||||
pub witness: Option<[[f64; 3]; 2]>,
|
||||
}
|
||||
|
||||
fn array(p: Point3) -> [f64; 3] {
|
||||
[p.x, p.y, p.z]
|
||||
}
|
||||
|
||||
/// A copy of `body`'s kernel body, the body released at once: the contact test takes its
|
||||
/// operands mutably (for its classifications' caches) and never edits the caller's, and a
|
||||
/// body tested against itself is locked only once at a time.
|
||||
fn copy_of(k: &mut Kernel, body: &Body) -> solidcore::Result<Brep> {
|
||||
let b = body.lock();
|
||||
brep_copy(k, &b)
|
||||
}
|
||||
|
||||
/// The verdict as an answer: `Yes` meets, `No` does not, `Undecided` refused with the
|
||||
/// bracket, which names the tolerance that would decide.
|
||||
fn decided(contact: &Contact, other: usize) -> Result<bool> {
|
||||
match contact.verdict {
|
||||
Verdict::Yes => Ok(true),
|
||||
Verdict::No => Ok(false),
|
||||
Verdict::Undecided => Err(Error::ContactUndecided {
|
||||
other,
|
||||
lower: contact.lower,
|
||||
upper: contact.upper,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn poisoned(bodies: &[&Body]) -> Result<()> {
|
||||
if bodies.iter().any(|b| b.is_poisoned()) {
|
||||
return Err(Error::Internal(
|
||||
"contact: a body was left unfinished by an earlier panic".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Body {
|
||||
/// Whether this body and `other` meet at `tol` (the caller's: ops invents none): share
|
||||
/// material, or have boundaries within `tol` of each other
|
||||
/// ([`solidcore::query::contact::meets`]). Neither body changes; `other` may be this body.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`Error::ContactUndecided`] when the kernel's bracket does not decide it (`other` 0):
|
||||
/// the bracket, never read as either answer; [`Error::Status`] `SM_ERR_INVALID_INPUT` for a
|
||||
/// tolerance not finite and positive or a body without faces; those of the session.
|
||||
pub fn meets(&self, other: &Body, tol: f64) -> Result<bool> {
|
||||
poisoned(&[self, other])?;
|
||||
let contact = run_on("contact::meets", &[("a", self), ("b", other)], |k| {
|
||||
let mut a = copy_of(k, self)?;
|
||||
let mut b = copy_of(k, other)?;
|
||||
c::meets(k, &mut a, &mut b, tol)
|
||||
})?;
|
||||
decided(&contact, 0)
|
||||
}
|
||||
|
||||
/// [`Body::meets`] against each of `others`, each with its own tolerance, this body
|
||||
/// prepared once (its faces' pieces about the centre of its box,
|
||||
/// [`Prepared::about`]).
|
||||
///
|
||||
/// # Errors
|
||||
/// [`Error::ContactUndecided`] for the first of `others` the kernel's bracket does not
|
||||
/// decide, by its index; those of [`Body::meets`].
|
||||
pub fn meets_each(&self, others: &[(&Body, f64)]) -> Result<Vec<bool>> {
|
||||
let mut bodies: Vec<&Body> = vec![self];
|
||||
bodies.extend(others.iter().map(|(b, _)| *b));
|
||||
poisoned(&bodies)?;
|
||||
let inputs: Vec<(&'static str, &Body)> = bodies.iter().map(|b| ("brep", *b)).collect();
|
||||
let contacts = run_on("contact::meets_each", &inputs, |k| {
|
||||
let mut a = copy_of(k, self)?;
|
||||
let prepared = Prepared::about(k, &a)?;
|
||||
let mut contacts = Vec::new();
|
||||
for (other, tol) in others {
|
||||
let mut b = copy_of(k, other)?;
|
||||
contacts.push(c::meets_prepared(k, &mut a, &prepared, &mut b, *tol)?);
|
||||
}
|
||||
Ok(contacts)
|
||||
})?;
|
||||
contacts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, contact)| decided(contact, i))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The distance between this body's boundary and `other`'s, bracketed to at most `width`
|
||||
/// (relative to its upper end, absolute below 1;
|
||||
/// [`solidcore::query::contact::boundary_distance`]).
|
||||
///
|
||||
/// # Errors
|
||||
/// [`Error::ContactUndecided`] (`other` 0) with the narrowest bracket reached, when the
|
||||
/// kernel's work runs out before `width`; [`Error::Status`] `SM_ERR_INVALID_INPUT` for a
|
||||
/// width not finite and positive or a body without faces; those of the session.
|
||||
pub fn boundary_distance(&self, other: &Body, width: f64) -> Result<Gap> {
|
||||
poisoned(&[self, other])?;
|
||||
let contact = run_on(
|
||||
"contact::boundary_distance",
|
||||
&[("a", self), ("b", other)],
|
||||
|k| {
|
||||
let mut a = copy_of(k, self)?;
|
||||
let mut b = copy_of(k, other)?;
|
||||
c::boundary_distance(k, &mut a, &mut b, width)
|
||||
},
|
||||
)?;
|
||||
decided(&contact, 0)?;
|
||||
Ok(Gap {
|
||||
lower: contact.lower,
|
||||
upper: contact.upper,
|
||||
witness: contact.witness.map(|[p, q]| [array(p), array(q)]),
|
||||
})
|
||||
}
|
||||
}
|
||||
+21
@@ -134,6 +134,19 @@ pub enum Error {
|
||||
/// The kernel found its own state inconsistent part way through a call, or was called
|
||||
/// before the preparation it needs.
|
||||
Exception,
|
||||
/// A contact test the kernel's certified bracket of the boundary distance does not decide
|
||||
/// (`solidcore::query::contact`): the tolerance lies inside `[lower, upper]` (or the
|
||||
/// boundaries are apart but one body's enclosure of the other is not decided), or a
|
||||
/// distance's bracket is still wider than asked. Never read as either answer. `other` is
|
||||
/// the index of the body tested among the call's others.
|
||||
ContactUndecided {
|
||||
/// The other body's index among the call's (0 for one).
|
||||
other: usize,
|
||||
/// No point of either boundary is nearer the other than this.
|
||||
lower: f64,
|
||||
/// A pair of points, one on each boundary, is this far apart.
|
||||
upper: f64,
|
||||
},
|
||||
/// The application cancelled the call.
|
||||
Cancelled,
|
||||
/// Ops was called from inside a kernel call or from the failure callback.
|
||||
@@ -325,6 +338,14 @@ impl std::fmt::Display for Error {
|
||||
),
|
||||
Error::TooFar { largest } => write!(f, "too far to extend (largest {largest:?})"),
|
||||
Error::Exception => write!(f, "solidcore raised an internal exception"),
|
||||
Error::ContactUndecided {
|
||||
other,
|
||||
lower,
|
||||
upper,
|
||||
} => write!(
|
||||
f,
|
||||
"contact with body {other} is undecided: the boundary distance is in [{lower}, {upper}]"
|
||||
),
|
||||
Error::Cancelled => write!(f, "cancelled"),
|
||||
Error::Reentered => write!(
|
||||
f,
|
||||
|
||||
+9
-2
@@ -19,8 +19,8 @@
|
||||
//! - [`provenance`]: the hook an application's construction history rides on.
|
||||
//!
|
||||
//! The families: [`measure`] (mass properties, area, box, point class, gap samples, the picks'
|
||||
//! distances and normals), [`boolean`], [`stitch`], [`prism`] (planar region booleans and
|
||||
//! extrusion, prism sections).
|
||||
//! distances and normals), [`contact`] (whether bodies meet, their boundary distance),
|
||||
//! [`boolean`], [`stitch`], [`prism`] (planar region booleans and extrusion, prism sections).
|
||||
//!
|
||||
//! Operations change the body they are given, as the kernel does. An application that keeps
|
||||
//! its input copies it first ([`Body::copy`]).
|
||||
@@ -32,6 +32,7 @@ pub mod blend;
|
||||
pub mod body;
|
||||
pub mod boolean;
|
||||
pub mod check;
|
||||
pub mod contact;
|
||||
pub mod error;
|
||||
pub mod extend;
|
||||
pub mod file;
|
||||
@@ -42,8 +43,10 @@ pub mod prism;
|
||||
pub mod provenance;
|
||||
pub mod session;
|
||||
pub mod split;
|
||||
pub mod step_assembly;
|
||||
pub mod stitch;
|
||||
pub mod sweep;
|
||||
pub mod tangent;
|
||||
pub mod untrim;
|
||||
|
||||
pub use blend::{
|
||||
@@ -54,6 +57,7 @@ pub use blend::{
|
||||
pub use body::{make_box, make_cone, make_cylinder, make_sphere, make_torus, Body};
|
||||
pub use boolean::BooleanOp;
|
||||
pub use check::{CheckLevel, CheckReport};
|
||||
pub use contact::Gap;
|
||||
pub use error::{Error, FitKind, Result, RoomReason, Unblendable};
|
||||
pub use measure::{
|
||||
point_distance, Distance, EdgePoint, FaceCentroid, GapSample, MassPropertiesEstimate, Normal,
|
||||
@@ -61,7 +65,10 @@ pub use measure::{
|
||||
};
|
||||
pub use offset::{draft, offset_faces, offset_sheet, shell, thicken};
|
||||
pub use order::Order;
|
||||
pub use step_assembly::{placed_bodies, PlacedBody};
|
||||
|
||||
#[cfg(test)]
|
||||
mod noisy_round_tests;
|
||||
#[cfg(test)]
|
||||
mod test_corpus;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
//! #3066: a round on supports read from a record with rounding in their frames keeps its exact
|
||||
//! definition. The class is decided within the body's tolerance; the round lies on a torus of
|
||||
//! its own, its net certified there at bound 0, and that torus's distance from the class's
|
||||
//! torus from the supports is held to the body's tolerance, so the round answers
|
||||
//! `procedural_blend()`.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing,
|
||||
clippy::disallowed_methods
|
||||
)] // test
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use solidcore::surface::surface::SurfaceKind;
|
||||
use solidcore::topology::face::p2 as face;
|
||||
use solidcore_record::brep::Brep as Record;
|
||||
|
||||
use crate::blend::{blend, Blend, Corners};
|
||||
use crate::boolean::boolean;
|
||||
use crate::{make_box, make_cylinder, session, Body, BooleanOp, CheckLevel};
|
||||
|
||||
fn fixes() {
|
||||
let fixes: Vec<String> = serde_json::from_str(include_str!(
|
||||
"../../solidcore/tests/kernel_cases/fixes.json"
|
||||
))
|
||||
.unwrap();
|
||||
session::set_fixes(fixes.iter().map(String::as_str)).unwrap();
|
||||
}
|
||||
|
||||
/// The faces of `b` that are no plane, as (with a definition, without one).
|
||||
fn definitions(b: &Body) -> (usize, usize) {
|
||||
let brep = b.lock();
|
||||
let (mut with, mut without) = (0, 0);
|
||||
for &f in brep.face_list() {
|
||||
let s = face::surface(&brep, f).unwrap().unwrap();
|
||||
if matches!(s.kind(), SurfaceKind::Plane(_)) {
|
||||
continue;
|
||||
}
|
||||
if s.procedural_blend().is_some() {
|
||||
with += 1;
|
||||
} else {
|
||||
without += 1;
|
||||
}
|
||||
}
|
||||
(with, without)
|
||||
}
|
||||
|
||||
/// The edge of `b` through `p`.
|
||||
fn edge_through(b: &Body, p: [f64; 3]) -> usize {
|
||||
let k = solidcore::Kernel::default();
|
||||
let brep = b.lock();
|
||||
let q = solidcore::Vec3::new(p[0], p[1], p[2]);
|
||||
brep.edge_list()
|
||||
.iter()
|
||||
.position(|&e| {
|
||||
solidcore::query::api_queries::edge_closest_point(&k, &brep, e, q)
|
||||
.unwrap()
|
||||
.closest
|
||||
.distance
|
||||
< 1e-6
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// A boss R 8 standing 10 out of a 40 x 40 x 10 block, its axis turned 1e-10 off square to the
|
||||
/// block's top, its root rounded 2: a circle-class round on supports only that close to the
|
||||
/// class (the error the 1e-10 turn makes over the part is far inside the body's tolerance, and
|
||||
/// far outside the rounding the net is certified to). The round is concave: the corner between
|
||||
/// the wall and the top, less the quarter disc, turned about the axis. By Pappus, it adds
|
||||
/// 2 pi (R + x) r^2 (1 - pi / 4), x = r (10 - 3 pi) / (12 - 3 pi) the corner's centroid from the
|
||||
/// wall.
|
||||
#[test]
|
||||
fn a_round_on_a_boss_tilted_off_the_class_keeps_its_definition() {
|
||||
fixes();
|
||||
let block = make_box([0.0; 3], [40.0, 40.0, 10.0]).unwrap();
|
||||
// the boss from z = 5 to 20, its axis turned 1e-10 about x off square to the block's top
|
||||
let boss = make_cylinder([0.0; 3], 8.0, 15.0).unwrap();
|
||||
let (c, s) = (1e-10_f64.cos(), 1e-10_f64.sin());
|
||||
boss.transform(&[
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, c, s, 0.0],
|
||||
[0.0, -s, c, 0.0],
|
||||
[20.0, 20.0, 5.0, 1.0],
|
||||
])
|
||||
.unwrap();
|
||||
let b = boolean(block, boss, BooleanOp::Union).unwrap().unwrap();
|
||||
let e = edge_through(&b, [28.0, 20.0, 10.0]);
|
||||
let (r, big_r) = (2.0, 8.0);
|
||||
blend(&b, Blend::Round, &[e], &[r], &[], Corners::Filled).unwrap();
|
||||
let report = b.check(CheckLevel::All).unwrap();
|
||||
assert!(report.is_valid(), "{}", report.text);
|
||||
// the boss's wall and the round: the round keeps its definition
|
||||
assert_eq!(definitions(&b), (1, 1));
|
||||
let x = r * (10.0 - 3.0 * PI) / (12.0 - 3.0 * PI);
|
||||
let want =
|
||||
16000.0 + PI * big_r * big_r * 10.0 + 2.0 * PI * (big_r + x) * r * r * (1.0 - PI / 4.0);
|
||||
let got = b.volume(1e-10).unwrap();
|
||||
assert!((got - want).abs() <= 1e-9 * want, "{got} {want}");
|
||||
}
|
||||
|
||||
/// SolidCraft's raked wedge (`tests/data/raked_wedge_noisy_cylinders.json`): its end outline's
|
||||
/// rounds keep their definitions, the two circle-class rounds on the read-back cylinders among
|
||||
/// them; the operand's own faces, read from the record, have none.
|
||||
#[test]
|
||||
fn the_raked_wedges_rounds_keep_their_definitions() {
|
||||
fixes();
|
||||
let record: Record = serde_json::from_str(include_str!(
|
||||
"../tests/data/raked_wedge_noisy_cylinders.json"
|
||||
))
|
||||
.unwrap();
|
||||
let (b, _) = Body::from_record(&record, true).unwrap();
|
||||
let (_, before) = definitions(&b);
|
||||
let top = 60.0 - 60.0 * 10f64.to_radians().tan();
|
||||
let mut seeds = Vec::new();
|
||||
for x in [-69.0, 69.0] {
|
||||
for p in [
|
||||
[x, -41.0, 22.0],
|
||||
[x, 8.0, 41.0],
|
||||
[x, (38.0 + top) / 2.0, 60.0],
|
||||
] {
|
||||
seeds.push(edge_through(&b, p));
|
||||
}
|
||||
}
|
||||
let chain = crate::blend::tangent_chain(&b, &seeds, 0.5_f64.to_radians()).unwrap();
|
||||
blend(&b, Blend::Round, &chain, &[0.854], &[], Corners::Filled).unwrap();
|
||||
let report = b.check(CheckLevel::All).unwrap();
|
||||
assert!(report.is_valid(), "{}", report.text);
|
||||
let (with, without) = definitions(&b);
|
||||
// as many of the new rounds keep their definitions as on the same wedge with exact
|
||||
// cylinders, and the rest are as bare there
|
||||
let (exact_with, exact_bare) = exact_wedge_rounds();
|
||||
assert_eq!((with, without - before), (exact_with, exact_bare));
|
||||
assert_eq!(with + without - before, chain.len());
|
||||
}
|
||||
|
||||
/// The same wedge built here, its cylinders exact, its end outline rounded as SolidCraft's: of the
|
||||
/// rounds that last call makes, how many keep a definition and how many don't.
|
||||
fn exact_wedge_rounds() -> (usize, usize) {
|
||||
use crate::sweep::{extrude, Curve, DraftCorner};
|
||||
let top = 60.0 - 60.0 * 10f64.to_radians().tan();
|
||||
let profile = [
|
||||
[-60.0, 0.0],
|
||||
[60.0, 0.0],
|
||||
[top, 60.0],
|
||||
[38.0, 60.0],
|
||||
[-22.0, 22.0],
|
||||
[-60.0, 22.0],
|
||||
];
|
||||
let at = |p: [f64; 2]| [-69.0, p[0], p[1]];
|
||||
let curves: Vec<Curve> = (0..6)
|
||||
.map(|i| Curve::Line {
|
||||
start: at(profile[i]),
|
||||
end: at(profile[(i + 1) % 6]),
|
||||
})
|
||||
.collect();
|
||||
let b = extrude(
|
||||
&curves,
|
||||
[1.0, 0.0, 0.0],
|
||||
138.0,
|
||||
0.0,
|
||||
DraftCorner::Curvature,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let round = |pts: &[[f64; 3]], r: f64, chain: bool| {
|
||||
let seeds: Vec<usize> = pts.iter().map(|&p| edge_through(&b, p)).collect();
|
||||
let edges = if chain {
|
||||
crate::blend::tangent_chain(&b, &seeds, 0.5_f64.to_radians()).unwrap()
|
||||
} else {
|
||||
seeds
|
||||
};
|
||||
blend(&b, Blend::Round, &edges, &[r], &[], Corners::Filled).unwrap();
|
||||
};
|
||||
round(&[[0.0, -60.0, 22.0]], 1.2, true);
|
||||
round(&[[0.0, -22.0, 22.0]], 1.5, true);
|
||||
round(&[[0.0, 38.0, 60.0]], 1.5, true);
|
||||
let back = (60.0 + top) / 2.0;
|
||||
let four = [
|
||||
[-69.0, -60.0, 12.0],
|
||||
[69.0, -60.0, 12.0],
|
||||
[-69.0, back, 30.0],
|
||||
[69.0, back, 30.0],
|
||||
];
|
||||
round(&four, 4.0, false);
|
||||
let (with_before, bare_before) = definitions(&b);
|
||||
let mut ends = Vec::new();
|
||||
for x in [-69.0, 69.0] {
|
||||
ends.extend([
|
||||
[x, -41.0, 22.0],
|
||||
[x, 8.0, 41.0],
|
||||
[x, (38.0 + top) / 2.0, 60.0],
|
||||
]);
|
||||
}
|
||||
round(&ends, 0.854, true);
|
||||
let (with, bare) = definitions(&b);
|
||||
(with - with_before, bare - bare_before)
|
||||
}
|
||||
+467
@@ -0,0 +1,467 @@
|
||||
//! Compose a STEP product structure into placed body instances.
|
||||
//!
|
||||
//! The record reader keeps the placements exactly as the file writes them: assembly
|
||||
//! occurrences, `MAPPED_ITEM` targets, representation maps and transformation operators.
|
||||
//! This module turns those raw placements into kernel matrices, walks the product tree and
|
||||
//! returns every body with the transform that places it in the file's world frame.
|
||||
//!
|
||||
//! It computes no geometry of its own: frame and operator matrices, their products and
|
||||
//! inverses come from `solidcore::geom::step_matrix`. Body construction is the caller's:
|
||||
//! each returned [`PlacedBody::entity`] is a STEP entity id and every occurrence of a shared
|
||||
//! body is emitted separately with its own transform.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use solidcore::geom::step_matrix::{
|
||||
concat, frame_matrix, inverse, operator_matrix, Matrix4, IDENTITY,
|
||||
};
|
||||
use solidcore_record::step::geom::Placement;
|
||||
use solidcore_record::step::product::{
|
||||
Frame, ItemKind, Link, Occurrence, ProductStructure, Transformation, MAX_ASSEMBLY_DEPTH,
|
||||
MAX_VISITS,
|
||||
};
|
||||
|
||||
/// One body as it appears in the expanded product structure.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PlacedBody {
|
||||
/// The STEP entity id of the body (`MANIFOLD_SOLID_BREP`, …).
|
||||
pub entity: u64,
|
||||
/// The body's placement in the file's world frame, in millimetres.
|
||||
pub transform: Matrix4,
|
||||
}
|
||||
|
||||
struct Walk<'a> {
|
||||
structure: &'a ProductStructure,
|
||||
/// Representations reachable from each definition's own shapes through plain
|
||||
/// representation relationships.
|
||||
closure: Vec<Vec<usize>>,
|
||||
/// Child occurrences of each definition, in file order.
|
||||
children: Vec<Vec<usize>>,
|
||||
/// The definitions on the current path, to refuse a cycle.
|
||||
path: Vec<usize>,
|
||||
visits: usize,
|
||||
out: Vec<PlacedBody>,
|
||||
}
|
||||
|
||||
/// Expand the product structure and return every body with its world placement.
|
||||
///
|
||||
/// A root definition is placed by the identity. Each occurrence multiplies the child's
|
||||
/// placement by the occurrence transformation, and each mapped item places the mapped
|
||||
/// representation by `target · origin⁻¹`. Refuses cycles and past the record's depth or
|
||||
/// instance bounds.
|
||||
pub fn placed_bodies(structure: &ProductStructure) -> Result<Vec<PlacedBody>, String> {
|
||||
let mut children = vec![Vec::new(); structure.definitions.len()];
|
||||
for (index, occurrence) in structure.occurrences.iter().enumerate() {
|
||||
if let Some(list) = children.get_mut(occurrence.parent) {
|
||||
list.push(index);
|
||||
}
|
||||
}
|
||||
let closure = (0..structure.definitions.len())
|
||||
.map(|definition| representation_closure(structure, definition))
|
||||
.collect();
|
||||
let mut walk = Walk {
|
||||
structure,
|
||||
closure,
|
||||
children,
|
||||
path: Vec::new(),
|
||||
visits: 0,
|
||||
out: Vec::new(),
|
||||
};
|
||||
for &root in &structure.roots {
|
||||
walk.definition(root, IDENTITY, 0)?;
|
||||
}
|
||||
Ok(walk.out)
|
||||
}
|
||||
|
||||
fn representation_closure(structure: &ProductStructure, definition: usize) -> Vec<usize> {
|
||||
let Some(definition) = structure.definitions.get(definition) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut seen = BTreeSet::new();
|
||||
let mut out = Vec::new();
|
||||
let mut stack = definition.shapes.clone();
|
||||
while let Some(representation) = stack.pop() {
|
||||
if !seen.insert(representation) {
|
||||
continue;
|
||||
}
|
||||
out.push(representation);
|
||||
for link in &structure.links {
|
||||
if link.transformation.is_some() {
|
||||
continue;
|
||||
}
|
||||
if link.rep_1 == representation {
|
||||
stack.push(link.rep_2);
|
||||
} else if link.rep_2 == representation {
|
||||
stack.push(link.rep_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
impl Walk<'_> {
|
||||
fn definition(
|
||||
&mut self,
|
||||
definition: usize,
|
||||
world: Matrix4,
|
||||
depth: usize,
|
||||
) -> Result<(), String> {
|
||||
if depth > MAX_ASSEMBLY_DEPTH {
|
||||
return Err("assembly nested too deeply".into());
|
||||
}
|
||||
if self.path.contains(&definition) {
|
||||
return Err("assembly cycle".into());
|
||||
}
|
||||
self.visits += 1;
|
||||
if self.visits > MAX_VISITS {
|
||||
return Err(format!("more than {MAX_VISITS} assembly instances"));
|
||||
}
|
||||
self.path.push(definition);
|
||||
let reps = self.closure.get(definition).cloned().unwrap_or_default();
|
||||
let mut walked = BTreeSet::new();
|
||||
for representation in reps {
|
||||
self.representation(representation, world, &mut walked, depth)?;
|
||||
}
|
||||
let child_definitions: Vec<usize> =
|
||||
self.children.get(definition).cloned().unwrap_or_default();
|
||||
for occurrence_index in child_definitions {
|
||||
let Some(occurrence) = self.structure.occurrences.get(occurrence_index) else {
|
||||
continue;
|
||||
};
|
||||
let child_reps = self
|
||||
.closure
|
||||
.get(occurrence.child)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let local = occurrence_matrix(self.structure, occurrence, &child_reps)?;
|
||||
self.definition(occurrence.child, concat(&world, &local), depth + 1)?;
|
||||
}
|
||||
self.path.pop();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn representation(
|
||||
&mut self,
|
||||
representation: usize,
|
||||
world: Matrix4,
|
||||
walked: &mut BTreeSet<usize>,
|
||||
depth: usize,
|
||||
) -> Result<(), String> {
|
||||
if depth > MAX_ASSEMBLY_DEPTH {
|
||||
return Err("mapped items nested too deeply".into());
|
||||
}
|
||||
if !walked.insert(representation) {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(rep) = self.structure.representations.get(representation) else {
|
||||
return Err(format!("missing representation #{representation}"));
|
||||
};
|
||||
for item in &rep.items {
|
||||
match &item.kind {
|
||||
ItemKind::Body(_) => self.out.push(PlacedBody {
|
||||
entity: item.entity,
|
||||
transform: world,
|
||||
}),
|
||||
ItemKind::Mapped { map, target } => {
|
||||
let Some(map) = self.structure.maps.get(*map) else {
|
||||
return Err(format!("missing representation map #{}", item.entity));
|
||||
};
|
||||
let target = frame(target)?;
|
||||
let origin = frame(&map.origin)?;
|
||||
let origin_inverse = inverse(&origin)
|
||||
.ok_or_else(|| format!("singular map origin #{}", map.entity))?;
|
||||
let local = concat(&target, &origin_inverse);
|
||||
let placed = concat(&world, &local);
|
||||
let mapped = map.representation;
|
||||
let mut mapped_walked = BTreeSet::new();
|
||||
self.representation(mapped, placed, &mut mapped_walked, depth + 1)?;
|
||||
for link in &self.structure.links {
|
||||
if link.transformation.is_some() {
|
||||
continue;
|
||||
}
|
||||
let other = if link.rep_1 == mapped {
|
||||
Some(link.rep_2)
|
||||
} else if link.rep_2 == mapped {
|
||||
Some(link.rep_1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(other) = other {
|
||||
self.representation(other, placed, &mut mapped_walked, depth + 1)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
ItemKind::Other(_) => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn occurrence_matrix(
|
||||
structure: &ProductStructure,
|
||||
occurrence: &Occurrence,
|
||||
child_reps: &[usize],
|
||||
) -> Result<Matrix4, String> {
|
||||
let Some(&link_index) = occurrence.placements.first() else {
|
||||
return Ok(IDENTITY);
|
||||
};
|
||||
let Some(link) = structure.links.get(link_index) else {
|
||||
return Err(format!("missing placement link #{link_index}"));
|
||||
};
|
||||
link_matrix(link, child_reps)
|
||||
}
|
||||
|
||||
fn link_matrix(link: &Link, child_reps: &[usize]) -> Result<Matrix4, String> {
|
||||
let Some(transformation) = &link.transformation else {
|
||||
return Ok(IDENTITY);
|
||||
};
|
||||
match transformation {
|
||||
Transformation::Frame(frame_value) => frame(frame_value),
|
||||
Transformation::Items { item_1, item_2, .. } => {
|
||||
let first = frame(item_1)?;
|
||||
let second = frame(item_2)?;
|
||||
let child_is_first =
|
||||
child_reps.contains(&link.rep_1) || !child_reps.contains(&link.rep_2);
|
||||
// Row-vector convention: a child in rep_1 is placed by
|
||||
// `item_1⁻¹ · item_2`; a child in rep_2 by `item_2⁻¹ · item_1`.
|
||||
let (from, to) = if child_is_first {
|
||||
(&first, &second)
|
||||
} else {
|
||||
(&second, &first)
|
||||
};
|
||||
let inverse_from = inverse(from)
|
||||
.ok_or_else(|| format!("singular occurrence placement #{}", link.entity))?;
|
||||
Ok(concat(&inverse_from, to))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(value: &Frame) -> Result<Matrix4, String> {
|
||||
match value {
|
||||
Frame::Placement(Placement {
|
||||
origin,
|
||||
axis,
|
||||
ref_direction,
|
||||
}) => frame_matrix(*origin, *axis, *ref_direction)
|
||||
.ok_or_else(|| "degenerate placement".to_string()),
|
||||
Frame::Operator(operator) => operator_matrix(
|
||||
operator.axis1,
|
||||
operator.axis2,
|
||||
operator.axis3,
|
||||
operator.origin,
|
||||
operator.scale,
|
||||
operator.scale2,
|
||||
operator.scale3,
|
||||
)
|
||||
.ok_or_else(|| "degenerate transformation operator".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solidcore_record::step::geom::Placement;
|
||||
use solidcore_record::step::product::{
|
||||
BodyKind, Definition, Item, OccurrenceKind, Representation,
|
||||
};
|
||||
use solidcore_record::step::units::{Context, Units};
|
||||
|
||||
fn translation(x: f64, y: f64, z: f64) -> Frame {
|
||||
Frame::Placement(Placement {
|
||||
origin: [x, y, z],
|
||||
axis: None,
|
||||
ref_direction: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn body(entity: u64) -> Item {
|
||||
Item {
|
||||
entity,
|
||||
name: String::new(),
|
||||
kind: ItemKind::Body(BodyKind::ManifoldSolidBrep),
|
||||
}
|
||||
}
|
||||
|
||||
fn representation(entity: u64, items: Vec<Item>) -> Representation {
|
||||
Representation {
|
||||
entity,
|
||||
kind: "SHAPE_REPRESENTATION".into(),
|
||||
name: String::new(),
|
||||
items,
|
||||
context: None,
|
||||
units: Context {
|
||||
units: Units { len: 1.0, ang: 1.0 },
|
||||
uncertainty: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn definition(entity: u64, shape: usize) -> Definition {
|
||||
Definition {
|
||||
entity,
|
||||
id: format!("def{entity}"),
|
||||
description: String::new(),
|
||||
formation: 0,
|
||||
product: 0,
|
||||
shapes: vec![shape],
|
||||
}
|
||||
}
|
||||
|
||||
fn structure() -> ProductStructure {
|
||||
ProductStructure {
|
||||
products: Vec::new(),
|
||||
definitions: vec![definition(1, 0), definition(2, 1)],
|
||||
representations: vec![
|
||||
representation(10, Vec::new()),
|
||||
representation(20, vec![body(200)]),
|
||||
],
|
||||
maps: Vec::new(),
|
||||
links: Vec::new(),
|
||||
occurrences: Vec::new(),
|
||||
roots: vec![0],
|
||||
styles: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_placed_child_body_is_transformed_by_its_occurrence() {
|
||||
let mut structure = structure();
|
||||
structure.representations[0] = representation(10, Vec::new());
|
||||
structure.occurrences.push(Occurrence {
|
||||
entity: 30,
|
||||
kind: OccurrenceKind::NextAssemblyUsage,
|
||||
id: "occ".into(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
parent: 0,
|
||||
child: 1,
|
||||
reference_designator: None,
|
||||
placements: vec![0],
|
||||
});
|
||||
structure.links.push(Link {
|
||||
entity: 40,
|
||||
rep_1: 1,
|
||||
rep_2: 0,
|
||||
transformation: Some(Transformation::Items {
|
||||
entity: 41,
|
||||
item_1: translation(0.0, 0.0, 0.0),
|
||||
item_2: translation(2.0, 3.0, 0.0),
|
||||
}),
|
||||
});
|
||||
let placed = placed_bodies(&structure).unwrap();
|
||||
assert_eq!(placed.len(), 1);
|
||||
assert_eq!(placed[0].entity, 200);
|
||||
assert_eq!(
|
||||
placed[0].transform,
|
||||
[
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[2.0, 3.0, 0.0, 1.0],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mapped_items_place_the_map_origin_onto_their_target() {
|
||||
let mut structure = structure();
|
||||
structure.representations[0] = representation(10, Vec::new());
|
||||
structure.representations[0].items.push(Item {
|
||||
entity: 100,
|
||||
name: String::new(),
|
||||
kind: ItemKind::Mapped {
|
||||
map: 0,
|
||||
target: translation(5.0, 0.0, 0.0),
|
||||
},
|
||||
});
|
||||
structure
|
||||
.maps
|
||||
.push(solidcore_record::step::product::RepresentationMap {
|
||||
entity: 50,
|
||||
origin: translation(1.0, 0.0, 0.0),
|
||||
representation: 1,
|
||||
});
|
||||
let placed = placed_bodies(&structure).unwrap();
|
||||
assert_eq!(placed.len(), 1);
|
||||
assert_eq!(placed[0].transform[3], [4.0, 0.0, 0.0, 1.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_occurrence_placements_compose() {
|
||||
let mut structure = structure();
|
||||
structure.definitions.push(definition(3, 2));
|
||||
structure
|
||||
.representations
|
||||
.push(representation(30, vec![body(300)]));
|
||||
structure.occurrences.extend([
|
||||
Occurrence {
|
||||
entity: 31,
|
||||
kind: OccurrenceKind::NextAssemblyUsage,
|
||||
id: "a".into(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
parent: 0,
|
||||
child: 1,
|
||||
reference_designator: None,
|
||||
placements: vec![0],
|
||||
},
|
||||
Occurrence {
|
||||
entity: 32,
|
||||
kind: OccurrenceKind::NextAssemblyUsage,
|
||||
id: "b".into(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
parent: 1,
|
||||
child: 2,
|
||||
reference_designator: None,
|
||||
placements: vec![1],
|
||||
},
|
||||
]);
|
||||
structure.links.extend([
|
||||
Link {
|
||||
entity: 40,
|
||||
rep_1: 1,
|
||||
rep_2: 0,
|
||||
transformation: Some(Transformation::Items {
|
||||
entity: 41,
|
||||
item_1: translation(0.0, 0.0, 0.0),
|
||||
item_2: translation(1.0, 0.0, 0.0),
|
||||
}),
|
||||
},
|
||||
Link {
|
||||
entity: 42,
|
||||
rep_1: 2,
|
||||
rep_2: 1,
|
||||
transformation: Some(Transformation::Items {
|
||||
entity: 43,
|
||||
item_1: translation(0.0, 0.0, 0.0),
|
||||
item_2: translation(0.0, 2.0, 0.0),
|
||||
}),
|
||||
},
|
||||
]);
|
||||
let placed = placed_bodies(&structure).unwrap();
|
||||
let grandchild = placed
|
||||
.iter()
|
||||
.find(|body| body.entity == 300)
|
||||
.expect("grandchild body");
|
||||
assert_eq!(grandchild.transform[3], [1.0, 2.0, 0.0, 1.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cycle_is_refused() {
|
||||
let mut structure = structure();
|
||||
structure.occurrences.push(Occurrence {
|
||||
entity: 30,
|
||||
kind: OccurrenceKind::NextAssemblyUsage,
|
||||
id: "occ".into(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
parent: 0,
|
||||
child: 0,
|
||||
reference_designator: None,
|
||||
placements: Vec::new(),
|
||||
});
|
||||
assert!(placed_bodies(&structure).is_err());
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
//! Tangent chains on record order: the edges a pick runs on into tangentially, and the faces
|
||||
//! joined to picked faces across smooth edges. The walks are here; every measure they stop
|
||||
//! on is the kernel's: whether an edge's faces meet tangentially all along it
|
||||
//! ([`solidcore::query::tangent_edge`]), how far one edge turns from running straight on into
|
||||
//! another at a vertex and whether an edge is convex there ([`solidcore::query::edge_turn`]).
|
||||
//!
|
||||
//! The caller's `angle` (radians) is the one tolerance: edges meeting at less than it continue
|
||||
//! one another, and faces meeting at less than it all along an edge make it smooth. Nothing
|
||||
//! here picks one (SolidCraft passes its shaded view's half a degree).
|
||||
//!
|
||||
//! - [`tangent_chain_ordered`]: the picked edges, then every edge that continues one of them
|
||||
//! within `angle` at a shared vertex, and so on. An edge does not join when it is not
|
||||
//! between two faces (a one-face edge), when it is smooth (its faces meet tangentially all
|
||||
//! along it, a seam among them), or when it turns over (convex where the edge it continues
|
||||
//! is concave, or the reverse).
|
||||
//! - [`tangent_face_chain_ordered`]: the picked faces, then every face across a smooth edge
|
||||
//! from one of them, and so on.
|
||||
//!
|
||||
//! An edge whose smoothness the kernel leaves undecided (a blend's net within its margin of
|
||||
//! the angle, [`solidcore::query::tangent_edge::Tangency::Undecided`]) joins nothing and is
|
||||
//! named in the answer.
|
||||
|
||||
use solidcore::query::edge_turn::{convex_at, turn_at};
|
||||
use solidcore::query::tangent_edge::{tangent_all_along, Tangency};
|
||||
use solidcore::topology::edge;
|
||||
use solidcore::{Brep, EdgeId, FaceId, Kernel};
|
||||
|
||||
use crate::session::with_body;
|
||||
use crate::{Body, Error, Order, Result};
|
||||
|
||||
/// What [`tangent_chain_ordered`] and [`tangent_face_chain_ordered`] answer, in record
|
||||
/// indices.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Chain {
|
||||
/// The picks (each once, in order), then what joined them, in the order it joined.
|
||||
pub members: Vec<usize>,
|
||||
/// Edges the walk met whose smoothness the kernel left undecided: they joined nothing.
|
||||
pub undecided: Vec<usize>,
|
||||
}
|
||||
|
||||
/// A kernel edge's smoothness, decided once.
|
||||
struct Smooth {
|
||||
known: Vec<(EdgeId, Option<Tangency>)>,
|
||||
}
|
||||
|
||||
impl Smooth {
|
||||
fn of(
|
||||
&mut self,
|
||||
k: &Kernel,
|
||||
b: &mut Brep,
|
||||
e: EdgeId,
|
||||
angle: f64,
|
||||
) -> solidcore::Result<Option<Tangency>> {
|
||||
if let Some((_, t)) = self.known.iter().find(|(f, _)| *f == e) {
|
||||
return Ok(*t);
|
||||
}
|
||||
let t = tangent_all_along(k, b, e, angle)?;
|
||||
self.known.push((e, t));
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// An angle a chain takes: finite, strictly between 0 and a right angle.
|
||||
fn angle_ok(angle: f64) -> bool {
|
||||
angle > 0.0 && angle < std::f64::consts::FRAC_PI_2
|
||||
}
|
||||
|
||||
fn index_of<T: PartialEq + Copy>(list: &[T], x: T) -> Option<usize> {
|
||||
list.iter().position(|&y| y == x)
|
||||
}
|
||||
|
||||
/// The tangent chain of `body`'s edges at record indices `edges` (through `order`, its
|
||||
/// record's [`Order`]), as the module documentation describes, at the caller's `angle`;
|
||||
/// `passed_over` names edges a pick passes over, which never join.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`Error::Argument`] for no edges, an angle not finite and strictly between 0 and a right
|
||||
/// angle, or an index past the order or naming an edge the body does not have; the kernel's
|
||||
/// refusals (a face with no NURBS form, an edge use with no pcurve, an edge too costly to
|
||||
/// decide).
|
||||
pub fn tangent_chain_ordered(
|
||||
body: &Body,
|
||||
order: &Order,
|
||||
edges: &[usize],
|
||||
passed_over: &[usize],
|
||||
angle: f64,
|
||||
) -> Result<Chain> {
|
||||
if edges.is_empty() || !angle_ok(angle) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let (copy, mapped) = body.copy_with_order(order)?;
|
||||
with_body("tangent_chain_ordered", ©, |k, b| {
|
||||
let list = mapped.edges.clone();
|
||||
let mut chain: Vec<EdgeId> = Vec::new();
|
||||
for &i in edges {
|
||||
match list.get(i) {
|
||||
Some(&e) if b.edge_list().contains(&e) => {
|
||||
if !chain.contains(&e) {
|
||||
chain.push(e);
|
||||
}
|
||||
}
|
||||
_ => return Ok(Err(Error::Argument)),
|
||||
}
|
||||
}
|
||||
let skip: Vec<EdgeId> = passed_over
|
||||
.iter()
|
||||
.filter_map(|&i| list.get(i).copied())
|
||||
.collect();
|
||||
let mut smooth = Smooth { known: Vec::new() };
|
||||
let mut undecided: Vec<EdgeId> = Vec::new();
|
||||
let mut next = 0;
|
||||
while let Some(&e) = chain.get(next) {
|
||||
next += 1;
|
||||
let ends = [edge::start_vertex(b, e)?, edge::end_vertex(b, e)?];
|
||||
for v in ends.into_iter().flatten() {
|
||||
for &f in &list {
|
||||
if chain.contains(&f) || skip.contains(&f) {
|
||||
continue;
|
||||
}
|
||||
let meets = [edge::start_vertex(b, f)?, edge::end_vertex(b, f)?];
|
||||
if !meets.contains(&Some(v)) {
|
||||
continue;
|
||||
}
|
||||
match smooth.of(k, b, f, angle)? {
|
||||
None | Some(Tangency::Tangent) => continue,
|
||||
Some(Tangency::Undecided { .. }) => {
|
||||
if !undecided.contains(&f) {
|
||||
undecided.push(f);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Some(Tangency::NotTangent) => {}
|
||||
}
|
||||
let turns_over = matches!(
|
||||
(convex_at(k, b, e, v)?, convex_at(k, b, f, v)?),
|
||||
(Some(a), Some(c)) if a != c
|
||||
);
|
||||
if turns_over {
|
||||
continue;
|
||||
}
|
||||
if turn_at(k, b, e, f, v)?.is_some_and(|turn| turn < angle) {
|
||||
chain.push(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let indices = |of: &[EdgeId]| -> Option<Vec<usize>> {
|
||||
of.iter().map(|&e| index_of(&list, e)).collect()
|
||||
};
|
||||
Ok(match (indices(&chain), indices(&undecided)) {
|
||||
(Some(members), Some(undecided)) => Ok(Chain { members, undecided }),
|
||||
_ => Err(Error::Exception),
|
||||
})
|
||||
})?
|
||||
}
|
||||
|
||||
/// The faces of `body` at record indices `faces` (through `order`), then every face across a
|
||||
/// smooth edge from one of them, and so on (module documentation), at the caller's `angle`.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`Error::Argument`] for no faces, an angle as for [`tangent_chain_ordered`], or an index
|
||||
/// past the order or naming a face the body does not have; the kernel's refusals as for
|
||||
/// [`tangent_chain_ordered`].
|
||||
pub fn tangent_face_chain_ordered(
|
||||
body: &Body,
|
||||
order: &Order,
|
||||
faces: &[usize],
|
||||
angle: f64,
|
||||
) -> Result<Chain> {
|
||||
if faces.is_empty() || !angle_ok(angle) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let (copy, mapped) = body.copy_with_order(order)?;
|
||||
with_body("tangent_face_chain_ordered", ©, |k, b| {
|
||||
let face_list = mapped.faces.clone();
|
||||
let edge_list = mapped.edges.clone();
|
||||
let mut chain: Vec<FaceId> = Vec::new();
|
||||
for &i in faces {
|
||||
match face_list.get(i) {
|
||||
Some(&f) if b.face_list().contains(&f) => {
|
||||
if !chain.contains(&f) {
|
||||
chain.push(f);
|
||||
}
|
||||
}
|
||||
_ => return Ok(Err(Error::Argument)),
|
||||
}
|
||||
}
|
||||
let mut smooth = Smooth { known: Vec::new() };
|
||||
let mut undecided: Vec<EdgeId> = Vec::new();
|
||||
let mut next = 0;
|
||||
while let Some(&f) = chain.get(next) {
|
||||
next += 1;
|
||||
for &e in &edge_list {
|
||||
let across = solidcore::query::edge::edge_faces(b, e)?;
|
||||
if !across.contains(&f) {
|
||||
continue;
|
||||
}
|
||||
match smooth.of(k, b, e, angle)? {
|
||||
Some(Tangency::Tangent) => {}
|
||||
Some(Tangency::Undecided { .. }) => {
|
||||
if !undecided.contains(&e) {
|
||||
undecided.push(e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
None | Some(Tangency::NotTangent) => continue,
|
||||
}
|
||||
for g in across {
|
||||
if !chain.contains(&g) {
|
||||
chain.push(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let members: Option<Vec<usize>> = chain.iter().map(|&f| index_of(&face_list, f)).collect();
|
||||
let undecided: Option<Vec<usize>> =
|
||||
undecided.iter().map(|&e| index_of(&edge_list, e)).collect();
|
||||
Ok(match (members, undecided) {
|
||||
(Some(members), Some(undecided)) => Ok(Chain { members, undecided }),
|
||||
_ => Err(Error::Exception),
|
||||
})
|
||||
})?
|
||||
}
|
||||
+71
-3
@@ -1,8 +1,12 @@
|
||||
//! The boolean family on boxes whose answers are known.
|
||||
#![allow(clippy::unwrap_used)]
|
||||
//! The boolean family on bodies whose answers are known.
|
||||
#![allow(clippy::unwrap_used, clippy::indexing_slicing)] // test
|
||||
|
||||
use std::io::Read as _;
|
||||
|
||||
use serde_json::Value;
|
||||
use solidcore_ops::boolean::{boolean, imprint, union_all};
|
||||
use solidcore_ops::{make_box, session, Body, BooleanOp, Error};
|
||||
use solidcore_ops::{make_box, session, Body, BooleanOp, CheckLevel, Error};
|
||||
use solidcore_record::brep::Brep as Record;
|
||||
|
||||
fn all_fixes() {
|
||||
session::set_thread_fixes(solidcore::kernel::UpstreamFixes::NAMES.iter().copied()).unwrap();
|
||||
@@ -76,3 +80,67 @@ fn an_imprint_cuts_the_faces_the_tool_crosses() {
|
||||
assert_eq!(r.counts().unwrap(), [6, 12, 8]);
|
||||
assert!(near(volume(&r), 1.0), "{}", volume(&r));
|
||||
}
|
||||
|
||||
/// The kernel rig's case `booleans/<case>`: its entry, and its operand bodies by name, read
|
||||
/// under the corpus's fix set.
|
||||
fn corpus_case(case: &str) -> (Value, impl Fn(&str) -> Body) {
|
||||
let corpus = format!(
|
||||
"{}/../solidcore/tests/kernel_cases",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let fixes: Vec<String> =
|
||||
serde_json::from_str(&std::fs::read_to_string(format!("{corpus}/fixes.json")).unwrap())
|
||||
.unwrap();
|
||||
session::set_fixes(fixes.iter().map(String::as_str)).unwrap();
|
||||
let entry: Value = serde_json::from_str(
|
||||
&std::fs::read_to_string(format!("{corpus}/booleans/{case}.json")).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let gz = std::fs::File::open(format!("{corpus}/booleans/{case}.bodies.json.gz")).unwrap();
|
||||
let mut text = String::new();
|
||||
flate2::read::GzDecoder::new(gz)
|
||||
.read_to_string(&mut text)
|
||||
.unwrap();
|
||||
let records: Value = serde_json::from_str(&text).unwrap();
|
||||
let trusted = entry["bodies"].clone();
|
||||
let body = move |name: &str| {
|
||||
let record: Record = serde_json::from_value(records[name].clone()).unwrap();
|
||||
Body::from_record(&record, trusted[name]["trusted"].as_bool().unwrap())
|
||||
.unwrap()
|
||||
.0
|
||||
};
|
||||
(entry, body)
|
||||
}
|
||||
|
||||
/// A rod cut by two one-turn screw sweeps of one channel profile, one of each hand, from its
|
||||
/// top face: the profile's radial walls meet the rod's cylinder, so each sweep's rail there is
|
||||
/// a helix on it, and the two rails cross. The crossing is found more than once, a few 1e-12
|
||||
/// apart in the rail's parameter; the new edge along the second rail took the interval its
|
||||
/// UV curve's trim had snapped onto a knot of its own, 2.4e-12 before the 3D curve's end
|
||||
/// (`SmEdge` test 2). Cut in turn and cut by the two sweeps' union, the result is one valid
|
||||
/// solid of the case's closed form (its `expected.method`).
|
||||
#[test]
|
||||
fn a_rod_cut_by_two_counter_wound_channel_sweeps_is_valid() {
|
||||
let (entry, body) = corpus_case("13462-rod-cut-by-two-counter-wound-channel-coils");
|
||||
let want = entry["expected"]["volume_mm3"].as_f64().unwrap();
|
||||
let rel = entry["expected"]["tolerance"]["rel"].as_f64().unwrap();
|
||||
// b6 the rod, b4 and b5 the right- and left-handed sweeps, b14 the rod cut by b4 as the
|
||||
// case's run read it back from its record
|
||||
let in_turn = boolean(body("b14"), body("b5"), BooleanOp::Subtract)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let both = union_all(vec![body("b4"), body("b5")]).unwrap();
|
||||
let at_once = boolean(body("b6"), both, BooleanOp::Subtract)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
for (how, r) in [("in turn", &in_turn), ("by the union", &at_once)] {
|
||||
let report = r.check(CheckLevel::All).unwrap();
|
||||
assert!(report.is_valid(), "cut {how}: {}", report.text);
|
||||
assert_eq!(r.solid_count().unwrap(), 1, "cut {how}");
|
||||
let v = volume(r);
|
||||
assert!(
|
||||
(v - want).abs() <= rel * want,
|
||||
"cut {how}: volume {v}, closed form {want}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
//! Contact between bodies on closed forms: meets, meets against several, the boundary
|
||||
//! distance, and the refusals.
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use solidcore_ops::session;
|
||||
use solidcore_ops::{make_box, make_sphere, Error};
|
||||
|
||||
fn all_fixes() {
|
||||
session::set_thread_fixes(solidcore::kernel::UpstreamFixes::NAMES.iter().copied()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_sharing_a_face_meet() {
|
||||
all_fixes();
|
||||
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let b = make_box([1.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
assert!(a.meets(&b, 1e-6).unwrap());
|
||||
assert!(b.meets(&a, 1e-6).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_one_apart_do_not_meet_and_their_distance_is_one() {
|
||||
all_fixes();
|
||||
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let b = make_box([2.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
assert!(!a.meets(&b, 1e-3).unwrap());
|
||||
let g = a.boundary_distance(&b, 1e-9).unwrap();
|
||||
assert!(g.lower <= 1.0 && 1.0 <= g.upper, "{g:?}");
|
||||
assert!(g.upper - g.lower <= 1e-9 * g.upper.max(1.0), "{g:?}");
|
||||
let [p, q] = g.witness.unwrap();
|
||||
assert!(
|
||||
(p[0] - 1.0).abs() <= 1e-12 && (q[0] - 2.0).abs() <= 1e-12,
|
||||
"{g:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_body_meets_itself_and_one_it_encloses() {
|
||||
all_fixes();
|
||||
let a = make_box([0.0, 0.0, 0.0], [3.0, 3.0, 3.0]).unwrap();
|
||||
let inner = make_box([1.0, 1.0, 1.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
assert!(a.meets(&a, 1e-6).unwrap());
|
||||
assert!(a.meets(&inner, 1e-6).unwrap());
|
||||
assert!(inner.meets(&a, 1e-6).unwrap());
|
||||
// neither body changes
|
||||
assert_eq!(a.counts().unwrap(), [6, 12, 8]);
|
||||
assert_eq!(inner.counts().unwrap(), [6, 12, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn meets_each_answers_each_with_its_own_tolerance() {
|
||||
all_fixes();
|
||||
let a = make_sphere([0.0, 0.0, 0.0], 1.0).unwrap();
|
||||
let touching = make_box([1.0, -1.0, -1.0], [1.0, 2.0, 2.0]).unwrap();
|
||||
let near = make_sphere([2.5, 0.0, 0.0], 1.0).unwrap(); // 0.5 apart
|
||||
let far = make_box([5.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let got = a
|
||||
.meets_each(&[(&touching, 1e-6), (&near, 0.1), (&near, 0.6), (&far, 1.0)])
|
||||
.unwrap();
|
||||
assert_eq!(got, vec![true, false, true, false]);
|
||||
}
|
||||
|
||||
/// The refusal is the kernel's own bracket: a tolerance equal to a gap the kernel brackets
|
||||
/// to its rounding is inside the bracket, and neither answer is given.
|
||||
#[test]
|
||||
fn a_tolerance_inside_the_bracket_is_refused_with_it() {
|
||||
all_fixes();
|
||||
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let b = make_box([2.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let far = make_box([5.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
match a.meets(&b, 1.0) {
|
||||
Err(Error::ContactUndecided {
|
||||
other: 0,
|
||||
lower,
|
||||
upper,
|
||||
}) => assert!(lower <= 1.0 && 1.0 <= upper, "[{lower}, {upper}]"),
|
||||
r => panic!("{r:?}"),
|
||||
}
|
||||
match a.meets_each(&[(&far, 1e-3), (&b, 1.0)]) {
|
||||
Err(Error::ContactUndecided { other: 1, .. }) => {}
|
||||
r => panic!("{r:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tolerance_or_width_not_finite_and_positive_is_refused() {
|
||||
all_fixes();
|
||||
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let b = make_box([2.0, 0.0, 0.0], [1.0, 1.0, 1.0]).unwrap();
|
||||
let invalid = Error::Status(solidcore::error::SM_ERR_INVALID_INPUT);
|
||||
for bad in [0.0, -1.0, f64::NAN, f64::INFINITY] {
|
||||
assert_eq!(a.meets(&b, bad).unwrap_err(), invalid, "{bad}");
|
||||
assert_eq!(a.boundary_distance(&b, bad).unwrap_err(), invalid, "{bad}");
|
||||
}
|
||||
}
|
||||
+90
-23
@@ -32,7 +32,9 @@
|
||||
//! - `KERNEL_CASES_TIMEOUT_S`: a case's time limit, 180 s by default; one past it is a hang, and
|
||||
//! the cases after it are not run (it holds the process kernel);
|
||||
//! - `KERNEL_CASES_REPORT`: a file to write the per-area table and the lists to;
|
||||
//! - `KERNEL_CASES_REPORT_ONLY`: set, the test reports without failing.
|
||||
//! - `KERNEL_CASES_REPORT_ONLY`: set, the test reports without failing;
|
||||
//! - `KERNEL_CASES_SMB`: a directory to write each run case's bodies to as `<case>.smb`, for
|
||||
//! its render (`solidcore-diag`): the final bodies, or the inputs of the refusal that ends it.
|
||||
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
@@ -231,31 +233,38 @@ fn f64s<const N: usize>(v: &Value) -> Option<[f64; N]> {
|
||||
(a.len() == N).then_some(out)
|
||||
}
|
||||
|
||||
/// A pick's record indices, optionally bounded by its record order's length.
|
||||
/// A pick's record indices, optionally bounded by its record order's length. Every pick's shape
|
||||
/// and range is checked first, so bad data fails wherever it sits; only then is a traced miss
|
||||
/// reported as unsupported (#2993).
|
||||
fn picked(op: &str, selection: &Value, length: Option<usize>) -> Result<Vec<usize>, Stop> {
|
||||
let bad = |what: &str| Stop::Fail(format!("{op}: parameter {what}"));
|
||||
selection["picked"]
|
||||
let picks = selection["picked"]
|
||||
.as_array()
|
||||
.ok_or_else(|| bad("picked"))?
|
||||
.iter()
|
||||
.map(|pick| {
|
||||
if let Some(miss) = pick.get("miss") {
|
||||
let why = miss.as_str().ok_or_else(|| bad("miss"))?;
|
||||
if pick.get("index").is_some() {
|
||||
return Err(bad("pick must hit or miss"));
|
||||
}
|
||||
return Err(Stop::Unsupported(format!("{op}: pick missed: {why}")));
|
||||
.ok_or_else(|| bad("picked"))?;
|
||||
let mut indices = Vec::with_capacity(picks.len());
|
||||
let mut missed = None;
|
||||
for pick in picks {
|
||||
if let Some(miss) = pick.get("miss") {
|
||||
let why = miss.as_str().ok_or_else(|| bad("miss"))?;
|
||||
if pick.get("index").is_some() {
|
||||
return Err(bad("pick must hit or miss"));
|
||||
}
|
||||
let index = pick["index"]
|
||||
.as_u64()
|
||||
.and_then(|i| usize::try_from(i).ok())
|
||||
.ok_or_else(|| bad("index"))?;
|
||||
if length.is_some_and(|length| index >= length) {
|
||||
return Err(bad("pick outside record order"));
|
||||
}
|
||||
Ok(index)
|
||||
})
|
||||
.collect()
|
||||
missed.get_or_insert(why);
|
||||
continue;
|
||||
}
|
||||
let index = pick["index"]
|
||||
.as_u64()
|
||||
.and_then(|i| usize::try_from(i).ok())
|
||||
.ok_or_else(|| bad("index"))?;
|
||||
if length.is_some_and(|length| index >= length) {
|
||||
return Err(bad("pick outside record order"));
|
||||
}
|
||||
indices.push(index);
|
||||
}
|
||||
match missed {
|
||||
Some(why) => Err(Stop::Unsupported(format!("{op}: pick missed: {why}"))),
|
||||
None => Ok(indices),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses typed record picks without reordering them: misses are unsupported; bad data fails.
|
||||
@@ -1197,6 +1206,21 @@ fn grade_seams(result: &[Slot], expected: &Value) -> Result<(), Stop> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// With `KERNEL_CASES_SMB` set, writes `bodies` there as `<case>.smb`, `<case>-1.smb`, ...
|
||||
fn write_smb(e: &Value, bodies: &[Slot]) {
|
||||
let Some(dir) = std::env::var_os("KERNEL_CASES_SMB") else {
|
||||
return;
|
||||
};
|
||||
let case = e["case"].as_str().unwrap();
|
||||
for (i, s) in bodies.iter().enumerate() {
|
||||
let name = match i {
|
||||
0 => format!("{case}.smb"),
|
||||
_ => format!("{case}-{i}.smb"),
|
||||
};
|
||||
std::fs::write(Path::new(&dir).join(name), s.body.to_smb(true).unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a case's calls and grades the result against `rig` (or, for a hand-written case
|
||||
/// without one, `expected`): `Ok(deviation)` for a match.
|
||||
fn run_case(entry: &Path, e: &Value) -> Result<Option<f64>, Stop> {
|
||||
@@ -1298,7 +1322,10 @@ fn run_case_with_records(
|
||||
// A handled refusal is an attempt, not the case's outcome. Keep checking the
|
||||
// later operations and final geometry; an unmarked refusal still ends the case.
|
||||
(Err(_), Some(_)) if continued => {}
|
||||
(Err(_), Some(_)) => return Ok(None),
|
||||
(Err(_), Some(_)) => {
|
||||
write_smb(e, &ins);
|
||||
return Ok(None);
|
||||
}
|
||||
(Err(x), None) => return Err(Stop::Fail(format!("call {i} ({op}) refused: {x}"))),
|
||||
(Ok(_), Some(want)) => {
|
||||
return Err(Stop::Fail(format!(
|
||||
@@ -1321,6 +1348,7 @@ fn run_case_with_records(
|
||||
.ok_or_else(|| Stop::Fail(format!("no final body {n}")))?,
|
||||
);
|
||||
}
|
||||
write_smb(e, &result);
|
||||
let got = measure(&result).map_err(Stop::Fail)?;
|
||||
if let Some(seams) = e["expected"].get("seams") {
|
||||
grade_seams(&result, seams)?;
|
||||
@@ -1422,7 +1450,16 @@ fn seam_gaps_are_limited_independently_of_stored_tolerances() {
|
||||
|
||||
let body = solidcore_ops::make_box([0.0; 3], [10.0; 3]).unwrap();
|
||||
let (mut record, _) = body.to_record().unwrap();
|
||||
// every stored tolerance inflated, not the body's alone: the import keeps each edge's and
|
||||
// vertex's own and takes the body's as the least of them (#3187, as SolidCraft's flat
|
||||
// import does), so a body tolerance alone no longer reaches the edges the grade reads
|
||||
record.tolerance = 0.01;
|
||||
for edge in &mut record.edges {
|
||||
edge.tolerance = 0.01;
|
||||
}
|
||||
for vertex in &mut record.vertices {
|
||||
vertex.tolerance = 0.01;
|
||||
}
|
||||
for face in &mut record.faces {
|
||||
match &mut face.surface {
|
||||
Surface::Plane { origin, .. } => {
|
||||
@@ -3907,6 +3944,36 @@ fn record_picks_preserve_order_and_duplicate_parameter_alignment() {
|
||||
));
|
||||
}
|
||||
|
||||
/// Bad pick data fails wherever it sits in the list; a traced miss is unsupported only when
|
||||
/// every other pick is well formed (#2993).
|
||||
#[test]
|
||||
fn a_traced_miss_is_unsupported_only_among_well_formed_picks() {
|
||||
let list = |picked: Value| serde_json::json!({"kind": "edge", "picked": picked});
|
||||
for bad in [
|
||||
serde_json::json!([{"miss": "none"}, {"index": 999}]),
|
||||
serde_json::json!([{"index": 999}, {"miss": "none"}]),
|
||||
serde_json::json!([{"miss": "none"}, {}]),
|
||||
serde_json::json!([{"miss": "none"}, {"miss": "none", "index": 0}]),
|
||||
] {
|
||||
assert!(
|
||||
matches!(
|
||||
record_picks("blend", &list(bad.clone()), "edge", 2),
|
||||
Err(Stop::Fail(_))
|
||||
),
|
||||
"{bad}"
|
||||
);
|
||||
}
|
||||
assert!(matches!(
|
||||
record_picks(
|
||||
"blend",
|
||||
&list(serde_json::json!([{"index": 0}, {"miss": "none"}])),
|
||||
"edge",
|
||||
2
|
||||
),
|
||||
Err(Stop::Unsupported(_))
|
||||
));
|
||||
}
|
||||
|
||||
/// Captured split followed by merging its redundant faces, with the original grading.
|
||||
#[test]
|
||||
fn split_body_replays_face_plane() {
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
//! `tangent::tangent_chain_ordered` and `tangent_face_chain_ordered` (#3141), on record order.
|
||||
//!
|
||||
//! A stadium prism: two straight walls and two half-cylinders, 4 high. Its top outline is
|
||||
//! four edges each running on tangentially into the next, so a pick of one top edge chains
|
||||
//! all four and nothing else (the walls meet each other smoothly, so the vertical edges
|
||||
//! between them are smooth, and the bottom outline turns square from the top's). Its walls
|
||||
//! meet across smooth edges, so a pick of one wall chains the four walls and not the caps. A
|
||||
//! box's edges and faces all meet square: a pick chains itself alone.
|
||||
//!
|
||||
//! Ported from SolidCraft (`crates/kernel/tests/chamfer_curved_holder.rs`,
|
||||
//! `one_chamfer_pick_follows_the_tangent_chain_at_any_part_size`, its #2672): a plate of side
|
||||
//! 140, 145 and 250, 10 thick, with a 50 x 10 stadium boss 10 tall on top. One pick on the
|
||||
//! boss's top straight edge chains the boss's whole top rim, its four edges at z = 20, at
|
||||
//! every plate size (SolidCraft's walk once stopped at the straight edge past a 200 mm part).
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing
|
||||
)] // test helpers
|
||||
|
||||
use solidcore_ops::boolean::{boolean, BooleanOp};
|
||||
use solidcore_ops::sweep::{extrude, Curve, DraftCorner};
|
||||
use solidcore_ops::tangent::{tangent_chain_ordered, tangent_face_chain_ordered};
|
||||
use solidcore_ops::{make_box, Body};
|
||||
use solidcore_record::brep::{Brep as Record, Surface};
|
||||
use std::f64::consts::{FRAC_PI_2, PI};
|
||||
|
||||
const H: f64 = 4.0;
|
||||
/// SolidCraft's smooth-edge angle, the caller's: half a degree.
|
||||
const ANGLE: f64 = 0.5 * PI / 180.0;
|
||||
|
||||
fn stadium() -> Body {
|
||||
let profile = [
|
||||
Curve::Line {
|
||||
start: [0.0, 0.0, 0.0],
|
||||
end: [20.0, 0.0, 0.0],
|
||||
},
|
||||
Curve::Arc {
|
||||
center: [20.0, 5.0, 0.0],
|
||||
axis: [0.0, 0.0, 1.0],
|
||||
reference: [1.0, 0.0, 0.0],
|
||||
radius: 5.0,
|
||||
start: -FRAC_PI_2,
|
||||
end: FRAC_PI_2,
|
||||
},
|
||||
Curve::Line {
|
||||
start: [20.0, 10.0, 0.0],
|
||||
end: [0.0, 10.0, 0.0],
|
||||
},
|
||||
Curve::Arc {
|
||||
center: [0.0, 5.0, 0.0],
|
||||
axis: [0.0, 0.0, 1.0],
|
||||
reference: [1.0, 0.0, 0.0],
|
||||
radius: 5.0,
|
||||
start: FRAC_PI_2,
|
||||
end: 1.5 * PI,
|
||||
},
|
||||
];
|
||||
extrude(
|
||||
&profile,
|
||||
[0.0, 0.0, 1.0],
|
||||
H,
|
||||
0.0,
|
||||
DraftCorner::default(),
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// The record index of the edge between the vertices at `a` and `b`.
|
||||
fn edge(r: &Record, a: [f64; 3], b: [f64; 3]) -> usize {
|
||||
let near = |i: u32, p: [f64; 3]| {
|
||||
let q = r.vertices[i as usize].point;
|
||||
(0..3).all(|j| (q[j] - p[j]).abs() < 1e-9)
|
||||
};
|
||||
r.edges
|
||||
.iter()
|
||||
.position(|e| {
|
||||
let [s, t] = e.vertices;
|
||||
(near(s, a) && near(t, b)) || (near(s, b) && near(t, a))
|
||||
})
|
||||
.expect("an edge between the points")
|
||||
}
|
||||
|
||||
/// Whether record edge `i` lies in the top, both its ends at z = H.
|
||||
fn on_top(r: &Record, i: usize) -> bool {
|
||||
r.edges[i]
|
||||
.vertices
|
||||
.iter()
|
||||
.all(|&v| (r.vertices[v as usize].point[2] - H).abs() < 1e-9)
|
||||
}
|
||||
|
||||
/// The record index of the planar face whose normal is along `n` (up to sign).
|
||||
fn plane_along(r: &Record, n: [f64; 3]) -> Vec<usize> {
|
||||
let cross = |a: [f64; 3], b: [f64; 3]| {
|
||||
[
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
a[0] * b[1] - a[1] * b[0],
|
||||
]
|
||||
};
|
||||
let along = |m: [f64; 3]| {
|
||||
let c = cross(m, n);
|
||||
c.iter().all(|x| x.abs() < 1e-9) && m.iter().any(|x| x.abs() > 1e-9)
|
||||
};
|
||||
(0..r.faces.len())
|
||||
.filter(|&i| match r.faces[i].surface {
|
||||
Surface::Plane { u_axis, v_axis, .. } => along(cross(u_axis, v_axis)),
|
||||
_ => false,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stadium_s_top_outline_is_one_chain() {
|
||||
let b = stadium();
|
||||
let (r, order) = b.to_record().unwrap();
|
||||
let front = edge(&r, [0.0, 0.0, H], [20.0, 0.0, H]);
|
||||
let chain = tangent_chain_ordered(&b, &order, &[front], &[], ANGLE).unwrap();
|
||||
assert!(chain.undecided.is_empty(), "{chain:?}");
|
||||
assert_eq!(chain.members.len(), 4, "{chain:?}");
|
||||
assert_eq!(chain.members[0], front);
|
||||
assert!(chain.members.iter().all(|&e| on_top(&r, e)), "{chain:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_passed_over_edge_stops_the_chain() {
|
||||
let b = stadium();
|
||||
let (r, order) = b.to_record().unwrap();
|
||||
let front = edge(&r, [0.0, 0.0, H], [20.0, 0.0, H]);
|
||||
let back = edge(&r, [20.0, 10.0, H], [0.0, 10.0, H]);
|
||||
let chain = tangent_chain_ordered(&b, &order, &[front], &[back], ANGLE).unwrap();
|
||||
assert_eq!(chain.members.len(), 3, "{chain:?}");
|
||||
assert!(!chain.members.contains(&back));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_edge_chains_alone() {
|
||||
let b = make_box([0.0; 3], [10.0, 6.0, H]).unwrap();
|
||||
let (r, order) = b.to_record().unwrap();
|
||||
let front = edge(&r, [0.0, 0.0, H], [10.0, 0.0, H]);
|
||||
let chain = tangent_chain_ordered(&b, &order, &[front], &[], ANGLE).unwrap();
|
||||
assert_eq!(chain.members, vec![front]);
|
||||
assert!(chain.undecided.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stadium_s_walls_are_one_face_chain() {
|
||||
let b = stadium();
|
||||
let (r, order) = b.to_record().unwrap();
|
||||
let walls = plane_along(&r, [0.0, 1.0, 0.0]);
|
||||
assert_eq!(walls.len(), 2);
|
||||
let chain = tangent_face_chain_ordered(&b, &order, &walls[..1], ANGLE).unwrap();
|
||||
assert!(chain.undecided.is_empty(), "{chain:?}");
|
||||
assert_eq!(chain.members.len(), 4, "{chain:?}");
|
||||
for &f in &chain.members {
|
||||
let caps = plane_along(&r, [0.0, 0.0, 1.0]);
|
||||
assert!(!caps.contains(&f), "a cap joined: {chain:?}");
|
||||
}
|
||||
assert!(walls.iter().all(|w| chain.members.contains(w)), "{chain:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_face_chains_alone() {
|
||||
let b = make_box([0.0; 3], [10.0, 6.0, H]).unwrap();
|
||||
let (r, order) = b.to_record().unwrap();
|
||||
let top = plane_along(&r, [0.0, 0.0, 1.0]);
|
||||
let chain = tangent_face_chain_ordered(&b, &order, &top[..1], ANGLE).unwrap();
|
||||
assert_eq!(chain.members, vec![top[0]]);
|
||||
}
|
||||
|
||||
/// The plate of side `plate` with the stadium boss on top.
|
||||
fn plate_with_stadium(plate: f64) -> Body {
|
||||
let base = make_box([-plate / 2.0, -plate / 2.0, 0.0], [plate, plate, 10.0]).unwrap();
|
||||
let z = 10.0;
|
||||
let profile = [
|
||||
Curve::Line {
|
||||
start: [-20.0, -5.0, z],
|
||||
end: [20.0, -5.0, z],
|
||||
},
|
||||
Curve::Arc {
|
||||
center: [20.0, 0.0, z],
|
||||
axis: [0.0, 0.0, 1.0],
|
||||
reference: [1.0, 0.0, 0.0],
|
||||
radius: 5.0,
|
||||
start: -FRAC_PI_2,
|
||||
end: FRAC_PI_2,
|
||||
},
|
||||
Curve::Line {
|
||||
start: [20.0, 5.0, z],
|
||||
end: [-20.0, 5.0, z],
|
||||
},
|
||||
Curve::Arc {
|
||||
center: [-20.0, 0.0, z],
|
||||
axis: [0.0, 0.0, 1.0],
|
||||
reference: [1.0, 0.0, 0.0],
|
||||
radius: 5.0,
|
||||
start: FRAC_PI_2,
|
||||
end: 1.5 * PI,
|
||||
},
|
||||
];
|
||||
let boss = extrude(
|
||||
&profile,
|
||||
[0.0, 0.0, 1.0],
|
||||
10.0,
|
||||
0.0,
|
||||
DraftCorner::default(),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
boolean(base, boss, BooleanOp::Union).unwrap().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_pick_follows_the_boss_rim_at_any_part_size() {
|
||||
for plate in [140.0, 145.0, 250.0] {
|
||||
let b = plate_with_stadium(plate);
|
||||
let (r, order) = b.to_record().unwrap();
|
||||
let back = edge(&r, [20.0, 5.0, 20.0], [-20.0, 5.0, 20.0]);
|
||||
let chain = tangent_chain_ordered(&b, &order, &[back], &[], ANGLE).unwrap();
|
||||
assert!(chain.undecided.is_empty(), "plate {plate}: {chain:?}");
|
||||
assert_eq!(chain.members.len(), 4, "plate {plate}: {chain:?}");
|
||||
let at_top = |i: usize| {
|
||||
r.edges[i]
|
||||
.vertices
|
||||
.iter()
|
||||
.all(|&v| (r.vertices[v as usize].point[2] - 20.0).abs() < 1e-9)
|
||||
};
|
||||
assert!(
|
||||
chain.members.iter().all(|&e| at_top(e)),
|
||||
"plate {plate}: {chain:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
//! and structural validation ([`brep`]), its history and metadata records, the native body
|
||||
//! file ([`native`]), and the plain data of vectors ([`Vec2`], [`Vec3`]), sketch planes
|
||||
//! ([`Plane`]) and planar profiles ([`Seg2`], [`Loop2`], [`Region2`]), and STEP's Part 21
|
||||
//! exchange structure as parsed from a file ([`step`]).
|
||||
//! exchange structure as parsed from a file with the units of its representation contexts and
|
||||
//! its Part 42 curves and surfaces ([`step`]).
|
||||
//!
|
||||
//! The record computes no geometric quantity: no square roots, trigonometry, dot or cross
|
||||
//! products, normalising or solves (clippy `disallowed-methods`, this crate's `clippy.toml`). It
|
||||
|
||||
+7
-1
@@ -1,3 +1,9 @@
|
||||
//! STEP (ISO 10303) as data: the exchange structure a file is read into ([`p21`]). Building
|
||||
//! STEP (ISO 10303) as data: the exchange structure a file is read into ([`p21`]), the units
|
||||
//! of its representation contexts ([`units`]), its Part 42 curves and surfaces ([`geom`])
|
||||
//! as the entity reader ([`read`]) maps them, and its product structure ([`product`]). Building
|
||||
//! bodies from it is `solidcore-ops`' job (docs/layers.md §2, "Importers are solidcore's").
|
||||
pub mod geom;
|
||||
pub mod p21;
|
||||
pub mod product;
|
||||
pub mod read;
|
||||
pub mod units;
|
||||
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
//! STEP geometry (ISO 10303-42) as plain data: the curves and surfaces a file defines, exactly
|
||||
//! as it defines them, with lengths in millimetres and angles in radians. Nothing is
|
||||
//! approximated, bounded, normalised or turned into another kind on the way in: a circle stays a
|
||||
//! circle with its placement, an unbounded cylinder stays unbounded, a B-spline keeps its own
|
||||
//! knots and form, and a direction keeps the ratios the file writes. Every curve and surface keeps
|
||||
//! STEP's own parameterisation, so a 2D curve in a surface's parameter space means the same thing
|
||||
//! here as in the file.
|
||||
//!
|
||||
//! Entities the file shares (one surface under many edges' pcurves) are shared here too, through
|
||||
//! `Arc`, so reading is linear in the file however often an entity is referenced.
|
||||
//!
|
||||
//! The record computes no geometric quantity (docs/layers.md §1, §4): a direction is not made a
|
||||
//! unit vector, a placement's reference direction is not made orthogonal to its axis (nor chosen
|
||||
//! when the file omits it), its y axis is not formed, a vector's length is not taken, and nothing
|
||||
//! is evaluated. Those are the kernel's, on its own types, when it builds from this data (#2578,
|
||||
//! slice 4). The formulas on the variants below say what the data means, in ISO 10303-42's
|
||||
//! terms; they are not computed here.
|
||||
//!
|
||||
//! Origin: SolidCraft's `crates/step/src/geom.rs` at `a5b71ca2` (MIT OR Apache-2.0).
|
||||
//!
|
||||
//! Deviations from SolidCraft: [`Placement`] keeps `axis` and `ref_direction` as the file gives
|
||||
//! them (each optional, neither normalised; was: both unit and orthogonal, a missing reference
|
||||
//! direction chosen perpendicular to the axis), and [`Axis1::direction`] likewise. A `VECTOR` is
|
||||
//! a [`Vector`] of the written orientation, magnitude and unit factors (was: a unit direction
|
||||
//! and the vector's length), in [`Curve::Line`] and [`Surface::Extrusion`]. Removed, to come back
|
||||
//! as kernel primitives in slice 4: `Placement::y`, `Placement::at` and the vector helpers `add`,
|
||||
//! `sub`, `scale`, `dot`, `cross`, `norm`. Doc comments on every public item and field.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A point or vector: `[x, y, z]` (z = 0 in a 2D parameter space).
|
||||
pub type P3 = [f64; 3];
|
||||
|
||||
/// `AXIS2_PLACEMENT_3D`, or `AXIS2_PLACEMENT_2D` (no `axis`): an origin and the directions of
|
||||
/// its local Z and X axes, as the file writes them.
|
||||
///
|
||||
/// ISO 10303-42 makes the local axes from these: Z is `axis` (+Z when absent), X is
|
||||
/// `ref_direction` made orthogonal to Z (a chosen perpendicular when absent), Y is Z × X. That
|
||||
/// construction, with its checks (a zero or parallel reference direction), is the kernel's.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Placement {
|
||||
/// The location, in millimetres (or the parameter space's units).
|
||||
pub origin: P3,
|
||||
/// Local Z as the file gives it (not normalised); `None` when the file omits it and for an
|
||||
/// `AXIS2_PLACEMENT_2D`, which ISO 10303-42 defines as +Z.
|
||||
pub axis: Option<P3>,
|
||||
/// Local X as the file gives it (not normalised, not made orthogonal to `axis`); `None` when
|
||||
/// the file omits it.
|
||||
pub ref_direction: Option<P3>,
|
||||
}
|
||||
|
||||
/// `AXIS1_PLACEMENT`: an origin and a direction, as the file writes them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Axis1 {
|
||||
/// The location, in millimetres.
|
||||
pub origin: P3,
|
||||
/// The direction as the file gives it (not normalised); `None` when the file omits it, which
|
||||
/// ISO 10303-42 defines as +Z.
|
||||
pub direction: Option<P3>,
|
||||
}
|
||||
|
||||
/// `VECTOR`: an orientation and a magnitude, as the file writes them, and the unit factors of
|
||||
/// the space they are in.
|
||||
///
|
||||
/// In millimetres and radians, component `i` of the vector is
|
||||
/// `scale[i] · magnitude · orientation[i] / |orientation|`. The record does not form it; the
|
||||
/// kernel does (slice 4).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Vector {
|
||||
/// The direction ratios as written (not normalised; never all zero).
|
||||
pub orientation: P3,
|
||||
/// The magnitude as written, in file units (positive).
|
||||
pub magnitude: f64,
|
||||
/// The unit factor of each component: the length unit in model space, the parameter's unit
|
||||
/// (length, angle, or 1) in a surface's parameter space.
|
||||
pub scale: P3,
|
||||
}
|
||||
|
||||
/// A STEP LOGICAL.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Logical {
|
||||
/// `.T.`
|
||||
True,
|
||||
/// `.F.`
|
||||
False,
|
||||
/// `.U.`
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// `b_spline_curve_form`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CurveForm {
|
||||
/// `.POLYLINE_FORM.`
|
||||
PolylineForm,
|
||||
/// `.CIRCULAR_ARC.`
|
||||
CircularArc,
|
||||
/// `.ELLIPTIC_ARC.`
|
||||
EllipticArc,
|
||||
/// `.PARABOLIC_ARC.`
|
||||
ParabolicArc,
|
||||
/// `.HYPERBOLIC_ARC.`
|
||||
HyperbolicArc,
|
||||
/// `.UNSPECIFIED.`, or anything else.
|
||||
Unspecified,
|
||||
}
|
||||
|
||||
/// `b_spline_surface_form`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SurfaceForm {
|
||||
/// `.PLANE_SURF.`
|
||||
PlaneSurf,
|
||||
/// `.CYLINDRICAL_SURF.`
|
||||
CylindricalSurf,
|
||||
/// `.CONICAL_SURF.`
|
||||
ConicalSurf,
|
||||
/// `.SPHERICAL_SURF.`
|
||||
SphericalSurf,
|
||||
/// `.TOROIDAL_SURF.`
|
||||
ToroidalSurf,
|
||||
/// `.SURF_OF_REVOLUTION.`
|
||||
SurfOfRevolution,
|
||||
/// `.RULED_SURF.`
|
||||
RuledSurf,
|
||||
/// `.GENERALISED_CONE.`
|
||||
GeneralisedCone,
|
||||
/// `.QUADRIC_SURF.`
|
||||
QuadricSurf,
|
||||
/// `.SURF_OF_LINEAR_EXTRUSION.`
|
||||
SurfOfLinearExtrusion,
|
||||
/// `.UNSPECIFIED.`, or anything else.
|
||||
Unspecified,
|
||||
}
|
||||
|
||||
/// `knot_type`. A B-spline given without knots (`BEZIER_CURVE`, `UNIFORM_CURVE`,
|
||||
/// `QUASI_UNIFORM_CURVE` and their surfaces) has them laid out on reading, and keeps the
|
||||
/// matching type here.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum KnotSpec {
|
||||
/// `.UNIFORM_KNOTS.`
|
||||
UniformKnots,
|
||||
/// `.QUASI_UNIFORM_KNOTS.`
|
||||
QuasiUniformKnots,
|
||||
/// `.PIECEWISE_BEZIER_KNOTS.`
|
||||
PiecewiseBezierKnots,
|
||||
/// `.UNSPECIFIED.`, or anything else.
|
||||
Unspecified,
|
||||
}
|
||||
|
||||
/// A B-spline curve: distinct knots with their multiplicities, as STEP writes them.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct BSplineCurve {
|
||||
/// The degree (1 to `read::MAX_DEGREE`).
|
||||
pub degree: usize,
|
||||
/// The control points.
|
||||
pub points: Vec<P3>,
|
||||
/// Present for a rational curve: one positive weight per point.
|
||||
pub weights: Option<Vec<f64>>,
|
||||
/// The distinct knots, increasing.
|
||||
pub knots: Vec<f64>,
|
||||
/// Each knot's multiplicity (1 to degree + 1); they add up to points + degree + 1.
|
||||
pub multiplicities: Vec<usize>,
|
||||
/// The curve form.
|
||||
pub form: CurveForm,
|
||||
/// `closed_curve`.
|
||||
pub closed: Logical,
|
||||
/// `self_intersect`.
|
||||
pub self_intersect: Logical,
|
||||
/// The knot type.
|
||||
pub knot_spec: KnotSpec,
|
||||
}
|
||||
|
||||
/// A B-spline surface; `points[i][j]` runs along u with `i`, along v with `j`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct BSplineSurface {
|
||||
/// The degree in u.
|
||||
pub u_degree: usize,
|
||||
/// The degree in v.
|
||||
pub v_degree: usize,
|
||||
/// The control net, rows along u, every row the same length.
|
||||
pub points: Vec<Vec<P3>>,
|
||||
/// Present for a rational surface: one positive weight per control point.
|
||||
pub weights: Option<Vec<Vec<f64>>>,
|
||||
/// The distinct u knots, increasing.
|
||||
pub u_knots: Vec<f64>,
|
||||
/// Each u knot's multiplicity.
|
||||
pub u_multiplicities: Vec<usize>,
|
||||
/// The distinct v knots, increasing.
|
||||
pub v_knots: Vec<f64>,
|
||||
/// Each v knot's multiplicity.
|
||||
pub v_multiplicities: Vec<usize>,
|
||||
/// The surface form.
|
||||
pub form: SurfaceForm,
|
||||
/// `u_closed`.
|
||||
pub u_closed: Logical,
|
||||
/// `v_closed`.
|
||||
pub v_closed: Logical,
|
||||
/// `self_intersect`.
|
||||
pub self_intersect: Logical,
|
||||
/// The knot type.
|
||||
pub knot_spec: KnotSpec,
|
||||
}
|
||||
|
||||
/// One end of a `TRIMMED_CURVE`: a point on the basis curve or its parameter (a trim lists one
|
||||
/// or both).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Trim {
|
||||
/// A `CARTESIAN_POINT`.
|
||||
Point(P3),
|
||||
/// A `PARAMETER_VALUE` (radians on a circle or an ellipse).
|
||||
Parameter(f64),
|
||||
}
|
||||
|
||||
/// `trimming_preference`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TrimPreference {
|
||||
/// `.CARTESIAN.`
|
||||
Cartesian,
|
||||
/// `.PARAMETER.`
|
||||
Parameter,
|
||||
/// `.UNSPECIFIED.`, or anything else.
|
||||
Unspecified,
|
||||
}
|
||||
|
||||
/// `preferred_surface_curve_representation`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PreferredRep {
|
||||
/// `.CURVE_3D.`, or anything else.
|
||||
Curve3d,
|
||||
/// `.PCURVE_S1.`
|
||||
PcurveS1,
|
||||
/// `.PCURVE_S2.`
|
||||
PcurveS2,
|
||||
}
|
||||
|
||||
/// Which `SURFACE_CURVE` subtype.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SurfaceCurveKind {
|
||||
/// `SURFACE_CURVE`.
|
||||
Surface,
|
||||
/// `SEAM_CURVE`.
|
||||
Seam,
|
||||
/// `INTERSECTION_CURVE`.
|
||||
Intersection,
|
||||
/// `BOUNDED_SURFACE_CURVE`.
|
||||
BoundedSurface,
|
||||
}
|
||||
|
||||
/// `PCURVE`: a 2D curve in the parameter space of `surface`. Its coordinates are the surface's
|
||||
/// own parameters (lengths in mm, angles in radians).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Pcurve {
|
||||
/// The surface.
|
||||
pub surface: Arc<Surface>,
|
||||
/// A 2D curve: every point and placement has z = 0 and no placement has an axis.
|
||||
pub curve: Arc<Curve>,
|
||||
}
|
||||
|
||||
/// An item of a `SURFACE_CURVE`'s `associated_geometry`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Associated {
|
||||
/// A `PCURVE`.
|
||||
Pcurve(Pcurve),
|
||||
/// A surface.
|
||||
Surface(Arc<Surface>),
|
||||
}
|
||||
|
||||
/// A STEP curve.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Curve {
|
||||
/// `LINE`: `origin + t·v`, with `v` the [`Vector`].
|
||||
Line {
|
||||
/// `pnt`.
|
||||
origin: P3,
|
||||
/// `dir`.
|
||||
vector: Vector,
|
||||
},
|
||||
/// `CIRCLE`. Parameter: the angle from the placement's X axis.
|
||||
Circle {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// The radius (positive).
|
||||
radius: f64,
|
||||
},
|
||||
/// `ELLIPSE`: `origin + a·cos t·X + b·sin t·Y`.
|
||||
Ellipse {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `a`, along X (positive).
|
||||
semi_axis_1: f64,
|
||||
/// `b`, along Y (positive).
|
||||
semi_axis_2: f64,
|
||||
},
|
||||
/// `HYPERBOLA`: `origin + a·cosh t·X + b·sinh t·Y`.
|
||||
Hyperbola {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `a` (positive).
|
||||
semi_axis: f64,
|
||||
/// `b` (positive).
|
||||
semi_imag_axis: f64,
|
||||
},
|
||||
/// `PARABOLA`: `origin + f·t²·X + 2f·t·Y`.
|
||||
Parabola {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `f` (not zero).
|
||||
focal_dist: f64,
|
||||
},
|
||||
/// `B_SPLINE_CURVE` and its subtypes.
|
||||
BSpline(Box<BSplineCurve>),
|
||||
/// `POLYLINE`: segment `i` (from 0) is the parameters `[i, i + 1]`.
|
||||
Polyline(Vec<P3>),
|
||||
/// `TRIMMED_CURVE`.
|
||||
Trimmed {
|
||||
/// `basis_curve`.
|
||||
basis: Arc<Curve>,
|
||||
/// `trim_1` (one or two items).
|
||||
trim_1: Vec<Trim>,
|
||||
/// `trim_2` (one or two items).
|
||||
trim_2: Vec<Trim>,
|
||||
/// `sense_agreement`.
|
||||
sense: bool,
|
||||
/// `master_representation`.
|
||||
preference: TrimPreference,
|
||||
},
|
||||
/// `SURFACE_CURVE`, `SEAM_CURVE`, `INTERSECTION_CURVE`, `BOUNDED_SURFACE_CURVE`.
|
||||
SurfaceCurve {
|
||||
/// Which subtype.
|
||||
kind: SurfaceCurveKind,
|
||||
/// `curve_3d`.
|
||||
curve_3d: Arc<Curve>,
|
||||
/// `associated_geometry` (at most two items).
|
||||
associated: Vec<Associated>,
|
||||
/// `master_representation`.
|
||||
master: PreferredRep,
|
||||
},
|
||||
/// `OFFSET_CURVE_3D`.
|
||||
OffsetCurve3d {
|
||||
/// `basis_curve`.
|
||||
basis: Arc<Curve>,
|
||||
/// `distance`, in millimetres.
|
||||
distance: f64,
|
||||
/// `self_intersect`.
|
||||
self_intersect: Logical,
|
||||
/// `ref_direction` as the file gives it (not normalised).
|
||||
ref_direction: P3,
|
||||
},
|
||||
}
|
||||
|
||||
/// A STEP surface.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Surface {
|
||||
/// `PLANE`: `origin + u·X + v·Y`.
|
||||
Plane {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
},
|
||||
/// `CYLINDRICAL_SURFACE`: `origin + r·(cos u·X + sin u·Y) + v·Z`.
|
||||
Cylinder {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `r` (positive).
|
||||
radius: f64,
|
||||
},
|
||||
/// `CONICAL_SURFACE`: `origin + (r + v·tan a)·(cos u·X + sin u·Y) + v·Z`.
|
||||
Cone {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `r` (not negative).
|
||||
radius: f64,
|
||||
/// `a`, in radians, its size in (0, π/2).
|
||||
semi_angle: f64,
|
||||
},
|
||||
/// `SPHERICAL_SURFACE`: `origin + r·cos v·(cos u·X + sin u·Y) + r·sin v·Z`.
|
||||
Sphere {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `r` (positive).
|
||||
radius: f64,
|
||||
},
|
||||
/// `TOROIDAL_SURFACE`: `origin + (R + r·cos v)·(cos u·X + sin u·Y) + r·sin v·Z`.
|
||||
Torus {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `R` (may be negative: some writers use that for a spindle torus).
|
||||
major_radius: f64,
|
||||
/// `r` (positive).
|
||||
minor_radius: f64,
|
||||
},
|
||||
/// `DEGENERATE_TOROIDAL_SURFACE`: a torus whose minor radius reaches the axis;
|
||||
/// `select_outer` keeps the outer part.
|
||||
DegenerateTorus {
|
||||
/// The placement.
|
||||
position: Placement,
|
||||
/// `R`.
|
||||
major_radius: f64,
|
||||
/// `r` (positive).
|
||||
minor_radius: f64,
|
||||
/// `select_outer`.
|
||||
select_outer: bool,
|
||||
},
|
||||
/// `B_SPLINE_SURFACE` and its subtypes.
|
||||
BSpline(Box<BSplineSurface>),
|
||||
/// `SURFACE_OF_REVOLUTION`: the profile at `v` turned by the angle `u` about the axis.
|
||||
Revolution {
|
||||
/// `swept_curve`.
|
||||
profile: Arc<Curve>,
|
||||
/// `axis_position`.
|
||||
axis: Axis1,
|
||||
},
|
||||
/// `SURFACE_OF_LINEAR_EXTRUSION`: `profile(u) + v·w`, with `w` the [`Vector`].
|
||||
Extrusion {
|
||||
/// `swept_curve`.
|
||||
profile: Arc<Curve>,
|
||||
/// `extrusion_axis`.
|
||||
vector: Vector,
|
||||
},
|
||||
/// `OFFSET_SURFACE`.
|
||||
Offset {
|
||||
/// `basis_surface`.
|
||||
basis: Arc<Surface>,
|
||||
/// `distance`, in millimetres.
|
||||
distance: f64,
|
||||
/// `self_intersect`.
|
||||
self_intersect: Logical,
|
||||
},
|
||||
/// `RECTANGULAR_TRIMMED_SURFACE`, its bounds in the basis surface's parameter units.
|
||||
RectangularTrimmed {
|
||||
/// `basis_surface`.
|
||||
basis: Arc<Surface>,
|
||||
/// `u1`.
|
||||
u1: f64,
|
||||
/// `u2`.
|
||||
u2: f64,
|
||||
/// `v1`.
|
||||
v1: f64,
|
||||
/// `v2`.
|
||||
v2: f64,
|
||||
/// `usense`.
|
||||
u_sense: bool,
|
||||
/// `vsense`.
|
||||
v_sense: bool,
|
||||
},
|
||||
}
|
||||
+1088
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,713 @@
|
||||
//! The product structure, one case at a time: a part, an assembly with a shared sub-assembly,
|
||||
//! mapped items, colours, names with escapes, and what is refused. Lengths are the file's
|
||||
//! numbers times the context's unit, written as the same products, so every comparison is
|
||||
//! exact.
|
||||
//!
|
||||
//! Origin: the data cases of SolidCraft's `crates/kernel/src/step_in/tests.rs` at `a5b71ca2`
|
||||
//! (`hand_written_part_in_inches_with_colour`, `two_instance_assembly`,
|
||||
//! `assembly_cycles_are_bounded`, `exponential_assemblies_are_bounded`), checking the data the
|
||||
//! import builds from instead of the bodies it builds.
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use super::{
|
||||
BodyKind, Colour, Definition, Frame, Instance, Item, ItemKind, Link, MAX_ASSEMBLY_DEPTH,
|
||||
MAX_VISITS, Operator, Product, ProductStructure, Representation, RepresentationMap, Side,
|
||||
StyledItem, SurfaceStyle, Transformation, read,
|
||||
};
|
||||
use crate::step::geom::Placement;
|
||||
use crate::step::p21::{self, Exchange};
|
||||
use crate::step::units::{Context, Units};
|
||||
|
||||
fn file(data: &str) -> Exchange {
|
||||
let t = format!(
|
||||
"ISO-10303-21;\nHEADER;FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));ENDSEC;\nDATA;\n{data}\nENDSEC;\nEND-ISO-10303-21;\n"
|
||||
);
|
||||
match p21::parse(&t) {
|
||||
Ok(ex) if ex.duplicates.is_empty() => ex,
|
||||
Ok(ex) => panic!("ids defined twice: {:?}\n{t}", ex.duplicates),
|
||||
Err(e) => panic!("{e}\n{t}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn structure(data: &str) -> ProductStructure {
|
||||
read(&file(data)).unwrap_or_else(|e| panic!("{e}"))
|
||||
}
|
||||
|
||||
fn refused(data: &str) -> String {
|
||||
match read(&file(data)) {
|
||||
Ok(s) => panic!("read: {s:?}"),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
/// The application contexts and a body: #8, #9, the solid #101 'Cube' (its shell #100 empty:
|
||||
/// the product structure does not read topology).
|
||||
const BASE: &str = "#8=PRODUCT_DEFINITION_CONTEXT('part definition',#9,'design');\
|
||||
#9=APPLICATION_CONTEXT('');#100=CLOSED_SHELL('',());#101=MANIFOLD_SOLID_BREP('Cube',#100);\n";
|
||||
|
||||
/// A geometric context `#id` in the length unit `unit` (#id+2), radians (#id+3) and an
|
||||
/// uncertainty of 1e-6 in that unit (#id+1).
|
||||
fn context(id: u64, unit: &str) -> String {
|
||||
format!(
|
||||
"#{id}=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#{}))GLOBAL_UNIT_ASSIGNED_CONTEXT((#{},#{}))REPRESENTATION_CONTEXT('',''));\n#{}=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-6),#{},'','');\n#{}={unit};\n#{}=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));\n",
|
||||
id + 1,
|
||||
id + 2,
|
||||
id + 3,
|
||||
id + 1,
|
||||
id + 2,
|
||||
id + 2,
|
||||
id + 3
|
||||
)
|
||||
}
|
||||
|
||||
const MM: &str = "(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.))";
|
||||
const CM: &str = "(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.CENTI.,.METRE.))";
|
||||
/// Millimetres per centimetre as `units` forms it (prefix times metre).
|
||||
const CM_MM: f64 = 1e-2 * 1000.0;
|
||||
|
||||
/// A product `name` and its definition `#pd`: `PRODUCT` #pd+1, `PRODUCT_CONTEXT` #pd+2, formation
|
||||
/// #pd+3, `PRODUCT_DEFINITION_SHAPE` #pd+4.
|
||||
fn product(pd: u64, name: &str) -> String {
|
||||
format!(
|
||||
"#{}=PRODUCT('{name}','{name}','',(#{}));\n#{}=PRODUCT_CONTEXT('',#9,'mechanical');\n#{}=PRODUCT_DEFINITION_FORMATION('','',#{});\n#{pd}=PRODUCT_DEFINITION('design','',#{},#8);\n#{}=PRODUCT_DEFINITION_SHAPE('','',#{pd});\n",
|
||||
pd + 1,
|
||||
pd + 2,
|
||||
pd + 2,
|
||||
pd + 3,
|
||||
pd + 1,
|
||||
pd + 3,
|
||||
pd + 4
|
||||
)
|
||||
}
|
||||
|
||||
fn mm(len: f64) -> Context {
|
||||
Context {
|
||||
units: Units { len, ang: 1.0 },
|
||||
uncertainty: Some(1.0E-6 * len),
|
||||
}
|
||||
}
|
||||
|
||||
fn product_of(entity: u64, name: &str) -> Product {
|
||||
Product {
|
||||
entity,
|
||||
id: name.into(),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn placement(origin: [f64; 3], axis: Option<[f64; 3]>, ref_direction: Option<[f64; 3]>) -> Frame {
|
||||
Frame::Placement(Placement {
|
||||
origin,
|
||||
axis,
|
||||
ref_direction,
|
||||
})
|
||||
}
|
||||
|
||||
/// A part in inches with a colour: SolidCraft's `hand_written_part_in_inches_with_colour`. The
|
||||
/// definition's shape is an empty representation linked to the one with the solid.
|
||||
#[test]
|
||||
fn a_part() {
|
||||
let mut t = String::from(BASE);
|
||||
t += &context(
|
||||
20,
|
||||
"(CONVERSION_BASED_UNIT('INCH',#30)LENGTH_UNIT()NAMED_UNIT(*));\n#30=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(25.4),#31);\n#31=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.))",
|
||||
);
|
||||
t += &product(50, "Widget");
|
||||
t += "#60=ADVANCED_BREP_SHAPE_REPRESENTATION('brep',(#101,#65),#20);\n#61=SHAPE_DEFINITION_REPRESENTATION(#54,#62);\n#62=SHAPE_REPRESENTATION('',(),#20);\n#63=SHAPE_REPRESENTATION_RELATIONSHIP('','',#62,#60);\n";
|
||||
t += "#65=AXIS2_PLACEMENT_3D('',#66,$,$);\n#66=CARTESIAN_POINT('',(0.,0.,0.));\n";
|
||||
t += "#70=STYLED_ITEM('',(#71),#101);\n#71=PRESENTATION_STYLE_ASSIGNMENT((#72));\n#72=SURFACE_STYLE_USAGE(.BOTH.,#73);\n#73=SURFACE_SIDE_STYLE('',(#74));\n#74=SURFACE_STYLE_FILL_AREA(#75);\n#75=FILL_AREA_STYLE('',(#76));\n#76=FILL_AREA_STYLE_COLOUR('',#77);\n#77=COLOUR_RGB('',1.,0.5,0.);\n";
|
||||
let inch = Context {
|
||||
units: Units {
|
||||
len: 25.4 * 1.0,
|
||||
ang: 1.0,
|
||||
},
|
||||
uncertainty: Some(1.0E-6 * (25.4 * 1.0)),
|
||||
};
|
||||
assert_eq!(
|
||||
structure(&t),
|
||||
ProductStructure {
|
||||
products: vec![product_of(51, "Widget")],
|
||||
definitions: vec![Definition {
|
||||
entity: 50,
|
||||
id: "design".into(),
|
||||
description: String::new(),
|
||||
formation: 53,
|
||||
product: 0,
|
||||
shapes: vec![1],
|
||||
}],
|
||||
representations: vec![
|
||||
Representation {
|
||||
entity: 60,
|
||||
kind: "ADVANCED_BREP_SHAPE_REPRESENTATION".into(),
|
||||
name: "brep".into(),
|
||||
items: vec![
|
||||
Item {
|
||||
entity: 101,
|
||||
name: "Cube".into(),
|
||||
kind: ItemKind::Body(BodyKind::ManifoldSolidBrep),
|
||||
},
|
||||
Item {
|
||||
entity: 65,
|
||||
name: String::new(),
|
||||
kind: ItemKind::Other("AXIS2_PLACEMENT_3D".into()),
|
||||
},
|
||||
],
|
||||
context: Some(20),
|
||||
units: inch,
|
||||
},
|
||||
Representation {
|
||||
entity: 62,
|
||||
kind: "SHAPE_REPRESENTATION".into(),
|
||||
name: String::new(),
|
||||
items: Vec::new(),
|
||||
context: Some(20),
|
||||
units: inch,
|
||||
},
|
||||
],
|
||||
maps: Vec::new(),
|
||||
links: vec![Link {
|
||||
entity: 63,
|
||||
rep_1: 1,
|
||||
rep_2: 0,
|
||||
transformation: None,
|
||||
}],
|
||||
occurrences: Vec::new(),
|
||||
roots: vec![0],
|
||||
styles: vec![StyledItem {
|
||||
entity: 70,
|
||||
name: String::new(),
|
||||
item: 101,
|
||||
overrides: None,
|
||||
surface: vec![SurfaceStyle {
|
||||
side: Side::Both,
|
||||
fill: Some(Colour::Rgb {
|
||||
name: String::new(),
|
||||
rgb: [1.0, 0.5, 0.0],
|
||||
}),
|
||||
rendering: None,
|
||||
transparency: None,
|
||||
}],
|
||||
curve: Vec::new(),
|
||||
}],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// A solid outside any product: its representation is still read, and nothing else.
|
||||
#[test]
|
||||
fn a_loose_solid() {
|
||||
let mut t = String::from(BASE);
|
||||
t += &context(20, MM);
|
||||
t += "#60=MANIFOLD_SURFACE_SHAPE_REPRESENTATION('',(#101),#20);\n";
|
||||
let s = structure(&t);
|
||||
assert_eq!(
|
||||
(s.products.len(), s.definitions.len(), s.roots.len()),
|
||||
(0, 0, 0)
|
||||
);
|
||||
assert_eq!(s.representations.len(), 1);
|
||||
assert_eq!(s.representations[0].entity, 60);
|
||||
assert_eq!(s.representations[0].units, mm(1e-3 * 1000.0));
|
||||
}
|
||||
|
||||
/// The occurrence `#n` of `child` in `parent` placed by an `ITEM_DEFINED_TRANSFORMATION` of
|
||||
/// `item_1` (in `rep_1`) onto `item_2` (in `rep_2`): NAUO #n, its PDS #n+1, the transformation
|
||||
/// #n+2, the relationship #n+3, the CDSR #n+4.
|
||||
fn occurrence(
|
||||
n: u64,
|
||||
name: &str,
|
||||
parent: u64,
|
||||
child: u64,
|
||||
reps: (u64, u64),
|
||||
items: (u64, u64),
|
||||
) -> String {
|
||||
let mut t = String::new();
|
||||
let _ = write!(
|
||||
t,
|
||||
"#{n}=NEXT_ASSEMBLY_USAGE_OCCURRENCE('{n}','{name}','',#{parent},#{child},$);\n\
|
||||
#{}=PRODUCT_DEFINITION_SHAPE('','',#{n});\n\
|
||||
#{}=ITEM_DEFINED_TRANSFORMATION('','',#{},#{});\n\
|
||||
#{}=(REPRESENTATION_RELATIONSHIP('','',#{},#{})REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#{})SHAPE_REPRESENTATION_RELATIONSHIP());\n\
|
||||
#{}=CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#{},#{});\n",
|
||||
n + 1,
|
||||
n + 2,
|
||||
items.0,
|
||||
items.1,
|
||||
n + 3,
|
||||
reps.0,
|
||||
reps.1,
|
||||
n + 2,
|
||||
n + 4,
|
||||
n + 3,
|
||||
n + 1
|
||||
);
|
||||
t
|
||||
}
|
||||
|
||||
/// Top uses Sub twice and Part once; Sub uses Part once. Sub's placements are in centimetres,
|
||||
/// Top's and Part's in millimetres. Four occurrences, six instances; the transformation is the
|
||||
/// two placements as written, each in its representation's unit.
|
||||
#[test]
|
||||
fn an_assembly_with_a_shared_sub_assembly() {
|
||||
let mut t = String::from(BASE);
|
||||
t += &context(20, CM);
|
||||
t += &context(30, MM);
|
||||
t += &product(50, "Top");
|
||||
t += &product(70, "Sub");
|
||||
t += &product(90, "Part");
|
||||
// Top (mm): its own frame #161 and the places of its three components #163, #165, #167.
|
||||
t += "#160=SHAPE_REPRESENTATION('Top',(#161,#163,#165,#167),#30);\n#162=SHAPE_DEFINITION_REPRESENTATION(#54,#160);\n";
|
||||
t += "#161=AXIS2_PLACEMENT_3D('',#1610,$,$);\n#1610=CARTESIAN_POINT('',(0.,0.,0.));\n";
|
||||
t += "#163=AXIS2_PLACEMENT_3D('',#1630,#1631,#1632);\n#1630=CARTESIAN_POINT('',(100.,0.,0.));\n#1631=DIRECTION('',(0.,0.,1.));\n#1632=DIRECTION('',(0.,2.,0.));\n";
|
||||
t += "#165=AXIS2_PLACEMENT_3D('',#1650,$,$);\n#1650=CARTESIAN_POINT('',(0.,100.,0.));\n";
|
||||
t += "#167=AXIS2_PLACEMENT_3D('',#1670,$,$);\n#1670=CARTESIAN_POINT('',(0.,0.,100.));\n";
|
||||
// Sub (cm): its frame #171 and the place of its part #173.
|
||||
t += "#170=SHAPE_REPRESENTATION('Sub',(#171,#173),#20);\n#172=SHAPE_DEFINITION_REPRESENTATION(#74,#170);\n";
|
||||
t += "#171=AXIS2_PLACEMENT_3D('',#1710,$,$);\n#1710=CARTESIAN_POINT('',(1.,2.,3.));\n";
|
||||
t += "#173=AXIS2_PLACEMENT_3D('',#1730,$,$);\n#1730=CARTESIAN_POINT('',(5.,0.,0.));\n";
|
||||
// Part (mm): the solid and its frame.
|
||||
t += "#180=ADVANCED_BREP_SHAPE_REPRESENTATION('Part',(#101,#181),#30);\n#182=SHAPE_DEFINITION_REPRESENTATION(#94,#180);\n";
|
||||
t += "#181=AXIS2_PLACEMENT_3D('',#1810,$,$);\n#1810=CARTESIAN_POINT('',(0.,0.,0.));\n";
|
||||
t += &occurrence(600, "Sub:1", 50, 70, (170, 160), (171, 163));
|
||||
t += &occurrence(610, "Sub:2", 50, 70, (170, 160), (171, 165));
|
||||
t += &occurrence(620, "Part:1", 70, 90, (180, 170), (181, 173));
|
||||
t += &occurrence(630, "Part:2", 50, 90, (180, 160), (181, 167));
|
||||
let s = structure(&t);
|
||||
assert_eq!(
|
||||
s.products,
|
||||
[
|
||||
product_of(51, "Top"),
|
||||
product_of(71, "Sub"),
|
||||
product_of(91, "Part")
|
||||
]
|
||||
);
|
||||
// Representations in id order: #160 Top, #170 Sub, #180 Part.
|
||||
let shapes: Vec<_> = s
|
||||
.definitions
|
||||
.iter()
|
||||
.map(|d| (d.entity, d.product, d.shapes.clone()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
shapes,
|
||||
[(50, 0, vec![0]), (70, 1, vec![1]), (90, 2, vec![2])]
|
||||
);
|
||||
assert_eq!(s.roots, [0]);
|
||||
let occ: Vec<_> = s
|
||||
.occurrences
|
||||
.iter()
|
||||
.map(|o| {
|
||||
(
|
||||
o.entity,
|
||||
o.id.as_str(),
|
||||
o.name.as_str(),
|
||||
o.parent,
|
||||
o.child,
|
||||
o.placements.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
occ,
|
||||
[
|
||||
(600, "600", "Sub:1", 0, 1, vec![0]),
|
||||
(610, "610", "Sub:2", 0, 1, vec![1]),
|
||||
(620, "620", "Part:1", 1, 2, vec![2]),
|
||||
(630, "630", "Part:2", 0, 2, vec![3]),
|
||||
]
|
||||
);
|
||||
assert_eq!(s.occurrences[0].reference_designator, None);
|
||||
// Sub:1: Sub's frame (cm: 1, 2, 3 → 10, 20, 30 mm) onto Top's place for it, as written.
|
||||
assert_eq!(
|
||||
s.links[0],
|
||||
Link {
|
||||
entity: 603,
|
||||
rep_1: 1,
|
||||
rep_2: 0,
|
||||
transformation: Some(Transformation::Items {
|
||||
entity: 602,
|
||||
item_1: placement([1.0 * CM_MM, 2.0 * CM_MM, 3.0 * CM_MM], None, None),
|
||||
item_2: placement(
|
||||
[100.0, 0.0, 0.0],
|
||||
Some([0.0, 0.0, 1.0]),
|
||||
Some([0.0, 2.0, 0.0])
|
||||
),
|
||||
}),
|
||||
}
|
||||
);
|
||||
// Part:1: Part's frame (mm) onto Sub's place for it (cm: 5 → 50 mm).
|
||||
assert_eq!(
|
||||
s.links[2].transformation,
|
||||
Some(Transformation::Items {
|
||||
entity: 622,
|
||||
item_1: placement([0.0; 3], None, None),
|
||||
item_2: placement([5.0 * CM_MM, 0.0, 0.0], None, None),
|
||||
})
|
||||
);
|
||||
// Expanded: Top; Sub:1 with its Part:1; Sub:2 with its Part:1; Part:2.
|
||||
let inst = s.instances().unwrap();
|
||||
assert_eq!(
|
||||
inst,
|
||||
[
|
||||
Instance {
|
||||
definition: 0,
|
||||
occurrence: None,
|
||||
parent: None
|
||||
},
|
||||
Instance {
|
||||
definition: 1,
|
||||
occurrence: Some(0),
|
||||
parent: Some(0)
|
||||
},
|
||||
Instance {
|
||||
definition: 2,
|
||||
occurrence: Some(2),
|
||||
parent: Some(1)
|
||||
},
|
||||
Instance {
|
||||
definition: 1,
|
||||
occurrence: Some(1),
|
||||
parent: Some(0)
|
||||
},
|
||||
Instance {
|
||||
definition: 2,
|
||||
occurrence: Some(2),
|
||||
parent: Some(3)
|
||||
},
|
||||
Instance {
|
||||
definition: 2,
|
||||
occurrence: Some(3),
|
||||
parent: Some(0)
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// SolidCraft's `exponential_assemblies_are_bounded`: P0 uses P1 twice, P1 uses P2 twice, … 30
|
||||
/// levels. Reading is linear (31 definitions, 60 occurrences); expanding stops at
|
||||
/// [`MAX_VISITS`]. Three levels expand to 1 + 2 + 4 + 8 instances.
|
||||
#[test]
|
||||
fn a_sub_assembly_shared_at_every_level_is_read_once() {
|
||||
let levels = |n: u64| {
|
||||
let mut t = String::from(BASE);
|
||||
for l in 0..=n {
|
||||
t += &product(1000 + l * 10, &format!("P{l}"));
|
||||
if l < n {
|
||||
for k in 0..2 {
|
||||
let _ = writeln!(
|
||||
t,
|
||||
"#{}=NEXT_ASSEMBLY_USAGE_OCCURRENCE('','','',#{},#{},$);",
|
||||
2000 + l * 10 + k,
|
||||
1000 + l * 10,
|
||||
1000 + (l + 1) * 10
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
t
|
||||
};
|
||||
let s = structure(&levels(30));
|
||||
assert_eq!(
|
||||
(s.definitions.len(), s.occurrences.len(), s.roots.clone()),
|
||||
(31, 60, vec![0])
|
||||
);
|
||||
assert_eq!(
|
||||
s.instances(),
|
||||
Err(format!("more than {MAX_VISITS} assembly instances"))
|
||||
);
|
||||
assert_eq!(structure(&levels(3)).instances().map(|i| i.len()), Ok(15));
|
||||
}
|
||||
|
||||
/// A representation placed twice by `MAPPED_ITEM`s: once by a placement, once by a
|
||||
/// non-uniform operator. The map's origin is in the mapped representation's unit (inches), each
|
||||
/// target in the unit of the representation that lists it (millimetres).
|
||||
#[test]
|
||||
fn mapped_items() {
|
||||
let mut t = String::from(BASE);
|
||||
t += &context(20, MM);
|
||||
t += &context(
|
||||
30,
|
||||
"(CONVERSION_BASED_UNIT('INCH',#40)LENGTH_UNIT()NAMED_UNIT(*));\n#40=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(25.4),#41);\n#41=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.))",
|
||||
);
|
||||
t += &product(50, "Holder");
|
||||
t += "#60=SHAPE_REPRESENTATION('',(#110,#115),#20);\n#61=SHAPE_DEFINITION_REPRESENTATION(#54,#60);\n";
|
||||
t += "#110=MAPPED_ITEM('first',#111,#112);\n#111=REPRESENTATION_MAP(#113,#120);\n";
|
||||
t += "#112=AXIS2_PLACEMENT_3D('',#1120,#1121,$);\n#1120=CARTESIAN_POINT('',(5.,0.,0.));\n#1121=DIRECTION('',(0.,0.,-1.));\n";
|
||||
t += "#113=AXIS2_PLACEMENT_3D('',#1130,$,$);\n#1130=CARTESIAN_POINT('',(1.,0.,0.));\n";
|
||||
t += "#115=MAPPED_ITEM('second',#111,#116);\n";
|
||||
t += "#116=CARTESIAN_TRANSFORMATION_OPERATOR_3D_NON_UNIFORM('','',#1161,$,#1160,2.,$,$,3.);\n#1160=CARTESIAN_POINT('',(0.,7.,0.));\n#1161=DIRECTION('',(0.,1.,0.));\n";
|
||||
t += "#120=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#101,#113),#30);\n";
|
||||
let s = structure(&t);
|
||||
assert_eq!(
|
||||
s.representations[0].items,
|
||||
[
|
||||
Item {
|
||||
entity: 110,
|
||||
name: "first".into(),
|
||||
kind: ItemKind::Mapped {
|
||||
map: 0,
|
||||
target: placement([5.0, 0.0, 0.0], Some([0.0, 0.0, -1.0]), None),
|
||||
},
|
||||
},
|
||||
Item {
|
||||
entity: 115,
|
||||
name: "second".into(),
|
||||
kind: ItemKind::Mapped {
|
||||
map: 0,
|
||||
target: Frame::Operator(Operator {
|
||||
axis1: Some([0.0, 1.0, 0.0]),
|
||||
axis2: None,
|
||||
axis3: None,
|
||||
origin: [0.0, 7.0, 0.0],
|
||||
scale: Some(2.0),
|
||||
scale2: None,
|
||||
scale3: Some(3.0),
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
s.maps,
|
||||
[RepresentationMap {
|
||||
entity: 111,
|
||||
origin: placement([1.0 * (25.4 * 1.0), 0.0, 0.0], None, None),
|
||||
representation: 1,
|
||||
}]
|
||||
);
|
||||
assert_eq!(s.representations[1].entity, 120);
|
||||
assert_eq!(
|
||||
s.representations[1].items[0].kind,
|
||||
ItemKind::Body(BodyKind::ManifoldSolidBrep)
|
||||
);
|
||||
}
|
||||
|
||||
/// Colours on a body and on a face: a fill colour, a rendering colour with a transparency, a
|
||||
/// pre-defined colour, one ISO 10303-46 does not name, a curve style's colour, and an override.
|
||||
#[test]
|
||||
fn colours_of_bodies_and_faces() {
|
||||
let mut t = String::from(BASE);
|
||||
t += "#102=ADVANCED_FACE('',(),#103,.T.);\n#103=PLANE('',$);\n";
|
||||
// The body: fill colour and the curve colour of its edges.
|
||||
t += "#70=STYLED_ITEM('body',(#71,#78),#101);\n#71=PRESENTATION_STYLE_ASSIGNMENT((#72));\n#72=SURFACE_STYLE_USAGE(.POSITIVE.,#73);\n#73=SURFACE_SIDE_STYLE('',(#74));\n#74=SURFACE_STYLE_FILL_AREA(#75);\n#75=FILL_AREA_STYLE('',(#76));\n#76=FILL_AREA_STYLE_COLOUR('',#77);\n#77=COLOUR_RGB('steel',0.2,0.25,1.5);\n";
|
||||
t += "#78=PRESENTATION_STYLE_ASSIGNMENT((#79));\n#79=CURVE_STYLE('',#80,POSITIVE_LENGTH_MEASURE(0.1),#81);\n#80=DRAUGHTING_PRE_DEFINED_CURVE_FONT('continuous');\n#81=DRAUGHTING_PRE_DEFINED_COLOUR('black');\n";
|
||||
// The face, over the body's style: rendered red, a quarter transparent, filled 'mauve'.
|
||||
t += "#90=OVER_RIDING_STYLED_ITEM('face',(#91),#102,#70);\n#91=PRESENTATION_STYLE_ASSIGNMENT((#92));\n#92=SURFACE_STYLE_USAGE(.BOTH.,#93);\n#93=SURFACE_SIDE_STYLE('',(#94,#97));\n";
|
||||
t += "#94=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#95,(#96));\n#95=DRAUGHTING_PRE_DEFINED_COLOUR('red');\n#96=SURFACE_STYLE_TRANSPARENT(0.25);\n";
|
||||
t += "#97=SURFACE_STYLE_FILL_AREA(#98);\n#98=FILL_AREA_STYLE('',(#99));\n#99=FILL_AREA_STYLE_COLOUR('',#89);\n#89=DRAUGHTING_PRE_DEFINED_COLOUR('mauve');\n";
|
||||
let s = structure(&t);
|
||||
assert_eq!(
|
||||
s.styles,
|
||||
[
|
||||
StyledItem {
|
||||
entity: 70,
|
||||
name: "body".into(),
|
||||
item: 101,
|
||||
overrides: None,
|
||||
surface: vec![SurfaceStyle {
|
||||
side: Side::Positive,
|
||||
fill: Some(Colour::Rgb {
|
||||
name: "steel".into(),
|
||||
rgb: [0.2, 0.25, 1.5],
|
||||
}),
|
||||
rendering: None,
|
||||
transparency: None,
|
||||
}],
|
||||
curve: vec![Colour::PreDefined {
|
||||
name: "black".into(),
|
||||
rgb: Some([0.0, 0.0, 0.0]),
|
||||
}],
|
||||
},
|
||||
StyledItem {
|
||||
entity: 90,
|
||||
name: "face".into(),
|
||||
item: 102,
|
||||
overrides: Some(70),
|
||||
surface: vec![SurfaceStyle {
|
||||
side: Side::Both,
|
||||
fill: Some(Colour::PreDefined {
|
||||
name: "mauve".into(),
|
||||
rgb: None,
|
||||
}),
|
||||
rendering: Some(Colour::PreDefined {
|
||||
name: "red".into(),
|
||||
rgb: Some([1.0, 0.0, 0.0]),
|
||||
}),
|
||||
transparency: Some(0.25),
|
||||
}],
|
||||
curve: Vec::new(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// ISO 10303-46's eight pre-defined colours.
|
||||
#[test]
|
||||
fn pre_defined_colours() {
|
||||
let names = [
|
||||
"red", "green", "blue", "yellow", "magenta", "cyan", "black", "white",
|
||||
];
|
||||
let rgb: Vec<_> = names.iter().map(|n| super::pre_defined_rgb(n)).collect();
|
||||
assert_eq!(
|
||||
rgb,
|
||||
[
|
||||
Some([1.0, 0.0, 0.0]),
|
||||
Some([0.0, 1.0, 0.0]),
|
||||
Some([0.0, 0.0, 1.0]),
|
||||
Some([1.0, 1.0, 0.0]),
|
||||
Some([1.0, 0.0, 1.0]),
|
||||
Some([0.0, 1.0, 1.0]),
|
||||
Some([0.0, 0.0, 0.0]),
|
||||
Some([1.0, 1.0, 1.0]),
|
||||
]
|
||||
);
|
||||
assert_eq!(super::pre_defined_rgb("Red"), None);
|
||||
}
|
||||
|
||||
/// Names keep their escapes decoded, and are not trimmed.
|
||||
#[test]
|
||||
fn names_with_escapes() {
|
||||
let mut t = String::from(BASE);
|
||||
t += "#51=PRODUCT('P-\\X2\\00E9\\X0\\',' B\\X\\E4r ''7'' ','\\X2\\5706\\X0\\',(#52));\n#52=PRODUCT_CONTEXT('',#9,'mechanical');\n#53=PRODUCT_DEFINITION_FORMATION('','',#51);\n#50=PRODUCT_DEFINITION('d\\\\1',$,#53,#8);\n";
|
||||
t += &product(70, "Child");
|
||||
t += "#600=NEXT_ASSEMBLY_USAGE_OCCURRENCE('NAUO1','Gr\\X\\FC\\X\\DFe:1','',#50,#70,'R\\X2\\00E9\\X0\\f');\n";
|
||||
let s = structure(&t);
|
||||
assert_eq!(
|
||||
s.products[0],
|
||||
Product {
|
||||
entity: 51,
|
||||
id: "P-\u{e9}".into(),
|
||||
name: " B\u{e4}r '7' ".into(),
|
||||
description: "\u{5706}".into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
s.definitions[0].id.as_str(),
|
||||
s.definitions[0].description.as_str()
|
||||
),
|
||||
("d\\1", "")
|
||||
);
|
||||
let o = &s.occurrences[0];
|
||||
assert_eq!(
|
||||
(
|
||||
o.id.as_str(),
|
||||
o.name.as_str(),
|
||||
o.reference_designator.as_deref()
|
||||
),
|
||||
("NAUO1", "Gr\u{fc}\u{df}e:1", Some("R\u{e9}f"))
|
||||
);
|
||||
}
|
||||
|
||||
/// A chain of `n` + 1 definitions, each the only component of the one before.
|
||||
fn chain(n: u64) -> String {
|
||||
let mut t = String::from(BASE);
|
||||
for l in 0..=n {
|
||||
t += &product(1000 + l * 10, &format!("P{l}"));
|
||||
if l < n {
|
||||
let _ = writeln!(
|
||||
t,
|
||||
"#{}=NEXT_ASSEMBLY_USAGE_OCCURRENCE('','','',#{},#{},$);",
|
||||
5000 + l,
|
||||
1000 + l * 10,
|
||||
1000 + (l + 1) * 10
|
||||
);
|
||||
}
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nesting_is_bounded() {
|
||||
let depth = MAX_ASSEMBLY_DEPTH as u64;
|
||||
let s = structure(&chain(depth));
|
||||
assert_eq!(s.instances().map(|i| i.len()), Ok(MAX_ASSEMBLY_DEPTH + 1));
|
||||
assert_eq!(refused(&chain(depth + 1)), "assembly nested too deeply");
|
||||
}
|
||||
|
||||
/// What is refused, with its message.
|
||||
#[test]
|
||||
fn refusals_say_why() {
|
||||
let two = || {
|
||||
let mut t = String::from(BASE);
|
||||
t += &product(50, "A");
|
||||
t += &product(70, "B");
|
||||
t
|
||||
};
|
||||
// A contains B contains A (SolidCraft's `assembly_cycles_are_bounded`).
|
||||
let cycle = two()
|
||||
+ "#90=NEXT_ASSEMBLY_USAGE_OCCURRENCE('1','','',#50,#70,$);\n#91=NEXT_ASSEMBLY_USAGE_OCCURRENCE('2','','',#70,#50,$);\n";
|
||||
assert_eq!(refused(&cycle), "assembly cycle");
|
||||
let itself = two() + "#90=NEXT_ASSEMBLY_USAGE_OCCURRENCE('1','','',#50,#50,$);\n";
|
||||
assert_eq!(refused(&itself), "assembly cycle");
|
||||
let missing_child = two() + "#90=NEXT_ASSEMBLY_USAGE_OCCURRENCE('1','','',#50,#999,$);\n";
|
||||
assert_eq!(
|
||||
refused(&missing_child),
|
||||
"occurrence #90: #999: expected a PRODUCT_DEFINITION"
|
||||
);
|
||||
let not_a_definition = two() + "#90=NEXT_ASSEMBLY_USAGE_OCCURRENCE('1','','',#50,#51,$);\n";
|
||||
assert_eq!(
|
||||
refused(¬_a_definition),
|
||||
"occurrence #90: #51: expected a PRODUCT_DEFINITION"
|
||||
);
|
||||
let missing_product = String::from(BASE)
|
||||
+ "#53=PRODUCT_DEFINITION_FORMATION('','',#51);\n#50=PRODUCT_DEFINITION('design','',#53,#8);\n";
|
||||
assert_eq!(refused(&missing_product), "#51: expected a PRODUCT");
|
||||
let missing_rep = two() + "#61=SHAPE_DEFINITION_REPRESENTATION(#54,#999);\n";
|
||||
assert_eq!(refused(&missing_rep), "missing entity #999");
|
||||
let not_a_rep = two() + "#61=SHAPE_DEFINITION_REPRESENTATION(#54,#101);\n";
|
||||
assert_eq!(
|
||||
refused(¬_a_rep),
|
||||
"#101: expected a representation, found MANIFOLD_SOLID_BREP"
|
||||
);
|
||||
let missing_item = two()
|
||||
+ "#60=SHAPE_REPRESENTATION('',(#101,#999),$);\n#61=SHAPE_DEFINITION_REPRESENTATION(#54,#60);\n";
|
||||
assert_eq!(refused(&missing_item), "missing entity #999");
|
||||
let mapped = |rest: &str| {
|
||||
two()
|
||||
+ "#60=SHAPE_REPRESENTATION('',(#110),$);\n#61=SHAPE_DEFINITION_REPRESENTATION(#54,#60);\n#110=MAPPED_ITEM('',#111,#112);\n#112=AXIS2_PLACEMENT_3D('',#113,$,$);\n#113=CARTESIAN_POINT('',(0.,0.,0.));\n"
|
||||
+ rest
|
||||
};
|
||||
assert_eq!(
|
||||
refused(&mapped("#111=REPRESENTATION_MAP(#112,#60);\n")),
|
||||
"mapped item cycle"
|
||||
);
|
||||
assert_eq!(
|
||||
refused(&mapped("#111=SHAPE_REPRESENTATION('',(),$);\n")),
|
||||
"#110: mapping source #111 is a SHAPE_REPRESENTATION, not a REPRESENTATION_MAP"
|
||||
);
|
||||
assert_eq!(
|
||||
refused(&mapped("#111=REPRESENTATION_MAP(#112,#999);\n")),
|
||||
"missing entity #999"
|
||||
);
|
||||
assert_eq!(
|
||||
refused(&mapped(
|
||||
"#111=REPRESENTATION_MAP(#112,#120);\n#120=SHAPE_REPRESENTATION('',(#101),$);\n"
|
||||
)
|
||||
.replace(
|
||||
"#110=MAPPED_ITEM('',#111,#112);",
|
||||
"#110=MAPPED_ITEM('',#111,#114);#114=CARTESIAN_TRANSFORMATION_OPERATOR_3D('','',$,$,#113,0.,$);"
|
||||
)),
|
||||
"mapped item #110: operator scale: expected a number other than zero"
|
||||
);
|
||||
assert_eq!(
|
||||
refused(&mapped(
|
||||
"#111=REPRESENTATION_MAP(#113,#120);\n#120=SHAPE_REPRESENTATION('',(#101),$);\n"
|
||||
)),
|
||||
"representation map #111: #113: expected a placement, found CARTESIAN_POINT"
|
||||
);
|
||||
let styled = |rest: &str| two() + rest;
|
||||
assert_eq!(
|
||||
refused(&styled("#170=STYLED_ITEM('',(),#999);\n")),
|
||||
"styled item #170: missing entity #999"
|
||||
);
|
||||
assert_eq!(
|
||||
refused(&styled(
|
||||
"#170=STYLED_ITEM('',(#171),#101);\n#171=PRESENTATION_STYLE_ASSIGNMENT((#172));\n#172=SURFACE_STYLE_USAGE(.BOTH.,#173);\n#173=SURFACE_SIDE_STYLE('',(#174));\n#174=SURFACE_STYLE_RENDERING(.NORMAL_SHADING.,#175);\n#175=COLOUR_RGB('',1.,$,0.);\n"
|
||||
)),
|
||||
"styled item #170: green: expected a number"
|
||||
);
|
||||
let placed = two()
|
||||
+ "#60=SHAPE_REPRESENTATION('',(),$);\n#80=SHAPE_REPRESENTATION('',(),$);\n#90=NEXT_ASSEMBLY_USAGE_OCCURRENCE('1','','',#50,#70,$);\n#91=PRODUCT_DEFINITION_SHAPE('','',#90);\n#93=(REPRESENTATION_RELATIONSHIP('','',#60,#80)REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#92)SHAPE_REPRESENTATION_RELATIONSHIP());\n#94=CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#93,#91);\n";
|
||||
assert_eq!(refused(&placed), "relationship #93: missing entity #92");
|
||||
}
|
||||
+1084
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+466
@@ -0,0 +1,466 @@
|
||||
//! The units of a representation context (ISO 10303-41 measures and units, ISO 10303-43
|
||||
//! representation contexts): `GLOBAL_UNIT_ASSIGNED_CONTEXT`'s length and plane angle units and
|
||||
//! `GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT`'s distance uncertainty, read from a parsed
|
||||
//! [`Exchange`]. Missing or malformed units read as millimetres and radians; nothing here fails.
|
||||
//!
|
||||
//! A unit is a factor: millimetres per file length unit, radians per file plane angle unit. An
|
||||
//! SI unit is its prefix times its base (`METRE` = 1000 mm, `RADIAN` = 1 rad); a
|
||||
//! `CONVERSION_BASED_UNIT` is the value of its measure times the factor of the measure's own unit
|
||||
//! (an inch is `LENGTH_MEASURE(25.4)` of millimetres, a degree `PLANE_ANGLE_MEASURE(0.01745…)`
|
||||
//! of radians). Only multiplications: the factor of a degree is the number the file writes, not
|
||||
//! a π computed here (the record computes no geometric quantity, docs/layers.md §4).
|
||||
//!
|
||||
//! Origin: SolidCraft's `crates/step/src/units.rs` and the `Units` type of
|
||||
//! `crates/step/src/read.rs` at `a5b71ca2` (MIT OR Apache-2.0), carried over with their
|
||||
//! behaviour unchanged.
|
||||
//!
|
||||
//! Deviations from SolidCraft: [`Units`] lives here (it was in `read.rs`, which is not carried
|
||||
//! over yet). The nesting bound of a `CONVERSION_BASED_UNIT` chain is the named
|
||||
//! `MAX_CONVERSION_DEPTH` (was the literal 4). Otherwise only formatting and doc comments on every
|
||||
//! public item.
|
||||
|
||||
use super::p21::{Exchange, Param};
|
||||
|
||||
/// Most `CONVERSION_BASED_UNIT` links followed from one unit (a unit defined from a unit defined
|
||||
/// from …); a deeper chain, or a cycle, reads as no unit.
|
||||
const MAX_CONVERSION_DEPTH: usize = 4;
|
||||
|
||||
/// The units of a representation context.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Units {
|
||||
/// Millimetres per file length unit.
|
||||
pub len: f64,
|
||||
/// Radians per file plane angle unit.
|
||||
pub ang: f64,
|
||||
}
|
||||
|
||||
impl Default for Units {
|
||||
/// Millimetres and radians.
|
||||
fn default() -> Self {
|
||||
Units { len: 1.0, ang: 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// What a representation context says about its numbers.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Context {
|
||||
/// The length and plane angle units (millimetres and radians where the context names none
|
||||
/// that can be read).
|
||||
pub units: Units,
|
||||
/// The context's distance uncertainty in millimetres (the last `UNCERTAINTY_MEASURE_WITH_UNIT`
|
||||
/// it lists), if any.
|
||||
pub uncertainty: Option<f64>,
|
||||
}
|
||||
|
||||
/// The factor of an SI prefix (`$` or an unknown prefix: 1).
|
||||
fn si_prefix(p: Option<&Param>) -> f64 {
|
||||
match p.and_then(Param::as_enum) {
|
||||
Some("EXA") => 1e18,
|
||||
Some("PETA") => 1e15,
|
||||
Some("TERA") => 1e12,
|
||||
Some("GIGA") => 1e9,
|
||||
Some("MEGA") => 1e6,
|
||||
Some("KILO") => 1e3,
|
||||
Some("HECTO") => 1e2,
|
||||
Some("DECA") => 1e1,
|
||||
Some("DECI") => 1e-1,
|
||||
Some("CENTI") => 1e-2,
|
||||
Some("MILLI") => 1e-3,
|
||||
Some("MICRO") => 1e-6,
|
||||
Some("NANO") => 1e-9,
|
||||
Some("PICO") => 1e-12,
|
||||
Some("FEMTO") => 1e-15,
|
||||
Some("ATTO") => 1e-18,
|
||||
_ => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// What a unit measures.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum UnitKind {
|
||||
/// A `LENGTH_UNIT`.
|
||||
Length,
|
||||
/// A `PLANE_ANGLE_UNIT`.
|
||||
Angle,
|
||||
/// Any other unit (solid angle, mass, …).
|
||||
Other,
|
||||
}
|
||||
|
||||
/// A unit's kind and size: millimetres for lengths, radians for plane angles (1 for anything
|
||||
/// else, or a unit that cannot be read). `None` when `#id` is not an entity, or is a
|
||||
/// `CONVERSION_BASED_UNIT` whose measure cannot be read or is not finite and positive.
|
||||
pub fn unit_factor(ex: &Exchange, id: u64) -> Option<(UnitKind, f64)> {
|
||||
factor(ex, id, 0)
|
||||
}
|
||||
|
||||
fn factor(ex: &Exchange, id: u64, depth: usize) -> Option<(UnitKind, f64)> {
|
||||
if depth > MAX_CONVERSION_DEPTH {
|
||||
return None;
|
||||
}
|
||||
let e = ex.get(id)?;
|
||||
let kind = if e.has("LENGTH_UNIT") {
|
||||
UnitKind::Length
|
||||
} else if e.has("PLANE_ANGLE_UNIT") {
|
||||
UnitKind::Angle
|
||||
} else {
|
||||
UnitKind::Other
|
||||
};
|
||||
if let Some(si) = e.record("SI_UNIT") {
|
||||
let base = match si.get(1).and_then(Param::as_enum) {
|
||||
Some("METRE") => 1000.0,
|
||||
Some("RADIAN") => 1.0,
|
||||
_ => return Some((kind, 1.0)),
|
||||
};
|
||||
return Some((kind, si_prefix(si.first()) * base));
|
||||
}
|
||||
if let Some(cb) = e.record("CONVERSION_BASED_UNIT") {
|
||||
let m = ex.get(cb.get(1)?.as_ref_id()?)?;
|
||||
let mp = m.params();
|
||||
let v = mp.first()?.as_f64()?;
|
||||
let (_, f) = factor(ex, mp.get(1)?.as_ref_id()?, depth + 1)?;
|
||||
let x = v * f;
|
||||
return (x.is_finite() && x > 0.0).then_some((kind, x));
|
||||
}
|
||||
Some((kind, 1.0))
|
||||
}
|
||||
|
||||
/// The units and uncertainty of the representation context `#id` (the defaults when `#id` is
|
||||
/// not an entity or lists no readable unit).
|
||||
///
|
||||
/// Each unit `GLOBAL_UNIT_ASSIGNED_CONTEXT` lists that reads as a finite, positive length or
|
||||
/// plane angle factor sets that unit; a later one wins. Each `UNCERTAINTY_MEASURE_WITH_UNIT` that
|
||||
/// `GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT` lists sets the uncertainty to its value times the factor
|
||||
/// of its unit (the context's length unit when that cannot be read), or clears it when the
|
||||
/// product is not finite and positive; the last one wins.
|
||||
pub fn context(ex: &Exchange, id: u64) -> Context {
|
||||
let mut c = Context::default();
|
||||
let Some(e) = ex.get(id) else { return c };
|
||||
if let Some(units) = e
|
||||
.record("GLOBAL_UNIT_ASSIGNED_CONTEXT")
|
||||
.and_then(<[Param]>::first)
|
||||
.and_then(Param::as_list)
|
||||
{
|
||||
for uid in units.iter().filter_map(Param::as_ref_id) {
|
||||
match unit_factor(ex, uid) {
|
||||
Some((UnitKind::Length, f)) if f.is_finite() && f > 0.0 => c.units.len = f,
|
||||
Some((UnitKind::Angle, f)) if f.is_finite() && f > 0.0 => c.units.ang = f,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(unc) = e
|
||||
.record("GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT")
|
||||
.and_then(<[Param]>::first)
|
||||
.and_then(Param::as_list)
|
||||
{
|
||||
for uid in unc.iter().filter_map(Param::as_ref_id) {
|
||||
if let Some(m) = ex.get(uid)
|
||||
&& let Some(v) = m.params().first().and_then(Param::as_f64)
|
||||
{
|
||||
let f = m
|
||||
.params()
|
||||
.get(1)
|
||||
.and_then(Param::as_ref_id)
|
||||
.and_then(|x| unit_factor(ex, x))
|
||||
.map(|x| x.1);
|
||||
c.uncertainty =
|
||||
Some(v * f.unwrap_or(c.units.len)).filter(|t| t.is_finite() && *t > 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::step::p21;
|
||||
use std::f64::consts::PI;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
/// Millimetres, radians and steradians as SolidCraft's writer and its topology tests
|
||||
/// (`crates/step/src/topology_tests.rs`, `file`) write them.
|
||||
const MM: &str = "#1=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));\
|
||||
#2=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));\
|
||||
#3=(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT());";
|
||||
|
||||
fn parse(data: &str) -> Exchange {
|
||||
let t = format!(
|
||||
"ISO-10303-21;\nHEADER;FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));ENDSEC;\nDATA;\n{data}\nENDSEC;\nEND-ISO-10303-21;\n"
|
||||
);
|
||||
match p21::parse(&t) {
|
||||
Ok(ex) => ex,
|
||||
Err(e) => panic!("{e}\n{t}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// A context `#100` with the units `units` and the uncertainties `unc` (entity ids).
|
||||
fn ctx(data: &str, units: &str, unc: &str) -> Context {
|
||||
let c = format!(
|
||||
"#100=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT(({unc}))\
|
||||
GLOBAL_UNIT_ASSIGNED_CONTEXT(({units}))REPRESENTATION_CONTEXT('',''));"
|
||||
);
|
||||
context(&parse(&format!("{data}{c}")), 100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solidcraft_file_context() {
|
||||
// SolidCraft's topology test file: mm, rad, sr and an uncertainty of 1e-7 mm.
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{MM}#4=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#1,'distance_accuracy_value','');"
|
||||
),
|
||||
"#1,#2,#3",
|
||||
"#4",
|
||||
);
|
||||
assert_eq!(c.units, Units { len: 1.0, ang: 1.0 });
|
||||
// 1e-7 times MILLI (1e-3) times METRE (1000 mm).
|
||||
assert_eq!(c.uncertainty, Some(1e-7 * (1e-3 * 1000.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn si_prefixes() {
|
||||
for (prefix, mm) in [
|
||||
(".MILLI.", 1e-3 * 1000.0),
|
||||
("$", 1000.0),
|
||||
(".MICRO.", 1e-6 * 1000.0),
|
||||
(".CENTI.", 1e-2 * 1000.0),
|
||||
(".KILO.", 1e3 * 1000.0),
|
||||
] {
|
||||
let c = ctx(
|
||||
&format!("#1=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT({prefix},.METRE.));"),
|
||||
"#1",
|
||||
"",
|
||||
);
|
||||
assert_eq!(c.units.len, mm, "{prefix}");
|
||||
assert_eq!(c.units.ang, 1.0, "{prefix}");
|
||||
assert_eq!(c.uncertainty, None, "{prefix}");
|
||||
}
|
||||
// The products the table defines: a millimetre is exactly 1, a metre 1000.
|
||||
assert_eq!(1e-3 * 1000.0, 1.0);
|
||||
let ex = parse("#1=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MICRO.,.METRE.));");
|
||||
assert_eq!(unit_factor(&ex, 1), Some((UnitKind::Length, 1e-6 * 1000.0)));
|
||||
// A milliradian.
|
||||
let ex = parse("#1=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT(.MILLI.,.RADIAN.));");
|
||||
assert_eq!(unit_factor(&ex, 1), Some((UnitKind::Angle, 1e-3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inches_and_degrees() {
|
||||
// An inch as 25.4 mm, a degree as π/180 rad (written as the shortest decimal of
|
||||
// PI / 180, so it reads as exactly that double).
|
||||
let data = format!(
|
||||
"{MM}#10=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(25.4),#1);\
|
||||
#11=(CONVERSION_BASED_UNIT('INCH',#10)LENGTH_UNIT()NAMED_UNIT(#12));\
|
||||
#12=DIMENSIONAL_EXPONENTS(1.,0.,0.,0.,0.,0.,0.);\
|
||||
#13=PLANE_ANGLE_MEASURE_WITH_UNIT(PLANE_ANGLE_MEASURE(0.017453292519943295),#2);\
|
||||
#14=(CONVERSION_BASED_UNIT('DEGREE',#13)NAMED_UNIT(#15)PLANE_ANGLE_UNIT());\
|
||||
#15=DIMENSIONAL_EXPONENTS(0.,0.,0.,0.,0.,0.,0.);"
|
||||
);
|
||||
let c = ctx(&data, "#11,#14,#3", "");
|
||||
assert_eq!(
|
||||
c.units,
|
||||
Units {
|
||||
len: 25.4,
|
||||
ang: PI / 180.0
|
||||
}
|
||||
);
|
||||
// An inch defined from metres: 0.0254 times 1000 mm.
|
||||
let c = ctx(
|
||||
"#1=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT($,.METRE.));\
|
||||
#10=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.0254),#1);\
|
||||
#11=(CONVERSION_BASED_UNIT('INCH',#10)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#11",
|
||||
"",
|
||||
);
|
||||
assert_eq!(c.units.len, 0.0254 * 1000.0);
|
||||
// A foot defined from the inch: a chain of two conversions, 12 times 25.4 mm.
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{data}#20=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(12.),#11);\
|
||||
#21=(CONVERSION_BASED_UNIT('FOOT',#20)LENGTH_UNIT()NAMED_UNIT(*));"
|
||||
),
|
||||
"#21",
|
||||
"",
|
||||
);
|
||||
assert_eq!(c.units.len, 12.0 * 25.4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_broken_units_read_as_mm_and_rad() {
|
||||
let mm_rad = Units { len: 1.0, ang: 1.0 };
|
||||
// No such context, or a context without units.
|
||||
assert_eq!(context(&parse(MM), 100), Context::default());
|
||||
assert_eq!(context(&parse(MM), 1), Context::default());
|
||||
assert_eq!(Context::default().units, mm_rad);
|
||||
for (data, units) in [
|
||||
// Dangling references.
|
||||
("", "#7,#8"),
|
||||
// An SI unit with an unknown base: factor 1.
|
||||
(
|
||||
"#1=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.FURLONG.));",
|
||||
"#1",
|
||||
),
|
||||
// A conversion whose measure is missing, unreadable, zero, negative or infinite.
|
||||
(
|
||||
"#1=(CONVERSION_BASED_UNIT('INCH',#9)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#1",
|
||||
),
|
||||
(
|
||||
"#2=(NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.)LENGTH_UNIT());\
|
||||
#3=LENGTH_MEASURE_WITH_UNIT($,#2);\
|
||||
#1=(CONVERSION_BASED_UNIT('INCH',#3)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#1",
|
||||
),
|
||||
(
|
||||
"#2=(NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.)LENGTH_UNIT());\
|
||||
#3=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.),#2);\
|
||||
#1=(CONVERSION_BASED_UNIT('INCH',#3)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#1",
|
||||
),
|
||||
(
|
||||
"#2=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));\
|
||||
#3=PLANE_ANGLE_MEASURE_WITH_UNIT(PLANE_ANGLE_MEASURE(-1.),#2);\
|
||||
#1=(CONVERSION_BASED_UNIT('DEGREE',#3)PLANE_ANGLE_UNIT()NAMED_UNIT(*));",
|
||||
"#1",
|
||||
),
|
||||
(
|
||||
"#2=(NAMED_UNIT(*)SI_UNIT(.EXA.,.METRE.)LENGTH_UNIT());\
|
||||
#3=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E300),#2);\
|
||||
#1=(CONVERSION_BASED_UNIT('BIG',#3)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#1",
|
||||
),
|
||||
// A conversion that refers to itself: the depth bound ends it.
|
||||
(
|
||||
"#3=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(2.),#1);\
|
||||
#1=(CONVERSION_BASED_UNIT('LOOP',#3)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#1",
|
||||
),
|
||||
// Units that are neither lengths nor plane angles.
|
||||
(MM, "#3"),
|
||||
// An empty list.
|
||||
(MM, ""),
|
||||
] {
|
||||
let c = ctx(data, units, "");
|
||||
assert_eq!(c.units, mm_rad, "{data} / {units}");
|
||||
}
|
||||
// A unit list that is not a list.
|
||||
let ex = parse(&format!(
|
||||
"{MM}#100=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNIT_ASSIGNED_CONTEXT(#1)REPRESENTATION_CONTEXT('',''));"
|
||||
));
|
||||
assert_eq!(context(&ex, 100), Context::default());
|
||||
// A broken length unit leaves the readable angle unit in place.
|
||||
let c = ctx(
|
||||
"#2=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT(.MILLI.,.RADIAN.));\
|
||||
#1=(CONVERSION_BASED_UNIT('INCH',#9)LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
"#1,#2",
|
||||
"",
|
||||
);
|
||||
assert_eq!(
|
||||
c.units,
|
||||
Units {
|
||||
len: 1.0,
|
||||
ang: 1e-3
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_depth_is_bounded() {
|
||||
// #1 is millimetres; #2..=#7 each 2 of the one before, so #(1+k) is 2^k mm and
|
||||
// MAX_CONVERSION_DEPTH links are followed from the unit asked for.
|
||||
let mut data = String::from("#1=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));");
|
||||
for k in 2..=7 {
|
||||
write!(
|
||||
data,
|
||||
"#{m}=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(2.),#{p});\
|
||||
#{k}=(CONVERSION_BASED_UNIT('U',#{m})LENGTH_UNIT()NAMED_UNIT(*));",
|
||||
m = 100 + k,
|
||||
p = k - 1
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let ex = parse(&data);
|
||||
assert_eq!(unit_factor(&ex, 5), Some((UnitKind::Length, 16.0)));
|
||||
assert_eq!(unit_factor(&ex, 6), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncertainty_in_file_units() {
|
||||
let inch = format!(
|
||||
"{MM}#10=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(25.4),#1);\
|
||||
#11=(CONVERSION_BASED_UNIT('INCH',#10)LENGTH_UNIT()NAMED_UNIT(*));"
|
||||
);
|
||||
// 1e-4 inch is 1e-4 * 25.4 mm.
|
||||
let c = ctx(
|
||||
&format!("{inch}#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-04),#11,'d','');"),
|
||||
"#11,#2",
|
||||
"#20",
|
||||
);
|
||||
assert_eq!(c.units.len, 25.4);
|
||||
assert_eq!(c.uncertainty, Some(1e-4 * 25.4));
|
||||
// A measure in metres in an inch context: its own unit, 1e-6 * 1000 mm.
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{inch}#12=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT($,.METRE.));\
|
||||
#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-06),#12,'d','');"
|
||||
),
|
||||
"#11",
|
||||
"#20",
|
||||
);
|
||||
assert_eq!(c.uncertainty, Some(1e-6 * 1000.0));
|
||||
// A measure whose unit cannot be read is in the context's length unit.
|
||||
let c = ctx(
|
||||
&format!("{inch}#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-04),$,'d','');"),
|
||||
"#11",
|
||||
"#20",
|
||||
);
|
||||
assert_eq!(c.uncertainty, Some(1e-4 * 25.4));
|
||||
// The last one listed wins.
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{MM}#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-04),#1,'d','');\
|
||||
#21=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(2.E-04),#1,'d','');"
|
||||
),
|
||||
"#1",
|
||||
"#20,#21",
|
||||
);
|
||||
assert_eq!(c.uncertainty, Some(2e-4 * (1e-3 * 1000.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncertainty_zero_negative_or_infinite_is_none() {
|
||||
for v in ["0.", "-1.E-04", "1.E300"] {
|
||||
// In exametres (1e21 mm): 0, negative, and 1e300 * 1e21 mm, which is infinite.
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{MM}#12=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.EXA.,.METRE.));\
|
||||
#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE({v}),#12,'d','');"
|
||||
),
|
||||
"#1",
|
||||
"#20",
|
||||
);
|
||||
assert_eq!(c.uncertainty, None, "{v}");
|
||||
}
|
||||
// A bad one after a good one clears it (the last one wins).
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{MM}#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-04),#1,'d','');\
|
||||
#21=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.),#1,'d','');"
|
||||
),
|
||||
"#1",
|
||||
"#20,#21",
|
||||
);
|
||||
assert_eq!(c.uncertainty, None);
|
||||
// One without a readable value, or a dangling reference, is skipped.
|
||||
let c = ctx(
|
||||
&format!(
|
||||
"{MM}#20=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-04),#1,'d','');\
|
||||
#21=UNCERTAINTY_MEASURE_WITH_UNIT($,#1,'d','');"
|
||||
),
|
||||
"#1",
|
||||
"#20,#21,#99",
|
||||
);
|
||||
assert_eq!(c.uncertainty, Some(1e-4 * (1e-3 * 1000.0)));
|
||||
}
|
||||
}
|
||||
+644
-1
@@ -1,4 +1,5 @@
|
||||
//! The Part 21 parser over a STEP corpus: every file parses.
|
||||
//! The Part 21 parser over a STEP corpus: every file parses, the units of its representation
|
||||
//! contexts, and its Part 42 curves and surfaces.
|
||||
//!
|
||||
//! Ignored by default; the corpus is not in the repository. Run it with the directory of
|
||||
//! `.stp` / `.step` files (read in place, recursively) in `SOLIDCORE_STEP_CORPUS`:
|
||||
@@ -13,6 +14,31 @@
|
||||
//! caller holding the file's bytes would. The test fails when any file does not parse. The
|
||||
//! STEP files committed to the repository are parsed by `repository_files_parse`, which always
|
||||
//! runs.
|
||||
//!
|
||||
//! `corpus_units` (ignored too, same variable and command with `corpus_units` as the filter)
|
||||
//! reads every geometric representation context with assigned units in each file and prints
|
||||
//! one line per file and a table of how often each length and plane angle unit occurs, and
|
||||
//! how many contexts state an uncertainty. It fails when a file does not parse. Files with no
|
||||
//! context whose length unit can be read are listed and counted, not failed: a file written
|
||||
//! with Part 21 short names (`SUNT`, `LNGUNT`, `GMRPCN`, …) or one without geometry has none.
|
||||
//!
|
||||
//! `corpus_geometry` (ignored too, same variable, `corpus_geometry` as the filter) reads every
|
||||
//! curve and surface entity of each file with `step::read::Reader`, in the units of the file's
|
||||
//! first geometric context: curves in model space with `curve`, the curves of a parameter space
|
||||
//! through their `PCURVE` with `pcurve`, surfaces with `surface`. It prints one line per file
|
||||
//! and, per group and entity kind, how many were read and how many refused, with the reasons.
|
||||
//! It also lists the entities where a check the record no longer makes (a direction shorter than
|
||||
//! 1e-12, unit factors equal within 1e-12) would have decided otherwise. It fails when a file
|
||||
//! does not parse; refusals are counted, not failed.
|
||||
//!
|
||||
//! `corpus_product` (ignored too, same variable, `corpus_product` as the filter) reads the
|
||||
//! product structure of each file with `step::product::read` and expands its assembly tree. It
|
||||
//! prints one line per file and the files with assemblies, and checks the structure against the
|
||||
//! file's own entity counts: products, definitions, occurrences, body items (every solid the
|
||||
//! file has is an item of a representation the structure holds), relationships with a
|
||||
//! transformation and styled items must all be there; a mapped item it does not hold must be
|
||||
//! listed only by representations it does not reach (counted by the kind that lists them). It
|
||||
//! fails when a file does not parse, is refused, or a count differs.
|
||||
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
@@ -21,9 +47,11 @@
|
||||
clippy::indexing_slicing
|
||||
)] // test
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use solidcore_record::step::p21;
|
||||
use solidcore_record::step::units::{self, UnitKind};
|
||||
|
||||
fn files(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
let Ok(entries) = std::fs::read_dir(dir) else {
|
||||
@@ -109,6 +137,116 @@ fn corpus_parses() {
|
||||
);
|
||||
}
|
||||
|
||||
/// A length factor's name (millimetres per unit).
|
||||
fn length_name(f: f64) -> String {
|
||||
match f {
|
||||
1.0 => "mm".into(),
|
||||
25.4 => "inch".into(),
|
||||
1000.0 => "m".into(),
|
||||
10.0 => "cm".into(),
|
||||
304.8 => "foot".into(),
|
||||
_ => format!("{f} mm"),
|
||||
}
|
||||
}
|
||||
|
||||
/// A plane angle factor's name (radians per unit). A degree is the number the file writes:
|
||||
/// `degree` when it is π/180 to 12 digits or more, else the written factor, so a file that
|
||||
/// rounds π/180 shorter (`0.0174532925`) shows in the table as what it is.
|
||||
fn angle_name(f: f64) -> String {
|
||||
if f == 1.0 {
|
||||
"rad".into()
|
||||
} else if (f - std::f64::consts::PI / 180.0).abs() <= 1e-14 {
|
||||
"degree".into()
|
||||
} else {
|
||||
format!("{f} rad")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs a STEP corpus in SOLIDCORE_STEP_CORPUS"]
|
||||
fn corpus_units() {
|
||||
let Some(dir) = std::env::var_os("SOLIDCORE_STEP_CORPUS") else {
|
||||
eprintln!("SOLIDCORE_STEP_CORPUS is not set");
|
||||
return;
|
||||
};
|
||||
let root = Path::new(&dir);
|
||||
let mut paths = Vec::new();
|
||||
files(root, &mut paths);
|
||||
assert!(!paths.is_empty(), "no STEP files under {dir:?}");
|
||||
let mut failures = Vec::new();
|
||||
let mut unread = Vec::new();
|
||||
let mut lengths: BTreeMap<String, usize> = BTreeMap::new();
|
||||
let mut angles: BTreeMap<String, usize> = BTreeMap::new();
|
||||
let (mut contexts, mut with_uncertainty, mut files_read) = (0usize, 0usize, 0usize);
|
||||
for path in &paths {
|
||||
let name = path
|
||||
.strip_prefix(root)
|
||||
.unwrap_or(path)
|
||||
.display()
|
||||
.to_string();
|
||||
let ex = match std::fs::read(path)
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|b| p21::parse(&String::from_utf8_lossy(&b)).map_err(|e| e.to_string()))
|
||||
{
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
println!("{name}: ERROR {e}");
|
||||
failures.push(format!("{name}: {e}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut here = Vec::new();
|
||||
let mut readable = false;
|
||||
for (&id, e) in &ex.entities {
|
||||
if !(e.has("GEOMETRIC_REPRESENTATION_CONTEXT") && e.has("GLOBAL_UNIT_ASSIGNED_CONTEXT"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Whether the context names a length unit that reads as a factor, rather than
|
||||
// falling back to millimetres.
|
||||
readable |= e
|
||||
.record("GLOBAL_UNIT_ASSIGNED_CONTEXT")
|
||||
.and_then(<[p21::Param]>::first)
|
||||
.and_then(p21::Param::as_list)
|
||||
.is_some_and(|l| {
|
||||
l.iter().filter_map(p21::Param::as_ref_id).any(|u| {
|
||||
matches!(units::unit_factor(&ex, u),
|
||||
Some((UnitKind::Length, f)) if f.is_finite() && f > 0.0)
|
||||
})
|
||||
});
|
||||
let c = units::context(&ex, id);
|
||||
contexts += 1;
|
||||
with_uncertainty += usize::from(c.uncertainty.is_some());
|
||||
let (l, a) = (length_name(c.units.len), angle_name(c.units.ang));
|
||||
*lengths.entry(l.clone()).or_default() += 1;
|
||||
*angles.entry(a.clone()).or_default() += 1;
|
||||
here.push(format!("#{id} {l}/{a} uncertainty {:?}", c.uncertainty));
|
||||
}
|
||||
println!("{name}: {} contexts: {}", here.len(), here.join(", "));
|
||||
if readable {
|
||||
files_read += 1;
|
||||
} else {
|
||||
println!("{name}: no context with a readable length unit");
|
||||
unread.push(name);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{files_read} of {} files have a context with a readable length unit; {contexts} contexts, {with_uncertainty} with an uncertainty",
|
||||
paths.len()
|
||||
);
|
||||
println!("length units (contexts): {lengths:?}");
|
||||
println!("plane angle units (contexts): {angles:?}");
|
||||
println!(
|
||||
"no context with a readable length unit: {} files {unread:?}",
|
||||
unread.len()
|
||||
);
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"files that did not parse:\n{}",
|
||||
failures.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
/// The STEP files in the repository parse to every instance they hold, each id once: a parser
|
||||
/// that dropped or merged instances would change the counts (the instance lines in the files).
|
||||
#[test]
|
||||
@@ -130,3 +268,508 @@ fn repository_files_parse() {
|
||||
assert!(ex.duplicates.is_empty(), "{path}: {:?}", ex.duplicates);
|
||||
}
|
||||
}
|
||||
|
||||
/// Curve entity names (simple instances) the corpus count looks at: every one the reader maps,
|
||||
/// and the other ISO 10303-42 curves a file may hold, which it refuses.
|
||||
const CURVES: &[&str] = &[
|
||||
"B_SPLINE_CURVE",
|
||||
"B_SPLINE_CURVE_WITH_KNOTS",
|
||||
"BEZIER_CURVE",
|
||||
"BOUNDED_SURFACE_CURVE",
|
||||
"CIRCLE",
|
||||
"CLOTHOID",
|
||||
"COMPOSITE_CURVE",
|
||||
"COMPOSITE_CURVE_ON_SURFACE",
|
||||
"CURVE_REPLICA",
|
||||
"ELLIPSE",
|
||||
"HYPERBOLA",
|
||||
"INTERSECTION_CURVE",
|
||||
"LINE",
|
||||
"OFFSET_CURVE_2D",
|
||||
"OFFSET_CURVE_3D",
|
||||
"PARABOLA",
|
||||
"POLYLINE",
|
||||
"QUASI_UNIFORM_CURVE",
|
||||
"SEAM_CURVE",
|
||||
"SURFACE_CURVE",
|
||||
"TRIMMED_CURVE",
|
||||
"UNIFORM_CURVE",
|
||||
];
|
||||
|
||||
/// Surface entity names, likewise.
|
||||
const SURFACES: &[&str] = &[
|
||||
"B_SPLINE_SURFACE",
|
||||
"B_SPLINE_SURFACE_WITH_KNOTS",
|
||||
"BEZIER_SURFACE",
|
||||
"CONICAL_SURFACE",
|
||||
"CURVE_BOUNDED_SURFACE",
|
||||
"CYLINDRICAL_SURFACE",
|
||||
"DEGENERATE_TOROIDAL_SURFACE",
|
||||
"OFFSET_SURFACE",
|
||||
"ORIENTED_SURFACE",
|
||||
"PLANE",
|
||||
"QUASI_UNIFORM_SURFACE",
|
||||
"RECTANGULAR_COMPOSITE_SURFACE",
|
||||
"RECTANGULAR_TRIMMED_SURFACE",
|
||||
"SPHERICAL_SURFACE",
|
||||
"SURFACE_OF_LINEAR_EXTRUSION",
|
||||
"SURFACE_OF_REVOLUTION",
|
||||
"SURFACE_REPLICA",
|
||||
"TOROIDAL_SURFACE",
|
||||
"UNIFORM_SURFACE",
|
||||
];
|
||||
|
||||
/// Whether `e` is a curve or a surface for the count, and its kind: the entity name, or for a
|
||||
/// complex instance `(B_SPLINE_CURVE …)` with `rational` when it is.
|
||||
fn geometry_kind(e: &p21::Entity) -> Option<(bool, String)> {
|
||||
if e.is_complex() {
|
||||
let rational = if e.has("RATIONAL_B_SPLINE_CURVE") || e.has("RATIONAL_B_SPLINE_SURFACE") {
|
||||
" rational"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let is_curve =
|
||||
e.has("B_SPLINE_CURVE") || e.records.iter().any(|r| CURVES.contains(&r.name.as_str()));
|
||||
let is_surface = e.has("B_SPLINE_SURFACE")
|
||||
|| e.records
|
||||
.iter()
|
||||
.any(|r| SURFACES.contains(&r.name.as_str()));
|
||||
let names: Vec<&str> = e
|
||||
.records
|
||||
.iter()
|
||||
.map(|r| r.name.as_str())
|
||||
.filter(|n| CURVES.contains(n) || SURFACES.contains(n))
|
||||
.collect();
|
||||
let kind = format!("({}){rational}", names.join(" "));
|
||||
return match (is_curve, is_surface) {
|
||||
(true, false) => Some((true, kind)),
|
||||
(false, true) => Some((false, kind)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
let n = e.name();
|
||||
if CURVES.contains(&n) {
|
||||
Some((true, n.to_string()))
|
||||
} else if SURFACES.contains(&n) {
|
||||
Some((false, n.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An error message with its entity ids replaced by `#n`, so equal reasons count together.
|
||||
fn reason(e: &str) -> String {
|
||||
let mut out = String::new();
|
||||
let mut chars = e.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
out.push(c);
|
||||
if c == '#' && chars.peek().is_some_and(char::is_ascii_digit) {
|
||||
out.push('n');
|
||||
while chars.peek().is_some_and(char::is_ascii_digit) {
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The curves of a file that lie in a parameter space: the items of its
|
||||
/// `DEFINITIONAL_REPRESENTATION`s and the curves they refer to (a 2D trimmed curve's basis …).
|
||||
fn curves_2d(ex: &p21::Exchange) -> std::collections::BTreeSet<u64> {
|
||||
fn refs(p: &p21::Param, out: &mut Vec<u64>) {
|
||||
match p {
|
||||
p21::Param::Ref(r) => out.push(*r),
|
||||
p21::Param::List(l) | p21::Param::Typed(_, l) => l.iter().for_each(|x| refs(x, out)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut stack = Vec::new();
|
||||
for e in ex.entities.values() {
|
||||
if e.name() == "DEFINITIONAL_REPRESENTATION"
|
||||
&& let Some(items) = e.params().get(1)
|
||||
{
|
||||
refs(items, &mut stack);
|
||||
}
|
||||
}
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
while let Some(id) = stack.pop() {
|
||||
let Some(e) = ex.get(id) else { continue };
|
||||
if !matches!(geometry_kind(e), Some((true, _))) || !seen.insert(id) {
|
||||
continue;
|
||||
}
|
||||
for r in &e.records {
|
||||
r.params.iter().for_each(|p| refs(p, &mut stack));
|
||||
}
|
||||
}
|
||||
seen
|
||||
}
|
||||
|
||||
/// Read or refused, by reason.
|
||||
#[derive(Default)]
|
||||
struct Tally {
|
||||
read: usize,
|
||||
refused: BTreeMap<String, usize>,
|
||||
}
|
||||
|
||||
impl Tally {
|
||||
fn add<T>(&mut self, r: &Result<T, String>) {
|
||||
match r {
|
||||
Ok(_) => self.read += 1,
|
||||
Err(e) => *self.refused.entry(reason(e)).or_default() += 1,
|
||||
}
|
||||
}
|
||||
fn refused(&self) -> usize {
|
||||
self.refused.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs a STEP corpus in SOLIDCORE_STEP_CORPUS"]
|
||||
fn corpus_geometry() {
|
||||
use solidcore_record::step::read::Reader;
|
||||
let Some(dir) = std::env::var_os("SOLIDCORE_STEP_CORPUS") else {
|
||||
eprintln!("SOLIDCORE_STEP_CORPUS is not set");
|
||||
return;
|
||||
};
|
||||
let root = Path::new(&dir);
|
||||
let mut paths = Vec::new();
|
||||
files(root, &mut paths);
|
||||
assert!(!paths.is_empty(), "no STEP files under {dir:?}");
|
||||
let mut failures = Vec::new();
|
||||
// (group, kind) -> tally, groups "curve", "surface", "pcurve" (by its 2D curve's kind).
|
||||
let mut kinds: BTreeMap<(&str, String), Tally> = BTreeMap::new();
|
||||
let mut mixed_units = Vec::new();
|
||||
let mut removed = Vec::new();
|
||||
for path in &paths {
|
||||
let name = path
|
||||
.strip_prefix(root)
|
||||
.unwrap_or(path)
|
||||
.display()
|
||||
.to_string();
|
||||
let ex = match std::fs::read(path)
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|b| p21::parse(&String::from_utf8_lossy(&b)).map_err(|e| e.to_string()))
|
||||
{
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
println!("{name}: ERROR {e}");
|
||||
failures.push(format!("{name}: {e}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// The units of the file's first geometric context (the topology reader, which comes
|
||||
// later, reads each representation in its own context).
|
||||
let contexts: Vec<units::Units> = ex
|
||||
.entities
|
||||
.iter()
|
||||
.filter(|(_, e)| {
|
||||
e.has("GEOMETRIC_REPRESENTATION_CONTEXT") && e.has("GLOBAL_UNIT_ASSIGNED_CONTEXT")
|
||||
})
|
||||
.map(|(&id, _)| units::context(&ex, id).units)
|
||||
.collect();
|
||||
let u = contexts.first().copied().unwrap_or_default();
|
||||
if contexts.iter().any(|c| *c != u) {
|
||||
mixed_units.push(name.clone());
|
||||
}
|
||||
// Where a removed check would have decided otherwise: SolidCraft refused a direction
|
||||
// whose length is at most 1e-12 (here: only one that is exactly zero), and took the two
|
||||
// unit factors of a conic's parameter space as equal within 1e-12 relative (here: only
|
||||
// when equal).
|
||||
for (&id, e) in &ex.entities {
|
||||
if e.name() == "DIRECTION"
|
||||
&& let Some(r) = e.params().get(1).and_then(p21::Param::as_list)
|
||||
{
|
||||
let sq: f64 = r.iter().filter_map(p21::Param::as_f64).map(|x| x * x).sum();
|
||||
if sq > 0.0 && sq <= 1e-24 {
|
||||
removed.push(format!("{name}: #{id} a direction of length at most 1e-12"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if u.len != u.ang && (u.len - u.ang).abs() <= 1e-12 * u.len.abs().max(u.ang.abs()) {
|
||||
removed.push(format!(
|
||||
"{name}: length and angle factors equal within 1e-12"
|
||||
));
|
||||
}
|
||||
let flat = curves_2d(&ex);
|
||||
let mut reader = Reader::new(&ex, u);
|
||||
let mut here = (Tally::default(), Tally::default(), Tally::default());
|
||||
for (&id, e) in &ex.entities {
|
||||
if e.name() == "PCURVE" {
|
||||
let kind = e
|
||||
.params()
|
||||
.get(2)
|
||||
.and_then(p21::Param::as_ref_id)
|
||||
.and_then(|r| ex.get(r))
|
||||
.and_then(|r| r.params().get(1)?.as_list()?.first()?.as_ref_id())
|
||||
.and_then(|c| ex.get(c))
|
||||
.and_then(geometry_kind)
|
||||
.map_or_else(|| "?".to_string(), |k| k.1);
|
||||
let r = reader.pcurve(id);
|
||||
here.2.add(&r);
|
||||
kinds.entry(("pcurve", kind)).or_default().add(&r);
|
||||
continue;
|
||||
}
|
||||
let Some((is_curve, kind)) = geometry_kind(e) else {
|
||||
continue;
|
||||
};
|
||||
if is_curve {
|
||||
if flat.contains(&id) {
|
||||
continue;
|
||||
}
|
||||
let r = reader.curve(id);
|
||||
here.0.add(&r);
|
||||
kinds.entry(("curve", kind)).or_default().add(&r);
|
||||
} else {
|
||||
let r = reader.surface(id);
|
||||
here.1.add(&r);
|
||||
kinds.entry(("surface", kind)).or_default().add(&r);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{name}: curves {} read, {} refused; surfaces {} read, {} refused; pcurves {} read, {} refused",
|
||||
here.0.read,
|
||||
here.0.refused(),
|
||||
here.1.read,
|
||||
here.1.refused(),
|
||||
here.2.read,
|
||||
here.2.refused()
|
||||
);
|
||||
}
|
||||
println!("by kind (group, kind: read / refused, with reasons):");
|
||||
let mut totals: BTreeMap<&str, (usize, usize)> = BTreeMap::new();
|
||||
for ((group, kind), t) in &kinds {
|
||||
println!(" {group} {kind}: {} / {}", t.read, t.refused());
|
||||
for (why, n) in &t.refused {
|
||||
println!(" {n} x {why}");
|
||||
}
|
||||
let g = totals.entry(group).or_default();
|
||||
g.0 += t.read;
|
||||
g.1 += t.refused();
|
||||
}
|
||||
println!("totals (read / refused): {totals:?}");
|
||||
println!(
|
||||
"files whose contexts have different units (read in the first): {} {mixed_units:?}",
|
||||
mixed_units.len()
|
||||
);
|
||||
println!(
|
||||
"entities a removed check would have decided otherwise: {} {removed:?}",
|
||||
removed.len()
|
||||
);
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"files that did not parse:\n{}",
|
||||
failures.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
/// The entity types counted by `corpus_product` against what the product structure holds.
|
||||
const BODY_KINDS: [&str; 4] = [
|
||||
"MANIFOLD_SOLID_BREP",
|
||||
"BREP_WITH_VOIDS",
|
||||
"FACETED_BREP",
|
||||
"SHELL_BASED_SURFACE_MODEL",
|
||||
];
|
||||
const OCCURRENCE_KINDS: [&str; 3] = [
|
||||
"NEXT_ASSEMBLY_USAGE_OCCURRENCE",
|
||||
"ASSEMBLY_COMPONENT_USAGE",
|
||||
"SPECIFIED_HIGHER_USAGE_OCCURRENCE",
|
||||
];
|
||||
const STYLED_KINDS: [&str; 3] = [
|
||||
"STYLED_ITEM",
|
||||
"OVER_RIDING_STYLED_ITEM",
|
||||
"CONTEXT_DEPENDENT_OVER_RIDING_STYLED_ITEM",
|
||||
];
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs a STEP corpus in SOLIDCORE_STEP_CORPUS"]
|
||||
fn corpus_product() {
|
||||
use solidcore_record::step::product::{self, ItemKind};
|
||||
let Some(dir) = std::env::var_os("SOLIDCORE_STEP_CORPUS") else {
|
||||
eprintln!("SOLIDCORE_STEP_CORPUS is not set");
|
||||
return;
|
||||
};
|
||||
let root = Path::new(&dir);
|
||||
let mut paths = Vec::new();
|
||||
files(root, &mut paths);
|
||||
assert!(!paths.is_empty(), "no STEP files under {dir:?}");
|
||||
let mut failures = Vec::new();
|
||||
let mut assemblies = Vec::new();
|
||||
// Totals: name -> (held, census).
|
||||
let mut totals: BTreeMap<&str, (usize, usize)> = BTreeMap::new();
|
||||
let mut coloured_by_kind: BTreeMap<String, usize> = BTreeMap::new();
|
||||
let mut instances_total = 0usize;
|
||||
let mut unreached_mapped: BTreeMap<String, usize> = BTreeMap::new();
|
||||
for path in &paths {
|
||||
let name = path
|
||||
.strip_prefix(root)
|
||||
.unwrap_or(path)
|
||||
.display()
|
||||
.to_string();
|
||||
let ex = match std::fs::read(path)
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|b| p21::parse(&String::from_utf8_lossy(&b)).map_err(|e| e.to_string()))
|
||||
{
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
println!("{name}: ERROR {e}");
|
||||
failures.push(format!("{name}: {e}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let census = |names: &[&str]| {
|
||||
ex.entities
|
||||
.values()
|
||||
.filter(|e| names.contains(&e.name()))
|
||||
.count()
|
||||
};
|
||||
let s = match product::read(&ex) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("{name}: REFUSED {e}");
|
||||
failures.push(format!("{name}: refused: {e}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut bodies = std::collections::BTreeSet::new();
|
||||
let mut mapped = std::collections::BTreeSet::new();
|
||||
for r in &s.representations {
|
||||
for i in &r.items {
|
||||
match i.kind {
|
||||
ItemKind::Body(_) => {
|
||||
bodies.insert(i.entity);
|
||||
}
|
||||
ItemKind::Mapped { .. } => {
|
||||
mapped.insert(i.entity);
|
||||
}
|
||||
ItemKind::Other(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let placed = s
|
||||
.links
|
||||
.iter()
|
||||
.filter(|l| l.transformation.is_some())
|
||||
.count();
|
||||
let coloured: Vec<_> = s
|
||||
.styles
|
||||
.iter()
|
||||
.filter(|st| {
|
||||
!st.curve.is_empty()
|
||||
|| st
|
||||
.surface
|
||||
.iter()
|
||||
.any(|f| f.fill.is_some() || f.rendering.is_some())
|
||||
})
|
||||
.collect();
|
||||
for st in &coloured {
|
||||
let kind = ex.get(st.item).map_or("?", p21::Entity::name);
|
||||
*coloured_by_kind.entry(kind.to_string()).or_default() += 1;
|
||||
}
|
||||
let instances = match s.instances() {
|
||||
Ok(i) => i.len(),
|
||||
Err(e) => {
|
||||
failures.push(format!("{name}: instances: {e}"));
|
||||
0
|
||||
}
|
||||
};
|
||||
instances_total += instances;
|
||||
let rows = [
|
||||
("products", s.products.len(), census(&["PRODUCT"])),
|
||||
(
|
||||
"definitions",
|
||||
s.definitions.len(),
|
||||
census(&[
|
||||
"PRODUCT_DEFINITION",
|
||||
"PRODUCT_DEFINITION_WITH_ASSOCIATED_DOCUMENTS",
|
||||
]),
|
||||
),
|
||||
(
|
||||
"occurrences",
|
||||
s.occurrences.len(),
|
||||
census(&OCCURRENCE_KINDS),
|
||||
),
|
||||
("body items", bodies.len(), census(&BODY_KINDS)),
|
||||
(
|
||||
"placed links",
|
||||
placed,
|
||||
ex.entities
|
||||
.values()
|
||||
.filter(|e| e.has("REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION"))
|
||||
.count(),
|
||||
),
|
||||
("styled items", s.styles.len(), census(&STYLED_KINDS)),
|
||||
];
|
||||
// A mapped item the structure does not hold is listed only by representations it does
|
||||
// not reach (the draughting models of PMI views): checked, and counted apart.
|
||||
let reached: std::collections::BTreeSet<u64> =
|
||||
s.representations.iter().map(|r| r.entity).collect();
|
||||
let mut listers: BTreeMap<u64, Vec<(u64, &str)>> = BTreeMap::new();
|
||||
for (&id, e) in &ex.entities {
|
||||
for r in &e.records {
|
||||
for p in &r.params {
|
||||
for m in p
|
||||
.as_list()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter_map(p21::Param::as_ref_id)
|
||||
{
|
||||
if ex.get(m).is_some_and(|m| m.name() == "MAPPED_ITEM") {
|
||||
listers.entry(m).or_default().push((id, e.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let all_mapped = census(&["MAPPED_ITEM"]);
|
||||
for (&id, e) in &ex.entities {
|
||||
if e.name() != "MAPPED_ITEM" || mapped.contains(&id) {
|
||||
continue;
|
||||
}
|
||||
let by = listers.get(&id).cloned().unwrap_or_default();
|
||||
if by.iter().any(|(l, _)| reached.contains(l)) {
|
||||
failures.push(format!(
|
||||
"{name}: mapped item #{id} of a reached representation not held"
|
||||
));
|
||||
}
|
||||
for (_, kind) in by {
|
||||
*unreached_mapped.entry(kind.to_string()).or_default() += 1;
|
||||
}
|
||||
}
|
||||
let t = totals.entry("mapped items").or_default();
|
||||
t.0 += mapped.len();
|
||||
t.1 += all_mapped;
|
||||
let mut line = format!("{name}: mapped items {}/{all_mapped};", mapped.len());
|
||||
for (what, held, n) in rows {
|
||||
let _ = std::fmt::Write::write_fmt(&mut line, format_args!(" {what} {held}/{n};"));
|
||||
let t = totals.entry(what).or_default();
|
||||
t.0 += held;
|
||||
t.1 += n;
|
||||
if held != n {
|
||||
failures.push(format!("{name}: {what}: {held} held, {n} in the file"));
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{line} coloured items {}; roots {}; instances {instances}",
|
||||
coloured.len(),
|
||||
s.roots.len()
|
||||
);
|
||||
if !s.occurrences.is_empty() {
|
||||
assemblies.push(format!(
|
||||
"{name}: {} definitions, {} occurrences, {} roots, {instances} instances, {} mapped items",
|
||||
s.definitions.len(),
|
||||
s.occurrences.len(),
|
||||
s.roots.len(),
|
||||
mapped.len()
|
||||
));
|
||||
}
|
||||
}
|
||||
println!("totals (held / in the files): {totals:?}");
|
||||
println!("coloured items by kind: {coloured_by_kind:?}");
|
||||
println!("mapped items not held, by the kind of entity listing them: {unreached_mapped:?}");
|
||||
println!("instances: {instances_total}");
|
||||
println!("files with assemblies: {}", assemblies.len());
|
||||
for a in &assemblies {
|
||||
println!(" {a}");
|
||||
}
|
||||
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
||||
}
|
||||
|
||||
+14
-4
@@ -2298,16 +2298,26 @@ fn plan_face(
|
||||
}
|
||||
// Whatever the layout, no triangle leaves the surface by more than the tolerance.
|
||||
if !planar {
|
||||
if sag::refine(s, tol, reach, &fixed, &mut all, &mut triangles, MAX_REFINE)
|
||||
.map_err(|e| sag_stop(e, all.len()))?
|
||||
// The kernel's classifier is asked once per node over the three passes.
|
||||
let mut poles = sag::Poles::new(s, reach);
|
||||
if sag::refine(
|
||||
s,
|
||||
tol,
|
||||
&mut poles,
|
||||
&fixed,
|
||||
&mut all,
|
||||
&mut triangles,
|
||||
MAX_REFINE,
|
||||
)
|
||||
.map_err(|e| sag_stop(e, all.len()))?
|
||||
{
|
||||
caps.push(Cap::Refine);
|
||||
}
|
||||
sag::flip(s, tol, reach, &fixed, &all, &mut triangles)
|
||||
sag::flip(s, tol, &mut poles, &fixed, &all, &mut triangles)
|
||||
.map_err(|e| sag_stop(e, all.len()))?;
|
||||
// A face a bound limited is named with the sag its triangles reach.
|
||||
if !caps.is_empty() {
|
||||
let (sag, unmeasured) = sag::worst(s, reach, &fixed, &all, &triangles)
|
||||
let (sag, unmeasured) = sag::worst(s, &mut poles, &fixed, &all, &triangles)
|
||||
.map_err(|e| sag_stop(e, all.len()))?;
|
||||
stats.over_tolerance.push(OverTolerance {
|
||||
face: fi as usize,
|
||||
|
||||
+15
-2
@@ -379,7 +379,14 @@ fn the_flips_name_a_middle_the_surface_does_not_evaluate_at() {
|
||||
let uv = vec![[0.0, 0.0], [0.75, -0.5], [0.75, 0.5], [0.0, 1.0]];
|
||||
let fixed: Vec<Vec3> = uv.iter().map(|q| point(&s, *q)).collect();
|
||||
let mut tris = vec![[0, 1, 2], [0, 2, 3]];
|
||||
match super::sag::flip(&s, 0.01, 1e-9, &fixed, &uv, &mut tris) {
|
||||
match super::sag::flip(
|
||||
&s,
|
||||
0.01,
|
||||
&mut super::sag::Poles::new(&s, 1e-9),
|
||||
&fixed,
|
||||
&uv,
|
||||
&mut tris,
|
||||
) {
|
||||
Err(super::sag::Stop::NoValue(NoValue(q))) => assert_eq!(q[0], 0.5, "{q:?}"),
|
||||
other => panic!("{other:?}, flipped to {tris:?}"),
|
||||
}
|
||||
@@ -544,7 +551,13 @@ fn a_step_inwards_the_surface_has_no_point_at_refuses_the_face() {
|
||||
fn the_worst_sag_is_measured_where_the_surface_evaluates() {
|
||||
let s = cylinder_without(0.5);
|
||||
let uv = [[0.0, 0.0], [0.75, -0.5], [0.75, 0.5]];
|
||||
let got = super::sag::worst(&s, 1e-9, &[], &uv, &[[0, 1, 2]]);
|
||||
let got = super::sag::worst(
|
||||
&s,
|
||||
&mut super::sag::Poles::new(&s, 1e-9),
|
||||
&[],
|
||||
&uv,
|
||||
&[[0, 1, 2]],
|
||||
);
|
||||
// Measured at its three sides' middles; its middle, unmeasured, is counted, so the face is
|
||||
// never taken as within its tolerance over it.
|
||||
assert!(
|
||||
|
||||
+197
-81
@@ -18,8 +18,9 @@ use super::{NoValue, Unclassified};
|
||||
|
||||
/// Split the triangles `tris` of a face (indices into `uv`; the first `fixed.len()` points sit
|
||||
/// at `fixed`, the rest on the surface at their parameters) until none of them lies farther
|
||||
/// than `tol` from the surface `s`, adding at most `max_points` points to `uv`. `reach`: how near
|
||||
/// a pole a point is taken as one (see [`solidcore::eval::uv::degenerate_within`]).
|
||||
/// than `tol` from the surface `s`, adding at most `max_points` points to `uv`. `poles`: the
|
||||
/// classifier of the face's nodes, shared by [`refine`], [`flip`] and [`worst`] (how near a pole a
|
||||
/// point is taken as one: see [`solidcore::eval::uv::degenerate_within`]).
|
||||
///
|
||||
/// A point the surface does not evaluate at (a node, or a middle the check needs) is the
|
||||
/// evaluator refusing the face: the error names it, and the caller leaves the face out whole.
|
||||
@@ -50,7 +51,7 @@ impl From<Unclassified> for Stop {
|
||||
pub(crate) fn refine(
|
||||
s: &EvalSurface,
|
||||
tol: f64,
|
||||
reach: f64,
|
||||
poles: &mut Poles,
|
||||
fixed: &[Vec3],
|
||||
uv: &mut Vec<[f64; 2]>,
|
||||
tris: &mut Vec<[u32; 3]>,
|
||||
@@ -64,7 +65,6 @@ pub(crate) fn refine(
|
||||
.map(|d| kernel::record_vec3(d.0))
|
||||
.ok_or(NoValue(q))
|
||||
};
|
||||
let mean = |qs: &[[f64; 2]]| mean(s, reach, qs);
|
||||
// How far the flat point `c` lies from the surface, measured along the normal at the surface
|
||||
// point of the same parameters: those parameters need not be the nearest (a straight line in
|
||||
// the parameters is no geodesic near a pole), and only the departure across the surface
|
||||
@@ -103,10 +103,7 @@ pub(crate) fn refine(
|
||||
continue;
|
||||
};
|
||||
let p = |i: u32| pos.get(i as usize).copied();
|
||||
let q = |i: u32| uv.get(i as usize).copied();
|
||||
let (Some(pa), Some(pb), Some(pc), Some(qa), Some(qb), Some(qc)) =
|
||||
(p(t[0]), p(t[1]), p(t[2]), q(t[0]), q(t[1]), q(t[2]))
|
||||
else {
|
||||
let (Some(pa), Some(pb), Some(pc)) = (p(t[0]), p(t[1]), p(t[2])) else {
|
||||
continue;
|
||||
};
|
||||
// Sides inside the face (two triangles on them), long enough to split, with their sag.
|
||||
@@ -117,7 +114,7 @@ pub(crate) fn refine(
|
||||
if sides.get(&e).is_none_or(|ts| ts.len() != 2) {
|
||||
continue;
|
||||
}
|
||||
let (Some(xa), Some(xb), Some(ua), Some(ub)) = (p(a), p(b), q(a), q(b)) else {
|
||||
let (Some(xa), Some(xb)) = (p(a), p(b)) else {
|
||||
continue;
|
||||
};
|
||||
let len = xa.dist(xb);
|
||||
@@ -127,7 +124,7 @@ pub(crate) fn refine(
|
||||
let sag = if let Some(&sag) = sag_of.get(&e) {
|
||||
sag
|
||||
} else {
|
||||
let sag = off(mean(&[ua, ub])?, (xa + xb) * 0.5)?;
|
||||
let sag = off(poles.mean(uv, &[a, b])?, (xa + xb) * 0.5)?;
|
||||
sag_of.insert(e, sag);
|
||||
sag
|
||||
};
|
||||
@@ -138,7 +135,7 @@ pub(crate) fn refine(
|
||||
// inside the face divides by four. A triangle whose middle is off splits its side inside
|
||||
// the face that sags most, while that side still contributes; what is left comes from
|
||||
// its boundary sides, which the edges' own sampling keeps within the tolerance.
|
||||
let centroid = mean(&[qa, qb, qc])?;
|
||||
let centroid = poles.mean(uv, &t)?;
|
||||
let worst = inner.iter().max_by(|x, y| x.0.total_cmp(&y.0)).copied();
|
||||
let split = match worst {
|
||||
Some((sag, _, e)) if sag > tol => e,
|
||||
@@ -174,14 +171,11 @@ pub(crate) fn refine(
|
||||
if from != ti {
|
||||
queue.push(ti);
|
||||
}
|
||||
let (Some(ua), Some(ub)) = (q(split.0), q(split.1)) else {
|
||||
continue;
|
||||
};
|
||||
// A split is wanted and there is no room for its point.
|
||||
if uv.len() >= limit {
|
||||
return Ok(true);
|
||||
}
|
||||
let m = mean(&[ua, ub])?;
|
||||
let m = poles.mean(uv, &[split.0, split.1])?;
|
||||
let pm = at(m)?;
|
||||
// Proven: `plan_face` checks its nodes and the MAX_REFINE more it lets this add are at most
|
||||
// MAX_FACE_NODES, so the index fits a u32.
|
||||
@@ -194,29 +188,82 @@ pub(crate) fn refine(
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// The mean of parameter points, as the surface point between them: at a pole the parameter
|
||||
/// along the degenerate direction is free (the pole copy carries a neighbour's), so there the
|
||||
/// mean takes it from the other points alone.
|
||||
/// A point whose poles the kernel's classifier cannot tell is the error.
|
||||
fn mean(s: &EvalSurface, reach: f64, qs: &[[f64; 2]]) -> Result<[f64; 2], Unclassified> {
|
||||
let mut out = [0.0; 2];
|
||||
// Proven: `d` < 2 enumerates `out` and indexes the `[f64; 2]` parameters `qs`.
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
for (d, slot) in out.iter_mut().enumerate() {
|
||||
let mut free: Vec<f64> = Vec::new();
|
||||
for q in qs {
|
||||
if !super::pole_at(s, *q, d, reach)? {
|
||||
free.push(q[d]);
|
||||
/// Which parameter directions the surface is collapsed along at each node of a face's
|
||||
/// triangulation, as the kernel's classifier says (within `reach`): a property of the node's
|
||||
/// parameters, so it is asked once per node however many triangles and rounds look at it.
|
||||
pub(crate) struct Poles<'a> {
|
||||
s: &'a EvalSurface,
|
||||
reach: f64,
|
||||
/// Per node index; `None` until asked.
|
||||
flags: Vec<Option<[bool; 2]>>,
|
||||
/// Calls of the kernel's classifier made, for the tests.
|
||||
#[cfg(test)]
|
||||
asked: usize,
|
||||
}
|
||||
|
||||
impl<'a> Poles<'a> {
|
||||
pub(crate) fn new(s: &'a EvalSurface, reach: f64) -> Self {
|
||||
Poles {
|
||||
s,
|
||||
reach,
|
||||
flags: Vec::new(),
|
||||
#[cfg(test)]
|
||||
asked: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether each direction is collapsed at node `i`, whose parameters are `q`.
|
||||
fn at(&mut self, i: u32, q: [f64; 2]) -> Result<[bool; 2], Unclassified> {
|
||||
let i = i as usize;
|
||||
if let Some(Some(f)) = self.flags.get(i) {
|
||||
return Ok(*f);
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.asked += 2;
|
||||
}
|
||||
let f = [
|
||||
super::pole_at(self.s, q, 0, self.reach)?,
|
||||
super::pole_at(self.s, q, 1, self.reach)?,
|
||||
];
|
||||
if self.flags.len() <= i {
|
||||
self.flags.resize(i + 1, None);
|
||||
}
|
||||
if let Some(slot) = self.flags.get_mut(i) {
|
||||
*slot = Some(f);
|
||||
}
|
||||
Ok(f)
|
||||
}
|
||||
|
||||
/// The mean of nodes' parameters, as the surface point between them: at a pole the
|
||||
/// parameter along the degenerate direction is free (the pole copy carries a neighbour's),
|
||||
/// so there the mean takes it from the other nodes alone. A node whose poles the kernel's
|
||||
/// classifier cannot tell is the error.
|
||||
fn mean(&mut self, uv: &[[f64; 2]], nodes: &[u32]) -> Result<[f64; 2], Unclassified> {
|
||||
let mut qs: Vec<([f64; 2], [bool; 2])> = Vec::new();
|
||||
for &i in nodes {
|
||||
if let Some(&q) = uv.get(i as usize) {
|
||||
qs.push((q, self.at(i, q)?));
|
||||
}
|
||||
}
|
||||
let pick: Vec<f64> = if free.is_empty() {
|
||||
qs.iter().map(|q| q[d]).collect()
|
||||
} else {
|
||||
free
|
||||
};
|
||||
*slot = pick.iter().sum::<f64>() / pick.len().max(1) as f64;
|
||||
let mut out = [0.0; 2];
|
||||
// Proven: `d` < 2 enumerates `out` and indexes the `[f64; 2]` parameters and flags.
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
for (d, slot) in out.iter_mut().enumerate() {
|
||||
let free: Vec<f64> = qs
|
||||
.iter()
|
||||
.filter(|(_, f)| !f[d])
|
||||
.map(|(q, _)| q[d])
|
||||
.collect();
|
||||
let pick: Vec<f64> = if free.is_empty() {
|
||||
qs.iter().map(|(q, _)| q[d]).collect()
|
||||
} else {
|
||||
free
|
||||
};
|
||||
*slot = pick.iter().sum::<f64>() / pick.len().max(1) as f64;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The farthest the triangles `tris` (as for [`refine`]) lie from the surface, measured as
|
||||
@@ -229,7 +276,7 @@ fn mean(s: &EvalSurface, reach: f64, qs: &[[f64; 2]]) -> Result<[f64; 2], Unclas
|
||||
/// classifier cannot tell.
|
||||
pub(crate) fn worst(
|
||||
s: &EvalSurface,
|
||||
reach: f64,
|
||||
poles: &mut Poles,
|
||||
fixed: &[Vec3],
|
||||
uv: &[[f64; 2]],
|
||||
tris: &[[u32; 3]],
|
||||
@@ -245,8 +292,8 @@ pub(crate) fn worst(
|
||||
})
|
||||
.collect();
|
||||
// How far the flat point `c` lies from the surface at the mean of `qs`, as `refine`'s `off`.
|
||||
let off = |qs: &[[f64; 2]], c: Vec3| -> Result<Option<f64>, Unclassified> {
|
||||
let m = mean(s, reach, qs)?;
|
||||
let mut off = |nodes: &[u32], c: Vec3| -> Result<Option<f64>, Unclassified> {
|
||||
let m = poles.mean(uv, nodes)?;
|
||||
let Some((x, su, sv)) = s.derivs(m[0], m[1]).map(kernel::record_triple) else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -259,7 +306,7 @@ pub(crate) fn worst(
|
||||
let (mut worst, mut unmeasured) = (0.0f64, 0usize);
|
||||
for (k, t) in tris.iter().enumerate() {
|
||||
let corner = |i: u32| Some((*uv.get(i as usize)?, *pos.get(i as usize)?));
|
||||
let (Some((qa, pa)), Some((qb, pb)), Some((qc, pc))) =
|
||||
let (Some((_, pa)), Some((_, pb)), Some((_, pc))) =
|
||||
(corner(t[0]), corner(t[1]), corner(t[2]))
|
||||
else {
|
||||
return Err(Stop::Corner(k));
|
||||
@@ -268,16 +315,16 @@ pub(crate) fn worst(
|
||||
// corner they need).
|
||||
let mut measured: Vec<Option<f64>> = Vec::new();
|
||||
measured.push(match (pa, pb, pc) {
|
||||
(Some(a), Some(b), Some(c)) => off(&[qa, qb, qc], (a + b + c) * (1.0 / 3.0))?,
|
||||
(Some(a), Some(b), Some(c)) => off(t, (a + b + c) * (1.0 / 3.0))?,
|
||||
_ => None,
|
||||
});
|
||||
for ((qx, px), (qy, py)) in [
|
||||
((qa, pa), (qb, pb)),
|
||||
((qb, pb), (qc, pc)),
|
||||
((qc, pc), (qa, pa)),
|
||||
for ((nx, px), (ny, py)) in [
|
||||
((t[0], pa), (t[1], pb)),
|
||||
((t[1], pb), (t[2], pc)),
|
||||
((t[2], pc), (t[0], pa)),
|
||||
] {
|
||||
measured.push(match (px, py) {
|
||||
(Some(x), Some(y)) => off(&[qx, qy], (x + y) * 0.5)?,
|
||||
(Some(x), Some(y)) => off(&[nx, ny], (x + y) * 0.5)?,
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
@@ -388,7 +435,7 @@ const MIN_FLIP_ANGLE: f64 = 1e-3;
|
||||
pub(crate) fn flip(
|
||||
s: &EvalSurface,
|
||||
tol: f64,
|
||||
reach: f64,
|
||||
poles: &mut Poles,
|
||||
fixed: &[Vec3],
|
||||
uv: &[[f64; 2]],
|
||||
tris: &mut [[u32; 3]],
|
||||
@@ -396,7 +443,6 @@ pub(crate) fn flip(
|
||||
if !(tol.is_finite() && tol > 0.0) || tris.len() < 2 {
|
||||
return Ok(());
|
||||
}
|
||||
let mean = |qs: &[[f64; 2]]| mean(s, reach, qs);
|
||||
let derivs = |q: [f64; 2]| {
|
||||
s.derivs(q[0], q[1])
|
||||
.map(kernel::record_triple)
|
||||
@@ -441,13 +487,11 @@ pub(crate) fn flip(
|
||||
angle(a, b, c).min(angle(b, c, a)).min(angle(c, a, b))
|
||||
};
|
||||
// How far a facet's normal turns from the surface normal at its middle (radians, 0 to pi/2).
|
||||
let deviation = |t: [u32; 3]| -> Result<f64, Stop> {
|
||||
let (Some(a), Some(b), Some(c), Some(qa), Some(qb), Some(qc)) =
|
||||
(p(t[0]), p(t[1]), p(t[2]), q(t[0]), q(t[1]), q(t[2]))
|
||||
else {
|
||||
let deviation = |poles: &mut Poles, t: [u32; 3]| -> Result<f64, Stop> {
|
||||
let (Some(a), Some(b), Some(c)) = (p(t[0]), p(t[1]), p(t[2])) else {
|
||||
return Ok(0.0);
|
||||
};
|
||||
let m = mean(&[qa, qb, qc])?;
|
||||
let m = poles.mean(uv, &t)?;
|
||||
let facet = (b - a).cross(c - a).normalized();
|
||||
let (_, su, sv) = derivs(m)?;
|
||||
Ok(match (facet, su.cross(sv).normalized()) {
|
||||
@@ -458,20 +502,16 @@ pub(crate) fn flip(
|
||||
};
|
||||
// Whether the facet's normal lies on the side of the surface normal that its (u, v)
|
||||
// orientation `sign` says.
|
||||
let upright = |t: [u32; 3], sign: f64| -> Result<bool, Stop> {
|
||||
let (Some(a), Some(b), Some(c), Some(qa), Some(qb), Some(qc)) =
|
||||
(p(t[0]), p(t[1]), p(t[2]), q(t[0]), q(t[1]), q(t[2]))
|
||||
else {
|
||||
let upright = |poles: &mut Poles, t: [u32; 3], sign: f64| -> Result<bool, Stop> {
|
||||
let (Some(a), Some(b), Some(c)) = (p(t[0]), p(t[1]), p(t[2])) else {
|
||||
return Ok(false);
|
||||
};
|
||||
let (_, su, sv) = derivs(mean(&[qa, qb, qc])?)?;
|
||||
let (_, su, sv) = derivs(poles.mean(uv, &t)?)?;
|
||||
Ok((b - a).cross(c - a).dot(su.cross(sv)) * sign > 0.0)
|
||||
};
|
||||
let centroid_off = |t: [u32; 3]| -> Result<f64, Stop> {
|
||||
match (p(t[0]), p(t[1]), p(t[2]), q(t[0]), q(t[1]), q(t[2])) {
|
||||
(Some(a), Some(b), Some(c), Some(qa), Some(qb), Some(qc)) => {
|
||||
Ok(off(mean(&[qa, qb, qc])?, (a + b + c) * (1.0 / 3.0))?)
|
||||
}
|
||||
let centroid_off = |poles: &mut Poles, t: [u32; 3]| -> Result<f64, Stop> {
|
||||
match (p(t[0]), p(t[1]), p(t[2])) {
|
||||
(Some(a), Some(b), Some(c)) => Ok(off(poles.mean(uv, &t)?, (a + b + c) * (1.0 / 3.0))?),
|
||||
_ => Ok(f64::INFINITY),
|
||||
}
|
||||
};
|
||||
@@ -523,20 +563,20 @@ pub(crate) fn flip(
|
||||
// The pair the other way must lie closer along the surface (its facets turn less from
|
||||
// the surface normal), by more than a degree: a pair within a degree of the surface is
|
||||
// left as the layout made it.
|
||||
let mut cached = |ti: u32, t: [u32; 3]| -> Result<f64, Stop> {
|
||||
let mut cached = |poles: &mut Poles, ti: u32, t: [u32; 3]| -> Result<f64, Stop> {
|
||||
if let Some(d) = dev_of.get(ti as usize).copied().flatten() {
|
||||
Ok(d)
|
||||
} else {
|
||||
let d = deviation(t)?;
|
||||
let d = deviation(poles, t)?;
|
||||
if let Some(slot) = dev_of.get_mut(ti as usize) {
|
||||
*slot = Some(d);
|
||||
}
|
||||
Ok(d)
|
||||
}
|
||||
};
|
||||
let worse = cached(t1, ta)?.max(cached(t2, tb)?);
|
||||
let worse = cached(poles, t1, ta)?.max(cached(poles, t2, tb)?);
|
||||
let degree = 1f64.to_radians();
|
||||
if worse < degree || deviation(n1)?.max(deviation(n2)?) >= worse - degree {
|
||||
if worse < degree || deviation(poles, n1)?.max(deviation(poles, n2)?) >= worse - degree {
|
||||
continue;
|
||||
}
|
||||
// ... and have area in 3D (three corners on one line of a cone have none, however they
|
||||
@@ -544,21 +584,19 @@ pub(crate) fn flip(
|
||||
if min_angle(n1).min(min_angle(n2)) < MIN_FLIP_ANGLE {
|
||||
continue;
|
||||
}
|
||||
if !(upright(n1, sign)? && upright(n2, sign)?) {
|
||||
if !(upright(poles, n1, sign)? && upright(poles, n2, sign)?) {
|
||||
continue;
|
||||
}
|
||||
let (Some(pa), Some(pb), Some(pc), Some(pd), Some(qa), Some(qb), Some(qc), Some(qd)) =
|
||||
(p(a), p(b), p(c), p(d), q(a), q(b), q(c), q(d))
|
||||
else {
|
||||
let (Some(pa), Some(pb), Some(pc), Some(pd)) = (p(a), p(b), p(c), p(d)) else {
|
||||
continue;
|
||||
};
|
||||
let allowed = tol
|
||||
.max(off(mean(&[qa, qb])?, (pa + pb) * 0.5)?)
|
||||
.max(centroid_off(ta)?)
|
||||
.max(centroid_off(tb)?);
|
||||
if off(mean(&[qc, qd])?, (pc + pd) * 0.5)? > allowed
|
||||
|| centroid_off(n1)? > allowed
|
||||
|| centroid_off(n2)? > allowed
|
||||
.max(off(poles.mean(uv, &[a, b])?, (pa + pb) * 0.5)?)
|
||||
.max(centroid_off(poles, ta)?)
|
||||
.max(centroid_off(poles, tb)?);
|
||||
if off(poles.mean(uv, &[c, d])?, (pc + pd) * 0.5)? > allowed
|
||||
|| centroid_off(poles, n1)? > allowed
|
||||
|| centroid_off(poles, n2)? > allowed
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -607,7 +645,15 @@ mod tests {
|
||||
assert_eq!(fixed.len(), 4);
|
||||
let mut tris = vec![[0, 1, 2], [0, 2, 3]];
|
||||
assert_eq!(
|
||||
refine(&s, 0.01, 1e-9, &fixed, &mut uv, &mut tris, 1000),
|
||||
refine(
|
||||
&s,
|
||||
0.01,
|
||||
&mut Poles::new(&s, 1e-9),
|
||||
&fixed,
|
||||
&mut uv,
|
||||
&mut tris,
|
||||
1000
|
||||
),
|
||||
Err(Stop::NoValue(NoValue([0.5, 0.5])))
|
||||
);
|
||||
assert_eq!(tris, vec![[0, 1, 2], [0, 2, 3]]);
|
||||
@@ -628,11 +674,19 @@ mod tests {
|
||||
assert_eq!(fixed.len(), 4);
|
||||
let mut tris = vec![[0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4]];
|
||||
assert_eq!(
|
||||
refine(&s, 0.01, 1e-9, &fixed, &mut uv, &mut tris, 1000),
|
||||
refine(
|
||||
&s,
|
||||
0.01,
|
||||
&mut Poles::new(&s, 1e-9),
|
||||
&fixed,
|
||||
&mut uv,
|
||||
&mut tris,
|
||||
1000
|
||||
),
|
||||
Err(Stop::NoValue(NoValue([0.5, 0.5])))
|
||||
);
|
||||
assert_eq!(
|
||||
flip(&s, 0.01, 1e-9, &fixed, &uv, &mut tris),
|
||||
flip(&s, 0.01, &mut Poles::new(&s, 1e-9), &fixed, &uv, &mut tris),
|
||||
Err(Stop::NoValue(NoValue([0.5, 0.5])))
|
||||
);
|
||||
}
|
||||
@@ -661,7 +715,7 @@ mod tests {
|
||||
};
|
||||
let uv = [[0.5, -0.1], [0.5, 0.1], [0.6, 0.0]];
|
||||
let side = 3.0 * (1.0 - 0.1f64.cos());
|
||||
let got = worst(&s, 0.0, &[], &uv, &[[0, 1, 2]]);
|
||||
let got = worst(&s, &mut Poles::new(&s, 0.0), &[], &uv, &[[0, 1, 2]]);
|
||||
assert!(
|
||||
got.as_ref()
|
||||
.is_ok_and(|(w, unmeasured)| (w - side).abs() < 1e-9 * side && *unmeasured == 0),
|
||||
@@ -669,8 +723,70 @@ mod tests {
|
||||
);
|
||||
// A corner past the nodes is the triangle's index.
|
||||
assert_eq!(
|
||||
worst(&s, 0.0, &[], &uv, &[[0, 1, 2], [0, 1, 3]]),
|
||||
worst(
|
||||
&s,
|
||||
&mut Poles::new(&s, 0.0),
|
||||
&[],
|
||||
&uv,
|
||||
&[[0, 1, 2], [0, 1, 3]]
|
||||
),
|
||||
Err(Stop::Corner(1))
|
||||
);
|
||||
}
|
||||
|
||||
/// Refining, flipping and measuring a coarse patch of a cylinder (every sag over the
|
||||
/// tolerance, so the nodes are looked at by many triangles, sides and rounds) asks the
|
||||
/// kernel's classifier once per node and direction, not once per look.
|
||||
#[test]
|
||||
fn the_classifier_is_asked_once_per_node() {
|
||||
use solidcore::eval::EvalCurve;
|
||||
let s = EvalSurface::Revolved {
|
||||
profile: EvalCurve::new(
|
||||
1,
|
||||
vec![0.0, 0.0, 1.0, 1.0],
|
||||
vec![[3.0, 0.0, 0.0, 1.0], [3.0, 0.0, 4.0, 1.0]],
|
||||
),
|
||||
origin: solidcore::Vec3::ZERO,
|
||||
axis: solidcore::Vec3::Z,
|
||||
transform: [
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
],
|
||||
swap: false,
|
||||
};
|
||||
let n = 6u32;
|
||||
let mut uv: Vec<[f64; 2]> = Vec::new();
|
||||
for i in 0..=n {
|
||||
for j in 0..=n {
|
||||
uv.push([
|
||||
f64::from(i) / f64::from(n),
|
||||
3.0 * f64::from(j) / f64::from(n),
|
||||
]);
|
||||
}
|
||||
}
|
||||
let mut tris: Vec<[u32; 3]> = Vec::new();
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
let a = i * (n + 1) + j;
|
||||
let (b, c, d) = (a + 1, a + n + 1, a + n + 2);
|
||||
tris.push([a, c, d]);
|
||||
tris.push([a, d, b]);
|
||||
}
|
||||
}
|
||||
let mut poles = Poles::new(&s, 1e-9);
|
||||
let start = uv.len();
|
||||
// Refinement is capped (it may or may not run out of room): the count holds either way.
|
||||
refine(&s, 5e-2, &mut poles, &[], &mut uv, &mut tris, 300).unwrap();
|
||||
flip(&s, 5e-2, &mut poles, &[], &uv, &mut tris).unwrap();
|
||||
worst(&s, &mut poles, &[], &uv, &tris).unwrap();
|
||||
assert!(uv.len() > 2 * start, "{} nodes: not refined", uv.len());
|
||||
assert!(poles.asked > 0);
|
||||
assert!(
|
||||
poles.asked <= 2 * uv.len(),
|
||||
"{} calls for {} nodes",
|
||||
poles.asked,
|
||||
uv.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -61,12 +61,28 @@ pub(crate) fn crossing(
|
||||
if n < 3 {
|
||||
continue;
|
||||
}
|
||||
// Each node is on a pole or not whichever two chords it ends: asked once.
|
||||
let mut on_pole: Vec<Option<bool>> = vec![None; n];
|
||||
let mut node_pole = |k: usize, q: [f64; 2]| -> Result<bool, super::Unclassified> {
|
||||
if let Some(p) = on_pole.get(k).copied().flatten() {
|
||||
return Ok(p);
|
||||
}
|
||||
let p = pole(q)?;
|
||||
if let Some(slot) = on_pole.get_mut(k) {
|
||||
*slot = Some(p);
|
||||
}
|
||||
Ok(p)
|
||||
};
|
||||
for k in 0..n {
|
||||
// Proven: n = l.uv.len() >= 3, checked above
|
||||
let (Some(&a), Some(&b)) = (l.uv.get(k), l.uv.get((k + 1) % n)) else {
|
||||
let next = (k + 1) % n;
|
||||
let (Some(&a), Some(&b)) = (l.uv.get(k), l.uv.get(next)) else {
|
||||
continue;
|
||||
};
|
||||
if !(a.iter().chain(&b).all(|x| x.is_finite())) || pole(a)? || pole(b)? {
|
||||
if !(a.iter().chain(&b).all(|x| x.is_finite()))
|
||||
|| node_pole(k, a)?
|
||||
|| node_pole(next, b)?
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// The loop's closing chord of a loop that winds round the surface ends at the copy of
|
||||
|
||||
+27
-2
@@ -1345,7 +1345,16 @@ fn sag_refinement_across_an_inflection_ends_without_slivers() {
|
||||
let fixed: Vec<Vec3> = Vec::new();
|
||||
let tol = 0.5;
|
||||
let start = uv.len();
|
||||
sag::refine(&s, tol, 1e-9, &fixed, &mut uv, &mut tris, 20_000).unwrap();
|
||||
sag::refine(
|
||||
&s,
|
||||
tol,
|
||||
&mut sag::Poles::new(&s, 1e-9),
|
||||
&fixed,
|
||||
&mut uv,
|
||||
&mut tris,
|
||||
20_000,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
uv.len() - start < 1_000,
|
||||
"{} points added: refinement ran to its budget",
|
||||
@@ -1400,7 +1409,7 @@ fn triangles_standing_across_a_curved_sheet_are_flipped_along_it() {
|
||||
.sum()
|
||||
};
|
||||
let before = area(&tris);
|
||||
sag::flip(&s, 0.1, 1e-9, &[], &uv, &mut tris).unwrap();
|
||||
sag::flip(&s, 0.1, &mut sag::Poles::new(&s, 1e-9), &[], &uv, &mut tris).unwrap();
|
||||
let after = area(&tris);
|
||||
let sides: Vec<(u32, u32)> = tris
|
||||
.iter()
|
||||
@@ -2138,3 +2147,19 @@ fn an_edge_stopped_at_its_depth_bound_is_named_within_its_tolerance() {
|
||||
assert!(capped.within && capped.sag <= capped.tol, "{capped:?}");
|
||||
assert!(capped.excess > 1.0, "{capped:?}");
|
||||
}
|
||||
|
||||
/// Refining a spline face moves no triangle count: the memo of the pole classifier only drops the
|
||||
/// repeated question (the coil took 1,089,434 / 1,091,452 calls at display / export before it).
|
||||
#[test]
|
||||
fn the_spline_coil_keeps_its_triangles() {
|
||||
let name = "smlib_coil_lower_half";
|
||||
let (b, size) = (fixture(name), size_of(name));
|
||||
for (t, want) in [
|
||||
(MeshTolerance::display_of(size), 30_776),
|
||||
(MeshTolerance::export_of(size), 30_808),
|
||||
] {
|
||||
let (p, _) = plan(&b, &t, size).unwrap();
|
||||
let (m, _) = p.evaluate();
|
||||
assert_eq!(m.triangles.len(), want);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
//! | `FilletBrep::calc_join_section` | `SmFilletVertex::CalcJoinSection` (543d25a) |
|
||||
//! | `FilletBrep::calc_rail_int_end_surface` | `SmFilletVertex::CalcRailIntEndSurface` (`SmBlendCornerEnd.cpp`, 3e26f76) |
|
||||
//! | [`mixed`] | the Mixed corner (`ClassifyMixed`, `MakeMixedTopology`, `CalcMixedVertGeom`, `CalcMixedEdgeGeom`, `CreateMixedPatches`; 6fd0411, 6e79de5, e1a2153) |
|
||||
//! | [`on_face`] | none: a Setback's spring curve kept inside its trimmed face (solidcore#2343 (d)) |
|
||||
//! | [`setback`] | the Setback (`ClassifySetBack`, `MakeSetBackTopology`, `BuildSetBackFill`, `CreateSetBackPatches`; 61547af, 1506e2f, f1d81f4) |
|
||||
//! | [`swept`] | the Swept turn (`Classify`'s Swept part, `BuildSweptSurface`, `CreateSweptPatch`) |
|
||||
//! | [`BlendChamferCorner`], [`chamfer`] | not the fork's: chamfers meeting at a vertex, closed by their planes (WR-716, #2345) |
|
||||
@@ -69,6 +70,7 @@ pub mod ball;
|
||||
pub mod chamfer;
|
||||
pub mod end;
|
||||
pub mod mixed;
|
||||
pub mod on_face;
|
||||
pub mod setback;
|
||||
pub mod swept;
|
||||
|
||||
|
||||
@@ -1423,7 +1423,8 @@ pub fn calc_mixed_edge_geom(
|
||||
Point3::new(to.0, to.1, 0.0),
|
||||
)?;
|
||||
let side_i = *nth(if turn { &which_t } else { &which }, kk, OP_EDGE)?;
|
||||
*nth_mut(of2m(&mut side_curves, f), side_i, OP_EDGE)? = Some(iso.clone());
|
||||
*nth_mut(of2m(&mut side_curves, f).as_mut_slice(), side_i, OP_EDGE)? =
|
||||
Some(iso.clone());
|
||||
of2m(&mut state.curves_3d, f).push(Curve::from(iso));
|
||||
of2m(&mut state.uv_curves, f).push(Curve::from(uv));
|
||||
of2m(&mut state.orients, f).push(*nth(&orients, kk, OP_EDGE)?);
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
//! Whether a Setback's spring curve stays inside its trimmed face: it comes within no gap of
|
||||
//! any of the face's other edges (solidcore#2343 (d), the owner's ruling B there).
|
||||
//!
|
||||
//! Upstream: none. The fork's ruling on smlib#85 part C asked that the spring curve's
|
||||
//! parameter-plane control triangle `(P^, X^, Q^)` lie inside the trimmed face; that is the
|
||||
//! convex-hull bound for the curve itself, sound but loose (it refuses setbacks whose spring
|
||||
//! curve stays on the face). Ruling B on solidcore#2343 checks the curve.
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |-------------------|----------|
|
||||
//! | [`Clearance`] | none |
|
||||
//! | [`bezier_pieces`] | none |
|
||||
//! | [`clear_of`] | none |
|
||||
//!
|
||||
//! Decided on control points, never at sample points (`docs/definitions-and-certificates.md`
|
||||
//! §1): a rational Bézier piece with positive weights lies in the convex hull of its
|
||||
//! control points (Farin, *Curves and Surfaces for CAGD*). Two pieces whose hulls are
|
||||
//! proved farther apart than the gap and both pieces' rounding (their boxes', or their gap
|
||||
//! along the line between their centroids: `apart`) come no nearer than the gap. Otherwise the wider of the two is halved (de Casteljau on its
|
||||
//! homogeneous points) and the halves are tried in turn. A piece halved [`DEPTH`] times is
|
||||
//! halved no further, and a pair still undecided then, or once [`PAIRS`] pairs have been
|
||||
//! tried, is [`Clearance::Near`]: an undecided spring curve is not taken as clear. The
|
||||
//! rounding is Higham's `gamma` of the operations each point has been through, as
|
||||
//! `blend_setback::containment` bounds its patches.
|
||||
//!
|
||||
//! Deviations: no recursion: the pairs are kept on an explicit stack, each piece halved at
|
||||
//! most [`DEPTH`] times, at most [`PAIRS`] pairs per curve and piece list.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::curve::curve::{Curve, CurveKind};
|
||||
use crate::error::Result;
|
||||
use crate::geom::vector3d::sqrt;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::Vec3;
|
||||
use crate::nurbs::b_spline_curve::BSplineCurve;
|
||||
use crate::nurbs::math::{gamma, NOW, NOZ};
|
||||
|
||||
const OP: &str = "blend::blend_corner::on_face::clear_of";
|
||||
|
||||
/// A homogeneous point `(x w, y w, z w, w)`.
|
||||
pub type Hom = [f64; 4];
|
||||
|
||||
/// How many times a piece is halved at most: a piece of a part's size, tens of millimetres,
|
||||
/// is then under 1e-10 long, the rounding of the part's coordinates (§4.4 of the
|
||||
/// definitions doc).
|
||||
pub const DEPTH: usize = 40;
|
||||
|
||||
/// How many pairs of pieces [`clear_of`] tries at most for one curve against one list of
|
||||
/// pieces. Near a closest approach a few pairs stay at each halving, so a decided case needs
|
||||
/// a few hundred.
|
||||
pub const PAIRS: usize = 4096;
|
||||
|
||||
/// Whether one curve comes within a gap of another ([`clear_of`]).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Clearance {
|
||||
/// Proved to stay farther than the gap from the other everywhere.
|
||||
Clear,
|
||||
/// Within the gap somewhere, or not shown not to be within [`DEPTH`] and [`PAIRS`].
|
||||
Near,
|
||||
}
|
||||
|
||||
/// The rational Bézier pieces of `curve` as homogeneous points, one piece per knot span: of
|
||||
/// the B-spline a line, a conic or a B-spline curve is built on (the whole of it, which holds
|
||||
/// an edge it is trimmed to; a line's or an ellipse's made on a copy where it holds none
|
||||
/// yet). `None` for a curve of another kind, a planar (2D) one, one whose B-spline is not
|
||||
/// made, or one whose weights are not all positive and finite: such a curve has no hull to
|
||||
/// bound it by.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of the B-spline's decomposition into its Bézier pieces; a cancellation while an
|
||||
/// ellipse's is made.
|
||||
pub fn bezier_pieces(k: &Kernel, curve: &Curve) -> Result<Option<Vec<Vec<Hom>>>> {
|
||||
let base: Cow<'_, BSplineCurve> = match curve.kind() {
|
||||
CurveKind::BSpline(c) => Cow::Borrowed(c),
|
||||
CurveKind::Line(c) => c.base_with_nurb()?,
|
||||
CurveKind::Ellipse(c) if c.base().nurb.is_none() => {
|
||||
let mut copy = c.clone();
|
||||
match copy.make_nurb(k) {
|
||||
Err(e) if e.is_unported_or_cancelled() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
Ok(()) => Cow::Owned(copy.base().clone()),
|
||||
}
|
||||
}
|
||||
CurveKind::Ellipse(c) => Cow::Borrowed(c.base()),
|
||||
CurveKind::Hyperbola(c) => Cow::Borrowed(&c.base),
|
||||
CurveKind::Parabola(c) => Cow::Borrowed(c.base()),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let Some(nurb) = base.nurb.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut pieces = Vec::new();
|
||||
for bez in nurb.decompose_bez()? {
|
||||
let mut piece = Vec::new();
|
||||
for cp in bez.cpts() {
|
||||
if cp.z == NOZ {
|
||||
return Ok(None);
|
||||
}
|
||||
let h = if cp.w == NOW {
|
||||
[cp.x, cp.y, cp.z, 1.0]
|
||||
} else {
|
||||
[cp.x, cp.y, cp.z, cp.w]
|
||||
};
|
||||
if !h.iter().all(|c| c.is_finite()) || h[3] <= 0.0 {
|
||||
return Ok(None);
|
||||
}
|
||||
piece.push(h);
|
||||
}
|
||||
pieces.push(piece);
|
||||
}
|
||||
Ok(Some(pieces))
|
||||
}
|
||||
|
||||
/// A piece on the stack: its homogeneous points, how many more times it may be halved, and
|
||||
/// the rounding bound of its Euclidean points (that of the piece it was halved from).
|
||||
#[derive(Clone)]
|
||||
struct Piece {
|
||||
points: Vec<Hom>,
|
||||
depth: usize,
|
||||
rounding: f64,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
/// A piece as given: its Euclidean points are within `rounding` of the exact ones after
|
||||
/// [`DEPTH`] halvings of `degree` averaging steps each and the division by the weight:
|
||||
/// `3 gamma(ops + 2)` of its largest homogeneous coordinate over its least weight (shrunk
|
||||
/// by the weights' own rounding), `ops = 8 + DEPTH degree` (the 8 for the knot insertions
|
||||
/// that cut a B-spline into its pieces and the division). `None` where a weight is not
|
||||
/// positive or a coordinate not finite.
|
||||
fn new(points: &[Hom]) -> Option<Self> {
|
||||
let degree = points.len().checked_sub(1)?;
|
||||
let (mut most, mut w_min) = (0.0f64, f64::INFINITY);
|
||||
for h in points {
|
||||
if !h.iter().all(|c| c.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
most = most.max(h[0].abs()).max(h[1].abs()).max(h[2].abs());
|
||||
w_min = w_min.min(h[3]);
|
||||
}
|
||||
let ops = 8 + DEPTH * degree;
|
||||
let shrink = 1.0 - gamma(ops);
|
||||
if !(w_min > 0.0 && shrink > 0.0) {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
points: points.to_vec(),
|
||||
depth: DEPTH,
|
||||
rounding: 3.0 * gamma(ops + 2) * most / (w_min * shrink),
|
||||
})
|
||||
}
|
||||
|
||||
/// Its Euclidean points; `None` where a weight is not positive.
|
||||
fn euclidean(&self) -> Option<Vec<Vec3>> {
|
||||
let mut out = Vec::new();
|
||||
for h in &self.points {
|
||||
if h[3].is_nan() || h[3] <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
out.push(Vec3::new(h[0] / h[3], h[1] / h[3], h[2] / h[3]));
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Its two halves at the middle of its parameter (de Casteljau), each halvable once less.
|
||||
fn halves(&self) -> (Self, Self) {
|
||||
let d = self.points.len().saturating_sub(1);
|
||||
let mut work = self.points.clone();
|
||||
let mut left = Vec::new();
|
||||
let mut right = Vec::new();
|
||||
for k in 0..=d {
|
||||
if let Some(&h) = work.first() {
|
||||
left.push(h);
|
||||
}
|
||||
if let Some(&h) = work.get(d - k) {
|
||||
right.push(h);
|
||||
}
|
||||
for i in 0..d - k {
|
||||
if let (Some(&a), Some(&b)) = (work.get(i), work.get(i + 1)) {
|
||||
if let Some(w) = work.get_mut(i) {
|
||||
*w = [
|
||||
0.5 * (a[0] + b[0]),
|
||||
0.5 * (a[1] + b[1]),
|
||||
0.5 * (a[2] + b[2]),
|
||||
0.5 * (a[3] + b[3]),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
right.reverse();
|
||||
let depth = self.depth.saturating_sub(1);
|
||||
(
|
||||
Self {
|
||||
points: left,
|
||||
depth,
|
||||
rounding: self.rounding,
|
||||
},
|
||||
Self {
|
||||
points: right,
|
||||
depth,
|
||||
rounding: self.rounding,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The widest extent of `points` along an axis.
|
||||
fn width(points: &[Vec3]) -> f64 {
|
||||
let (lo, hi) = extent(points);
|
||||
(hi.x - lo.x).max(hi.y - lo.y).max(hi.z - lo.z)
|
||||
}
|
||||
|
||||
/// The box of `points`, its least and greatest corners.
|
||||
fn extent(points: &[Vec3]) -> (Vec3, Vec3) {
|
||||
let mut lo = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
|
||||
let mut hi = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
|
||||
for p in points {
|
||||
lo = Vec3::new(lo.x.min(p.x), lo.y.min(p.y), lo.z.min(p.z));
|
||||
hi = Vec3::new(hi.x.max(p.x), hi.y.max(p.y), hi.z.max(p.z));
|
||||
}
|
||||
(lo, hi)
|
||||
}
|
||||
|
||||
/// A lower bound on the distance between the convex hulls of `a` and `b`, less its own
|
||||
/// rounding: the larger of the distance between their boxes and their gap along the line
|
||||
/// between their centroids (any unit direction `u` separates them by `min u . b - max u . a`;
|
||||
/// that one closes on the hulls' distance as near-parallel pieces shrink, where the boxes'
|
||||
/// does not). The boxes' gaps are rounded by `2 gamma(1)` of the largest coordinate `s`, their
|
||||
/// sum of squares by `gamma(6)`; each projection by `gamma(3) sqrt 3 s |u|`, and the quotient
|
||||
/// by `|u|` by `gamma(4)`.
|
||||
fn apart(a: &[Vec3], b: &[Vec3]) -> f64 {
|
||||
let ((alo, ahi), (blo, bhi)) = (extent(a), extent(b));
|
||||
let size = [alo, ahi, blo, bhi].iter().fold(0.0f64, |m, c| {
|
||||
m.max(c.x.abs()).max(c.y.abs()).max(c.z.abs())
|
||||
});
|
||||
let gap = |l: f64, h: f64| l - h;
|
||||
let mut squares = 0.0;
|
||||
for d in [
|
||||
gap(blo.x, ahi.x).max(gap(alo.x, bhi.x)),
|
||||
gap(blo.y, ahi.y).max(gap(alo.y, bhi.y)),
|
||||
gap(blo.z, ahi.z).max(gap(alo.z, bhi.z)),
|
||||
] {
|
||||
let d = (d - 2.0 * gamma(1) * size).max(0.0);
|
||||
squares += d * d;
|
||||
}
|
||||
let boxes = sqrt(squares * (1.0 - gamma(6))) * (1.0 - gamma(2));
|
||||
let centroid = |p: &[Vec3]| {
|
||||
let (sum, count) = p
|
||||
.iter()
|
||||
.fold((Vec3::ZERO, 0.0), |(s, c), &q| (s + q, c + 1.0));
|
||||
sum * (1.0 / count)
|
||||
};
|
||||
let w = centroid(b) - centroid(a);
|
||||
// the line between the centroids, and its parts square to each piece's chord (for
|
||||
// near-parallel pieces side by side, whatever their shift along each other)
|
||||
let square_to = |p: &[Vec3]| match (p.first(), p.last()) {
|
||||
(Some(&first), Some(&last)) => {
|
||||
let t = last - first;
|
||||
let tt = t.dot(t);
|
||||
if tt > 0.0 {
|
||||
w - t * (w.dot(t) / tt)
|
||||
} else {
|
||||
w
|
||||
}
|
||||
}
|
||||
_ => w,
|
||||
};
|
||||
let mut best = boxes;
|
||||
for u in [w, square_to(a), square_to(b)] {
|
||||
let length = u.length();
|
||||
if length.is_nan() || length <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
let reach_a = a.iter().fold(f64::NEG_INFINITY, |m, p| m.max(u.dot(*p)));
|
||||
let reach_b = b.iter().fold(f64::INFINITY, |m, p| m.min(u.dot(*p)));
|
||||
let along = (reach_b - reach_a) / length * (1.0 - gamma(4))
|
||||
- 2.0 * gamma(3) * sqrt(3.0) * size * (1.0 + gamma(4));
|
||||
best = best.max(along);
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
/// Whether the rational Bézier curve `curve` (its homogeneous Bernstein coefficients) comes
|
||||
/// within `gap` of any of `pieces` (each such a curve too): [`Clearance::Clear`] only where
|
||||
/// that is proved on the pieces' control points (module docs).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The kernel's cancellation.
|
||||
pub fn clear_of(k: &Kernel, curve: &[Hom], pieces: &[Vec<Hom>], gap: f64) -> Result<Clearance> {
|
||||
let Some(a) = Piece::new(curve) else {
|
||||
return Ok(Clearance::Near);
|
||||
};
|
||||
let mut stack = Vec::new();
|
||||
for p in pieces {
|
||||
let Some(b) = Piece::new(p) else {
|
||||
return Ok(Clearance::Near);
|
||||
};
|
||||
stack.push((a.clone(), b));
|
||||
}
|
||||
let mut tried = 0;
|
||||
while let Some((a, b)) = stack.pop() {
|
||||
k.check_cancelled(OP)?;
|
||||
tried += 1;
|
||||
if tried > PAIRS {
|
||||
return Ok(Clearance::Near);
|
||||
}
|
||||
let (Some(pa), Some(pb)) = (a.euclidean(), b.euclidean()) else {
|
||||
return Ok(Clearance::Near);
|
||||
};
|
||||
if apart(&pa, &pb) > gap + a.rounding + b.rounding {
|
||||
continue;
|
||||
}
|
||||
let (wa, wb) = (width(&pa), width(&pb));
|
||||
// the wider is halved, where it may be; neither: undecided
|
||||
let halve_a = a.depth > 0 && (wa >= wb || b.depth == 0);
|
||||
if halve_a {
|
||||
let (a0, a1) = a.halves();
|
||||
stack.push((a1, b.clone()));
|
||||
stack.push((a0, b));
|
||||
} else if b.depth > 0 {
|
||||
let (b0, b1) = b.halves();
|
||||
stack.push((a.clone(), b1));
|
||||
stack.push((a, b0));
|
||||
} else {
|
||||
return Ok(Clearance::Near);
|
||||
}
|
||||
}
|
||||
Ok(Clearance::Clear)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A quarter circle of radius `r` about `(cx, cy)` in z = 0, from angle 0 to 90 degrees:
|
||||
/// the rational quadratic with middle weight `cos 45`.
|
||||
fn quarter(cx: f64, cy: f64, r: f64) -> Vec<Hom> {
|
||||
let w = 0.5f64.sqrt();
|
||||
vec![
|
||||
[cx + r, cy, 0.0, 1.0],
|
||||
[(cx + r) * w, (cy + r) * w, 0.0, w],
|
||||
[cx, cy + r, 0.0, 1.0],
|
||||
]
|
||||
}
|
||||
|
||||
/// A segment from `a` to `b`, degree 1.
|
||||
fn segment(a: [f64; 3], b: [f64; 3]) -> Vec<Hom> {
|
||||
vec![[a[0], a[1], a[2], 1.0], [b[0], b[1], b[2], 1.0]]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_segment_near_a_circle_is_clear_by_more_than_the_gap_and_near_within_it() {
|
||||
let k = Kernel::default();
|
||||
let arc = vec![quarter(0.0, 0.0, 10.0)];
|
||||
// the line x + y = c outside the circle is closest to the arc at 45 degrees, c / sqrt 2
|
||||
// - 10 away, and runs along it within twice that for a hundredth of a turn
|
||||
let c = 2.0f64.sqrt() * (10.0 + 1e-3);
|
||||
let chord = segment([c, 0.0, 0.0], [0.0, c, 0.0]);
|
||||
assert_eq!(
|
||||
clear_of(&k, &chord, &arc, 0.9e-3).unwrap(),
|
||||
Clearance::Clear
|
||||
);
|
||||
assert_eq!(clear_of(&k, &chord, &arc, 1.1e-3).unwrap(), Clearance::Near);
|
||||
// inside it, crossing it twice: near at any gap
|
||||
let c = 2.0f64.sqrt() * (10.0 - 1e-3);
|
||||
let chord = segment([c, 0.0, 0.0], [0.0, c, 0.0]);
|
||||
assert_eq!(clear_of(&k, &chord, &arc, 0.0).unwrap(), Clearance::Near);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pieces_far_apart_are_clear_at_once_and_a_bad_weight_is_near() {
|
||||
let k = Kernel::default();
|
||||
let far = vec![quarter(100.0, 0.0, 1.0)];
|
||||
let chord = segment([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]);
|
||||
assert_eq!(clear_of(&k, &chord, &far, 1e-6).unwrap(), Clearance::Clear);
|
||||
let mut bad = quarter(100.0, 0.0, 1.0);
|
||||
bad[1][3] = 0.0;
|
||||
assert_eq!(clear_of(&k, &chord, &[bad], 1e-6).unwrap(), Clearance::Near);
|
||||
}
|
||||
}
|
||||
+333
-2
@@ -44,6 +44,7 @@
|
||||
//! | `curved_least` (private) | `sm_SbCurvedLeast` (37c9e1d) |
|
||||
//! | `gather_ends` (private) | `BuildSetBackFill`'s `gather` |
|
||||
//! | `grow_curved` (private) | `BuildSetBackFill`'s growth beside curved faces (37c9e1d) |
|
||||
//! | `springs_on_faces`, `springs_refusal` (private) | none (solidcore#2343 (d)) |
|
||||
//!
|
||||
//! Deviations: the Setback's own members are kept in [`SetbackCorner`] on the corner
|
||||
//! ([`FilletCorner::setback`]) from its creation (`SetFillOrder`) until its patches are
|
||||
@@ -60,19 +61,26 @@
|
||||
//! the vertex there, from the edge's direction at the setback, not the end of its rails
|
||||
//! nearer the vertex as the fork's `gather` takes it: a round on an arc, extended past a far
|
||||
//! corner that does not blend, comes back round nearer the vertex than a long setback's end
|
||||
//! section (solidcore#2835).
|
||||
//! section (solidcore#2835). Beside a curved face each spring curve is checked to stay inside
|
||||
//! its trimmed face before the fill is made, and again at setbacks the growth finds; a
|
||||
//! setback that takes one off its face is refused, naming the largest that keeps every one
|
||||
//! on its face, or none where none down to the rails' crossing does or the growth's
|
||||
//! setbacks take one off (solidcore#2343 (d); the fork's ruling checked the curve's parameter-plane
|
||||
//! control triangle instead, and built no check).
|
||||
//!
|
||||
//! [`FilletCorner::setback`]: crate::blend::fillet_corner::p3::FilletCorner::setback
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::blend::blend_corner::ball::{solver_of_edgeuse_or_mate, BallSolvers};
|
||||
use crate::blend::blend_corner::on_face::{self, Clearance};
|
||||
use crate::blend::blend_corner::swept::fmt_g;
|
||||
use crate::blend::blend_corner::{BlendCorner, BlendCornerEvent};
|
||||
use crate::blend::blend_refusal::BlendRefusal;
|
||||
use crate::blend::blend_setback::{
|
||||
bisect_thousandths, certified_scale, containment, distances, fill_hole_counted,
|
||||
fill_hole_probe, Edge, Face, FaceKind, Fill, FillStats, Probe, Round,
|
||||
fill_hole_probe, spring_curve, Edge, Face, FaceKind, Fill, FillStats, Probe, Round,
|
||||
SpringCurve,
|
||||
};
|
||||
use crate::blend::fillet_corner::p1::{face_by_two_edgeuses, test_g1_continuity};
|
||||
use crate::blend::fillet_corner::p3::{CornerPatchEnv, FilletCorner, FilletCornerKind};
|
||||
@@ -92,6 +100,7 @@ use crate::error::{EntityRef, Result, SolidError};
|
||||
use crate::geom::extent1d::Extent1d;
|
||||
use crate::geom::extent2d::Extent2d;
|
||||
use crate::geom::global_solver::SolverOperation;
|
||||
use crate::geom::vector3d::sqrt;
|
||||
use crate::intersect::point_class::PointClassType;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::{Point2, Point3, Vec3, BIG_DOUBLE, EFF_ZERO, EFF_ZERO_SQRT};
|
||||
@@ -105,6 +114,7 @@ use crate::surface::surface::Surface;
|
||||
use crate::topology::brep::p7::{make_face_with_curves, FaceCurvesInput};
|
||||
use crate::topology::edgeuse::p1 as eu1;
|
||||
use crate::topology::face::p2 as face2;
|
||||
use crate::topology::face::p4 as face4;
|
||||
use crate::topology::{edge, vertex, Brep, EdgeId, EdgeuseId, FaceId, VertexId};
|
||||
|
||||
const OP_CLASSIFY: &str = "blend::blend_corner::setback::classify_setback";
|
||||
@@ -129,6 +139,10 @@ pub struct SetbackCorner {
|
||||
/// curved face the length is the edge's arc length, as the setbacks are measured along
|
||||
/// it; between planes (straight edges) the distance between its ends.
|
||||
pub room: Vec<f64>,
|
||||
/// Not the fork (solidcore#2343 (d)): each blended edge's least setback, where the
|
||||
/// rails of the rounds beside it cross (`sm_SbSetBacks`'s `rvLeast`); a setback below it
|
||||
/// is too short.
|
||||
pub least: Vec<f64>,
|
||||
/// `m_pFill`: the fill, from the edge geometry until the patches take it.
|
||||
pub fill: Option<Fill>,
|
||||
/// `m_bSetBackOpen` (the fork's ef57570, smlib#125): an open G1 corner, its spring
|
||||
@@ -149,6 +163,7 @@ impl Default for SetbackCorner {
|
||||
convex: true,
|
||||
overflows: false,
|
||||
room: Vec::new(),
|
||||
least: Vec::new(),
|
||||
fill: None,
|
||||
open: false,
|
||||
definition: None,
|
||||
@@ -1234,6 +1249,7 @@ pub fn make_setback_topology(
|
||||
}
|
||||
sb.overflows = !fits;
|
||||
sb.room.clone_from(&room);
|
||||
sb.least.clone_from(&least);
|
||||
// a G1 fill is taken only certified convex: the least scale of the setbacks at which it
|
||||
// is (CertifiedScale). The rule's grow to it, each held at its room less the rule's
|
||||
// quarter radius (the others growing on); refused, naming them, where none within the
|
||||
@@ -1783,6 +1799,27 @@ fn grow_curved<S: SetbackSolvers>(
|
||||
let Some(ends) = growth.place(pseudo, corner, solvers, hi)? else {
|
||||
return Err(err());
|
||||
};
|
||||
// the setbacks the fill needs keep every spring curve on its face, or none is taken
|
||||
// (#2343 (d)): the growth finds no smaller one certified, and a spring curve off its face
|
||||
// at these is taken to stay off it at every larger one (as the growth takes
|
||||
// certification, checked on the cases, not proved)
|
||||
let springs = springs_on_faces(
|
||||
growth.k,
|
||||
growth.target,
|
||||
pseudo,
|
||||
corner,
|
||||
growth.planes,
|
||||
growth.tol,
|
||||
)?;
|
||||
if springs == Springs::Leave {
|
||||
return Ok(Err(SetbackRefusal::no_room(
|
||||
format!(
|
||||
"Fillet Error: the setback corner's fill stays no more concave than its faces only from {} times its setbacks, where a spring curve is not shown to stay on its face: no setback builds",
|
||||
fmt_g(hi)
|
||||
),
|
||||
Some(0.0),
|
||||
)));
|
||||
}
|
||||
if let Some(a) = asked {
|
||||
let room = &corner.setback.as_ref().ok_or_else(err)?.room;
|
||||
let fits_room = room.iter().all(|&r| hi * a.setback + a.ease < r);
|
||||
@@ -1805,6 +1842,292 @@ fn grow_curved<S: SetbackSolvers>(
|
||||
Ok(Ok(ends))
|
||||
}
|
||||
|
||||
/// Where a Setback's spring curves stand on its faces ([`springs_on_faces`]).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Springs {
|
||||
/// Each proved to keep clear of every other edge of its face.
|
||||
Clear,
|
||||
/// One comes within its gap of another edge of its face, or is not shown not to.
|
||||
Leave,
|
||||
/// One is not made: [`spring_curve`] refuses the rails' ends (the fill, which makes it
|
||||
/// from the same ends, says why).
|
||||
Unmade,
|
||||
}
|
||||
|
||||
/// The derivative along `v` of `face`'s unit normal field ([`Face::distance`]'s normal, out
|
||||
/// of the material, at any point) at `x`: zero on a plane; a sphere's `side (v - (v . u) u) /
|
||||
/// |x - c|` (u the unit direction from its centre); a cylinder's or cone's `side cos v_t /
|
||||
/// rho`, `v_t` the part of `v` along the circle about the axis through `x`, rho that circle's
|
||||
/// radius and `cos` that of the cone's half angle (1 on a cylinder). `None` on a quadric's
|
||||
/// centre or axis.
|
||||
fn normal_turn(face: &Face, x: Point3, v: Vec3) -> Option<Vec3> {
|
||||
let u = x - face.point;
|
||||
match face.kind {
|
||||
FaceKind::Plane => Some(Vec3::ZERO),
|
||||
FaceKind::Sphere => {
|
||||
let len = u.length();
|
||||
let d = u.unitize().ok()?;
|
||||
Some((v - d * v.dot(d)) * (face.side / len))
|
||||
}
|
||||
FaceKind::Cylinder | FaceKind::Cone => {
|
||||
let r = u - face.axis * u.dot(face.axis);
|
||||
let rho = r.length();
|
||||
let d = r.unitize().ok()?;
|
||||
let along = v - d * v.dot(d) - face.axis * v.dot(face.axis);
|
||||
let cos = 1.0 / sqrt(1.0 + face.slope * face.slope);
|
||||
Some(along * (face.side * cos / rho))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The unit direction of the rail on `face` of the round of radius `r` along `edge_id`, at
|
||||
/// its end `p` on `face` whose other end is `q` on `other` (the round's two contact points in
|
||||
/// one section), turned toward the corner's vertex (`sign` `+1` at a convex corner, `-1` at a
|
||||
/// concave one). In closed form, not from the rail as traced, which runs only from the
|
||||
/// setback the corner was made at outward: the ball's centre is `c = p - sign r n(p)` and
|
||||
/// runs along `n(p) x m(q)` (n, m the faces' normals, constant along their normal lines), the
|
||||
/// rail is `c + sign r n(c)`, so its direction is that of the centre's plus `sign r` times the
|
||||
/// normal's turn along it ([`normal_turn`]). Toward the vertex: the side the edge runs on to
|
||||
/// the vertex at its setback (as `gather_ends` takes the corner's side of an end section).
|
||||
/// `None` where the faces' normals are parallel there, or a normal's turn is not defined.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn rail_toward_vertex(
|
||||
k: &Kernel,
|
||||
target: &Brep,
|
||||
corner: &FilletCorner,
|
||||
edge_id: EdgeId,
|
||||
face: &Face,
|
||||
p: Point3,
|
||||
other: &Face,
|
||||
q: Point3,
|
||||
r: f64,
|
||||
sign: f64,
|
||||
) -> Result<Option<Vec3>> {
|
||||
let nul = || SolidError::null_pointer(OP_FILL);
|
||||
let vtx = corner.vertex.ok_or_else(nul)?;
|
||||
let (_, n) = face.distance(p);
|
||||
let (_, m) = other.distance(q);
|
||||
let centre = p - n * (sign * r);
|
||||
let along = n.cross(m);
|
||||
let Some(turn) = normal_turn(face, centre, along) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Ok(t) = (along + turn * (sign * r)).unitize() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let setback = corner.set_back_dist_along(target, edge_id)?;
|
||||
let (_, edge_dir) = set_back_frame(k, target, edge_id, Some(vtx), setback, OP_FILL)?;
|
||||
let from_start = edge::start_vertex(target, edge_id)? == Some(vtx);
|
||||
let side = t.dot(if from_start { -edge_dir } else { edge_dir });
|
||||
if !side.is_finite() || side.abs() <= 0.0 {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(if side > 0.0 { t } else { -t }))
|
||||
}
|
||||
|
||||
/// The corner's spring curves, one per face, at the setbacks its vertices are placed at, as
|
||||
/// the fill makes them ([`spring_curve`]): on face `F_i`, from `S_i`, the end of the blend
|
||||
/// after `F_i`, leaving its rail toward the vertex, to `M_i`, the end of the blend before,
|
||||
/// tangent to both rails ([`rail_toward_vertex`], each with the blend's other end, on its
|
||||
/// other face, the mate of its end on `F_i`). [`Springs::Leave`] where a rail's direction is
|
||||
/// not found (not shown to stay on its face); [`Springs::Unmade`] where [`spring_curve`]
|
||||
/// refuses the rails' ends.
|
||||
fn corner_springs(
|
||||
k: &Kernel,
|
||||
target: &Brep,
|
||||
pseudo: &FilletBrep,
|
||||
corner: &FilletCorner,
|
||||
planes: &CornerPlanes,
|
||||
) -> Result<core::result::Result<Vec<SpringCurve>, Springs>> {
|
||||
let nul = || SolidError::null_pointer(OP_FILL);
|
||||
let all = corner.all_eus.as_ref().ok_or_else(nul)?;
|
||||
let sign = if corner.setback.as_ref().ok_or_else(nul)?.convex {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
let n = planes.faces.len();
|
||||
let point = |i: usize| -> Result<Point3> {
|
||||
pseudo.vertex_point(*corner.vertices.get(i).ok_or_else(nul)?)
|
||||
};
|
||||
let mut springs = Vec::new();
|
||||
for (f, plane) in planes.planes.iter().enumerate() {
|
||||
// (no face: no corner, refused by the checked remainders)
|
||||
let before = (f + n - 1).checked_rem(n).ok_or_else(nul)?;
|
||||
let after = (f + 1).checked_rem(n).ok_or_else(nul)?;
|
||||
let mut ends = [(Point3::ZERO, None); 2];
|
||||
// S_f with its mate M_(f+1) on F_(f+1); M_f with its mate S_(f-1) on F_(f-1)
|
||||
for (slot, (blend, at, mate, other)) in ends.iter_mut().zip([
|
||||
(f, 3 * f + 2, 3 * after, after),
|
||||
(before, 3 * f, 3 * before + 2, before),
|
||||
]) {
|
||||
let e = eu1::edge(target, *all.get(blend).ok_or_else(nul)?)?.ok_or_else(nul)?;
|
||||
let p = point(at)?;
|
||||
let tangent = rail_toward_vertex(
|
||||
k,
|
||||
target,
|
||||
corner,
|
||||
e,
|
||||
plane,
|
||||
p,
|
||||
planes.planes.get(other).ok_or_else(nul)?,
|
||||
point(mate)?,
|
||||
planes.edges.get(blend).ok_or_else(nul)?.radius,
|
||||
sign,
|
||||
)?;
|
||||
*slot = (p, tangent);
|
||||
}
|
||||
let [(p, Some(tp)), (q, Some(tq))] = ends else {
|
||||
return Ok(Err(Springs::Leave));
|
||||
};
|
||||
let Ok(spring) = spring_curve(plane, p, tp, q, -tq) else {
|
||||
return Ok(Err(Springs::Unmade));
|
||||
};
|
||||
springs.push(spring);
|
||||
}
|
||||
Ok(Ok(springs))
|
||||
}
|
||||
|
||||
/// solidcore#2343 (d), the owner's ruling B there: whether the corner's spring curves
|
||||
/// ([`corner_springs`]) stay inside their trimmed faces. Each starts inside its face, so it
|
||||
/// stays inside where it keeps clear of every edge of the face but the two blended edges at
|
||||
/// the vertex ([`on_face::clear_of`]: by more than the corner's tolerance and the edge's). The
|
||||
/// two blended edges are left out whole: between them the spring curve lies past the rails of
|
||||
/// their rounds, which run inside the face along them. An edge without a curve, or whose
|
||||
/// curve is not built on a B-spline, gives no hull to bound it by: the spring curve is not
|
||||
/// shown clear of it.
|
||||
fn springs_on_faces(
|
||||
k: &Kernel,
|
||||
target: &Brep,
|
||||
pseudo: &FilletBrep,
|
||||
corner: &FilletCorner,
|
||||
planes: &CornerPlanes,
|
||||
tol: f64,
|
||||
) -> Result<Springs> {
|
||||
let nul = || SolidError::null_pointer(OP_FILL);
|
||||
let vtx = corner.vertex.ok_or_else(nul)?;
|
||||
let springs = match corner_springs(k, target, pseudo, corner, planes)? {
|
||||
Ok(springs) => springs,
|
||||
Err(verdict) => return Ok(verdict),
|
||||
};
|
||||
for (&face, spring) in planes.faces.iter().zip(&springs) {
|
||||
let mut seen = Vec::new();
|
||||
for e in face4::edges(target, face, None, false, None)? {
|
||||
if seen.contains(&e) {
|
||||
continue;
|
||||
}
|
||||
seen.push(e);
|
||||
if edge::start_vertex(target, e)? == Some(vtx)
|
||||
|| edge::end_vertex(target, e)? == Some(vtx)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(curve) = edge::curve(target, e)? else {
|
||||
return Ok(Springs::Leave);
|
||||
};
|
||||
let Some(pieces) = on_face::bezier_pieces(k, curve)? else {
|
||||
return Ok(Springs::Leave);
|
||||
};
|
||||
let gap = tol + edge::tolerance(target, e)?;
|
||||
for half in [&spring.from_p.curve, &spring.from_q.curve] {
|
||||
if on_face::clear_of(k, half, &pieces, gap)? == Clearance::Near {
|
||||
return Ok(Springs::Leave);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Springs::Clear)
|
||||
}
|
||||
|
||||
/// What a setback that takes a spring curve off its face does, for [`setback_too_long`].
|
||||
const SPRING_LEAVES: &str =
|
||||
"takes a setback corner's spring curve off its face (it comes within the tolerances of another edge of the face, or is not shown not to)";
|
||||
|
||||
/// solidcore#2343 (d): where a spring curve leaves its face at the corner's setbacks
|
||||
/// ([`springs_on_faces`]), the refusal naming the largest common scale of them on the grid of
|
||||
/// thousandths at which every spring curve stays inside its face. It is found by the growth's
|
||||
/// bisection ([`bisect_thousandths`], each trial placing the setback vertices again as the
|
||||
/// growth does), between 1 and the least scale at which every setback reaches its edge's
|
||||
/// least, where the rails cross; the search assumes a spring curve on its face at a scale
|
||||
/// stays on it at every smaller one (the curve shrinks toward the vertex with its setbacks),
|
||||
/// as the growth assumes certification monotone: checked on the cases, not proved. One asked
|
||||
/// for is refused as [`BlendRefusal::SetbackTooLong`] naming that scale times it; the
|
||||
/// rule's, naming those setbacks. None at or above the least: no setback builds. A trial
|
||||
/// whose vertices are not placed counts as off its face, so the scale named is at worst
|
||||
/// smaller than need be, never one off its face; whether the fill certifies there is the
|
||||
/// fill's to say when that setback is asked. `None` where every spring curve stays on its
|
||||
/// face, or one is not made (the fill says why).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn springs_refusal<S: SetbackSolvers>(
|
||||
k: &Kernel,
|
||||
target: &Brep,
|
||||
pseudo: &mut FilletBrep,
|
||||
corner: &mut FilletCorner,
|
||||
solvers: &mut S,
|
||||
planes: &CornerPlanes,
|
||||
fills: &[SetBackFill],
|
||||
tol: f64,
|
||||
) -> Result<Option<SetbackRefusal>> {
|
||||
let err = || SolidError::sm_err(OP_FILL);
|
||||
let at_base = springs_on_faces(k, target, pseudo, corner, planes, tol)?;
|
||||
if at_base != Springs::Leave {
|
||||
return Ok(None);
|
||||
}
|
||||
let vtx = corner.vertex.ok_or_else(err)?;
|
||||
let asked = set_back_fill_of(fills, vtx);
|
||||
let sb = corner.setback.as_ref().ok_or_else(err)?;
|
||||
let base = sb.setbacks.clone();
|
||||
if sb.least.len() != base.len() {
|
||||
return Err(err());
|
||||
}
|
||||
let mut least_scale: f64 = 0.0;
|
||||
for (&l, &s) in sb.least.iter().zip(&base) {
|
||||
least_scale = smax(least_scale, l / s);
|
||||
}
|
||||
let growth = Growth {
|
||||
k,
|
||||
target,
|
||||
planes,
|
||||
tol,
|
||||
most: vec![BIG_DOUBLE; base.len()],
|
||||
base,
|
||||
};
|
||||
let mut leaves = |i: i32| -> Result<bool> {
|
||||
Ok(
|
||||
match growth.place(pseudo, corner, solvers, f64::from(i) / 1000.0)? {
|
||||
Some(_) => {
|
||||
springs_on_faces(k, target, pseudo, corner, planes, tol)? != Springs::Clear
|
||||
}
|
||||
None => true,
|
||||
},
|
||||
)
|
||||
};
|
||||
let lo = (1..1000).find(|&i| f64::from(i) >= 1000.0 * least_scale);
|
||||
let fit = match lo {
|
||||
Some(lo) if !leaves(lo)? => Some(bisect_thousandths(lo, 1000, &mut leaves)? - 1),
|
||||
_ => None,
|
||||
};
|
||||
let Some(fit) = fit.map(|i| f64::from(i) / 1000.0) else {
|
||||
return Ok(Some(SetbackRefusal::no_room(
|
||||
"Fillet Error: no setback at or above where the rails cross keeps the setback corner's spring curves on their faces",
|
||||
Some(0.0),
|
||||
)));
|
||||
};
|
||||
if let Some(a) = asked {
|
||||
return Ok(Some(setback_too_long(
|
||||
a,
|
||||
vtx,
|
||||
fit * a.setback,
|
||||
SPRING_LEAVES,
|
||||
)));
|
||||
}
|
||||
let list: Vec<String> = growth.base.iter().map(|&s| fmt_g(fit * s)).collect();
|
||||
Ok(Some(SetbackRefusal::not_built(format!(
|
||||
"Fillet Error: the rule's setbacks take a setback corner's spring curve off its face (it comes within the tolerances of another edge of the face, or is not shown not to): setbacks of {} keep every spring curve on its face",
|
||||
list.join(", ")
|
||||
))))
|
||||
}
|
||||
|
||||
/// A setback asked at `vtx` that, with its ease, does not fit (`why`): refused, naming
|
||||
/// `largest`, below which one fits ([`BlendRefusal::SetbackTooLong`]).
|
||||
fn setback_too_long(a: SetBackFill, vtx: VertexId, largest: f64, why: &str) -> SetbackRefusal {
|
||||
@@ -1907,6 +2230,14 @@ pub fn build_setback_fill<S: SetbackSolvers>(
|
||||
if open && order == 1 {
|
||||
return open_setback_springs(pseudo, corner, &planes.planes, &planes.edges);
|
||||
}
|
||||
// beside a curved face, the spring curves stay inside their trimmed faces (#2343 (d))
|
||||
if curved {
|
||||
if let Some(refusal) =
|
||||
springs_refusal(k, target, pseudo, corner, solvers, &planes, fills, tol)?
|
||||
{
|
||||
return Ok(Err(refusal));
|
||||
}
|
||||
}
|
||||
let mut stats = FillStats::default();
|
||||
let first = fill_hole_counted(
|
||||
k,
|
||||
|
||||
@@ -1229,10 +1229,15 @@ pub fn check_nx1_degeneracy_status(
|
||||
| SolidError::Cancelled { .. }
|
||||
| SolidError::NotFound { .. }),
|
||||
) => Err(e),
|
||||
Err(
|
||||
e @ (SolidError::InvalidInput { reason, .. }
|
||||
| SolidError::PortInvariant { reason, .. }),
|
||||
) if reason == NULL_DEREF => Err(e),
|
||||
Err(e)
|
||||
if matches!(
|
||||
&e,
|
||||
SolidError::InvalidInput { reason, .. }
|
||||
| SolidError::PortInvariant { reason, .. } if *reason == NULL_DEREF
|
||||
) =>
|
||||
{
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => Ok(Err(e)),
|
||||
}
|
||||
};
|
||||
|
||||
+49
-58
@@ -2521,46 +2521,11 @@ fn keep_definition(
|
||||
let Some(surface) = pseudo.geom_mut(g)?.fillet_surface_mut(None)? else {
|
||||
return Err(SolidError::null_pointer(OP));
|
||||
};
|
||||
keep_round_if_proved(k, surface, round, inputs.tol)?;
|
||||
round.recertify(k, surface)?;
|
||||
surface.set_exact_round(Some(round));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `round` kept on `net` where it is proved ([`ExactRound::recertify`]), returning whether it
|
||||
/// was. The class is read within `tol`, the body's tolerance, and the certificate to rounding.
|
||||
/// Supports only that close to the class (a cylinder's axis 1e-13 off square to a plane, as
|
||||
/// read from a record) make a net that is the round but not provably on the class's surface:
|
||||
/// no bound of 0, so it keeps no definition. A net further off the
|
||||
/// class than `tol` ([`ExactRound::miss`]) is built wrongly, and stays an error.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The certificate's refusal of a net more than `tol` off the class, or of supports that
|
||||
/// give the class no surface; those of the certificate and of [`ExactRound::miss`].
|
||||
pub fn keep_round_if_proved(
|
||||
k: &Kernel,
|
||||
net: &mut Surface,
|
||||
round: ExactRound,
|
||||
tol: f64,
|
||||
) -> Result<bool> {
|
||||
match round.recertify(k, net) {
|
||||
Ok(_) => {
|
||||
net.set_exact_round(Some(round));
|
||||
Ok(true)
|
||||
}
|
||||
Err(e) => {
|
||||
let off = matches!(
|
||||
&e,
|
||||
SolidError::InvalidInput { operation, .. }
|
||||
if *operation == crate::surface::implicit::OP_EXACT
|
||||
);
|
||||
match off.then(|| round.miss(k, net)).transpose()?.flatten() {
|
||||
Some(miss) if miss <= tol => Ok(false),
|
||||
_ => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`calc_exact_fillet_geom`]'s build by the class (`CalcExactFilletGeom`), without the
|
||||
/// definition kept.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -2719,7 +2684,8 @@ fn exact_fillet_geom_of_class(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A box's edge rounded 2: the round's net, without its definition, and the definition.
|
||||
/// A cylinder R 10, 20 high, its top rim rounded 2 (a round of the circle class): the
|
||||
/// round's net, without its definition, and the definition.
|
||||
fn kept_round() -> (Kernel, Surface, ExactRound) {
|
||||
use crate::blend::fillet_executive::session::FilletSession;
|
||||
use crate::kernel::UpstreamFixes;
|
||||
@@ -2729,24 +2695,51 @@ mod tests {
|
||||
fixes: UpstreamFixes::all(),
|
||||
..Kernel::default()
|
||||
};
|
||||
let mut b = crate::primitive::api_primitives::create_box(
|
||||
let mut b = crate::primitive::api_primitives::create_cylinder(
|
||||
&mut k,
|
||||
Point3::new(0.0, 0.0, 0.0),
|
||||
30.0,
|
||||
20.0,
|
||||
10.0,
|
||||
20.0,
|
||||
)
|
||||
.unwrap();
|
||||
let edge = b.edge_list()[0];
|
||||
// the top rim: the edge through (10, 0, 20)
|
||||
let rim = *b
|
||||
.edge_list()
|
||||
.iter()
|
||||
.min_by(|&&x, &&y| {
|
||||
let d = |e| {
|
||||
crate::query::api_queries::edge_closest_point(
|
||||
&k,
|
||||
&b,
|
||||
e,
|
||||
Point3::new(10.0, 0.0, 20.0),
|
||||
)
|
||||
.unwrap()
|
||||
.closest
|
||||
.distance
|
||||
+ crate::query::api_queries::edge_closest_point(
|
||||
&k,
|
||||
&b,
|
||||
e,
|
||||
Point3::new(-10.0, 0.0, 20.0),
|
||||
)
|
||||
.unwrap()
|
||||
.closest
|
||||
.distance
|
||||
};
|
||||
d(x).total_cmp(&d(y))
|
||||
})
|
||||
.unwrap();
|
||||
{
|
||||
let mut s = FilletSession::new(&mut k, &mut b).unwrap();
|
||||
s.prepare_constant_radius_edges(&mut k, &[edge], 2.0, 1, 1, 1.0)
|
||||
s.prepare_constant_radius_edges(&mut k, &[rim], 2.0, 1, 1, 1.0)
|
||||
.unwrap();
|
||||
s.do_fillet(&mut k).unwrap();
|
||||
}
|
||||
for &f in b.face_list() {
|
||||
let s = face::surface(&b, f).unwrap().unwrap();
|
||||
if let Some(BlendRef::Exact { round, .. }) = s.procedural_blend() {
|
||||
assert_eq!(round.class, ExactClass::Circle);
|
||||
let round = round.clone();
|
||||
let mut net = s.clone();
|
||||
net.set_exact_round(None);
|
||||
@@ -2769,23 +2762,21 @@ mod tests {
|
||||
net
|
||||
}
|
||||
|
||||
/// The class is read within 2e-5 here. A net on it keeps its definition; one off it by
|
||||
/// less keeps none (the round of supports that close to the class); one further off is
|
||||
/// built wrongly, and stays an error.
|
||||
/// A circle-class round's definition is the torus its net is on, a stated distance from
|
||||
/// the supports' class torus within the tolerance the class was read at (2e-5 here): the
|
||||
/// net as built is at 0, the net moved by (1, 1, 1) 1e-7 is at sqrt 3 1e-7, and the net
|
||||
/// moved by (1, 1, 1) 1e-3 is refused.
|
||||
#[test]
|
||||
fn a_net_off_its_class_keeps_no_definition_within_the_tolerance_only() {
|
||||
let tol = 2e-5;
|
||||
fn a_circle_rounds_net_is_certified_on_its_own_torus_near_the_class() {
|
||||
let (k, net, round) = kept_round();
|
||||
let mut on = net.clone();
|
||||
assert!(keep_round_if_proved(&k, &mut on, round.clone(), tol).unwrap());
|
||||
assert!(on.procedural_blend().is_some());
|
||||
|
||||
let mut near = moved(&k, &net, 1e-7);
|
||||
assert!(!keep_round_if_proved(&k, &mut near, round.clone(), tol).unwrap());
|
||||
assert!(near.procedural_blend().is_none());
|
||||
|
||||
let mut far = moved(&k, &net, 1e-3);
|
||||
let refused = keep_round_if_proved(&k, &mut far, round, tol);
|
||||
assert_eq!(round.recertify(&k, &net).unwrap(), 0.0);
|
||||
assert!(round.supports_distance(&k, &net).unwrap() < 1e-12);
|
||||
let near = moved(&k, &net, 1e-7);
|
||||
assert_eq!(round.recertify(&k, &near).unwrap(), 0.0);
|
||||
let d = round.supports_distance(&k, &near).unwrap();
|
||||
assert!((d - 3.0_f64.sqrt() * 1e-7).abs() < 1e-12, "{d}");
|
||||
let far = moved(&k, &net, 1e-3);
|
||||
let refused = round.recertify(&k, &far);
|
||||
assert!(
|
||||
matches!(refused, Err(SolidError::InvalidInput { .. })),
|
||||
"{refused:?}"
|
||||
|
||||
@@ -1304,7 +1304,7 @@ fn rational_spine_parts(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::blend::fillet_exact::{classify_supports, keep_round_if_proved, ClassInputs};
|
||||
use crate::blend::fillet_exact::{classify_supports, ClassInputs};
|
||||
use crate::curve::curve::CurveKind;
|
||||
use crate::geom::axis2_placement::Axis2Placement;
|
||||
use crate::surface::cone::Cone;
|
||||
@@ -1779,10 +1779,10 @@ mod tests {
|
||||
inputs,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
keep_round_if_proved(&k, &mut net, round, inputs.tol).unwrap(),
|
||||
"{offsets:?}, {contact:?}"
|
||||
);
|
||||
if let Err(e) = round.recertify(&k, &net) {
|
||||
panic!("{offsets:?}, {contact:?}: {e}");
|
||||
}
|
||||
net.set_exact_round(Some(round));
|
||||
assert_eq!(
|
||||
net.procedural_blend().unwrap().exact().unwrap().bound(),
|
||||
0.0
|
||||
|
||||
@@ -308,6 +308,61 @@ pub fn nurbs_partials_over_piece(
|
||||
va: f64,
|
||||
vb: f64,
|
||||
lk: usize,
|
||||
) -> Result<Partials<Ivl>> {
|
||||
partials_over_piece(cn, ku, kv, ua, ub, va, vb, lk, None)
|
||||
}
|
||||
|
||||
/// [`nurbs_partials_over_piece`] for a box inside the piece's closed knot span (not the
|
||||
/// fork's): there the weight is a convex combination of the piece's control weights, so it
|
||||
/// lies between their least and greatest, and its enclosure is cut to that range before
|
||||
/// the quotient. Where the Taylor enclosure of the weight alone holds 0 (a rational piece of
|
||||
/// high degree over a large box) this proves it positive. A box past the span is enclosed
|
||||
/// as [`nurbs_partials_over_piece`] encloses it.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// As [`nurbs_partials_over_piece`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn nurbs_partials_within_span(
|
||||
cn: &Nurbs,
|
||||
ku: usize,
|
||||
kv: usize,
|
||||
ua: f64,
|
||||
ub: f64,
|
||||
va: f64,
|
||||
vb: f64,
|
||||
lk: usize,
|
||||
) -> Result<Partials<Ivl>> {
|
||||
let inside = |u: &[f64], k: usize, a: f64, b: f64| {
|
||||
knot(u, k as isize) <= a && b <= knot(u, k as isize + 1)
|
||||
};
|
||||
let hull = if inside(&cn.u, ku, ua, ub) && inside(&cn.v, kv, va, vb) {
|
||||
let mut h: Option<Ivl> = None;
|
||||
for i in (ku + 1).saturating_sub(cn.p + 1)..=ku {
|
||||
for j in (kv + 1).saturating_sub(cn.q + 1)..=kv {
|
||||
let w = Ivl::from(pw_at(&cn.pw, 4 * (i * cn.nv + j) + 3));
|
||||
h = Some(h.map_or(w, |x| x.hull(w)));
|
||||
}
|
||||
}
|
||||
h
|
||||
} else {
|
||||
None
|
||||
};
|
||||
partials_over_piece(cn, ku, kv, ua, ub, va, vb, lk, hull)
|
||||
}
|
||||
|
||||
/// [`nurbs_partials_over_piece`], the weight's enclosure cut to `weights` where given.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn partials_over_piece(
|
||||
cn: &Nurbs,
|
||||
ku: usize,
|
||||
kv: usize,
|
||||
ua: f64,
|
||||
ub: f64,
|
||||
va: f64,
|
||||
vb: f64,
|
||||
lk: usize,
|
||||
weights: Option<Ivl>,
|
||||
) -> Result<Partials<Ivl>> {
|
||||
let um = 0.5 * (ua + ub);
|
||||
let vm = 0.5 * (va + vb);
|
||||
@@ -377,6 +432,10 @@ pub fn nurbs_partials_over_piece(
|
||||
}
|
||||
}
|
||||
let w00 = at2(&wv, 0, 0, IZ);
|
||||
let w00 = match weights {
|
||||
Some(h) => Ivl::new(w00.lo.max(h.lo), w00.hi.min(h.hi)),
|
||||
None => w00,
|
||||
};
|
||||
let positive = w00.lo > 0.0;
|
||||
if !positive {
|
||||
// "SmGnSectionFSG: a face's weights are not positive over a box"
|
||||
@@ -2160,6 +2219,23 @@ mod tests {
|
||||
/// A box across a knot line: the piecewise enclosure holds every partial of the face on
|
||||
/// both sides (the third u-partial jumps at the knot), which one piece continued over the
|
||||
/// box does not; a knot of too low a continuity, and a box past the knots, are refused.
|
||||
#[test]
|
||||
fn a_box_past_its_span_keeps_the_unclipped_weight() {
|
||||
// the control weights bound the weight only inside the span: a box across the knot at
|
||||
// u = 1 is enclosed as `nurbs_partials_over_piece` encloses it
|
||||
let mut n = cubic_with_knot(1);
|
||||
// weights from 1 to 4.5: the weight continued past the span leaves their range
|
||||
for (i, w) in n.pw.chunks_mut(4).enumerate() {
|
||||
let k = 1.0 + 0.5 * i as f64;
|
||||
for c in w.iter_mut() {
|
||||
*c *= k;
|
||||
}
|
||||
}
|
||||
let a = nurbs_partials_within_span(&n, 3, 1, 0.9, 1.1, 0.2, 0.3, 3).unwrap();
|
||||
let b = nurbs_partials_over_piece(&n, 3, 1, 0.9, 1.1, 0.2, 0.3, 3).unwrap();
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_across_a_knot_line_is_enclosed_piece_by_piece() {
|
||||
let n = cubic_with_knot(1);
|
||||
|
||||
+8
-1
@@ -81,7 +81,14 @@
|
||||
//! - **Length.** Spans of degree above 1 use the ported `SmIntegrator` (unit
|
||||
//! `mass.integrator`; with `Kernel::fixes.integrator_propagates_failure`, WR-94,
|
||||
//! failed span integrals propagate instead of contributing zero). `Length` needs the
|
||||
//! kernel (the cache tessellation reads its tolerances).
|
||||
//! kernel (the cache tessellation reads its tolerances). Not upstream: the accuracy
|
||||
//! is held over the whole curve, not per span. Upstream integrates each span to
|
||||
//! `dDesiredAccuracy / span count` relative to that span's integral so far, so each of
|
||||
//! a span's steps may err by nearly that and the steps together by up to their number
|
||||
//! times it; here each step is held to half the accuracy relative to its share of the
|
||||
//! estimated length plus its own increment (`OdeOptions::rate`), shares that add up to
|
||||
//! at most twice the length ([`Curve::length`];
|
||||
//! `plan/findings/curve.curve_p5--curve-length-global-error-moves-cases-2026-11-08.md`).
|
||||
//! - **Out-params.** Flags with outputs are `Option`s (`IsLine`: point and vector;
|
||||
//! `IsArc`: frame, radius, start and end angle; the guesses); `IsLinear`'s
|
||||
//! optional outputs are the fields of [`Linearity`] (`want_deviation` stands for a
|
||||
|
||||
@@ -2330,6 +2330,40 @@ mod tests {
|
||||
assert_eq!(c.base().form(), BSplineCurveForm::EllipticArc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_1518_short_arc_inverts_each_point_to_itself() {
|
||||
// The 70.9232 x 16 ellipse of #1518 on an arc 7e-5 degrees long, shorter than
|
||||
// the angular tolerance (1e-4 degrees); atan2 puts its points a period up.
|
||||
let k = kernel();
|
||||
let (d0, d1) = (-10.0, -10.0 + 7.0e-5);
|
||||
let e = Ellipse::new(
|
||||
&k,
|
||||
Vec3::ZERO,
|
||||
Vec3::X,
|
||||
Vec3::Y,
|
||||
Extent1d::from_min_max(d0, d1),
|
||||
70.9232,
|
||||
16.0,
|
||||
&EllipseOptions::default(),
|
||||
)
|
||||
.expect("ellipse");
|
||||
let fractions = [0.25, 0.5, 0.75, 1.0];
|
||||
let points: Vec<(f64, Point3)> = fractions
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let a = d0 + f * (d1 - d0);
|
||||
(a, e.evaluate_step_point(a).expect("step point"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// each point inverts to its own angle, not to the arc's start (the pinned C++
|
||||
// returns -10 for all four)
|
||||
for &(a, p) in &points {
|
||||
let t = e.step_inversion(&k, p, false, 0.0).0;
|
||||
assert!((t - a).abs() <= 1e-9 * a.abs(), "{a}: {t}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn step_and_nurb_parameters_agree() {
|
||||
let k = kernel();
|
||||
|
||||
+71
-19
@@ -190,19 +190,33 @@ pub fn conic_on_form(k: &Kernel, conic: &Record, form: &Record) -> Result<Curve>
|
||||
Ok(Curve::from(e))
|
||||
}
|
||||
|
||||
/// A pcurve's numbers as the kernel's UV trim of an edge over `interval` (the edge's interval
|
||||
/// in the kernel's parameter): the record's pcurve runs along the edge over the edge's own
|
||||
/// interval in the record, and the kernel's trim runs along the edge over the edge's kernel
|
||||
/// interval, so its knots are mapped affinely from the pcurve's domain onto `interval` (copied
|
||||
/// as they are where the two are the same). Its points are scaled by `scale` in u and v: a
|
||||
/// plane face's kernel parameters are the record's times its axes' lengths (`[1, 1]`
|
||||
/// elsewhere, copied).
|
||||
/// Map a pcurve's entire natural support onto `interval` and scale its UV
|
||||
/// coordinates. This is for callers that intend to remap the whole support.
|
||||
/// A B-rep edge may occupy only part of that support: its importer must use
|
||||
/// [`uv_trim_on_edge`] with the record edge's actual interval instead.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `InvalidInput` for a spline whose knots do not fit its points or a pcurve or interval of no
|
||||
/// length; those of the constructors.
|
||||
pub fn uv_trim(c: &Record2, interval: &Extent1d, scale: [f64; 2]) -> Result<Curve> {
|
||||
let source = c
|
||||
.interval()
|
||||
.ok_or_else(|| SolidError::invalid_input(OP_TRIM, "invalid pcurve interval"))?;
|
||||
uv_trim_on_edge(c, source, interval, scale)
|
||||
}
|
||||
|
||||
/// Rebuild a pcurve while mapping the record edge parameter to the kernel edge
|
||||
/// parameter. The pcurve's own support may extend beyond either edge endpoint.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses invalid edge intervals or malformed UV spline data.
|
||||
pub fn uv_trim_on_edge(
|
||||
c: &Record2,
|
||||
source: [f64; 2],
|
||||
interval: &Extent1d,
|
||||
scale: [f64; 2],
|
||||
) -> Result<Curve> {
|
||||
let bad = |reason: &'static str| SolidError::invalid_input(OP_TRIM, reason);
|
||||
let [su, sv] = scale;
|
||||
let same = su == 1.0 && sv == 1.0;
|
||||
@@ -238,14 +252,35 @@ pub fn uv_trim(c: &Record2, interval: &Extent1d, scale: [f64; 2]) -> Result<Curv
|
||||
return Err(bad("too few knots"));
|
||||
};
|
||||
let (lo, hi) = (interval.min(), interval.max());
|
||||
if !(s_hi > s_lo) {
|
||||
let [from_lo, from_hi] = source;
|
||||
if !(s_lo.is_finite()
|
||||
&& s_hi.is_finite()
|
||||
&& s_hi > s_lo
|
||||
&& from_lo.is_finite()
|
||||
&& from_hi.is_finite()
|
||||
&& from_hi > from_lo
|
||||
&& lo.is_finite()
|
||||
&& hi.is_finite()
|
||||
&& hi > lo)
|
||||
{
|
||||
return Err(bad("the pcurve's domain has no length"));
|
||||
}
|
||||
let mapped: Vec<f64> = if (s_lo, s_hi) == (lo, hi) {
|
||||
let mapped: Vec<f64> = if (from_lo, from_hi) == (lo, hi) {
|
||||
knots.to_vec()
|
||||
} else {
|
||||
let by = (hi - lo) / (s_hi - s_lo);
|
||||
knots.iter().map(|s| lo + (s - s_lo) * by).collect()
|
||||
let by = (hi - lo) / (from_hi - from_lo);
|
||||
knots
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if *s == from_lo {
|
||||
lo
|
||||
} else if *s == from_hi {
|
||||
hi
|
||||
} else {
|
||||
lo + (s - from_lo) * by
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
Ok(Curve::from(spline(&mapped, &rows, 2, OP_TRIM)?))
|
||||
}
|
||||
@@ -273,9 +308,11 @@ pub struct RecordCurve {
|
||||
/// The record of the kernel's edge curve `c` over `interval` (its kernel parameter), the
|
||||
/// inverse of [`from_parameters`] and [`conic_on_form`]:
|
||||
///
|
||||
/// - **Line:** the line through the curve's points at the interval's ends, over `[0, 1]`. A
|
||||
/// kernel line is affine in its parameter (its point plus the parameter times its vector),
|
||||
/// so this is the same line, the record's parameter an affine image of the kernel's.
|
||||
/// - **Line:** the line as the kernel holds it, its point and its point plus its vector, over
|
||||
/// the STEP parameters of the interval's ends (`ConvertTFromNURBSToSTEP`); where those run
|
||||
/// down, the vector and the parameters are negated so the interval increases. This is what
|
||||
/// SolidCraft's flat export wrote (`FlatFromBrep`'s line origin and direction, its range in
|
||||
/// STEP parameters), so a line comes back as it went in, not reparameterised onto `[0, 1]`.
|
||||
/// - **Circle, ellipse:** the label on the conic's own frame (copied, not rebuilt by a cross
|
||||
/// product), its y axis reversed where the conic's NURBS runs against its angle (its own
|
||||
/// `inside_out`, no sampling), with that NURBS as its form, over the kernel's interval.
|
||||
@@ -298,15 +335,30 @@ pub fn record_of(k: &Kernel, c: &Curve, interval: &Extent1d) -> Result<RecordCur
|
||||
return Err(bad("the interval is not finite and increasing"));
|
||||
}
|
||||
match c.kind() {
|
||||
CurveKind::Line(_) => {
|
||||
let (a, b) = (c.evaluate_point(k, lo)?, c.evaluate_point(k, hi)?);
|
||||
CurveKind::Line(l) => {
|
||||
let can = l.canonical();
|
||||
let (mut s0, mut s1) = (
|
||||
c.convert_t_from_nurbs_to_step(k, lo)?,
|
||||
c.convert_t_from_nurbs_to_step(k, hi)?,
|
||||
);
|
||||
let mut v = can.vector;
|
||||
// a line whose parameter runs against its vector over the edge: the vector
|
||||
// reversed, the parameters negated, so the record's interval increases
|
||||
if s0 > s1 {
|
||||
(s0, s1) = (-s0, -s1);
|
||||
v = -v;
|
||||
}
|
||||
if !(s0.is_finite() && s1.is_finite() && s1 > s0) {
|
||||
return Err(bad("a line's parameters over the edge are not increasing"));
|
||||
}
|
||||
let p = can.point;
|
||||
return Ok(RecordCurve {
|
||||
curve: Record::Line {
|
||||
start: [a.x, a.y, a.z],
|
||||
end: [b.x, b.y, b.z],
|
||||
start: [p.x, p.y, p.z],
|
||||
end: [p.x + v.x, p.y + v.y, p.z + v.z],
|
||||
},
|
||||
nurbs: None,
|
||||
interval: [0.0, 1.0],
|
||||
interval: [s0, s1],
|
||||
});
|
||||
}
|
||||
CurveKind::Ellipse(e) => {
|
||||
|
||||
@@ -232,10 +232,11 @@ fn a_trim_runs_over_its_edges_kernel_interval() {
|
||||
assert_eq!((p.x, p.y), (1.0, 2.0));
|
||||
}
|
||||
|
||||
/// A kernel line goes to the record by its points at its interval's ends, over [0, 1]: the
|
||||
/// same line, its parameter an affine image of the kernel's, and back again unchanged.
|
||||
/// A kernel line goes to the record as the kernel holds it, its point and its point plus its
|
||||
/// vector, over the STEP parameters of its interval (#3137: as SolidCraft's flat export wrote
|
||||
/// it), and back again unchanged.
|
||||
#[test]
|
||||
fn a_line_goes_back_by_its_two_end_points() {
|
||||
fn a_line_goes_back_as_the_kernel_holds_it() {
|
||||
let k = k();
|
||||
let line = Curve::from(Line::create_canonical(
|
||||
Point3::new(1.0, 2.0, 3.0),
|
||||
@@ -245,11 +246,44 @@ fn a_line_goes_back_by_its_two_end_points() {
|
||||
assert_eq!(
|
||||
r.curve,
|
||||
Record::Line {
|
||||
start: [2.0, 2.0, 3.0],
|
||||
end: [5.0, 2.0, 3.0],
|
||||
start: [1.0, 2.0, 3.0],
|
||||
end: [3.0, 2.0, 3.0],
|
||||
}
|
||||
);
|
||||
assert_eq!((&r.nurbs, r.interval), (&None, [0.0, 1.0]));
|
||||
assert_eq!((&r.nurbs, r.interval), (&None, [0.5, 2.0]));
|
||||
let back = from_parameters(&k, &r.curve, r.interval).unwrap();
|
||||
assert_eq!(record_of(&k, &back.curve, &back.interval).unwrap(), r);
|
||||
}
|
||||
|
||||
/// A line whose STEP parameter runs against its NURBS parameter (`inside_out`: `s = -t`) goes to
|
||||
/// the record with its vector reversed and its interval negated, so the interval increases; the
|
||||
/// record's point at each end of it is the kernel's at that end, bit for bit, and the round
|
||||
/// trip is the identity.
|
||||
#[test]
|
||||
fn a_reversed_line_goes_back_with_its_vector_turned() {
|
||||
let k = k();
|
||||
let line = Curve::from(Line::infinite(
|
||||
Point3::new(1.0, 2.0, 3.0),
|
||||
Vec3::new(2.0, 0.0, 0.0),
|
||||
3,
|
||||
true,
|
||||
));
|
||||
let (lo, hi) = (0.5, 2.0);
|
||||
let r = record_of(&k, &line, &Extent1d::from_raw(lo, hi)).unwrap();
|
||||
let Record::Line { start, end } = r.curve else {
|
||||
panic!("not a line: {:?}", r.curve)
|
||||
};
|
||||
assert!(r.interval[0] < r.interval[1], "{:?}", r.interval);
|
||||
let on = |s: f64| {
|
||||
Point3::new(
|
||||
start[0] + s * (end[0] - start[0]),
|
||||
start[1] + s * (end[1] - start[1]),
|
||||
start[2] + s * (end[2] - start[2]),
|
||||
)
|
||||
};
|
||||
// the record's interval runs the kernel's ends in order
|
||||
assert_eq!(on(r.interval[0]), at(&k, &line, lo));
|
||||
assert_eq!(on(r.interval[1]), at(&k, &line, hi));
|
||||
let back = from_parameters(&k, &r.curve, r.interval).unwrap();
|
||||
assert_eq!(record_of(&k, &back.curve, &back.interval).unwrap(), r);
|
||||
}
|
||||
|
||||
+8
-1
@@ -172,8 +172,11 @@ pub enum Unsoundness {
|
||||
GeometryUnchecked,
|
||||
/// The input was a manifold solid and the body is not.
|
||||
NotManifoldSolid,
|
||||
/// The body is a manifold solid whose volume is not positive.
|
||||
/// The body is a manifold solid whose volume's enclosure lies below 0.
|
||||
VolumeNotPositive,
|
||||
/// The body is a manifold solid whose volume's enclosure still holds 0 after its
|
||||
/// budget: its sign is not proved.
|
||||
VolumeUndecided,
|
||||
}
|
||||
|
||||
/// How loops of an offset face touch without crossing ([`OffsetFailure::WallsTouch`]).
|
||||
@@ -449,6 +452,10 @@ impl core::fmt::Display for OffsetFailure {
|
||||
"the local operation's body is a manifold solid whose volume is not \
|
||||
positive: refused, the input unchanged",
|
||||
),
|
||||
Unsoundness::VolumeUndecided => f.write_str(
|
||||
"the local operation's body is a manifold solid whose volume's sign \
|
||||
could not be proved: refused, the input unchanged",
|
||||
),
|
||||
},
|
||||
Self::ExactCurveUncut => f.write_str(
|
||||
"the extended faces beside an edge meet on an exact curve that could not be \
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ pub mod polar_box;
|
||||
pub mod polynomial;
|
||||
pub mod pseudo_box;
|
||||
pub mod solution_array;
|
||||
pub mod step_matrix;
|
||||
pub mod tol;
|
||||
pub mod tools;
|
||||
pub mod transform;
|
||||
|
||||
+42
-6
@@ -72,6 +72,9 @@
|
||||
//! prefix and zero-fills, unbounded dimensions are not written, counts are
|
||||
//! incremented); arrays shorter than the dimension (undefined behaviour upstream)
|
||||
//! are rejected with [`SolidError::InvalidInput`].
|
||||
//! - `ContainsPeriodicValue` moves a value within the tolerance of the top of the period
|
||||
//! above the start down by the period, and keeps it where that lies in the interval,
|
||||
//! instead of setting it to the start (WR-509, #1518; tangency-labs/smlib#46).
|
||||
//! - `SM_ASSERT*` debug checks and the error-log side effect of `SE(...)` are not
|
||||
//! ported (they do not change results). The destructor's "undefined" sentinel has
|
||||
//! no Rust counterpart; [`Extent1d::is_defined`] still checks for it.
|
||||
@@ -844,12 +847,16 @@ impl Extent1d {
|
||||
|
||||
contained = map > self.min - zero && map < self.max + zero;
|
||||
|
||||
if !contained
|
||||
&& are_same_to_tol(map, ivl.max(), zero)
|
||||
&& are_same_to_tol(map - period, self.min, zero)
|
||||
{
|
||||
map = self.min;
|
||||
contained = true;
|
||||
let mut kept_down = false;
|
||||
if !contained && are_same_to_tol(map, ivl.max(), zero) {
|
||||
// a period down, kept where it lies in the interval; the C++ sets it to the
|
||||
// start instead (WR-509, #1518; tangency-labs/smlib#46)
|
||||
let down = map - period;
|
||||
if down > self.min - zero && down < self.max + zero {
|
||||
map = down;
|
||||
contained = true;
|
||||
kept_down = true;
|
||||
}
|
||||
}
|
||||
|
||||
let periodic_value =
|
||||
@@ -861,6 +868,12 @@ impl Extent1d {
|
||||
map
|
||||
};
|
||||
let mut dist_to_min = map - self.min;
|
||||
if kept_down {
|
||||
// it can lie up to the tolerance below the start: its distance from it, not a
|
||||
// signed offset (assert_valid takes min2 of the two). Only this new case: other
|
||||
// inputs keep the signed value upstream gives them.
|
||||
dist_to_min = dist_to_min.abs();
|
||||
}
|
||||
if dist_to_min > period / 2.0 {
|
||||
dist_to_min = period - dist_to_min;
|
||||
}
|
||||
@@ -1150,6 +1163,29 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_1518_value_past_start_moves_down_a_period() {
|
||||
// An interval shorter than the tolerance (1e-4 degrees), and a value 3e-5 past
|
||||
// its start given a period up: the C++ returns the start (-10).
|
||||
let q = Extent1d::from_min_max(-10.0, -10.0 + 7.0e-5);
|
||||
let v = 350.0 + 3.0e-5;
|
||||
let c = q.contains_periodic_value(v, 360.0, -1.0);
|
||||
assert!(c.contained);
|
||||
assert_eq!(c.periodic_value, v - 360.0);
|
||||
assert!((c.dist_to_min - 3.0e-5).abs() < 1e-12);
|
||||
assert_eq!(q.clamp_periodic_value(v, 360.0, -1.0), v - 360.0);
|
||||
|
||||
// a value within the tolerance below the start keeps its place a period down
|
||||
// (the C++: the start, 0)
|
||||
let r = Extent1d::from_min_max(0.0, 90.0);
|
||||
let w = 360.0 - 5.0e-5;
|
||||
let c = r.contains_periodic_value(w, 360.0, -1.0);
|
||||
assert!(c.contained);
|
||||
assert_eq!(c.periodic_value, w - 360.0);
|
||||
// its distance to the start is a distance, not the signed offset -5e-5
|
||||
assert!((c.dist_to_min - 5.0e-5).abs() < 1e-12, "{}", c.dist_to_min);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_vector() {
|
||||
let mut nd = ExtentNd::new(3);
|
||||
|
||||
+6
-4
@@ -1004,10 +1004,12 @@ mod tests {
|
||||
);
|
||||
assert!(!e1.is_point_on_boundary(p(1.1, 2.1), EFF_ZERO).0);
|
||||
|
||||
assert_eq!(
|
||||
e1.contains_periodic_point(p(8.9, 3.0), 4.0, 0.0, 0.2),
|
||||
(true, p(1.0, 3.0))
|
||||
);
|
||||
// u = 8.9 is 0.9 a period down, within the tolerance of [1, 3]: it keeps 0.9
|
||||
// (WR-509, #1518; upstream's MyTest expects the start, 1.0)
|
||||
let (inside, q) = e1.contains_periodic_point(p(8.9, 3.0), 4.0, 0.0, 0.2);
|
||||
assert!(inside);
|
||||
assert!((q.x - 0.9).abs() <= 1e-12, "{}", q.x);
|
||||
assert_eq!(q.y, 3.0);
|
||||
assert!(!e1.contains_periodic_point(p(8.9, 3.0), 4.0, 0.0, 0.0).0);
|
||||
|
||||
assert_eq!(e1.evaluate(0.5, 0.5), p(2.0, 3.0));
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
//! Affine matrices for STEP placements and transformations.
|
||||
//!
|
||||
//! STEP keeps placements as frames and transformation operators as directions and
|
||||
//! scales. Building their matrices and composing or inverting them is geometric
|
||||
//! work, so it lives here rather than in `solidcore-record` or `solidcore-ops`.
|
||||
//!
|
||||
//! The matrix convention is the kernel's: rows 0–2 are the images of X, Y and Z,
|
||||
//! row 3 is the translation (`p' = p·A + t`).
|
||||
|
||||
use crate::geom::vector3d::Vec3;
|
||||
|
||||
/// A 4×4 affine matrix in the kernel's row-major convention.
|
||||
pub type Matrix4 = [[f64; 4]; 4];
|
||||
|
||||
/// The identity matrix.
|
||||
pub const IDENTITY: Matrix4 = [
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
];
|
||||
|
||||
/// `a · b` in the row-vector convention.
|
||||
#[must_use]
|
||||
#[allow(clippy::indexing_slicing)] // 4×4 loops over fixed-size arrays
|
||||
pub fn concat(a: &Matrix4, b: &Matrix4) -> Matrix4 {
|
||||
let mut out = [[0.0; 4]; 4];
|
||||
for (row, out_row) in out.iter_mut().enumerate() {
|
||||
for (col, cell) in out_row.iter_mut().enumerate() {
|
||||
*cell = (0..4).map(|k| a[row][k] * b[k][col]).sum();
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The inverse of an affine matrix, or `None` when its linear part is singular or
|
||||
/// the entries are not finite.
|
||||
#[must_use]
|
||||
pub fn inverse(m: &Matrix4) -> Option<Matrix4> {
|
||||
let (a, b, c) = (
|
||||
Vec3::new(m[0][0], m[0][1], m[0][2]),
|
||||
Vec3::new(m[1][0], m[1][1], m[1][2]),
|
||||
Vec3::new(m[2][0], m[2][1], m[2][2]),
|
||||
);
|
||||
let det = a.dot(b.cross(c));
|
||||
if !det.is_finite() || det == 0.0 {
|
||||
return None;
|
||||
}
|
||||
let x = b.cross(c);
|
||||
let y = c.cross(a);
|
||||
let z = a.cross(b);
|
||||
// The inverse's columns are the cofactor cross products; its translation is
|
||||
// `-(t · A⁻¹)` in the row-vector convention.
|
||||
let linear = [
|
||||
[x.x / det, y.x / det, z.x / det],
|
||||
[x.y / det, y.y / det, z.y / det],
|
||||
[x.z / det, y.z / det, z.z / det],
|
||||
];
|
||||
let t = Vec3::new(m[3][0], m[3][1], m[3][2]);
|
||||
let translation = Vec3::new(
|
||||
-(t.x * linear[0][0] + t.y * linear[1][0] + t.z * linear[2][0]),
|
||||
-(t.x * linear[0][1] + t.y * linear[1][1] + t.z * linear[2][1]),
|
||||
-(t.x * linear[0][2] + t.y * linear[1][2] + t.z * linear[2][2]),
|
||||
);
|
||||
let out = [
|
||||
[linear[0][0], linear[0][1], linear[0][2], 0.0],
|
||||
[linear[1][0], linear[1][1], linear[1][2], 0.0],
|
||||
[linear[2][0], linear[2][1], linear[2][2], 0.0],
|
||||
[translation.x, translation.y, translation.z, 1.0],
|
||||
];
|
||||
out.iter().flatten().all(|v| v.is_finite()).then_some(out)
|
||||
}
|
||||
|
||||
fn unit(value: [f64; 3]) -> Option<Vec3> {
|
||||
let v = Vec3::from(value);
|
||||
v.unitize()
|
||||
.ok()
|
||||
.filter(|v| v.x.is_finite() && v.y.is_finite() && v.z.is_finite())
|
||||
}
|
||||
|
||||
/// The matrix of an `AXIS2_PLACEMENT`: origin, Z axis and X reference direction.
|
||||
///
|
||||
/// An omitted axis is +Z; an omitted reference direction chooses any X perpendicular
|
||||
/// to Z. The reference direction is projected onto the plane normal to Z.
|
||||
#[must_use]
|
||||
pub fn frame_matrix(
|
||||
origin: [f64; 3],
|
||||
axis: Option<[f64; 3]>,
|
||||
ref_direction: Option<[f64; 3]>,
|
||||
) -> Option<Matrix4> {
|
||||
let z = unit(axis.unwrap_or([0.0, 0.0, 1.0]))?;
|
||||
let x = if let Some(raw) = ref_direction {
|
||||
let v = Vec3::from(raw);
|
||||
let projected = v - z * v.dot(z);
|
||||
unit([projected.x, projected.y, projected.z])?
|
||||
} else {
|
||||
let candidate = if z.x.abs() < 0.9 {
|
||||
Vec3::new(1.0, 0.0, 0.0)
|
||||
} else {
|
||||
Vec3::new(0.0, 1.0, 0.0)
|
||||
};
|
||||
let v = candidate - z * candidate.dot(z);
|
||||
unit([v.x, v.y, v.z])?
|
||||
};
|
||||
let y = z.cross(x);
|
||||
Some([
|
||||
[x.x, x.y, x.z, 0.0],
|
||||
[y.x, y.y, y.z, 0.0],
|
||||
[z.x, z.y, z.z, 0.0],
|
||||
[origin[0], origin[1], origin[2], 1.0],
|
||||
])
|
||||
}
|
||||
|
||||
/// The matrix of a `CARTESIAN_TRANSFORMATION_OPERATOR_3D`.
|
||||
///
|
||||
/// An omitted direction is the matching global axis; an omitted scale is 1 (or
|
||||
/// `scale` for the second and third scale).
|
||||
#[must_use]
|
||||
pub fn operator_matrix(
|
||||
axis1: Option<[f64; 3]>,
|
||||
axis2: Option<[f64; 3]>,
|
||||
axis3: Option<[f64; 3]>,
|
||||
origin: [f64; 3],
|
||||
scale: Option<f64>,
|
||||
scale2: Option<f64>,
|
||||
scale3: Option<f64>,
|
||||
) -> Option<Matrix4> {
|
||||
let x = unit(axis1.unwrap_or([1.0, 0.0, 0.0]))?;
|
||||
let y = unit(axis2.unwrap_or([0.0, 1.0, 0.0]))?;
|
||||
let z = unit(axis3.unwrap_or([0.0, 0.0, 1.0]))?;
|
||||
let sx = scale.unwrap_or(1.0);
|
||||
let sy = scale2.or(scale).unwrap_or(1.0);
|
||||
let sz = scale3.or(scale).unwrap_or(1.0);
|
||||
if ![sx, sy, sz].iter().all(|v| v.is_finite() && *v != 0.0) {
|
||||
return None;
|
||||
}
|
||||
Some([
|
||||
[x.x * sx, x.y * sx, x.z * sx, 0.0],
|
||||
[y.x * sy, y.y * sy, y.z * sy, 0.0],
|
||||
[z.x * sz, z.y * sz, z.z * sz, 0.0],
|
||||
[origin[0], origin[1], origin[2], 1.0],
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn an_identity_inverse_is_the_identity() {
|
||||
assert_eq!(inverse(&IDENTITY), Some(IDENTITY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_translation_inverse_moves_back() {
|
||||
let mut translation = IDENTITY;
|
||||
translation[3] = [2.0, -3.0, 0.5, 1.0];
|
||||
let inverse = inverse(&translation).expect("translation is regular");
|
||||
assert_eq!(concat(&translation, &inverse), IDENTITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_omitted_frame_axis_is_z_and_omitted_x_is_chosen() {
|
||||
let frame = frame_matrix([1.0, 2.0, 3.0], None, None).expect("default frame");
|
||||
assert_eq!(frame[2], [0.0, 0.0, 1.0, 0.0]);
|
||||
assert_eq!(frame[3], [1.0, 2.0, 3.0, 1.0]);
|
||||
assert!(
|
||||
(frame[0][0] * frame[0][0] + frame[0][1] * frame[0][1] + frame[0][2] * frame[0][2]
|
||||
- 1.0)
|
||||
.abs()
|
||||
< 1e-12
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_operator_applies_its_scales() {
|
||||
let matrix = operator_matrix(None, None, None, [0.0; 3], Some(2.0), Some(3.0), None)
|
||||
.expect("operator");
|
||||
assert_eq!(matrix[0], [2.0, 0.0, 0.0, 0.0]);
|
||||
assert_eq!(matrix[1], [0.0, 3.0, 0.0, 0.0]);
|
||||
assert_eq!(matrix[2], [0.0, 0.0, 2.0, 0.0]);
|
||||
}
|
||||
}
|
||||
+8
-4
@@ -2374,10 +2374,14 @@ impl<O: LoopObject> FaceProps<O> {
|
||||
// (the status is only reported upstream; a handle type that cannot name the
|
||||
// loop bisector's objects is a limitation of the caller, not a status)
|
||||
match classify_result {
|
||||
Err(
|
||||
e @ (SolidError::InvalidInput { reason, .. }
|
||||
| SolidError::PortInvariant { reason, .. }),
|
||||
) if reason == crate::query::face::p3::NO_CURVE_HANDLES => {
|
||||
Err(e)
|
||||
if matches!(
|
||||
&e,
|
||||
SolidError::InvalidInput { reason, .. }
|
||||
| SolidError::PortInvariant { reason, .. }
|
||||
if *reason == crate::query::face::p3::NO_CURVE_HANDLES
|
||||
) =>
|
||||
{
|
||||
return Err(e);
|
||||
}
|
||||
r => Ok(ignored(r)?),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! unification and the API entry points) and [`p2`] (`DoStitching`, `StitchIntoSolid`,
|
||||
//! `StitchIntoShell`, `SplitEdgesWithVertices`, the one-list `StitchVertexPairs`).
|
||||
|
||||
mod bounds;
|
||||
pub mod p1;
|
||||
pub mod p2;
|
||||
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
//! Outward-rounded bounds for the geometry a stitch closure identifies.
|
||||
//!
|
||||
//! No upstream counterpart. The curve-pair bound is a bound on a parameter
|
||||
//! correspondence, hence on both directed Hausdorff distances. B-splines are
|
||||
//! enclosed on every knot span by homogeneous interval de Boor evaluation; no
|
||||
//! rounded control-polygon conversion or sampled distance enters the proof.
|
||||
//!
|
||||
//! Deviations: no recursion. Work is bounded by the stored knot count and the
|
||||
//! kernel's NURBS degree limit. Curves without an implemented exact enclosure,
|
||||
//! non-positive weights, extrapolation and non-finite bounds are refused.
|
||||
|
||||
use crate::blend::fillet_section::interval::Ivl;
|
||||
use crate::curve::curve::{Curve, CurveKind};
|
||||
use crate::curve::line::Line;
|
||||
use crate::geom::extent1d::Extent1d;
|
||||
use crate::math::Point3;
|
||||
use crate::nurbs::crv_basic::Curve as Nurbs;
|
||||
use crate::nurbs::knots::MAX_DEGREE;
|
||||
use crate::nurbs::math::{CPoint, NOW, NOZ};
|
||||
use crate::{Result, SolidError};
|
||||
|
||||
const OP: &str = "heal::stitch::bounds";
|
||||
type Box3 = [Ivl; 3];
|
||||
type Box4 = [Ivl; 4];
|
||||
|
||||
fn invalid(reason: &'static str) -> SolidError {
|
||||
SolidError::invalid_input(OP, reason)
|
||||
}
|
||||
|
||||
fn unsupported() -> SolidError {
|
||||
SolidError::Unsupported {
|
||||
operation: OP,
|
||||
reason: "stitch gap enclosure is implemented for lines and positive-weight B-splines only",
|
||||
}
|
||||
}
|
||||
|
||||
fn at<T: Copy>(values: &[T], index: usize) -> Result<T> {
|
||||
values
|
||||
.get(index)
|
||||
.copied()
|
||||
.ok_or_else(|| invalid("invalid spline storage"))
|
||||
}
|
||||
|
||||
fn finite(interval: Ivl) -> bool {
|
||||
interval.lo.is_finite() && interval.hi.is_finite() && interval.lo <= interval.hi
|
||||
}
|
||||
|
||||
fn norm(v: Box3) -> Result<f64> {
|
||||
if !v.into_iter().all(finite) {
|
||||
return Err(invalid("gap enclosure is not finite"));
|
||||
}
|
||||
let scale = v.into_iter().map(Ivl::mag).fold(0.0_f64, f64::max);
|
||||
if scale == 0.0 {
|
||||
return Ok(0.0);
|
||||
}
|
||||
// Scale before squaring: small distances must not underflow to a zero bound.
|
||||
let mut sum = Ivl::from(0.0);
|
||||
for coordinate in v {
|
||||
// Direct endpoint division avoids overflowing 1/scale for subnormals.
|
||||
let scaled = Ivl::checked(coordinate.lo / scale, coordinate.hi / scale);
|
||||
sum = sum + scaled * scaled;
|
||||
}
|
||||
let length = sum.sqrt() * Ivl::from(scale);
|
||||
if !finite(length) {
|
||||
return Err(invalid("gap enclosure overflows"));
|
||||
}
|
||||
Ok(length.hi.max(0.0))
|
||||
}
|
||||
|
||||
fn point_box(p: Point3) -> Result<Box3> {
|
||||
let coordinates = [p.x, p.y, p.z];
|
||||
if !coordinates.into_iter().all(f64::is_finite) {
|
||||
return Err(invalid("non-finite point"));
|
||||
}
|
||||
Ok(coordinates.map(Ivl::from))
|
||||
}
|
||||
|
||||
fn difference(a: Box3, b: Box3) -> Box3 {
|
||||
core::array::from_fn(|i| {
|
||||
let x = a.get(i).copied().unwrap_or(Ivl::whole());
|
||||
let y = b.get(i).copied().unwrap_or(Ivl::whole());
|
||||
if x.lo == x.hi && x == y {
|
||||
Ivl::from(0.0)
|
||||
} else {
|
||||
x - y
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Upper bound on the exact Euclidean distance between the stored points.
|
||||
pub(super) fn points(a: Point3, b: Point3) -> Result<f64> {
|
||||
norm(difference(point_box(a)?, point_box(b)?))
|
||||
}
|
||||
|
||||
/// Upper bound on the positive increase between two stored tolerances.
|
||||
pub(super) fn growth(before: f64, after: f64) -> Result<f64> {
|
||||
if ![before, after]
|
||||
.into_iter()
|
||||
.all(|v| v.is_finite() && v >= 0.0)
|
||||
{
|
||||
return Err(invalid("non-finite or negative tolerance"));
|
||||
}
|
||||
if after <= before {
|
||||
return Ok(0.0);
|
||||
}
|
||||
let increase = Ivl::from(after) - Ivl::from(before);
|
||||
if !finite(increase) {
|
||||
return Err(invalid("tolerance growth overflows"));
|
||||
}
|
||||
Ok(increase.hi)
|
||||
}
|
||||
|
||||
fn checked_range(range: [f64; 2]) -> Result<Ivl> {
|
||||
if !range.into_iter().all(f64::is_finite) {
|
||||
return Err(invalid("non-finite curve interval"));
|
||||
}
|
||||
Ok(Ivl::new(range[0].min(range[1]), range[0].max(range[1])))
|
||||
}
|
||||
|
||||
fn homogeneous(c: CPoint) -> Result<Box4> {
|
||||
let coordinates = [
|
||||
c.x,
|
||||
c.y,
|
||||
if c.z == NOZ { 0.0 } else { c.z },
|
||||
if c.w == NOW { 1.0 } else { c.w },
|
||||
];
|
||||
if !coordinates.into_iter().all(f64::is_finite) || coordinates.last().is_none_or(|w| *w <= 0.0)
|
||||
{
|
||||
return Err(invalid("non-finite spline data or non-positive weight"));
|
||||
}
|
||||
Ok(coordinates.map(Ivl::from))
|
||||
}
|
||||
|
||||
fn validate(n: &Nurbs, range: Ivl) -> Result<()> {
|
||||
let p = n.degree();
|
||||
let count = n.cpts().len();
|
||||
if p > MAX_DEGREE + 1 || count <= p || count.checked_add(p + 1) != Some(n.knots().len()) {
|
||||
return Err(invalid("invalid spline degree or storage"));
|
||||
}
|
||||
if !n.knots().iter().all(|k| k.is_finite())
|
||||
|| n.knots().windows(2).any(|w| matches!(w, [a, b] if a > b))
|
||||
{
|
||||
return Err(invalid("invalid knot vector"));
|
||||
}
|
||||
if at(n.knots(), p)? >= at(n.knots(), count)?
|
||||
|| range.lo < at(n.knots(), p)?
|
||||
|| range.hi > at(n.knots(), count)?
|
||||
{
|
||||
return Err(invalid("curve gap enclosure does not extrapolate"));
|
||||
}
|
||||
let first = at(n.cpts(), 0)?;
|
||||
for &c in n.cpts() {
|
||||
if (c.w == NOW) != (first.w == NOW) || (c.z == NOZ) != (first.z == NOZ) {
|
||||
return Err(invalid("mixed homogeneous coordinate storage"));
|
||||
}
|
||||
homogeneous(c)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The exact rational polynomial on one nonempty knot span, evaluated in intervals.
|
||||
fn piece(n: &Nurbs, span: usize, t: Ivl) -> Result<Box3> {
|
||||
let p = n.degree();
|
||||
let first = span
|
||||
.checked_sub(p)
|
||||
.ok_or_else(|| invalid("invalid spline span"))?;
|
||||
let mut d = Vec::new();
|
||||
let mut min_weight = f64::INFINITY;
|
||||
for j in 0..=p {
|
||||
let h = homogeneous(at(n.cpts(), first + j)?)?;
|
||||
min_weight = min_weight.min(at(&h, 3)?.lo);
|
||||
d.push(h);
|
||||
}
|
||||
for r in 1..=p {
|
||||
for j in (r..=p).rev() {
|
||||
let i = first + j;
|
||||
let denominator =
|
||||
Ivl::from(at(n.knots(), i + p + 1 - r)?) - Ivl::from(at(n.knots(), i)?);
|
||||
if denominator.lo <= 0.0 {
|
||||
return Err(invalid("unproved knot denominator"));
|
||||
}
|
||||
let alpha = (t - Ivl::from(at(n.knots(), i)?)) / denominator;
|
||||
// On the selected span, the exact alpha is in [0, 1]. Intersecting
|
||||
// with that proved range removes rounding spill, not geometric error.
|
||||
let alpha = Ivl::new(alpha.lo.max(0.0), alpha.hi.min(1.0));
|
||||
if !finite(alpha) {
|
||||
return Err(invalid("invalid spline parameter enclosure"));
|
||||
}
|
||||
let left = at(&d, j - 1)?;
|
||||
let right = at(&d, j)?;
|
||||
let value = core::array::from_fn(|axis| {
|
||||
let a = left.get(axis).copied().unwrap_or(Ivl::whole());
|
||||
let b = right.get(axis).copied().unwrap_or(Ivl::whole());
|
||||
(Ivl::from(1.0) - alpha) * a + alpha * b
|
||||
});
|
||||
*d.get_mut(j)
|
||||
.ok_or_else(|| invalid("invalid spline workspace"))? = value;
|
||||
}
|
||||
}
|
||||
let h = at(&d, p)?;
|
||||
// Positive basis functions sum to one, so W is at least the least active weight.
|
||||
let w = at(&h, 3)?;
|
||||
let w = Ivl::new(w.lo.max(min_weight), w.hi);
|
||||
if !finite(w) || w.lo <= 0.0 {
|
||||
return Err(invalid("unproved spline denominator"));
|
||||
}
|
||||
let result = [at(&h, 0)? / w, at(&h, 1)? / w, at(&h, 2)? / w];
|
||||
if !result.into_iter().all(finite) {
|
||||
return Err(invalid("curve enclosure overflows"));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn spline_box(n: &Nurbs, range: Ivl) -> Result<Box3> {
|
||||
validate(n, range)?;
|
||||
let mut result: Option<Box3> = None;
|
||||
for (span, pair) in n.knots().windows(2).enumerate() {
|
||||
let [a, b] = pair else {
|
||||
continue;
|
||||
};
|
||||
if span < n.degree() || span >= n.cpts().len() || a >= b || *b < range.lo || *a > range.hi {
|
||||
continue;
|
||||
}
|
||||
let t = Ivl::new(range.lo.max(*a), range.hi.min(*b));
|
||||
let part = piece(n, span, t)?;
|
||||
result = Some(match result {
|
||||
None => part,
|
||||
Some(old) => core::array::from_fn(|i| {
|
||||
old.get(i)
|
||||
.copied()
|
||||
.unwrap_or(Ivl::whole())
|
||||
.hull(part.get(i).copied().unwrap_or(Ivl::whole()))
|
||||
}),
|
||||
});
|
||||
}
|
||||
result.ok_or_else(|| invalid("no knot span covers the curve interval"))
|
||||
}
|
||||
|
||||
fn line_box(line: &Line, t: Ivl) -> Result<Box3> {
|
||||
let natural = line.natural_interval();
|
||||
let step = line.step_interval();
|
||||
if !finite(t)
|
||||
|| !finite(checked_range([natural.min(), natural.max()])?)
|
||||
|| !finite(checked_range([step.min(), step.max()])?)
|
||||
{
|
||||
return Err(invalid("non-finite line parameterization"));
|
||||
}
|
||||
if t.lo < natural.min() || t.hi > natural.max() {
|
||||
return Err(invalid("curve gap enclosure does not extrapolate"));
|
||||
}
|
||||
let u = if line.is_bounded() {
|
||||
let length = Ivl::from(natural.max()) - Ivl::from(natural.min());
|
||||
if length.lo <= 0.0 || crate::geom::tol::is_zero(natural.length()) {
|
||||
return Err(invalid("degenerate line parameterization"));
|
||||
}
|
||||
let (start, direction) = if line.inside_out() {
|
||||
(natural.max(), -1.0)
|
||||
} else {
|
||||
(natural.min(), 1.0)
|
||||
};
|
||||
Ivl::from(step.min())
|
||||
+ (t - Ivl::from(start)) / length
|
||||
* Ivl::from(direction)
|
||||
* (Ivl::from(step.max()) - Ivl::from(step.min()))
|
||||
} else if line.inside_out() {
|
||||
Ivl::from(0.0) - t
|
||||
} else {
|
||||
t
|
||||
};
|
||||
let p = point_box(line.line_point())?;
|
||||
let v = point_box(line.line_vector())?;
|
||||
if !line.line_scale().is_finite() {
|
||||
return Err(invalid("non-finite line scale"));
|
||||
}
|
||||
let result = core::array::from_fn(|i| {
|
||||
p.get(i).copied().unwrap_or(Ivl::whole())
|
||||
+ u * v.get(i).copied().unwrap_or(Ivl::whole()) * Ivl::from(line.line_scale())
|
||||
});
|
||||
if !result.into_iter().all(finite) {
|
||||
return Err(invalid("line enclosure overflows"));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn curve_box(curve: &Curve, range: Ivl) -> Result<Box3> {
|
||||
match curve.kind() {
|
||||
CurveKind::Line(l) => line_box(l, range),
|
||||
CurveKind::BSpline(b) => spline_box(
|
||||
b.gw_nurb().ok_or_else(|| invalid("missing spline data"))?,
|
||||
range,
|
||||
),
|
||||
_ => Err(unsupported()),
|
||||
}
|
||||
}
|
||||
|
||||
/// An upper bound for point-to-curve distance using an exact point of the curve.
|
||||
pub(super) fn point_at_curve(point: Point3, curve: &Curve, parameter: f64) -> Result<f64> {
|
||||
norm(difference(
|
||||
point_box(point)?,
|
||||
curve_box(curve, checked_range([parameter, parameter])?)?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Bounds the whole curve's distance from a point (the geometry a squeeze removes).
|
||||
pub(super) fn curve_to_point(curve: &Curve, interval: &Extent1d, point: Point3) -> Result<f64> {
|
||||
norm(difference(
|
||||
curve_box(curve, checked_range([interval.min(), interval.max()])?)?,
|
||||
point_box(point)?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Bounds both directed curve gaps under the stated endpoint correspondence.
|
||||
pub(super) fn curves(first: &Curve, a: [f64; 2], second: &Curve, b: [f64; 2]) -> Result<f64> {
|
||||
let ar = checked_range(a)?;
|
||||
let br = checked_range(b)?;
|
||||
// With a common positive rational basis the difference is a convex combination
|
||||
// of the dehomogenized control-point differences. This also proves identity
|
||||
// exactly, without subtracting independently widened position boxes.
|
||||
if a == b {
|
||||
if let (CurveKind::BSpline(x), CurveKind::BSpline(y)) = (first.kind(), second.kind()) {
|
||||
let x = x.gw_nurb().ok_or_else(|| invalid("missing spline data"))?;
|
||||
let y = y.gw_nurb().ok_or_else(|| invalid("missing spline data"))?;
|
||||
validate(x, ar)?;
|
||||
validate(y, br)?;
|
||||
if x.degree() == y.degree()
|
||||
&& x.knots() == y.knots()
|
||||
&& x.cpts().iter().zip(y.cpts()).all(|(p, q)| p.w == q.w)
|
||||
{
|
||||
let mut upper = 0.0_f64;
|
||||
for (&p, &q) in x.cpts().iter().zip(y.cpts()) {
|
||||
let p = homogeneous(p)?;
|
||||
let q = homogeneous(q)?;
|
||||
let delta = difference(
|
||||
[at(&p, 0)?, at(&p, 1)?, at(&p, 2)?],
|
||||
[at(&q, 0)?, at(&q, 1)?, at(&q, 2)?],
|
||||
);
|
||||
let w = at(&p, 3)?;
|
||||
upper = upper.max(norm(delta.map(|d| {
|
||||
if d == Ivl::from(0.0) {
|
||||
d
|
||||
} else {
|
||||
d / w
|
||||
}
|
||||
}))?);
|
||||
}
|
||||
return Ok(upper);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Affine curves attain the maximum norm of their difference at an endpoint.
|
||||
// Otherwise enclose every parameter cell, never only its sample stations.
|
||||
let cells = if matches!(
|
||||
(first.kind(), second.kind()),
|
||||
(CurveKind::Line(_), CurveKind::Line(_))
|
||||
) {
|
||||
1_u32
|
||||
} else {
|
||||
64_u32
|
||||
};
|
||||
let mut upper = 0.0_f64;
|
||||
for i in 0..cells {
|
||||
let s = Ivl::new(
|
||||
f64::from(i) / f64::from(cells),
|
||||
f64::from(i + 1) / f64::from(cells),
|
||||
);
|
||||
let mapped =
|
||||
|range: [f64; 2]| Ivl::from(range[0]) * (Ivl::from(1.0) - s) + Ivl::from(range[1]) * s;
|
||||
if cells == 1 {
|
||||
for j in [0, 1] {
|
||||
let t = at(&a, j)?;
|
||||
let u = at(&b, j)?;
|
||||
upper = upper.max(norm(difference(
|
||||
curve_box(first, Ivl::from(t))?,
|
||||
curve_box(second, Ivl::from(u))?,
|
||||
))?);
|
||||
}
|
||||
} else {
|
||||
// Clip only the rounding spill of the affine parameter map.
|
||||
let x = mapped(a);
|
||||
let y = mapped(b);
|
||||
upper = upper.max(norm(difference(
|
||||
curve_box(first, Ivl::new(x.lo.max(ar.lo), x.hi.min(ar.hi)))?,
|
||||
curve_box(second, Ivl::new(y.lo.max(br.lo), y.hi.min(br.hi)))?,
|
||||
))?);
|
||||
}
|
||||
}
|
||||
Ok(upper)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::curve::hermite_curve::HermiteCurve;
|
||||
use crate::nurbs::b_spline_curve::BSplineCurve;
|
||||
use crate::{Kernel, Vec3};
|
||||
|
||||
/// Retain the raw data: an ordinary clone/owned constructor tunes the knots.
|
||||
fn spline(points: Vec<CPoint>, degree: usize, knots: Vec<f64>) -> Curve {
|
||||
BSplineCurve::from_nurb(Nurbs::new(points, degree, knots).unwrap(), 3, true).into()
|
||||
}
|
||||
|
||||
fn cp(x: f64, y: f64, w: f64) -> CPoint {
|
||||
CPoint { x, y, z: 0.0, w }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_curve_bound_catches_the_interior_bump_five_samples_miss() {
|
||||
// A degree-one hat on [1/16, 3/16], peaking at 1/8. Every old sample
|
||||
// at 0, 1/4, 1/2, 3/4, 1 is exactly on the flat curve.
|
||||
let knots = vec![0.0, 0.0, 0.0625, 0.125, 0.1875, 1.0, 1.0];
|
||||
let flat = spline(
|
||||
vec![
|
||||
cp(0.0, 0.0, NOW),
|
||||
cp(0.0625, 0.0, NOW),
|
||||
cp(0.125, 0.0, NOW),
|
||||
cp(0.1875, 0.0, NOW),
|
||||
cp(1.0, 0.0, NOW),
|
||||
],
|
||||
1,
|
||||
knots.clone(),
|
||||
);
|
||||
let bump = spline(
|
||||
vec![
|
||||
cp(0.0, 0.0, NOW),
|
||||
cp(0.0625, 0.0, NOW),
|
||||
cp(0.125, 1.0, NOW),
|
||||
cp(0.1875, 0.0, NOW),
|
||||
cp(1.0, 0.0, NOW),
|
||||
],
|
||||
1,
|
||||
knots,
|
||||
);
|
||||
let k = Kernel::default();
|
||||
for t in [0.0, 0.25, 0.5, 0.75, 1.0] {
|
||||
assert_eq!(
|
||||
flat.evaluate_point(&k, t).unwrap(),
|
||||
bump.evaluate_point(&k, t).unwrap()
|
||||
);
|
||||
}
|
||||
assert_eq!(bump.evaluate_point(&k, 0.125).unwrap().y, 1.0);
|
||||
let bound = curves(&flat, [0.0, 1.0], &bump, [0.0, 1.0]).unwrap();
|
||||
assert!((1.0..1.0 + 1e-12).contains(&bound), "{bound}");
|
||||
let squeezed =
|
||||
curve_to_point(&bump, &Extent1d::from_min_max(0.0, 1.0), Vec3::ZERO).unwrap();
|
||||
assert!(squeezed >= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positive_rational_weights_are_enclosed_without_a_polynomial_substitute() {
|
||||
// Y(t) = 4t(1-t)/(1+2t(1-t)), maximum 2/3 at 1/2.
|
||||
let rational = spline(
|
||||
vec![cp(0.0, 0.0, 1.0), cp(1.0, 2.0, 2.0), cp(1.0, 0.0, 1.0)],
|
||||
2,
|
||||
vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
|
||||
);
|
||||
let line: Curve = Line::from_points(Vec3::ZERO, Vec3::new(1.0, 0.0, 0.0), 3).into();
|
||||
let bound = curves(&rational, [0.0, 1.0], &line, [0.0, 1.0]).unwrap();
|
||||
assert!((2.0 / 3.0..1.0).contains(&bound), "{bound}");
|
||||
let at_mid = point_at_curve(Vec3::new(0.5, 0.0, 0.0), &rational, 0.5).unwrap();
|
||||
assert!((2.0 / 3.0..2.0 / 3.0 + 1e-12).contains(&at_mid), "{at_mid}");
|
||||
assert_eq!(
|
||||
curves(&rational, [0.0, 1.0], &rational, [0.0, 1.0]).unwrap(),
|
||||
0.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reversed_and_subdomain_correspondences_cover_every_parameter() {
|
||||
let forward = spline(
|
||||
vec![cp(0.0, 0.0, NOW), cp(1.0, 0.0, NOW)],
|
||||
1,
|
||||
vec![0.0, 0.0, 1.0, 1.0],
|
||||
);
|
||||
let reverse = spline(
|
||||
vec![cp(1.0, 0.0, NOW), cp(0.0, 0.0, NOW)],
|
||||
1,
|
||||
vec![0.0, 0.0, 1.0, 1.0],
|
||||
);
|
||||
let bound = curves(&forward, [0.25, 0.75], &reverse, [0.75, 0.25]).unwrap();
|
||||
assert!(bound.is_finite() && bound < 0.1, "{bound}");
|
||||
let endpoint_bound = point_at_curve(Vec3::ZERO, &forward, 0.75).unwrap();
|
||||
assert!((0.75..0.75 + 1e-12).contains(&endpoint_bound));
|
||||
assert!(curves(&forward, [-0.1, 0.75], &reverse, [0.75, 0.25]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_and_growth_bounds_do_not_underflow_or_accept_nonfinite_results() {
|
||||
assert_eq!(points(Vec3::ZERO, Vec3::ZERO).unwrap(), 0.0);
|
||||
for tiny in [1e-200, f64::from_bits(1)] {
|
||||
let bound = points(Vec3::ZERO, Vec3::new(tiny, 0.0, 0.0)).unwrap();
|
||||
assert!(bound.is_finite() && bound >= tiny && bound > 0.0, "{bound}");
|
||||
}
|
||||
assert!(points(Vec3::ZERO, Vec3::new(1e300, 0.0, 0.0)).unwrap() >= 1e300);
|
||||
assert!(points(
|
||||
Vec3::new(-f64::MAX, 0.0, 0.0),
|
||||
Vec3::new(f64::MAX, 0.0, 0.0)
|
||||
)
|
||||
.is_err());
|
||||
assert!(points(Vec3::ZERO, Vec3::new(f64::NAN, 0.0, 0.0)).is_err());
|
||||
assert_eq!(growth(1.0, 1.0).unwrap(), 0.0);
|
||||
assert_eq!(growth(2.0, 1.0).unwrap(), 0.0);
|
||||
assert!(growth(1.0, 2.0).unwrap() >= 1.0);
|
||||
assert!(growth(0.0, f64::from_bits(1)).unwrap() > 0.0);
|
||||
assert!(growth(-1.0, 0.0).is_err());
|
||||
assert!(growth(0.0, f64::INFINITY).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_weights_knots_storage_and_unsupported_kinds_fail_explicitly() {
|
||||
for weight in [0.0, -1.0, f64::NAN, f64::INFINITY] {
|
||||
let c = spline(
|
||||
vec![cp(0.0, 0.0, weight), cp(1.0, 0.0, 1.0)],
|
||||
1,
|
||||
vec![0.0, 0.0, 1.0, 1.0],
|
||||
);
|
||||
assert!(point_at_curve(Vec3::ZERO, &c, 0.5).is_err());
|
||||
}
|
||||
for (degree, knots) in [
|
||||
(1, vec![0.0, 1.0]),
|
||||
(1, vec![0.0; 4]),
|
||||
(1, vec![0.0, 1.0, 0.0, 1.0]),
|
||||
(usize::MAX, vec![0.0, 0.0, 1.0, 1.0]),
|
||||
] {
|
||||
let c = spline(vec![cp(0.0, 0.0, NOW), cp(1.0, 0.0, NOW)], degree, knots);
|
||||
assert!(point_at_curve(Vec3::ZERO, &c, 0.5).is_err());
|
||||
}
|
||||
let mixed = spline(
|
||||
vec![cp(0.0, 0.0, NOW), cp(1.0, 0.0, 1.0)],
|
||||
1,
|
||||
vec![0.0, 0.0, 1.0, 1.0],
|
||||
);
|
||||
assert!(point_at_curve(Vec3::ZERO, &mixed, 0.5).is_err());
|
||||
let hermite: Curve = HermiteCurve::new(
|
||||
Vec3::ZERO,
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
3,
|
||||
)
|
||||
.into();
|
||||
assert!(matches!(
|
||||
point_at_curve(Vec3::ZERO, &hermite, 0.5),
|
||||
Err(SolidError::Unsupported { .. })
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,8 @@ use crate::topology::{
|
||||
};
|
||||
use crate::{Result, SolidError};
|
||||
|
||||
use super::p2::{ClosedGap, ClosedGapKind};
|
||||
|
||||
const OP: &str = "heal::stitch::p1";
|
||||
/// The operation of the `SM_API` entry points' `SM_ERR_INVALID_INPUT` (null arguments).
|
||||
const API_OP: &str = "heal::stitch::p1::api";
|
||||
@@ -932,6 +934,47 @@ pub struct Stitch<'c> {
|
||||
/// Remove sliver faces smaller than tolerance first (`m_bRemoveLaminarSlivers`,
|
||||
/// default `FALSE`).
|
||||
pub remove_laminar_slivers: bool,
|
||||
/// When `Some`, the stitch records what it closed: the entities, the gap it
|
||||
/// enclosed, and the tolerance growth its editing applied (not upstream,
|
||||
/// solidcore#2975). Nothing else changes.
|
||||
pub report_gaps: Option<Vec<ClosedGap>>,
|
||||
}
|
||||
|
||||
impl Stitch<'_> {
|
||||
/// The collected gap records, taking them out (`Ok(vec![])` when the caller did
|
||||
/// not ask for them).
|
||||
pub(crate) fn take_gaps(&mut self) -> Vec<ClosedGap> {
|
||||
self.report_gaps.take().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Records one closed gap when the caller asked for the report. The growth is
|
||||
/// an outward-rounded bound on how much the surviving entity's tolerance rose
|
||||
/// over its own tolerance before the closure, including inherited tolerance.
|
||||
pub(crate) fn record_gap(
|
||||
&mut self,
|
||||
kind: ClosedGapKind,
|
||||
gap: f64,
|
||||
before: f64,
|
||||
after: f64,
|
||||
) -> Result<()> {
|
||||
if let Some(gaps) = self.report_gaps.as_mut() {
|
||||
let growth = super::bounds::growth(before, after)?;
|
||||
if !gap.is_finite() || gap < 0.0 {
|
||||
return Err(SolidError::invalid_input(
|
||||
"heal::stitch::bounds",
|
||||
"non-finite or negative gap bound",
|
||||
));
|
||||
}
|
||||
gaps.push(ClosedGap {
|
||||
kind,
|
||||
gap,
|
||||
growth,
|
||||
tolerance_before: before,
|
||||
tolerance_after: after,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Stitch<'_> {
|
||||
@@ -1151,6 +1194,7 @@ impl<'c> Stitch<'c> {
|
||||
split_edges_with_vertices: true,
|
||||
making_manifold_solid: true,
|
||||
remove_laminar_slivers: false,
|
||||
report_gaps: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+186
@@ -351,6 +351,62 @@ pub struct VertexList {
|
||||
pub min_unstitched_vert_gap: f64,
|
||||
}
|
||||
|
||||
/// What the stitcher closed, as the entities it glued (solidcore#2975).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ClosedGapKind {
|
||||
/// A vertex pair glued into the kept vertex.
|
||||
Vertex {
|
||||
/// The vertex the glue kept.
|
||||
kept: VertexId,
|
||||
/// The vertex the glue deleted.
|
||||
deleted: VertexId,
|
||||
},
|
||||
/// An edge pair glued into the kept edge.
|
||||
Edge {
|
||||
/// The edge the glue kept.
|
||||
kept: EdgeId,
|
||||
/// The edge the glue deleted.
|
||||
deleted: EdgeId,
|
||||
},
|
||||
/// A vertex pair one edge was squeezed into (the kept vertex stays).
|
||||
Squeezed {
|
||||
/// The vertex the squeeze kept.
|
||||
kept: VertexId,
|
||||
/// The vertex the squeeze closed over.
|
||||
deleted: VertexId,
|
||||
},
|
||||
/// A vertex dropped onto an edge and split there, with the vertex its split created.
|
||||
Split {
|
||||
/// The edge the vertex was dropped onto and split.
|
||||
edge: EdgeId,
|
||||
/// The vertex the split created.
|
||||
created: VertexId,
|
||||
},
|
||||
}
|
||||
|
||||
/// A closure's certified geometry bound and tolerance accounting (solidcore#2975).
|
||||
/// The bound includes floating-point rounding. For edges it bounds both directed
|
||||
/// Hausdorff distances via an endpoint-to-endpoint parameter correspondence; it
|
||||
/// need not be the tightest Hausdorff bound. Unsupported enclosures return an
|
||||
/// error from the reporting operation, never a sampled estimate as a certificate.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ClosedGap {
|
||||
/// What the stitcher closed.
|
||||
pub kind: ClosedGapKind,
|
||||
/// Certified upper bound on the gap, in model units. A squeeze bounds the
|
||||
/// whole removed curve's distance to the kept point; a split bounds the source
|
||||
/// point's distance to the original curve at the split parameter.
|
||||
pub gap: f64,
|
||||
/// Upper bound on `max(tolerance_after - tolerance_before, 0)`, rounded up.
|
||||
/// Exactly zero when the surviving entity's stored tolerance did not grow.
|
||||
pub growth: f64,
|
||||
/// The surviving entity's own stored tolerance before this closure. For a
|
||||
/// split this is the new vertex's tolerance before its explicit adjustment.
|
||||
pub tolerance_before: f64,
|
||||
/// The surviving entity's stored tolerance after this closure.
|
||||
pub tolerance_after: f64,
|
||||
}
|
||||
|
||||
/// The result of [`Stitch::split_edges_with_vertices`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SplitEdges {
|
||||
@@ -479,9 +535,36 @@ impl Stitch<'_> {
|
||||
}
|
||||
|
||||
let surv_v = surv_v.ok_or_else(|| deref("edge without vertex"))?;
|
||||
let deleted_report = del_v.unwrap_or(surv_v);
|
||||
// Bound all the removed geometry, not its sampled length or
|
||||
// merely the distance between its endpoints.
|
||||
let squeeze_report = if self.report_gaps.is_some() {
|
||||
Some((
|
||||
vertex::tolerance(b, surv_v)?,
|
||||
super::bounds::curve_to_point(
|
||||
curve_of(b, e)?,
|
||||
&edge::interval(b, e)?,
|
||||
vertex::point(b, surv_v)?,
|
||||
)?,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// remove and delete edge from topology graph by squeezing
|
||||
// [vertex-edge-vertex] into [vertex]
|
||||
self.edit_squeeze_edge(k, b, Some(e), surv_v)?;
|
||||
if let Some((pre, gap)) = squeeze_report {
|
||||
let after = vertex::tolerance(b, surv_v)?;
|
||||
self.record_gap(
|
||||
ClosedGapKind::Squeezed {
|
||||
kept: surv_v,
|
||||
deleted: deleted_report,
|
||||
},
|
||||
gap,
|
||||
pre,
|
||||
after,
|
||||
)?;
|
||||
}
|
||||
|
||||
if candidate_vertices.is_some() {
|
||||
add_unique(&mut cand_vertices, surv_v); // This becomes a candidate.
|
||||
@@ -570,7 +653,42 @@ impl Stitch<'_> {
|
||||
// candidates
|
||||
let new_tol =
|
||||
smos_max(vertex::tolerance(b, v)?, vertex::tolerance(b, new_v)?);
|
||||
let split_report = if self.report_gaps.is_some() {
|
||||
let range = edge::interval(b, e)?;
|
||||
let parameter = if edge::start_vertex(b, e)? == Some(new_v) {
|
||||
range.min()
|
||||
} else if edge::end_vertex(b, e)? == Some(new_v) {
|
||||
range.max()
|
||||
} else {
|
||||
return Err(SolidError::invalid_input(
|
||||
"heal::stitch::bounds",
|
||||
"split vertex is not on either resulting edge end",
|
||||
));
|
||||
};
|
||||
Some((
|
||||
vertex::tolerance(b, new_v)?,
|
||||
super::bounds::point_at_curve(
|
||||
vertex::point(b, v)?,
|
||||
curve_of(b, e)?,
|
||||
parameter,
|
||||
)?,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
vertex::set_tolerance(b, new_v, zone_tol3d(k, new_tol), true, true)?;
|
||||
if let Some((pre, bound)) = split_report {
|
||||
let after = vertex::tolerance(b, new_v)?;
|
||||
self.record_gap(
|
||||
ClosedGapKind::Split {
|
||||
edge: e,
|
||||
created: new_v,
|
||||
},
|
||||
bound,
|
||||
pre,
|
||||
after,
|
||||
)?;
|
||||
}
|
||||
more_work_needed = true;
|
||||
|
||||
cand_vertices.push(new_v);
|
||||
@@ -734,6 +852,43 @@ impl Stitch<'_> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// (not upstream, #2975) capture the closure record's operands
|
||||
// before nothing else deflects them, and read the surviving
|
||||
// entity's tolerance after.
|
||||
let kept = if choice.keep_first { v } else { cv };
|
||||
let deleted = if choice.keep_first { cv } else { v };
|
||||
let pair_report = if self.report_gaps.is_some() && (do_glue || do_squeeze) {
|
||||
Some((
|
||||
vertex::tolerance(b, kept)?,
|
||||
if do_squeeze {
|
||||
if let Some(e) = squeeze_edge {
|
||||
super::bounds::curve_to_point(
|
||||
curve_of(b, e)?,
|
||||
&edge::interval(b, e)?,
|
||||
vertex::point(b, kept)?,
|
||||
)?
|
||||
} else {
|
||||
super::bounds::points(
|
||||
vertex::point(b, kept)?,
|
||||
vertex::point(b, deleted)?,
|
||||
)?
|
||||
}
|
||||
} else {
|
||||
super::bounds::points(
|
||||
vertex::point(b, kept)?,
|
||||
vertex::point(b, deleted)?,
|
||||
)?
|
||||
},
|
||||
if do_glue {
|
||||
ClosedGapKind::Vertex { kept, deleted }
|
||||
} else {
|
||||
ClosedGapKind::Squeezed { kept, deleted }
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// squeeze or glue vertices together (make pV the retained vertex)
|
||||
if choice.keep_first {
|
||||
if do_squeeze {
|
||||
@@ -749,6 +904,10 @@ impl Stitch<'_> {
|
||||
}
|
||||
v = cv;
|
||||
}
|
||||
if let Some((pre, bound, kind)) = pair_report {
|
||||
let after = vertex::tolerance(b, v)?;
|
||||
self.record_gap(kind, bound, pre, after)?;
|
||||
}
|
||||
|
||||
// The surviving vertex (which is pV) should be a candidate.
|
||||
if candidate_vertices.is_some() {
|
||||
@@ -1057,6 +1216,21 @@ impl Stitch<'_> {
|
||||
for f in edge::faces(b, delete, None)? {
|
||||
add_unique(modified_faces, f);
|
||||
}
|
||||
// (not upstream, #2975) capture the closure's operands before the
|
||||
// glue, and the kept edge's tolerance after.
|
||||
let edge_report = if self.report_gaps.is_some() {
|
||||
Some((
|
||||
edge::tolerance(b, keep)?,
|
||||
super::bounds::curves(
|
||||
curve_of(b, e)?,
|
||||
[e_ivl.min(), e_ivl.max()],
|
||||
curve_of(b, test_e)?,
|
||||
[t_start, t_end],
|
||||
)?,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match self.edit_glue_edges(
|
||||
k,
|
||||
b,
|
||||
@@ -1076,6 +1250,18 @@ impl Stitch<'_> {
|
||||
}
|
||||
set_none(&mut v_edges, kk)?;
|
||||
done_with_vertex = false;
|
||||
if let Some((pre, bound)) = edge_report {
|
||||
let after = edge::tolerance(b, e)?;
|
||||
self.record_gap(
|
||||
ClosedGapKind::Edge {
|
||||
kept: e,
|
||||
deleted: delete,
|
||||
},
|
||||
bound,
|
||||
pre,
|
||||
after,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Err(err) if err.is_unported_or_cancelled() => return Err(err),
|
||||
Err(_) if !self.ignore_problems => return Err(sm_err()),
|
||||
|
||||
@@ -2137,20 +2137,6 @@ pub fn is_point_on_tsect_curve(
|
||||
if point_to_test.crv_deriv().dot(tangent) < 0.0 {
|
||||
return Ok(true);
|
||||
}
|
||||
// Not upstream: the span ends on a crossing point. The march landed on it,
|
||||
// as it does when the predictor was clipped to a domain boundary the curve
|
||||
// touches there (#12858's boss), so the point is on the span whichever way
|
||||
// its direction runs. Upstream reads a same-direction end as "not passed
|
||||
// yet", and a trace that keeps landing on its crossing start, or on the
|
||||
// other touch point, never ends. A tangent point keeps the direction test:
|
||||
// a trace runs through a tangent start point lying on its curve (the seam
|
||||
// point of a sphere's equator on a coaxial cylinder) and ends on the
|
||||
// crossing point beyond it, as upstream's does.
|
||||
if t >= hermite_domain.max() - EFF_ZERO
|
||||
&& point_to_test.point_type == IntersectionPointType::Crossing
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Check value on interior. (Pass negative tol: just excludes end points.)
|
||||
@@ -3019,14 +3005,6 @@ mod tests {
|
||||
is_point_on_tsect_curve(&k, &end_forward, &prev, &next, 1.0e-3),
|
||||
Ok(false)
|
||||
);
|
||||
// the span ends on a crossing point: on it whichever way it points (not
|
||||
// upstream, which answers false here; see `is_point_on_tsect_curve`)
|
||||
let mut end_forward_crossing = end_forward.clone();
|
||||
end_forward_crossing.point_type = IntersectionPointType::Crossing;
|
||||
assert_eq!(
|
||||
is_point_on_tsect_curve(&k, &end_forward_crossing, &prev, &next, 1.0e-3),
|
||||
Ok(true)
|
||||
);
|
||||
assert_eq!(
|
||||
is_point_on_tsect_curve(&k, &interior, &prev, &next, 1.0e-3),
|
||||
Ok(true)
|
||||
|
||||
@@ -709,24 +709,6 @@ impl<'t, O: SolutionObject + Copy + PartialEq + 't> AdvSurfaceIntersector<'_, 't
|
||||
// Remove Last StartPoint from m_vStartPoint array
|
||||
self.base.start_points.pop();
|
||||
|
||||
// Not upstream: a start point whose direction runs along every domain
|
||||
// boundary it lies on is where the curve touches a boundary, not where it
|
||||
// crosses one. The curve stays inside, so its trace can come back to this
|
||||
// point: let it close on it, as an interior seed's trace does. Upstream
|
||||
// never sets the start point here, so a loop touching a face's edge
|
||||
// (#12858's boss) is traced round and round to the point limit.
|
||||
let along_both = {
|
||||
let sp = self.base.point(start)?;
|
||||
let along = |srf: SrfIndex| {
|
||||
(sp.u_param_type(srf) == ExtentPointType::Inside
|
||||
|| is_zero_to_tol(sp.uv_deriv(srf).x, EFF_ZERO))
|
||||
&& (sp.v_param_type(srf) == ExtentPointType::Inside
|
||||
|| is_zero_to_tol(sp.uv_deriv(srf).y, EFF_ZERO))
|
||||
};
|
||||
along(SrfIndex::First) && along(SrfIndex::Second)
|
||||
};
|
||||
self.base.start_point = if along_both { Some(start) } else { None };
|
||||
|
||||
// Trace an intersection curve onto m_vCurvePoints.
|
||||
let input_trace_direction = self.base.curve_trace_direction;
|
||||
let traced_ok = ignored(self.base.trace_curve(k, start, &out.curves_3d))?;
|
||||
@@ -766,7 +748,6 @@ impl<'t, O: SolutionObject + Copy + PartialEq + 't> AdvSurfaceIntersector<'_, 't
|
||||
if num_curves == out.curves_3d.len() {
|
||||
self.base.through_points.push(tmp_id);
|
||||
}
|
||||
self.base.start_point = None;
|
||||
}
|
||||
|
||||
self.base.set_trace_budget(false);
|
||||
|
||||
+1
@@ -5,4 +5,5 @@
|
||||
//! `SmVertexProps`, `SmObjProps` in `source/SMLib`, and the area/volume queries of
|
||||
//! `SM_API/SmApiQueries`.
|
||||
|
||||
pub mod enclosure;
|
||||
pub mod integrator;
|
||||
|
||||
+1790
File diff suppressed because it is too large
Load Diff
+71
-4
@@ -1354,10 +1354,8 @@ mod tests {
|
||||
assert!(error.abs() <= 1e-6, "{error}");
|
||||
}
|
||||
|
||||
/// The final step's last node is the interval's end itself: `-0.1 + (0.3 - -0.1)`
|
||||
/// rounds to `0.30000000000000004`, past the end, where an integrand whose end is a
|
||||
/// knot (a curve's span, a face's iso-u piece) would read the span beyond it. Every
|
||||
/// node lies within `[x1, x2]`, and the step that ends the integration ends at `x2`.
|
||||
/// A rate that is NaN, infinite or negative is refused before the integrand is
|
||||
/// evaluated, and nothing is written.
|
||||
#[test]
|
||||
fn a_rate_that_is_not_a_finite_nonnegative_number_is_refused() {
|
||||
for rate in [
|
||||
@@ -1424,6 +1422,75 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// `rate` on closed forms, as the curve length uses it: a range cut into `n` equal
|
||||
/// pieces, each integrated to half the accuracy with the range's mean as its rate.
|
||||
/// Each step is held to half the accuracy relative to its share of the range plus
|
||||
/// its own increment, shares that add up to twice the integral, so the pieces' sum is
|
||||
/// within the accuracy of the whole integral however many pieces and steps there are.
|
||||
#[test]
|
||||
fn a_rate_holds_a_range_cut_into_pieces_to_the_accuracy_of_the_whole() {
|
||||
use core::f64::consts::PI;
|
||||
type Case = (fn(f64) -> f64, f64, f64, f64);
|
||||
let cases: [Case; 3] = [
|
||||
// e^t on [0, 4]: e^4 - 1
|
||||
(f64::exp, 0.0, 4.0, 4.0_f64.exp() - 1.0),
|
||||
// Runge's 1 / (1 + 25 t^2) on [-1, 1]: 2/5 atan 5
|
||||
(
|
||||
|t| 1.0 / (1.0 + 25.0 * t * t),
|
||||
-1.0,
|
||||
1.0,
|
||||
0.4 * 5.0_f64.atan(),
|
||||
),
|
||||
// 2 + sin 20t on [0, pi]: 2 pi (the sine's ten periods cancel)
|
||||
(|t| 2.0 + (20.0 * t).sin(), 0.0, PI, 2.0 * PI),
|
||||
];
|
||||
for (g, a, b, exact) in cases {
|
||||
let rate = exact / (b - a);
|
||||
for accuracy in [1e-4, 1e-6, 1e-8, 1e-10] {
|
||||
for n in [1_u32, 8, 64] {
|
||||
let piece = (b - a) / f64::from(n);
|
||||
let mut sum = 0.0;
|
||||
for j in 0..n {
|
||||
let x1 = a + f64::from(j) * piece;
|
||||
let x2 = if j + 1 == n { b } else { x1 + piece };
|
||||
let mut y = [0.0];
|
||||
OdeIntegrator::default()
|
||||
.integrate(
|
||||
&mut y,
|
||||
x1,
|
||||
x2,
|
||||
OdeOptions {
|
||||
accuracy: accuracy / 2.0,
|
||||
initial_step: (x2 - x1) / 10.0,
|
||||
minimum_step: (x2 - x1) / 1e8,
|
||||
scale: None,
|
||||
quadrature: true,
|
||||
rounding: None,
|
||||
rate: Some(rate),
|
||||
},
|
||||
|t, _, dy| {
|
||||
dy[0] = g(t);
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
sum += y[0];
|
||||
}
|
||||
let error = (sum - exact).abs() / exact;
|
||||
assert!(
|
||||
error <= accuracy,
|
||||
"[{a}, {b}] in {n} pieces at {accuracy:e}: {sum} against {exact}, \
|
||||
relative error {error:e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The final step's last node is the interval's end itself: `-0.1 + (0.3 - -0.1)`
|
||||
/// rounds to `0.30000000000000004`, past the end, where an integrand whose end is a
|
||||
/// knot (a curve's span, a face's iso-u piece) would read the span beyond it. Every
|
||||
/// node lies within `[x1, x2]`, and the step that ends the integration ends at `x2`.
|
||||
#[test]
|
||||
fn the_final_step_ends_at_the_interval_end_exactly() {
|
||||
let (x1, x2) = (-0.1_f64, 0.3_f64);
|
||||
|
||||
+3
@@ -6,8 +6,10 @@
|
||||
|
||||
pub mod api_queries;
|
||||
pub mod brep;
|
||||
pub mod contact;
|
||||
pub mod distance_bound;
|
||||
pub mod edge;
|
||||
pub mod edge_turn;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod exact;
|
||||
pub mod face;
|
||||
@@ -16,6 +18,7 @@ pub mod r#loop;
|
||||
pub mod normal;
|
||||
pub mod poly;
|
||||
pub mod poly_properties;
|
||||
pub mod tangent_edge;
|
||||
pub mod tess;
|
||||
|
||||
pub use face_surface::face_surface_intersect;
|
||||
|
||||
+732
@@ -0,0 +1,732 @@
|
||||
//! Not upstream: whether two bodies meet (share material or touch within a tolerance),
|
||||
//! decided only where it is proved: [`meets`] and its distance, [`boundary_distance`].
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |------|----------|
|
||||
//! | [`meets`], [`boundary_distance`], [`Prepared`] | none (`SmApiBrepRelationship`, ported as [`crate::query::api_queries::brep_relationship`], is a Newton solve: its gap is a point pair, never a lower bound, and its containment counts samples) |
|
||||
//!
|
||||
//! **The bracket.** The distance between the two bodies' boundaries is enclosed in `[lower,
|
||||
//! upper]`:
|
||||
//!
|
||||
//! - `lower`: the least, over pairs of pieces of the two bodies' faces, of the distance
|
||||
//! between their hulls' boxes. The pieces are those of
|
||||
//! [`crate::query::distance_bound`] (each face's NURBS form over its UV box, its Bézier
|
||||
//! patches about one common point, every coefficient's error carried and each box widened
|
||||
//! by it), so no point of either boundary is nearer the other than `lower`. Pairs wait on a
|
||||
//! heap, nearest first; the nearest is split (its larger piece, in four), so `lower`
|
||||
//! rises. A piece whose UV rectangle is proved off its face (no trim curve's box meets it,
|
||||
//! and its centre classifies outside the face) is dropped: a face's hole is no material.
|
||||
//! - `upper` and its witness: a pair of points, one on each body, each on its face's surface
|
||||
//! at parameters the face classifies decisively inside (`face_classify_uv`, the face
|
||||
//! itself: neither on its boundary nor undecided), their distance rounded up. The
|
||||
//! candidates are the centres of the pairs taken from the heap, each moved to the other
|
||||
//! surface's closest point and back ([`crate::query::api_queries::surface_closest_point`],
|
||||
//! a few times), so two surfaces that cross reach a distance of their rounding.
|
||||
//!
|
||||
//! **Meets.** At a tolerance `tol`: `Yes` when `upper <= tol` (with the witness); when
|
||||
//! `lower > tol`, the boundaries do not touch, and the bodies meet only if one encloses the
|
||||
//! other: a point of each boundary is classified in the other body, `Yes` when one is
|
||||
//! inside its material, `No` when both are outside. A classification on the other's boundary
|
||||
//! or not decided is no answer, and neither is a bracket that still straddles `tol` when
|
||||
//! the pairs reach [`MAX_PAIRS`]: [`Verdict::Undecided`], with the bracket, which names
|
||||
//! the size that would decide.
|
||||
//!
|
||||
//! Deviations: none (no upstream counterpart). No recursion: the pairs wait on a heap.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
use crate::geom::certified::{gamma, Verdict};
|
||||
use crate::geom::extent1d::Extent1d;
|
||||
use crate::geom::tol::{brep_zone_tol_3d, SmallTopoType};
|
||||
use crate::geom::vector2d::Point2;
|
||||
use crate::intersect::point_class::PointClassType;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::mass::integrator::properties::region_material;
|
||||
use crate::math::Point3;
|
||||
use crate::query::api_queries::{
|
||||
edge_closest_point, face_bounding_box, face_classify_uv, surface_closest_point,
|
||||
};
|
||||
use crate::query::brep::BrepClassifier;
|
||||
use crate::query::distance_bound::{pieces_about, Piece};
|
||||
use crate::surface::surface::Surface;
|
||||
use crate::topology::{edgeuse::p1 as eu1, loopuse, Brep, FaceId};
|
||||
use crate::{EntityRef, Result, SolidError};
|
||||
|
||||
const OP: &str = "query::contact::meets";
|
||||
|
||||
/// The most piece pairs made for one answer.
|
||||
pub const MAX_PAIRS: usize = 1 << 16;
|
||||
|
||||
/// The times a witness is moved to the other surface's closest point and back.
|
||||
const PROJECTIONS: usize = 6;
|
||||
|
||||
/// How two bodies stand: the verdict at the tolerance asked, and the boundary distance's
|
||||
/// bracket.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Contact {
|
||||
/// `Yes` (they share material or their boundaries come within the tolerance), `No`, or
|
||||
/// `Undecided` (the bracket straddles the tolerance at the work's limit, or a
|
||||
/// classification was not decided).
|
||||
pub verdict: Verdict,
|
||||
/// A certified lower bound of the distance between the boundaries.
|
||||
pub lower: f64,
|
||||
/// An upper bound (the witness's distance, rounded up); infinite without a witness.
|
||||
pub upper: f64,
|
||||
/// Two points, one on each boundary, `upper` apart.
|
||||
pub witness: Option<[Point3; 2]>,
|
||||
}
|
||||
|
||||
/// A face's pieces and what decides whether a piece is on it.
|
||||
struct FacePieces {
|
||||
face: FaceId,
|
||||
surface: Surface,
|
||||
/// Its form's certified distance from its definition, lost from every bound.
|
||||
slack: f64,
|
||||
pieces: Vec<Piece>,
|
||||
/// The boxes of its own UV trim curves `[u0, u1, v0, v1]`; `None` when a trim curve or
|
||||
/// its box is missing (then no piece is dropped).
|
||||
trims: Option<Vec<[f64; 4]>>,
|
||||
}
|
||||
|
||||
/// A body made ready for contact tests: its faces' pieces about a common point.
|
||||
pub struct Prepared {
|
||||
origin: Point3,
|
||||
faces: Vec<FacePieces>,
|
||||
}
|
||||
|
||||
/// A pair of pieces, one of each body, with its lower bound.
|
||||
struct Pair {
|
||||
lower: f64,
|
||||
a: (usize, Piece),
|
||||
b: (usize, Piece),
|
||||
}
|
||||
|
||||
impl PartialEq for Pair {
|
||||
fn eq(&self, o: &Self) -> bool {
|
||||
self.lower.total_cmp(&o.lower) == Ordering::Equal
|
||||
}
|
||||
}
|
||||
impl Eq for Pair {}
|
||||
impl PartialOrd for Pair {
|
||||
fn partial_cmp(&self, o: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(o))
|
||||
}
|
||||
}
|
||||
impl Ord for Pair {
|
||||
// the heap pops the smallest bound first
|
||||
fn cmp(&self, o: &Self) -> Ordering {
|
||||
o.lower.total_cmp(&self.lower)
|
||||
}
|
||||
}
|
||||
|
||||
/// The box of body `b`'s faces, `(lo, hi)`.
|
||||
fn body_box(k: &Kernel, b: &Brep) -> Result<(Point3, Point3)> {
|
||||
let mut lo = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
|
||||
let mut hi = Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
|
||||
for &f in b.face_list() {
|
||||
let e = face_bounding_box(k, b, f, false)?;
|
||||
let (l, h) = (e.min(), e.max());
|
||||
lo = Point3::new(lo.x.min(l.x), lo.y.min(l.y), lo.z.min(l.z));
|
||||
hi = Point3::new(hi.x.max(h.x), hi.y.max(h.y), hi.z.max(h.z));
|
||||
}
|
||||
if !(lo.x <= hi.x && lo.y <= hi.y && lo.z <= hi.z) {
|
||||
return Err(SolidError::invalid_input(OP, "a body without faces"));
|
||||
}
|
||||
Ok((lo, hi))
|
||||
}
|
||||
|
||||
/// The common point two bodies' pieces are made about: the centre of their boxes.
|
||||
fn common_origin(k: &Kernel, a: &Brep, b: &Brep) -> Result<Point3> {
|
||||
let ((al, ah), (bl, bh)) = (body_box(k, a)?, body_box(k, b)?);
|
||||
let mid = |x: f64, y: f64, z: f64, w: f64| 0.5 * (x.min(z) + y.max(w));
|
||||
Ok(Point3::new(
|
||||
mid(al.x, ah.x, bl.x, bh.x),
|
||||
mid(al.y, ah.y, bl.y, bh.y),
|
||||
mid(al.z, ah.z, bl.z, bh.z),
|
||||
))
|
||||
}
|
||||
|
||||
/// The parts each Bézier segment of a trim curve is cut into for its boxes: a rectangle
|
||||
/// that no part's box meets is proved clear of the curve.
|
||||
const TRIM_PARTS: usize = 16;
|
||||
|
||||
/// The boxes, `[u0, u1, v0, v1]`, of a UV trim curve's NURBS form: each Bézier segment cut in
|
||||
/// [`TRIM_PARTS`] by de Casteljau on its homogeneous points, each part in the box of its
|
||||
/// Euclidean points (its hull: positive weights), widened by the subdivision's rounding;
|
||||
/// `None` for a form whose weights are not positive.
|
||||
fn nurb_boxes(c: &crate::nurbs::crv_basic::Curve) -> Result<Option<Vec<[f64; 4]>>> {
|
||||
use crate::nurbs::math::NOW;
|
||||
let mut out = Vec::new();
|
||||
for seg in c.decompose_bez()? {
|
||||
let pts: Vec<[f64; 3]> = seg
|
||||
.cpts()
|
||||
.iter()
|
||||
.map(|q| {
|
||||
let w = if q.w == NOW { 1.0 } else { q.w };
|
||||
[q.x, q.y, w]
|
||||
})
|
||||
.collect();
|
||||
if pts
|
||||
.iter()
|
||||
.any(|q| !(q[2] > 0.0 && q.iter().all(|v| v.is_finite())))
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
let size = pts.iter().flatten().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
let wmin = pts.iter().fold(f64::INFINITY, |m, q| m.min(q[2]));
|
||||
// the right part from t: de Casteljau at t on the homogeneous points
|
||||
let cut = |p: &[[f64; 3]], t: f64| -> (Vec<[f64; 3]>, Vec<[f64; 3]>) {
|
||||
let mut level = p.to_vec();
|
||||
let (mut left, mut right) = (Vec::new(), Vec::new());
|
||||
while let (Some(&f), Some(&l)) = (level.first(), level.last()) {
|
||||
left.push(f);
|
||||
right.push(l);
|
||||
level = level
|
||||
.windows(2)
|
||||
.filter_map(|w| match w {
|
||||
[a, b] => Some([
|
||||
(1.0 - t) * a[0] + t * b[0],
|
||||
(1.0 - t) * a[1] + t * b[1],
|
||||
(1.0 - t) * a[2] + t * b[2],
|
||||
]),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
right.reverse();
|
||||
(left, right)
|
||||
};
|
||||
let degree = pts.len().saturating_sub(1);
|
||||
// TRIM_PARTS levels of convex combinations at most: their rounding, of the size
|
||||
let err = gamma(3 * degree * TRIM_PARTS + 3) * size;
|
||||
let mut rest = pts;
|
||||
for i in 0..TRIM_PARTS {
|
||||
let t = 1.0 / (TRIM_PARTS - i) as f64;
|
||||
let (part, next) = if i + 1 == TRIM_PARTS {
|
||||
(rest.clone(), Vec::new())
|
||||
} else {
|
||||
cut(&rest, t)
|
||||
};
|
||||
rest = next;
|
||||
let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
|
||||
for q in &part {
|
||||
for (k, v) in [q[0] / q[2], q[1] / q[2]].into_iter().enumerate() {
|
||||
if let (Some(l), Some(h)) = (lo.get_mut(k), hi.get_mut(k)) {
|
||||
*l = l.min(v);
|
||||
*h = h.max(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
// a quotient of coefficients within err: within err (1 + |x|) / (w - err)
|
||||
let widen = err * (1.0 + size / wmin) / (wmin - err).max(f64::MIN_POSITIVE)
|
||||
+ gamma(2) * size / wmin;
|
||||
out.push([lo[0] - widen, hi[0] + widen, lo[1] - widen, hi[1] + widen]);
|
||||
}
|
||||
}
|
||||
Ok(Some(out))
|
||||
}
|
||||
|
||||
/// The boxes of face `f`'s own UV trim curves (each its NURBS form's parts,
|
||||
/// [`nurb_boxes`], or the curve's whole box where it has none); `None` where a trim curve or
|
||||
/// its box is missing.
|
||||
fn trim_boxes(k: &Kernel, b: &Brep, f: FaceId) -> Result<Option<Vec<[f64; 4]>>> {
|
||||
use crate::curve::curve::CurveKind;
|
||||
let Some(fu) = b.face(f)?.faceuse else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
for lu in eu1::faceuse_loopuses(b, fu, false, None)? {
|
||||
for eu in loopuse::edgeuses(b, lu, None, None)? {
|
||||
// its own, or its mate's on the face's other side (the same face's parameters)
|
||||
let Some(c) = eu1::uv_trim_curve(b, eu)?.0 else {
|
||||
return Ok(None);
|
||||
};
|
||||
let nurb = match c.kind() {
|
||||
CurveKind::BSpline(x) => x.gw_nurb(),
|
||||
CurveKind::Ellipse(x) => x.base().gw_nurb(),
|
||||
CurveKind::Hyperbola(x) => x.base().gw_nurb(),
|
||||
CurveKind::Parabola(x) => x.base().gw_nurb(),
|
||||
CurveKind::Line(x) => x.base().gw_nurb(),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(n) = nurb {
|
||||
if let Some(boxes) = nurb_boxes(n)? {
|
||||
out.extend(boxes);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let interval: Extent1d = c.natural_interval();
|
||||
let boxes = c.calculate_bounding_box(
|
||||
k,
|
||||
&interval,
|
||||
crate::nurbs::b_spline_curve::BoundingBoxRequest {
|
||||
normal: true,
|
||||
..Default::default()
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
let Some(e) = boxes.normal else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (l, h) = (e.min(), e.max());
|
||||
// the box's own rounding, a few ulps of its size
|
||||
let r = 16.0 * f64::EPSILON * (l.x.abs() + h.x.abs() + l.y.abs() + h.y.abs());
|
||||
out.push([l.x - r, h.x + r, l.y - r, h.y + r]);
|
||||
}
|
||||
}
|
||||
Ok(Some(out))
|
||||
}
|
||||
|
||||
impl Prepared {
|
||||
/// Body `b`'s faces' pieces about `origin`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `InvalidInput` for a body without faces or a face without a surface; those of the
|
||||
/// pieces ([`crate::query::distance_bound`]) and the trim curves' boxes.
|
||||
pub fn new(k: &Kernel, b: &Brep, origin: Point3) -> Result<Self> {
|
||||
let mut faces = Vec::new();
|
||||
for &f in b.face_list() {
|
||||
let face = b.face(f)?;
|
||||
let surface = face
|
||||
.surface
|
||||
.as_ref()
|
||||
.ok_or_else(|| SolidError::invalid_input(OP, "a face without a surface"))?
|
||||
.clone();
|
||||
let (pieces, slack) = pieces_about(k, &surface, Some(&face.uv_domain), origin)?;
|
||||
let trims = trim_boxes(k, b, f)?;
|
||||
faces.push(FacePieces {
|
||||
face: f,
|
||||
surface,
|
||||
slack,
|
||||
pieces,
|
||||
trims,
|
||||
});
|
||||
}
|
||||
if faces.is_empty() {
|
||||
return Err(SolidError::invalid_input(OP, "a body without faces"));
|
||||
}
|
||||
Ok(Self { origin, faces })
|
||||
}
|
||||
|
||||
/// Body `b`'s faces' pieces about the centre of its own box: the preparation of one
|
||||
/// body for tests against several ([`meets_prepared`]).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`Prepared::new`] and of the faces' boxes.
|
||||
pub fn about(k: &Kernel, b: &Brep) -> Result<Self> {
|
||||
Self::new(k, b, common_origin(k, b, b)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// The certified lower bound of the distance between two pieces about the same point: the
|
||||
/// larger of the distance of their widened boxes and, along a unit direction `axis` (the
|
||||
/// witness's, when there is one), the gap between their hulls' extents
|
||||
/// ([`Piece::extent_along`]), each rounded down. The axis steers which bound is tight, never
|
||||
/// whether either holds: any direction separates the hulls by at most their distance.
|
||||
fn pair_lower(a: &Piece, b: &Piece, axis: Option<[f64; 3]>) -> Result<f64> {
|
||||
let by_box = box_lower(a, b)?;
|
||||
let Some(u) = axis else {
|
||||
return Ok(by_box);
|
||||
};
|
||||
let len = (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]).sqrt() * (1.0 + gamma(4));
|
||||
if !(len > 0.0 && len.is_finite()) {
|
||||
return Ok(by_box);
|
||||
}
|
||||
let ((a0, a1), (b0, b1)) = (a.extent_along(u)?, b.extent_along(u)?);
|
||||
let gap = (b0 - a1).max(a0 - b1).max(0.0);
|
||||
// (the difference rounded down by gamma_1 of its terms, then over the length)
|
||||
let gap = (gap - gamma(1) * (a0.abs() + a1.abs() + b0.abs() + b1.abs())).max(0.0);
|
||||
let by_axis = gap / len * (1.0 - gamma(2));
|
||||
Ok(by_box.max(by_axis))
|
||||
}
|
||||
|
||||
/// The distance of two pieces' widened boxes, rounded down.
|
||||
fn box_lower(a: &Piece, b: &Piece) -> Result<f64> {
|
||||
let ((al, ah), (bl, bh)) = (a.widened_box()?, b.widened_box()?);
|
||||
let mut sum = 0.0_f64;
|
||||
for i in 0..3 {
|
||||
let (Some(&x0), Some(&x1), Some(&y0), Some(&y1)) =
|
||||
(al.get(i), ah.get(i), bl.get(i), bh.get(i))
|
||||
else {
|
||||
return Err(SolidError::sm_err(OP));
|
||||
};
|
||||
let gap = (y0 - x1).max(x0 - y1).max(0.0);
|
||||
// (the difference rounded up by at most gamma_1 of its terms)
|
||||
let gap = (gap - gamma(1) * (x0.abs() + x1.abs() + y0.abs() + y1.abs())).max(0.0);
|
||||
sum += gap * gap;
|
||||
}
|
||||
Ok(sum.sqrt() * (1.0 - gamma(5)))
|
||||
}
|
||||
|
||||
/// Where a piece stands against its face: `Some(true)` proved on it and decisively inside
|
||||
/// at its centre, `Some(false)` proved off it (no trim curve's box meets its rectangle and
|
||||
/// its centre is outside), `None` not decided.
|
||||
fn piece_on_face(k: &Kernel, b: &mut Brep, fp: &FacePieces, p: &Piece) -> Result<Option<bool>> {
|
||||
let [u0, u1, v0, v1] = p.uv;
|
||||
let crosses = match &fp.trims {
|
||||
None => true,
|
||||
Some(boxes) => boxes
|
||||
.iter()
|
||||
.any(|t| !(t[1] < u0 || t[0] > u1 || t[3] < v0 || t[2] > v1)),
|
||||
};
|
||||
let centre = Point2::new(0.5 * (u0 + u1), 0.5 * (v0 + v1));
|
||||
let class = match face_classify_uv(k, b, fp.face, centre) {
|
||||
Ok(c) => c,
|
||||
// a classification that fails decides nothing
|
||||
Err(e) if e.is_cancelled() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
Ok(match class {
|
||||
PointClassType::Unknown if !crosses => Some(false),
|
||||
PointClassType::Face => Some(true),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// A point of body `b` for face `fp` at `uv`: the surface's point when the face classifies
|
||||
/// `uv` decisively inside it; when it classifies it on the face's boundary (within its
|
||||
/// tolerance, either side), the nearest point of the face's edges to the surface's point,
|
||||
/// which is on the body whichever side `uv` was; `None` outside or not decided.
|
||||
fn on_face(k: &Kernel, b: &mut Brep, fp: &FacePieces, uv: Point2) -> Result<Option<Point3>> {
|
||||
match face_classify_uv(k, b, fp.face, uv) {
|
||||
Ok(PointClassType::Face) => Ok(Some(fp.surface.evaluate_point(k, uv)?)),
|
||||
Ok(PointClassType::Edge | PointClassType::Vertex) => {
|
||||
let x = fp.surface.evaluate_point(k, uv)?;
|
||||
nearest_edge_point(k, b, fp.face, x)
|
||||
}
|
||||
Err(e) if e.is_cancelled() => Err(e),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// The nearest point to `x` of face `f`'s edges (each solved on its edge, so on the body);
|
||||
/// `None` when no edge's solve succeeds.
|
||||
fn nearest_edge_point(k: &Kernel, b: &Brep, f: FaceId, x: Point3) -> Result<Option<Point3>> {
|
||||
let Some(fu) = b.face(f)?.faceuse else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut best: Option<(f64, Point3)> = None;
|
||||
for lu in eu1::faceuse_loopuses(b, fu, false, None)? {
|
||||
for eu in loopuse::edgeuses(b, lu, None, None)? {
|
||||
let Some(e) = b.edgeuse(eu)?.edge else {
|
||||
continue;
|
||||
};
|
||||
let Ok(c) = edge_closest_point(k, b, e, x) else {
|
||||
continue;
|
||||
};
|
||||
let d = c.closest.point.distance(x);
|
||||
if best.is_none_or(|(m, _)| d < m) {
|
||||
best = Some((d, c.closest.point));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(best.map(|(_, p)| p))
|
||||
}
|
||||
|
||||
/// The distance of two points rounded up: `|p - q|` within `gamma_5` of the terms' size.
|
||||
fn distance_up(p: Point3, q: Point3) -> f64 {
|
||||
let size = p.x.abs() + p.y.abs() + p.z.abs() + q.x.abs() + q.y.abs() + q.z.abs();
|
||||
p.distance(q) * (1.0 + gamma(5)) + gamma(5) * size
|
||||
}
|
||||
|
||||
/// The closest point of `fp`'s surface to `x`, kept when its face classifies it decisively
|
||||
/// inside; `None` for a solve that fails or a point off the face.
|
||||
fn closest_on(
|
||||
k: &Kernel,
|
||||
b: &mut Brep,
|
||||
fp: &FacePieces,
|
||||
x: Point3,
|
||||
) -> Result<Option<(Point3, Point2)>> {
|
||||
let Ok(c) = surface_closest_point(k, &fp.surface, x) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(&uv) = c.uvs.first() else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(on_face(k, b, fp, uv)?.map(|p| (p, uv)))
|
||||
}
|
||||
|
||||
/// The bracket between `a` and `b` (as [`Prepared`]), refined until `stop` says done or the
|
||||
/// pairs reach [`MAX_PAIRS`]: `stop(lower, upper)`.
|
||||
fn bracket(
|
||||
k: &Kernel,
|
||||
a: &mut Brep,
|
||||
pa: &Prepared,
|
||||
b: &mut Brep,
|
||||
pb: &Prepared,
|
||||
seed: f64,
|
||||
stop: &dyn Fn(f64, f64) -> bool,
|
||||
) -> Result<(f64, f64, Option<[Point3; 2]>, bool)> {
|
||||
let mut heap = BinaryHeap::new();
|
||||
let mut made = 0_usize;
|
||||
for (ia, fa) in pa.faces.iter().enumerate() {
|
||||
for (ib, fb) in pb.faces.iter().enumerate() {
|
||||
for x in &fa.pieces {
|
||||
for y in &fb.pieces {
|
||||
let lower = (pair_lower(x, y, None)? - fa.slack - fb.slack).max(0.0);
|
||||
heap.push(Pair {
|
||||
lower,
|
||||
a: (ia, x.clone()),
|
||||
b: (ib, y.clone()),
|
||||
});
|
||||
made += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (mut upper, mut witness) = (f64::INFINITY, None);
|
||||
// witnesses first from every face pair whose bound is within `seed`, nearest first (the
|
||||
// pairs tie closely where two bodies lie along each other; each is visited once here)
|
||||
let mut first: Vec<&Pair> = heap.iter().filter(|p| p.lower <= seed).collect();
|
||||
first.sort_by(|x, y| x.lower.total_cmp(&y.lower));
|
||||
for pair in first {
|
||||
let (Some(fa), Some(fb)) = (pa.faces.get(pair.a.0), pb.faces.get(pair.b.0)) else {
|
||||
return Err(SolidError::sm_err(OP));
|
||||
};
|
||||
if pair.lower < upper {
|
||||
witness_from(
|
||||
k,
|
||||
(&mut *a, fa, &pair.a.1),
|
||||
(&mut *b, fb, &pair.b.1),
|
||||
&mut upper,
|
||||
&mut witness,
|
||||
)?;
|
||||
}
|
||||
if stop(0.0, upper) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
loop {
|
||||
k.check_cancelled(OP)?;
|
||||
let Some(pair) = heap.pop() else {
|
||||
// every piece proved off its face: no boundary is there to bound
|
||||
return Ok((f64::INFINITY, upper, witness, true));
|
||||
};
|
||||
let lower = pair.lower.min(upper);
|
||||
if stop(lower, upper) {
|
||||
return Ok((lower, upper, witness, true));
|
||||
}
|
||||
if made >= MAX_PAIRS {
|
||||
return Ok((lower, upper, witness, false));
|
||||
}
|
||||
let (ia, x) = &pair.a;
|
||||
let (ib, y) = &pair.b;
|
||||
let (Some(fa), Some(fb)) = (pa.faces.get(*ia), pb.faces.get(*ib)) else {
|
||||
return Err(SolidError::sm_err(OP));
|
||||
};
|
||||
// a witness from the pair's centres (none can be nearer than the pair's bound, so a
|
||||
// pair past the witness so far is skipped)
|
||||
if pair.lower < upper {
|
||||
witness_from(
|
||||
k,
|
||||
(&mut *a, fa, x),
|
||||
(&mut *b, fb, y),
|
||||
&mut upper,
|
||||
&mut witness,
|
||||
)?;
|
||||
}
|
||||
// split the larger piece
|
||||
let size = |p: &Piece| -> Result<f64> {
|
||||
let (l, h) = p.widened_box()?;
|
||||
Ok((0..3)
|
||||
.map(|i| h.get(i).copied().unwrap_or(0.0) - l.get(i).copied().unwrap_or(0.0))
|
||||
.fold(0.0_f64, f64::max))
|
||||
};
|
||||
let split_a = size(x)? >= size(y)?;
|
||||
let (from, keep) = if split_a { (x, y) } else { (y, x) };
|
||||
// the witness's direction, for the hulls' extents along it
|
||||
let axis = witness.map(|[p, q]| [q.x - p.x, q.y - p.y, q.z - p.z]);
|
||||
for q in from.quarters()? {
|
||||
let (fp, brep) = if split_a {
|
||||
(fa, &mut *a)
|
||||
} else {
|
||||
(fb, &mut *b)
|
||||
};
|
||||
if piece_on_face(k, brep, fp, &q)? == Some(false) {
|
||||
continue;
|
||||
}
|
||||
let (qa, qb) = if split_a { (&q, keep) } else { (keep, &q) };
|
||||
let lower = (pair_lower(qa, qb, axis)? - fa.slack - fb.slack).max(0.0);
|
||||
heap.push(Pair {
|
||||
lower,
|
||||
a: (*ia, qa.clone()),
|
||||
b: (*ib, qb.clone()),
|
||||
});
|
||||
made += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A witness from a pair's centres, each moved to the other's surface's closest point and
|
||||
/// back ([`PROJECTIONS`] times), kept where both points are decisively on their faces and
|
||||
/// nearer than `upper`.
|
||||
fn witness_from(
|
||||
k: &Kernel,
|
||||
(a, fa, x): (&mut Brep, &FacePieces, &Piece),
|
||||
(b, fb, y): (&mut Brep, &FacePieces, &Piece),
|
||||
upper: &mut f64,
|
||||
witness: &mut Option<[Point3; 2]>,
|
||||
) -> Result<()> {
|
||||
let (_, ua) = x.centre();
|
||||
let (_, ub) = y.centre();
|
||||
let mut pa_pt = on_face(k, a, fa, Point2::new(ua[0], ua[1]))?;
|
||||
let mut pb_pt = on_face(k, b, fb, Point2::new(ub[0], ub[1]))?;
|
||||
for _ in 0..PROJECTIONS {
|
||||
if let Some(p) = pa_pt {
|
||||
if let Some((q, _)) = closest_on(k, b, fb, p)? {
|
||||
pb_pt = Some(q);
|
||||
}
|
||||
}
|
||||
if let Some(q) = pb_pt {
|
||||
if let Some((p, _)) = closest_on(k, a, fa, q)? {
|
||||
pa_pt = Some(p);
|
||||
}
|
||||
}
|
||||
if let (Some(p), Some(q)) = (pa_pt, pb_pt) {
|
||||
let d = distance_up(p, q) + fa.slack + fb.slack;
|
||||
if d < *upper {
|
||||
*upper = d;
|
||||
*witness = Some([p, q]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A point of `inner`'s boundary classified in `outer`: `Some(true)` inside its material,
|
||||
/// `Some(false)` outside it, `None` when none of `inner`'s points tried (its pieces'
|
||||
/// centres, where their faces hold them: [`on_face`]) is proved more than twice the
|
||||
/// classification's tolerance from `outer`'s boundary
|
||||
/// ([`crate::query::distance_bound::face_distance_lower_bound`] over `outer`'s faces), or
|
||||
/// its classification is not decided.
|
||||
fn point_in(
|
||||
k: &Kernel,
|
||||
inner: &mut Brep,
|
||||
p_inner: &Prepared,
|
||||
outer: &Brep,
|
||||
) -> Result<Option<bool>> {
|
||||
use crate::query::distance_bound::face_distance_lower_bound;
|
||||
let tol = brep_zone_tol_3d(k, outer, SmallTopoType::NormalSize);
|
||||
let clear = 2.0 * tol;
|
||||
for fp in &p_inner.faces {
|
||||
for piece in &fp.pieces {
|
||||
let (_, uv) = piece.centre();
|
||||
let Some(p) = on_face(k, inner, fp, Point2::new(uv[0], uv[1]))? else {
|
||||
continue;
|
||||
};
|
||||
let mut far = true;
|
||||
for &g in outer.face_list() {
|
||||
if face_distance_lower_bound(k, outer, g, p, clear)? <= clear {
|
||||
far = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !far {
|
||||
continue;
|
||||
}
|
||||
let mut classifier = BrepClassifier::of_brep(outer);
|
||||
let pc = match classifier.point_3d_classify(k, p, tol, true) {
|
||||
Ok(pc) => pc,
|
||||
Err(e) if e.is_cancelled() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
return Ok(match (pc.point_class(), pc.object()) {
|
||||
(PointClassType::Region, Some(EntityRef::Region(r))) => {
|
||||
Some(region_material(classifier.brep(), r)?)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Whether `a` and `b` meet at `tol` (module docs), `a` prepared once for several `b`s.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `InvalidInput` for a tolerance not finite and positive, or a body without faces; those
|
||||
/// of the pieces, the classifications' cancellation and the closest-point solves' setup.
|
||||
pub fn meets_prepared(
|
||||
k: &Kernel,
|
||||
a: &mut Brep,
|
||||
pa: &Prepared,
|
||||
b: &mut Brep,
|
||||
tol: f64,
|
||||
) -> Result<Contact> {
|
||||
if !(tol.is_finite() && tol > 0.0) {
|
||||
return Err(SolidError::invalid_input(
|
||||
OP,
|
||||
"the tolerance is finite and positive",
|
||||
));
|
||||
}
|
||||
let pb = Prepared::new(k, b, pa.origin)?;
|
||||
let stop = |lower: f64, upper: f64| upper <= tol || lower > tol;
|
||||
let (lower, upper, witness, done) = bracket(k, a, pa, b, &pb, tol, &stop)?;
|
||||
let verdict = if upper <= tol {
|
||||
Verdict::Yes
|
||||
} else if !done {
|
||||
Verdict::Undecided
|
||||
} else {
|
||||
// the boundaries are more than tol apart: they meet only if one encloses the other
|
||||
match (point_in(k, b, &pb, a)?, point_in(k, a, pa, b)?) {
|
||||
(Some(true), _) | (_, Some(true)) => Verdict::Yes,
|
||||
(Some(false), Some(false)) => Verdict::No,
|
||||
_ => Verdict::Undecided,
|
||||
}
|
||||
};
|
||||
Ok(Contact {
|
||||
verdict,
|
||||
lower,
|
||||
upper,
|
||||
witness,
|
||||
})
|
||||
}
|
||||
|
||||
/// [`meets_prepared`] for one pair: `a` prepared about the centre of both bodies' boxes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`meets_prepared`].
|
||||
pub fn meets(k: &Kernel, a: &mut Brep, b: &mut Brep, tol: f64) -> Result<Contact> {
|
||||
let origin = common_origin(k, a, b)?;
|
||||
let pa = Prepared::new(k, a, origin)?;
|
||||
meets_prepared(k, a, &pa, b, tol)
|
||||
}
|
||||
|
||||
/// The bracket of the distance between `a`'s and `b`'s boundaries, refined until it is at
|
||||
/// most `width` wide (relative to the upper end, or absolute below 1) or the pairs reach
|
||||
/// [`MAX_PAIRS`]; the verdict is `Yes` when it is that narrow, `Undecided` when the work
|
||||
/// ran out first. The bracket always holds the distance; it can stay wider than `width`
|
||||
/// where a nearest point lies on a face's trim, since a piece straddling the trim carries
|
||||
/// surface off the face and is cut finer only along the trim.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `InvalidInput` for a width not finite and positive; those of [`meets`].
|
||||
pub fn boundary_distance(k: &Kernel, a: &mut Brep, b: &mut Brep, width: f64) -> Result<Contact> {
|
||||
if !(width.is_finite() && width > 0.0) {
|
||||
return Err(SolidError::invalid_input(
|
||||
OP,
|
||||
"the width is finite and positive",
|
||||
));
|
||||
}
|
||||
let origin = common_origin(k, a, b)?;
|
||||
let pa = Prepared::new(k, a, origin)?;
|
||||
let pb = Prepared::new(k, b, origin)?;
|
||||
let stop = |lower: f64, upper: f64| upper - lower <= width * upper.max(1.0);
|
||||
let (lower, upper, witness, done) = bracket(k, a, &pa, b, &pb, f64::INFINITY, &stop)?;
|
||||
Ok(Contact {
|
||||
verdict: if done {
|
||||
Verdict::Yes
|
||||
} else {
|
||||
Verdict::Undecided
|
||||
},
|
||||
lower,
|
||||
upper,
|
||||
witness,
|
||||
})
|
||||
}
|
||||
+181
-72
@@ -44,15 +44,18 @@ pub const MAX_PATCHES: usize = 4096;
|
||||
/// A piece of a face's surface: its homogeneous coordinates about the point, as Bernstein
|
||||
/// polynomials on the unit square, and the absolute error each coefficient may carry.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Piece {
|
||||
pub(crate) struct Piece {
|
||||
/// `X W - p W`, `Y W - p W`, `Z W - p W`, `W`.
|
||||
h: [Bernstein2; 4],
|
||||
pub(crate) h: [Bernstein2; 4],
|
||||
/// The bound on every coefficient's error.
|
||||
err: f64,
|
||||
pub(crate) err: f64,
|
||||
/// The certified lower bound of the piece's distance from the point.
|
||||
lower: f64,
|
||||
pub(crate) lower: f64,
|
||||
/// Its subdivision depth.
|
||||
depth: usize,
|
||||
pub(crate) depth: usize,
|
||||
/// Its rectangle in the surface's NURB parameters, `[u0, u1, v0, v1]` (to the rounding
|
||||
/// of the splits that made it).
|
||||
pub(crate) uv: [f64; 4],
|
||||
}
|
||||
|
||||
impl PartialEq for Piece {
|
||||
@@ -74,21 +77,62 @@ impl Ord for Piece {
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
/// The piece with its bound, the coefficients `h` within `err` each.
|
||||
fn new(h: [Bernstein2; 4], err: f64, depth: usize) -> Result<Self> {
|
||||
/// The piece with its bound, the coefficients `h` within `err` each, over `uv`.
|
||||
fn new(h: [Bernstein2; 4], err: f64, depth: usize, uv: [f64; 4]) -> Result<Self> {
|
||||
let lower = hull_bound(&h, err)?;
|
||||
Ok(Self {
|
||||
h,
|
||||
err,
|
||||
lower,
|
||||
depth,
|
||||
uv,
|
||||
})
|
||||
}
|
||||
|
||||
/// The box of its Euclidean control points about the point, each coordinate widened
|
||||
/// by the error a quotient of coefficients within `err` can carry: every point of the
|
||||
/// piece lies in it (the hull, with positive weights).
|
||||
pub(crate) fn widened_box(&self) -> Result<([f64; 3], [f64; 3])> {
|
||||
let (lo, hi, widen) = euclid_box(&self.h, self.err)?;
|
||||
Ok((lo.map(|l| l - widen), hi.map(|u| u + widen)))
|
||||
}
|
||||
|
||||
/// Its centre's Euclidean point about the point, and the centre's parameters.
|
||||
pub(crate) fn centre(&self) -> ([f64; 3], [f64; 2]) {
|
||||
let [x, y, z, w] = &self.h;
|
||||
let wc = w.eval(0.5, 0.5);
|
||||
let [u0, u1, v0, v1] = self.uv;
|
||||
(
|
||||
[
|
||||
x.eval(0.5, 0.5) / wc,
|
||||
y.eval(0.5, 0.5) / wc,
|
||||
z.eval(0.5, 0.5) / wc,
|
||||
],
|
||||
[0.5 * (u0 + u1), 0.5 * (v0 + v1)],
|
||||
)
|
||||
}
|
||||
|
||||
/// The certified range of `x . u` over the piece, for a direction `u`: the least and
|
||||
/// largest of its Euclidean control points' (its hull), each moved out by the widening
|
||||
/// along `u` and the products' rounding.
|
||||
pub(crate) fn extent_along(&self, u: [f64; 3]) -> Result<(f64, f64)> {
|
||||
let (lo, hi, widen) = euclid_box(&self.h, self.err)?;
|
||||
let size = lo.iter().chain(&hi).fold(0.0_f64, |m, v| m.max(v.abs())) + widen;
|
||||
let norm1 = u[0].abs() + u[1].abs() + u[2].abs();
|
||||
let (mut least, mut most) = (f64::INFINITY, f64::NEG_INFINITY);
|
||||
for e in euclid_points(&self.h) {
|
||||
let d = e[0] * u[0] + e[1] * u[1] + e[2] * u[2];
|
||||
least = least.min(d);
|
||||
most = most.max(d);
|
||||
}
|
||||
let slack = widen * norm1 + gamma(3) * size * norm1;
|
||||
Ok((least - slack, most + slack))
|
||||
}
|
||||
|
||||
/// Its four quarters: one de Casteljau level each way, which adds the rounding of a
|
||||
/// convex combination per level (`gamma` of twice the degree plus two) of the largest
|
||||
/// coefficient.
|
||||
fn quarters(&self) -> Result<[Self; 4]> {
|
||||
pub(crate) fn quarters(&self) -> Result<[Self; 4]> {
|
||||
let largest = self
|
||||
.h
|
||||
.iter()
|
||||
@@ -108,14 +152,23 @@ impl Piece {
|
||||
split(&self.h[2]),
|
||||
split(&self.h[3]),
|
||||
];
|
||||
let [u0, u1, v0, v1] = self.uv;
|
||||
let (um, vm) = (0.5 * (u0 + u1), 0.5 * (v0 + v1));
|
||||
let rects = [
|
||||
[u0, um, v0, vm],
|
||||
[u0, um, vm, v1],
|
||||
[um, u1, v0, vm],
|
||||
[um, u1, vm, v1],
|
||||
];
|
||||
let mut out = Vec::new();
|
||||
for i in 0..4 {
|
||||
for (i, uv) in rects.into_iter().enumerate() {
|
||||
let pick =
|
||||
|a: &[Bernstein2; 4]| a.get(i).cloned().ok_or_else(|| SolidError::sm_err(OP));
|
||||
out.push(Self::new(
|
||||
[pick(&x)?, pick(&y)?, pick(&z)?, pick(&w)?],
|
||||
err,
|
||||
self.depth + 1,
|
||||
uv,
|
||||
)?);
|
||||
}
|
||||
out.try_into().map_err(|_| SolidError::sm_err(OP))
|
||||
@@ -130,42 +183,15 @@ impl Piece {
|
||||
/// least the least P . u, and |X| >= X . u / |u|), which, unlike the box, tightens with the
|
||||
/// square of the piece's size on a piece tilted to the axes.
|
||||
fn hull_bound(h: &[Bernstein2; 4], err: f64) -> Result<f64> {
|
||||
let [x, y, z, w] = h;
|
||||
let (xs, ys, zs, ws) = (
|
||||
x.coefficients(),
|
||||
y.coefficients(),
|
||||
z.coefficients(),
|
||||
w.coefficients(),
|
||||
);
|
||||
let w_min = ws.iter().copied().fold(f64::INFINITY, f64::min);
|
||||
if !above(w_min - err, 0.0) {
|
||||
return Err(SolidError::Unsupported {
|
||||
operation: OP,
|
||||
reason: "a patch's weights are not proved positive",
|
||||
});
|
||||
}
|
||||
let mut points = Vec::new();
|
||||
let mut lo = [f64::INFINITY; 3];
|
||||
let mut hi = [f64::NEG_INFINITY; 3];
|
||||
let mut size = 0.0_f64;
|
||||
let (lo, hi, widen) = euclid_box(h, err)?;
|
||||
let points = euclid_points(h);
|
||||
let size = lo.iter().chain(&hi).fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
let mut sum_points = [0.0_f64; 3];
|
||||
for (((&a, &b), &c), &wi) in xs.iter().zip(ys).zip(zs).zip(ws) {
|
||||
let e = [a / wi, b / wi, c / wi];
|
||||
for (k, v) in e.into_iter().enumerate() {
|
||||
size = size.max(v.abs());
|
||||
if let (Some(l), Some(u), Some(t)) =
|
||||
(lo.get_mut(k), hi.get_mut(k), sum_points.get_mut(k))
|
||||
{
|
||||
*l = l.min(v);
|
||||
*u = u.max(v);
|
||||
*t += v;
|
||||
}
|
||||
for e in &points {
|
||||
for (t, v) in sum_points.iter_mut().zip(e) {
|
||||
*t += v;
|
||||
}
|
||||
points.push(e);
|
||||
}
|
||||
// |(v + dv) / (w + dw) - v / w| <= err (1 + |v / w|) / (w - err), and the quotient's
|
||||
// own rounding
|
||||
let widen = err * (1.0 + size) / (w_min - err) + gamma(2) * size;
|
||||
let mut sum = 0.0_f64;
|
||||
for (l, u) in lo.into_iter().zip(hi) {
|
||||
let out = (l - widen).max(-(u + widen)).max(0.0);
|
||||
@@ -202,6 +228,61 @@ fn hull_bound(h: &[Bernstein2; 4], err: f64) -> Result<f64> {
|
||||
Ok(bound.max(0.0))
|
||||
}
|
||||
|
||||
/// The Euclidean control points of the homogeneous coefficients `h`.
|
||||
fn euclid_points(h: &[Bernstein2; 4]) -> Vec<[f64; 3]> {
|
||||
let [x, y, z, w] = h;
|
||||
x.coefficients()
|
||||
.iter()
|
||||
.zip(y.coefficients())
|
||||
.zip(z.coefficients())
|
||||
.zip(w.coefficients())
|
||||
.map(|(((&a, &b), &c), &wi)| [a / wi, b / wi, c / wi])
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The box of the Euclidean control points of `h`, and how far each coordinate is to be
|
||||
/// widened for coefficients within `err` (and the quotients' own rounding): `(lo, hi,
|
||||
/// widen)`.
|
||||
fn euclid_box(h: &[Bernstein2; 4], err: f64) -> Result<([f64; 3], [f64; 3], f64)> {
|
||||
let [x, y, z, w] = h;
|
||||
let (xs, ys, zs, ws) = (
|
||||
x.coefficients(),
|
||||
y.coefficients(),
|
||||
z.coefficients(),
|
||||
w.coefficients(),
|
||||
);
|
||||
let w_min = ws.iter().copied().fold(f64::INFINITY, f64::min);
|
||||
if !above(w_min - err, 0.0) {
|
||||
return Err(SolidError::Unsupported {
|
||||
operation: OP,
|
||||
reason: "a patch's weights are not proved positive",
|
||||
});
|
||||
}
|
||||
let mut lo = [f64::INFINITY; 3];
|
||||
let mut hi = [f64::NEG_INFINITY; 3];
|
||||
let mut size = 0.0_f64;
|
||||
for (((&a, &b), &c), &wi) in xs.iter().zip(ys).zip(zs).zip(ws) {
|
||||
let e = [a / wi, b / wi, c / wi];
|
||||
for (k, v) in e.into_iter().enumerate() {
|
||||
size = size.max(v.abs());
|
||||
if let (Some(l), Some(u)) = (lo.get_mut(k), hi.get_mut(k)) {
|
||||
*l = l.min(v);
|
||||
*u = u.max(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
// |(v + dv) / (w + dw) - v / w| <= err (1 + |v / w|) / (w - err), and the quotient's
|
||||
// own rounding
|
||||
let widen = err * (1.0 + size) / (w_min - err) + gamma(2) * size;
|
||||
if !(widen.is_finite() && lo.iter().chain(&hi).all(|v| v.is_finite())) {
|
||||
return Err(SolidError::Unsupported {
|
||||
operation: OP,
|
||||
reason: "a patch's box is not finite",
|
||||
});
|
||||
}
|
||||
Ok((lo, hi, widen))
|
||||
}
|
||||
|
||||
/// A certified lower bound of the distance from `p` to face `f` of `b`: no point of the face
|
||||
/// is nearer to `p`. The face is bounded by its surface over the face's UV box (its trims
|
||||
/// lie inside it): [`surface_distance_lower_bound`] on them.
|
||||
@@ -320,6 +401,51 @@ pub fn surface_distance_lower_bound(
|
||||
"the bound asked for is finite and not negative",
|
||||
));
|
||||
}
|
||||
let (pieces, slack) = pieces_about(k, surf, uv_box, p)?;
|
||||
let want_net = want + slack;
|
||||
let mut made = pieces.len();
|
||||
let mut heap: BinaryHeap<Piece> = pieces.into_iter().collect();
|
||||
let off = |lower: f64| (lower - slack).max(0.0);
|
||||
loop {
|
||||
// (the box is inside the patches' range, so some patch overlaps it)
|
||||
let nearest = heap.pop().ok_or_else(|| SolidError::sm_err(OP))?;
|
||||
if nearest.lower >= want_net || made >= MAX_PATCHES {
|
||||
return Ok(off(nearest.lower));
|
||||
}
|
||||
// the piece's centre is a point of the net over the box: nearer than the target, no
|
||||
// bound reaches it
|
||||
let (centre, _) = nearest.centre();
|
||||
if (centre[0] * centre[0] + centre[1] * centre[1] + centre[2] * centre[2]).sqrt() < want_net
|
||||
{
|
||||
return Ok(off(nearest.lower));
|
||||
}
|
||||
for q in nearest.quarters()? {
|
||||
heap.push(q);
|
||||
}
|
||||
made += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/// The pieces of `surf` over `uv_box` (as [`surface_distance_lower_bound`] takes it) about
|
||||
/// `p`: its NURBS form's Bézier patches restricted to the box, translated by `-p`, each
|
||||
/// with its coefficients' error (the form's against its definition, the extraction's and
|
||||
/// the translation's, module docs); and the slack every distance from them is to lose (a
|
||||
/// net's certified bound from its definition, 0 for a surface that evaluates on its net).
|
||||
/// The one construction of the face pieces that [`surface_distance_lower_bound`] and the
|
||||
/// contact test between bodies ([`crate::query::contact`]) bound distances with.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// As [`surface_distance_lower_bound`]'s, but for `want`.
|
||||
pub(crate) fn pieces_about(
|
||||
k: &Kernel,
|
||||
surf: &Surface,
|
||||
uv_box: Option<&Extent2d>,
|
||||
p: Point3,
|
||||
) -> Result<(Vec<Piece>, f64)> {
|
||||
if !(p.x.is_finite() && p.y.is_finite() && p.z.is_finite()) {
|
||||
return Err(SolidError::invalid_input(OP, "point not finite"));
|
||||
}
|
||||
if !measurable(k, surf)? {
|
||||
return Err(SolidError::Unsupported {
|
||||
operation: OP,
|
||||
@@ -424,9 +550,7 @@ pub fn surface_distance_lower_bound(
|
||||
));
|
||||
}
|
||||
}
|
||||
let want_net = want + slack;
|
||||
let mut heap = BinaryHeap::new();
|
||||
let mut made = 0_usize;
|
||||
let mut pieces = Vec::new();
|
||||
for patch in patches.iter().flatten() {
|
||||
let (pu, pv) = patch.degrees();
|
||||
let (uk, vk) = (patch.u_knots(), patch.v_knots());
|
||||
@@ -496,33 +620,15 @@ pub fn surface_distance_lower_bound(
|
||||
} else {
|
||||
h
|
||||
};
|
||||
heap.push(Piece::new(h, err, 0)?);
|
||||
made += 1;
|
||||
}
|
||||
let off = |lower: f64| (lower - slack).max(0.0);
|
||||
loop {
|
||||
// (the box is inside the patches' range, so some patch overlaps it)
|
||||
let nearest = heap.pop().ok_or_else(|| SolidError::sm_err(OP))?;
|
||||
if nearest.lower >= want_net || made >= MAX_PATCHES {
|
||||
return Ok(off(nearest.lower));
|
||||
}
|
||||
// the piece's centre is a point of the net over the box: nearer than the target, no
|
||||
// bound reaches it
|
||||
let [x, y, z, w] = &nearest.h;
|
||||
let wc = w.eval(0.5, 0.5);
|
||||
let centre = (
|
||||
x.eval(0.5, 0.5) / wc,
|
||||
y.eval(0.5, 0.5) / wc,
|
||||
z.eval(0.5, 0.5) / wc,
|
||||
);
|
||||
if (centre.0 * centre.0 + centre.1 * centre.1 + centre.2 * centre.2).sqrt() < want_net {
|
||||
return Ok(off(nearest.lower));
|
||||
}
|
||||
for q in nearest.quarters()? {
|
||||
heap.push(q);
|
||||
}
|
||||
made += 4;
|
||||
let uv = [
|
||||
u0 + a0 * (u1 - u0),
|
||||
u0 + a1 * (u1 - u0),
|
||||
v0 + b0 * (v1 - v0),
|
||||
v0 + b1 * (v1 - v0),
|
||||
];
|
||||
pieces.push(Piece::new(h, err, 0, uv)?);
|
||||
}
|
||||
Ok((pieces, slack))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -890,7 +996,10 @@ mod tests {
|
||||
.unwrap()
|
||||
});
|
||||
let exact = h.each_ref().map(exact_quarters);
|
||||
let quarters = Piece::new(h, 0.0, 0).unwrap().quarters().unwrap();
|
||||
let quarters = Piece::new(h, 0.0, 0, [0.0, 1.0, 0.0, 1.0])
|
||||
.unwrap()
|
||||
.quarters()
|
||||
.unwrap();
|
||||
for (q, quarter) in quarters.iter().enumerate() {
|
||||
for (axis, component) in quarter.h.iter().enumerate() {
|
||||
for (rounded, reference) in
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
//! At a vertex where edges meet: the direction an edge leaves it in, how far one edge turns
|
||||
//! from running straight on into another there, and whether an edge is convex there. The
|
||||
//! measures a tangent-chain walk takes at each vertex.
|
||||
//!
|
||||
//! Upstream: none (not in SMLib).
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |---------------|----------|
|
||||
//! | [`leaving`] | (none) |
|
||||
//! | [`turn_at`] | (none) |
|
||||
//! | [`convex_at`] | (none) |
|
||||
//!
|
||||
//! Deviations: no upstream counterpart.
|
||||
|
||||
use crate::error::{Result, SolidError};
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::Vec3;
|
||||
use crate::topology::edgeuse::p1 as eu1;
|
||||
use crate::topology::edgeuse::p3::{evaluate_binormal, BinormalOptions};
|
||||
use crate::topology::{edge, Brep, EdgeId, VertexId};
|
||||
|
||||
const OP: &str = "query::edge_turn";
|
||||
|
||||
fn invalid(reason: &'static str) -> SolidError {
|
||||
SolidError::invalid_input(OP, reason)
|
||||
}
|
||||
|
||||
/// Edge `e`'s parameter at its end `v`, and whether `v` is its start; `None` for a closed
|
||||
/// edge (both ends `v`).
|
||||
fn end_at(b: &Brep, e: EdgeId, v: VertexId) -> Result<Option<(f64, bool)>> {
|
||||
let start = edge::start_vertex(b, e)?;
|
||||
let end = edge::end_vertex(b, e)?;
|
||||
if start == end {
|
||||
return Ok(None);
|
||||
}
|
||||
let ivl = edge::interval(b, e)?;
|
||||
if start == Some(v) {
|
||||
Ok(Some((ivl.min(), true)))
|
||||
} else if end == Some(v) {
|
||||
Ok(Some((ivl.max(), false)))
|
||||
} else {
|
||||
Err(invalid("a vertex that is not an end of the edge"))
|
||||
}
|
||||
}
|
||||
|
||||
/// The unit direction edge `e` leaves its end `v` in: its curve's tangent there, turned to
|
||||
/// point away from `v`. `None` for a closed edge, an edge without curve, or a tangent of no
|
||||
/// length.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`SolidError::InvalidInput`] for a vertex that is not an end of `e`; those of the
|
||||
/// evaluation.
|
||||
pub fn leaving(k: &Kernel, b: &Brep, e: EdgeId, v: VertexId) -> Result<Option<Vec3>> {
|
||||
let Some((t, at_start)) = end_at(b, e, v)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(curve) = edge::curve(b, e)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
// from inside the edge: from the right at its start, from the left at its end
|
||||
let d = curve.evaluate(k, t, 1, !at_start, true)?;
|
||||
let Some(&tangent) = d.get(1) else {
|
||||
return Err(invalid("a curve's evaluation without its tangent"));
|
||||
};
|
||||
let Ok(unit) = tangent.unitize() else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(if at_start { unit } else { unit * -1.0 }))
|
||||
}
|
||||
|
||||
/// How far (radians, 0 to pi) edge `f` turns at their shared end `v` from running straight on
|
||||
/// from edge `e`: the angle between the direction `e` arrives at `v` in and the one `f` leaves
|
||||
/// it in. 0 where `f` continues `e` tangentially. `None` where either direction is undefined
|
||||
/// ([`leaving`]).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`leaving`].
|
||||
pub fn turn_at(k: &Kernel, b: &Brep, e: EdgeId, f: EdgeId, v: VertexId) -> Result<Option<f64>> {
|
||||
let (Some(arrives), Some(leaves)) = (leaving(k, b, e, v)?, leaving(k, b, f, v)?) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let arrives = arrives * -1.0;
|
||||
Ok(Some(
|
||||
arrives.cross(leaves).length().atan2(arrives.dot(leaves)),
|
||||
))
|
||||
}
|
||||
|
||||
/// Whether edge `e` is convex at its end `v` (its faces meet at less than 180 degrees inside
|
||||
/// the material there): the direction from the edge into its second face, square to it,
|
||||
/// points along its first face's inward normal (the face use normals of the edge use on the
|
||||
/// material's side, its face use's region not void, and of its radial partner, which point
|
||||
/// into the material). `None` for
|
||||
/// an edge not between two face uses, a closed edge, an edge with no edge use on a material
|
||||
/// region's side, or faces whose normal and binormal there are square to each other (tangent
|
||||
/// faces: no convexity).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`SolidError::InvalidInput`] for a vertex that is not an end of `e`; those of the
|
||||
/// evaluations.
|
||||
pub fn convex_at(k: &Kernel, b: &mut Brep, e: EdgeId, v: VertexId) -> Result<Option<bool>> {
|
||||
if !edge::is_manifold(b, e)? || eu1::edge_is_lamina(b, e)? {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some((t, _)) = end_at(b, e, v)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
// the edge use on the material's side: its face use and its radial partner's bound the
|
||||
// material, and their normals point into it (the primary edge use may be on the void's,
|
||||
// whose normals point out)
|
||||
let mut eu0 = None;
|
||||
for eu in edge::edgeuses(b, e, None, None)? {
|
||||
let Some(fu) = eu1::faceuse(b, eu)? else {
|
||||
continue;
|
||||
};
|
||||
if let Some(r) = eu1::faceuse_region(b, fu)? {
|
||||
if b.region(r)?.is_void() == 0 {
|
||||
eu0 = Some(eu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(eu0) = eu0 else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(eu_r) = eu1::radial(b, eu0)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let options = BinormalOptions::default();
|
||||
let first = evaluate_binormal(k, b, eu0, t, false, &options)?;
|
||||
let second = evaluate_binormal(k, b, eu_r, t, false, &options)?;
|
||||
let side = second.binormal.dot(first.face_normal);
|
||||
if side == 0.0 || !side.is_finite() {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(side > 0.0))
|
||||
}
|
||||
+1044
File diff suppressed because it is too large
Load Diff
+1
-6
@@ -1508,12 +1508,7 @@ impl Cone {
|
||||
hv: usize,
|
||||
flags: EvaluateFlags,
|
||||
) -> Result<Vec<Vec3>> {
|
||||
let rows = self.evaluate_rows(k, uv, hu, hv, flags)?;
|
||||
let mut d = Vec::with_capacity((hu + 1) * (hv + 1));
|
||||
for row in rows {
|
||||
d.extend(row);
|
||||
}
|
||||
Ok(d)
|
||||
Ok(self.evaluate_rows(k, uv, hu, hv, flags)?.concat())
|
||||
}
|
||||
|
||||
/// [`Self::evaluate`] as `D[i][j]` rows: exactly `hu + 1` rows of `hv + 1` entries
|
||||
|
||||
+122
-7
@@ -597,6 +597,13 @@ fn joint_removed(out: &Net, side: BoxSide, joint: f64) -> Result<Net> {
|
||||
/// curve: every homogeneous control point in a plane normal to the axis (a parallel) or
|
||||
/// through it (a meridian, a tube), within its rounding; a ruling is straight by its
|
||||
/// degree. Refused as not exact otherwise.
|
||||
///
|
||||
/// The limit, `8 gamma_32` times the point's own size (`|c|` plus the frame's size times
|
||||
/// `|w|`) plus the plane's height times `|w|`, has the shape of a rounding bound but its
|
||||
/// factor is tuned, not derived: `8` and `32` count no operations of `extract_iso_crv`, the
|
||||
/// dot product or the height. It is held to that size by a test (refused at twice it,
|
||||
/// accepted at half), and measured well inside it (largest off / limit 0.0123 over the
|
||||
/// offset and extension goldens); deriving it is open.
|
||||
fn certify_iso(
|
||||
s: &BSplineSurface,
|
||||
side: BoxSide,
|
||||
@@ -614,12 +621,6 @@ fn certify_iso(
|
||||
(middle.x, Direction::V)
|
||||
};
|
||||
let curve = extract_iso_crv(net, t, dir)?;
|
||||
// the iso is interpolated between the net's rows, so its points are known to the rounding
|
||||
// at the size of those rows, not at their own: a short arc's middle iso between rulings
|
||||
// extended 500 mm each way
|
||||
let rows = net.cpts().iter().flatten().fold(0.0_f64, |m, p| {
|
||||
m.max(p.x.abs()).max(p.y.abs()).max(p.z.abs())
|
||||
});
|
||||
let points: Vec<(Vec3, f64, f64)> = curve
|
||||
.cpts()
|
||||
.iter()
|
||||
@@ -627,7 +628,7 @@ fn certify_iso(
|
||||
let w = if p.w == NOW { 1.0 } else { p.w };
|
||||
let h = Vec3::new(p.x, p.y, p.z);
|
||||
// X W from the origin, and the size of its terms
|
||||
let mag = rows + f.size * w.abs();
|
||||
let mag = p.x.abs().max(p.y.abs()).max(p.z.abs()) + f.size * w.abs();
|
||||
(h - f.origin * w, w, mag)
|
||||
})
|
||||
.collect();
|
||||
@@ -921,3 +922,117 @@ fn slot_mut(cp: &mut [Vec<CPoint>], is_u: bool, a: usize, r: usize) -> Result<&m
|
||||
.and_then(|x| x.get_mut(j))
|
||||
.ok_or_else(|| SolidError::sm_err(OP))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing
|
||||
)]
|
||||
|
||||
use super::*;
|
||||
use crate::surface::cone::p1::Cone;
|
||||
|
||||
/// The limit `certify_iso` holds a parallel's homogeneous control point `c` to: the
|
||||
/// rounding at the point's own size and the frame's, `8 gamma_32 (|c| + size |w| +
|
||||
/// |height| |w|)` (written out here so that a change to the limit's size fails).
|
||||
fn limit(c: &CPoint, size: f64, height: f64) -> f64 {
|
||||
let w = if c.w == NOW { 1.0 } else { c.w };
|
||||
let mag = c.x.abs().max(c.y.abs()).max(c.z.abs()) + size * w.abs();
|
||||
8.0 * gamma(32) * (mag + height.abs() * w.abs())
|
||||
}
|
||||
|
||||
/// A cylinder of radius 1 about z, 60 degrees round, 1000 long: rows far larger than
|
||||
/// the turn's own points, so a limit taken at the rows' size would be about 1000 times
|
||||
/// the one at the iso's.
|
||||
fn long_cylinder() -> BSplineSurface {
|
||||
let k = Kernel::default();
|
||||
let s = Surface::from(Cone::new_cylinder_bounded(
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
1.0,
|
||||
0.0,
|
||||
60.0,
|
||||
1000.0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
crate::blend::fillet_section::station::as_b_spline(&k, &s)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// The net with every control point of index `i` along the iso across `side` moved
|
||||
/// `delta` along the axis (its homogeneous `delta w`): the iso's point `i` moves by
|
||||
/// `delta`, the rest stay.
|
||||
fn moved(form: &BSplineSurface, side: BoxSide, i: usize, delta: f64) -> BSplineSurface {
|
||||
let net = form.gw_nurb().unwrap();
|
||||
let mut cpts = net.cpts();
|
||||
for (a, row) in cpts.iter_mut().enumerate() {
|
||||
for (b, c) in row.iter_mut().enumerate() {
|
||||
let along = if side.is_u() { a } else { b };
|
||||
if along == i {
|
||||
let w = if c.w == NOW { 1.0 } else { c.w };
|
||||
c.z += delta * w;
|
||||
}
|
||||
}
|
||||
}
|
||||
let (p, q) = net.degrees();
|
||||
BSplineSurface::from_gw_nurb(
|
||||
&Net::new(cpts, p, q, net.u_knots().to_vec(), net.v_knots().to_vec()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// `certify_iso` refuses a parallel whose control point is off its plane by twice the
|
||||
/// limit at the point's own size and passes one off by half of it: the limit's size is
|
||||
/// pinned, and a limit taken at the net's rows (1000 here) would pass both.
|
||||
#[test]
|
||||
fn the_turn_certificate_holds_the_iso_to_its_own_size() {
|
||||
let form = long_cylinder();
|
||||
let frame = Frame {
|
||||
origin: Vec3::new(0.0, 0.0, 0.0),
|
||||
axis: Vec3::new(0.0, 0.0, 1.0),
|
||||
apex: None,
|
||||
size: 1.0,
|
||||
quarter: 0.0,
|
||||
tube_left: 0.0,
|
||||
};
|
||||
let dom = knot_box(&form).unwrap();
|
||||
let mut tested = 0;
|
||||
for side in BoxSide::ALL {
|
||||
let middle = side.point(&dom, 0.5);
|
||||
let (t, dir) = if side.is_u() {
|
||||
(middle.y, Direction::U)
|
||||
} else {
|
||||
(middle.x, Direction::V)
|
||||
};
|
||||
let iso = extract_iso_crv(form.gw_nurb().unwrap(), t, dir).unwrap();
|
||||
let pts = iso.cpts();
|
||||
// the parallel: every point at one height (the other side's iso is a ruling)
|
||||
let z = |c: &CPoint| c.z / if c.w == NOW { 1.0 } else { c.w };
|
||||
if pts.len() < 3 || pts.iter().any(|c| (z(c) - z(&pts[0])).abs() > 1e-9) {
|
||||
continue;
|
||||
}
|
||||
assert!(certify_iso(&form, side, middle, &frame, Iso::Parallel).is_ok());
|
||||
let lim = limit(&pts[1], frame.size, z(&pts[0]));
|
||||
let w1 = if pts[1].w == NOW { 1.0 } else { pts[1].w };
|
||||
let over = moved(&form, side, 1, 2.0 * lim / w1);
|
||||
assert!(
|
||||
certify_iso(&over, side, middle, &frame, Iso::Parallel).is_err(),
|
||||
"{side:?}: twice the limit passes"
|
||||
);
|
||||
let under = moved(&form, side, 1, 0.5 * lim / w1);
|
||||
assert!(
|
||||
certify_iso(&under, side, middle, &frame, Iso::Parallel).is_ok(),
|
||||
"{side:?}: half the limit is refused"
|
||||
);
|
||||
tested += 1;
|
||||
}
|
||||
assert!(tested > 0, "no side's iso is the parallel");
|
||||
}
|
||||
}
|
||||
|
||||
+334
-108
@@ -11,7 +11,6 @@
|
||||
use crate::error::{Result, SolidError};
|
||||
use crate::geom::axis2_placement::Axis2Placement;
|
||||
use crate::geom::extent2d::Extent2d;
|
||||
use crate::geom::vector2d::Point2;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::{Vec2, Vec3, PI};
|
||||
use crate::nurbs::b_spline_curve::BSplineCurve;
|
||||
@@ -69,42 +68,6 @@ pub enum Implicit {
|
||||
},
|
||||
}
|
||||
|
||||
impl Implicit {
|
||||
/// How far `x` is from this surface.
|
||||
#[must_use]
|
||||
pub fn distance(&self, x: Vec3) -> f64 {
|
||||
// the axial and radial parts of `x` about an axis through `o`
|
||||
let about = |o: Vec3, axis: Vec3| {
|
||||
let v = x - o;
|
||||
let h = v.dot(axis);
|
||||
(h, (v - axis * h).length())
|
||||
};
|
||||
match *self {
|
||||
Self::Plane { origin, normal } => (x - origin).dot(normal).abs(),
|
||||
Self::Cone {
|
||||
origin,
|
||||
axis,
|
||||
radius,
|
||||
tan_angle,
|
||||
} => {
|
||||
let (h, rho) = about(origin, axis);
|
||||
(rho - (radius + tan_angle * h)).abs() / (1.0 + tan_angle * tan_angle).sqrt()
|
||||
}
|
||||
Self::Sphere { centre, radius } => ((x - centre).length() - radius).abs(),
|
||||
Self::Torus {
|
||||
centre,
|
||||
axis,
|
||||
major,
|
||||
minor,
|
||||
} => {
|
||||
let (h, rho) = about(centre, axis);
|
||||
let (a, b) = (rho - major, h);
|
||||
((a * a + b * b).sqrt() - minor).abs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A polynomial in Bernstein form over a patch, degrees `(p, q)`, `c[i (q + 1) + j]`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct BPoly {
|
||||
@@ -343,7 +306,7 @@ fn bezier_on<'a>(
|
||||
f.c.iter().all(within)
|
||||
}
|
||||
|
||||
pub(crate) const OP_EXACT: &str = "surface::implicit::check_exact_net";
|
||||
const OP_EXACT: &str = "surface::implicit::check_exact_net";
|
||||
|
||||
/// A quadric `x.A.x + 2 b.x + c = 0` as `(A, b, c)`.
|
||||
pub type QuadricEquation = ([[f64; 3]; 3], [f64; 3], f64);
|
||||
@@ -439,46 +402,6 @@ pub fn check_exact_net(k: &Kernel, net: &Surface, check: &ExactNetCheck) -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The number of intervals [`net_miss`] samples a net at, each way.
|
||||
const MISS_SAMPLES: u32 = 16;
|
||||
|
||||
/// How far `net` strays from the class's `surface`, sampled over its domain. A guard, not a
|
||||
/// certificate: [`check_exact_net`] is the proof, and a net that passes only this keeps no
|
||||
/// definition. It tells a net that the class's rounding leaves off the surface from one
|
||||
/// built wrongly.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of the evaluations.
|
||||
pub fn net_miss(k: &Kernel, net: &Surface, surface: &ClassSurface) -> Result<f64> {
|
||||
let dom = net.natural_uv_domain_with_nurb(k)?;
|
||||
let (lo, hi) = (dom.min(), dom.max());
|
||||
let mut worst = 0.0_f64;
|
||||
for i in 0..=MISS_SAMPLES {
|
||||
for j in 0..=MISS_SAMPLES {
|
||||
let (fu, fv) = (
|
||||
f64::from(i) / f64::from(MISS_SAMPLES),
|
||||
f64::from(j) / f64::from(MISS_SAMPLES),
|
||||
);
|
||||
let uv = Point2::new(lo.x + (hi.x - lo.x) * fu, lo.y + (hi.y - lo.y) * fv);
|
||||
let x = net.evaluate_point(k, uv)?;
|
||||
let miss = match surface {
|
||||
ClassSurface::Implicit(a) => a.distance(x),
|
||||
ClassSurface::Canal {
|
||||
spine, map, radius, ..
|
||||
} => {
|
||||
let w = if map.along_v { uv.y } else { uv.x };
|
||||
let ivl = spine.natural_interval();
|
||||
let s = (map.a + map.b * w).max(ivl.min()).min(ivl.max());
|
||||
((x - spine.evaluate_point(s)?).length() - *radius).abs()
|
||||
}
|
||||
};
|
||||
worst = worst.max(miss);
|
||||
}
|
||||
}
|
||||
Ok(worst)
|
||||
}
|
||||
|
||||
/// A Bezier segment of a rational curve, Bernstein polynomials of degree `(p, 0)`: the
|
||||
/// homogeneous `(Y - O V)` components and the weight `V`.
|
||||
type CurveSegment = ([BPoly; 3], BPoly);
|
||||
@@ -1200,12 +1123,37 @@ impl AnalyticSupport {
|
||||
// a surface of revolution: the one the kernel recognises in the form, which must be
|
||||
// this one, take the form as its NURB and evaluate as the form at the form's own
|
||||
// parameters
|
||||
let not_form = || {
|
||||
SolidError::invalid_input(
|
||||
OP_BUILD,
|
||||
"a support's analytic surface is not its NURBS form's parametrisation",
|
||||
)
|
||||
};
|
||||
let surface = self.on_form(k, &form)?;
|
||||
if !evaluates_as(k, &surface, &form)? {
|
||||
return Err(not_form());
|
||||
}
|
||||
Ok(surface)
|
||||
}
|
||||
|
||||
/// The largest ratio, over the form's Greville points, of this support's analytic
|
||||
/// surface's distance from `form` to the rounding [`evaluates_as`] derives there (#3185):
|
||||
/// at most 1 where [`Self::build`]'s parametrisation test passes. The surface is the one
|
||||
/// `build` makes (the kernel's recognition of `form`, carrying this support's numbers);
|
||||
/// `lies_on` and the placement's axes are not checked here. `None` for a plane, or a
|
||||
/// surface the bound does not cover.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `InvalidInput` where the kernel recognises no surface of this kind in `form`; those of
|
||||
/// the recognisers, the conversions and the evaluations.
|
||||
pub fn parametrisation_margin(&self, k: &Kernel, form: &BSplineSurface) -> Result<Option<f64>> {
|
||||
if matches!(self, Self::Plane { .. }) {
|
||||
return Ok(None);
|
||||
}
|
||||
let surface = self.on_form(k, form)?;
|
||||
evaluation_margin(k, &surface, form)
|
||||
}
|
||||
|
||||
/// The surface of revolution of this support's kind the kernel recognises in `form`,
|
||||
/// carrying this support's own numbers and `form` as its NURB ([`Self::build`] after its
|
||||
/// checks of the point set, before the parametrisation's).
|
||||
fn on_form(&self, k: &Kernel, form: &BSplineSurface) -> Result<Surface> {
|
||||
let form = form.copy();
|
||||
let mut surface = recognise(k, self.kind(), &form)?.ok_or_else(not_form)?;
|
||||
// the recognised surface must be of this support's kind; the numbers recognition
|
||||
// recomputed are not compared with the support's, because it recomputes them from
|
||||
@@ -1255,13 +1203,18 @@ impl AnalyticSupport {
|
||||
}
|
||||
_ => return Err(not_form()),
|
||||
}
|
||||
if !evaluates_as(k, &surface, &form)? {
|
||||
return Err(not_form());
|
||||
}
|
||||
Ok(surface)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`AnalyticSupport::build`]'s refusal of a surface that is not its form's parametrisation.
|
||||
fn not_form() -> SolidError {
|
||||
SolidError::invalid_input(
|
||||
OP_BUILD,
|
||||
"a support's analytic surface is not its NURBS form's parametrisation",
|
||||
)
|
||||
}
|
||||
|
||||
/// A cylinder's or cone's recognised parametrisation carried by the support's own
|
||||
/// `placement`: its heights are measured along the axis from the placement's origin, so the
|
||||
/// recognised heights move by how far along the axis the recognised origin lies from it.
|
||||
@@ -1336,41 +1289,314 @@ fn recognise(k: &Kernel, kind: SupportKind, form: &BSplineSurface) -> Result<Opt
|
||||
}
|
||||
|
||||
/// Whether `surface` evaluates as `form` at the form's Greville parameters (each control
|
||||
/// point's), within the rounding of the two evaluations: `gamma_K` times the form's size,
|
||||
/// `K = 16 (p + q + 2)` for the rational evaluation of degrees `(p, q)` and the analytic
|
||||
/// one's conversion of the parameters and its trigonometry, a few ulps per operation.
|
||||
/// point's): at each, the analytic surface's point and the form's agree within the bound
|
||||
/// [`Rounding::at`] derives for that point, term by term below. The analytic side is the
|
||||
/// **STEP** evaluation of the converted parameters ([`Surface::convert_uv_from_nurbs_to_step`]
|
||||
/// and [`Surface::evaluate_step_point`]), which a cone, sphere, torus and surface of
|
||||
/// revolution compute from their placement, radii, semi-angle and generator.
|
||||
/// [`Surface::evaluate_point`] would not do: a sphere, a torus and a surface of revolution
|
||||
/// take it from their NURBS base, and that base is the `form` the caller has just installed,
|
||||
/// so it would compare the form with itself and read nothing of what the caller imposed
|
||||
/// (#2588).
|
||||
///
|
||||
/// The analytic side is the **STEP** evaluation of the converted parameters
|
||||
/// ([`Surface::convert_uv_from_nurbs_to_step`] and [`Surface::evaluate_step_point`]),
|
||||
/// which a cone, sphere, torus and surface of revolution compute from their placement,
|
||||
/// radii, semi-angle and generator. [`Surface::evaluate_point`] would not do: a sphere, a
|
||||
/// torus and a surface of revolution take it from their NURBS base, and that base is the
|
||||
/// `form` the caller has just installed, so it would compare the form with itself and read
|
||||
/// nothing of what the caller imposed (#2588; the cone already converted and evaluated
|
||||
/// analytically, which is why only the other kinds went unchecked).
|
||||
/// What the bound forgives and what it tests (#3185): it forgives the rounding of both
|
||||
/// evaluations and of the two parameter conversions, nothing else. It does not forgive the
|
||||
/// label's frame turned about the axis against the converter's, its axis, origin or radii,
|
||||
/// a form whose parameter is not the analytic surface's (a reparametrised arc), nor the
|
||||
/// form's distance from the analytic surface: those are what the comparison proves.
|
||||
///
|
||||
/// `u` is the unit roundoff, `gamma_n = n u / (1 - n u)`. Stated assumption: the libm
|
||||
/// `sin`, `cos`, `tan` and `atan2` are within one ulp (relative `2 u`).
|
||||
///
|
||||
/// 1. **The form** (`Surface::eval`, Cox-de Boor then two blends): each basis value is a
|
||||
/// nonnegative combination over `p` levels of at most 6 operations (two knot differences,
|
||||
/// a sum, a division, a product and a sum), so relative `gamma_6p`; the blends sum
|
||||
/// `q + 1` then `p + 1` products. With positive weights the homogeneous point's error is
|
||||
/// at most `gamma_(7 (p + q) + 4) W size` and the weight's `gamma_(7 (p + q) + 4) W`, so
|
||||
/// the Euclidean point's, after the division, `E_form = (2 gamma_(7 (p + q) + 4) + u)
|
||||
/// size` (`gamma_(7 (p + q) + 4) size` when polynomial), `size` the net's largest point.
|
||||
/// 2. **The STEP point**, `O + m (cos u X + sin u Y) + h Z`, `m` and `h` the generator's
|
||||
/// radial and axial parts (the torus `R + r cos v`, `r sin v`; the sphere `r cos v`,
|
||||
/// `r sin v`; the cone `r0 + v tan a`, `v`). An angle `x` in degrees goes to radians in
|
||||
/// two operations of the rounded `PI`: `gamma_3 |x|`; with one ulp of the cosine and sine,
|
||||
/// each is off by `dc(x) = 2 u + gamma_3 |x|`. Per coordinate (unit axes) the error is at
|
||||
/// most `gamma_3 (|O| + M + H) + M (gamma_2 + 2 dc(u)) + dm + dh`, `M`, `H` the sizes of
|
||||
/// `m`, `h` and `dm`, `dh` their errors: the torus `gamma_3 (R + r) + 2 r dc(v)`, the
|
||||
/// sphere `gamma_1 r + 2 r dc(v)`, the cone `gamma_2 (|r0| + |v t|) + |v| dt` (`dt =
|
||||
/// (1 + t^2) gamma_3 |a| + 2 u |t|` for the tangent of the half angle `a`); a general
|
||||
/// surface of revolution rotates its generator's point `G` (a B-spline: item 1's bound of
|
||||
/// its own net, `E_gen`) about the axis, `gamma_8 (|O| + 3 D) + 2 D dc(u) + E_gen`, `D`
|
||||
/// the net's reach from `O`. `E_step` is `sqrt 3` times the coordinate bound.
|
||||
/// 3. **The generator's parameter**: a circle (torus, sphere) inverts its NURBS point by
|
||||
/// `atan2` about its centre `c`: at its radius `r` the error is that point's (`E_form`)
|
||||
/// plus `gamma_4 (size + |c|)` and `gamma_4 r |v|` of the angle's own rounding; a line
|
||||
/// (cone) maps it affinely in 6 operations, `gamma_6 2 (|v0| + |v1|)` along the axis and
|
||||
/// its slope, times `1 + |t|`; a B-spline generator's parameter is its NURBS's (none).
|
||||
/// 4. **The sweep's parameter**: the polar converter evaluates its circle's NURBS at the
|
||||
/// parameter (item 1's bound of that NURBS's own net, `E_polar`) and inverts the point by
|
||||
/// `atan2` about the circle's centre `c` in its frame: at the circle's radius `rc` the
|
||||
/// angle is off by `(E_polar + gamma_4 (rc + 2 |c|)) / rc`, plus `gamma_4 |s|` of the
|
||||
/// angle's own rounding and the degrees' period (`gamma_1 2 pi`), times the STEP point's
|
||||
/// distance from that axis (the lever). The converter's ends are its domain's, which
|
||||
/// recognition takes from the form's own end meridians (#3199), so they are the inverted
|
||||
/// ends to the same rounding.
|
||||
///
|
||||
/// A surface of another kind, or a general surface of revolution whose generator is not a
|
||||
/// B-spline, has no bound here and is not proved.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of the parameter conversion and the evaluations and of the form's knots.
|
||||
fn evaluates_as(k: &Kernel, surface: &Surface, form: &BSplineSurface) -> Result<bool> {
|
||||
Ok(evaluation_margin(k, surface, form)?.is_some_and(|m| m <= 1.0))
|
||||
}
|
||||
|
||||
/// [`evaluates_as`]'s largest ratio of distance to bound over the Greville points (infinite
|
||||
/// for a distance that is not a number); `None` for a surface the bound does not cover.
|
||||
fn evaluation_margin(k: &Kernel, surface: &Surface, form: &BSplineSurface) -> Result<Option<f64>> {
|
||||
let (net, p, q) = euclidean_net(form)?;
|
||||
let size = net.iter().flatten().fold(0.0_f64, |a, x| a.max(x.length()));
|
||||
let tol = gamma(16 * (p + q + 2)) * size;
|
||||
let Some(r) = Rounding::of(surface, form.is_rational(), p, q, size, &net)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut worst = 0.0_f64;
|
||||
for u in greville(form, true)? {
|
||||
for v in greville(form, false)? {
|
||||
let uv = Vec2::new(u, v);
|
||||
let step_uv = surface.convert_uv_from_nurbs_to_step(k, uv)?;
|
||||
let off =
|
||||
(surface.evaluate_step_point(k, step_uv)? - form.evaluate_point(uv)?).length();
|
||||
if !matches!(
|
||||
off.partial_cmp(&tol),
|
||||
Some(core::cmp::Ordering::Less | core::cmp::Ordering::Equal)
|
||||
) {
|
||||
return Ok(false);
|
||||
}
|
||||
let s = surface.evaluate_step_point(k, step_uv)?;
|
||||
let f = form.evaluate_point(uv)?;
|
||||
let ratio = (s - f).length() / r.at(step_uv, s, f);
|
||||
worst = if ratio.is_nan() {
|
||||
f64::INFINITY
|
||||
} else {
|
||||
worst.max(ratio)
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
Ok(Some(worst))
|
||||
}
|
||||
|
||||
/// A surface of revolution's generator, for [`Rounding`]'s item 2 and 3.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Generator {
|
||||
/// `R + r cos v`, `r sin v` about the circle of centre `c`.
|
||||
Torus { major: f64, minor: f64, c: f64 },
|
||||
/// `r cos v`, `r sin v` about the circle of centre `c`.
|
||||
Sphere { radius: f64, c: f64 },
|
||||
/// `r0 + v t`, `v` (the half angle `a` in radians), its STEP `v` over `[v0, v1]`.
|
||||
Cone {
|
||||
r0: f64,
|
||||
t: f64,
|
||||
a: f64,
|
||||
v0: f64,
|
||||
v1: f64,
|
||||
},
|
||||
/// A B-spline generator, its own evaluation's bound.
|
||||
Spline { e_gen: f64 },
|
||||
}
|
||||
|
||||
/// The pieces of [`evaluates_as`]'s bound that do not depend on the point.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Rounding {
|
||||
e_form: f64,
|
||||
origin: Vec3,
|
||||
/// The net's reach from the origin.
|
||||
reach: f64,
|
||||
generator: Generator,
|
||||
/// The sweep converter's circle: centre, axes.
|
||||
polar: (Vec3, Vec3, Vec3, f64, f64),
|
||||
}
|
||||
|
||||
/// `|v|`'s largest coordinate.
|
||||
fn max_coord(v: Vec3) -> f64 {
|
||||
v.x.abs().max(v.y.abs()).max(v.z.abs())
|
||||
}
|
||||
|
||||
/// A rational (or polynomial) evaluation's bound of degrees summing to `n` over points of
|
||||
/// size `size` ([`evaluates_as`] item 1).
|
||||
fn eval_bound(rational: bool, n: usize, size: f64) -> f64 {
|
||||
let g = gamma(7 * n + 4);
|
||||
if rational {
|
||||
(2.0 * g + crate::nurbs::math::UNIT) * size
|
||||
} else {
|
||||
g * size
|
||||
}
|
||||
}
|
||||
|
||||
impl Rounding {
|
||||
/// The bound's data for `surface` (a cone, sphere, torus or surface of revolution with a
|
||||
/// B-spline generator) on its form of degrees `(p, q)`, net `net`; `None` for any other.
|
||||
fn of(
|
||||
surface: &Surface,
|
||||
rational: bool,
|
||||
p: usize,
|
||||
q: usize,
|
||||
size: f64,
|
||||
net: &[Vec<Vec3>],
|
||||
) -> Result<Option<Self>> {
|
||||
use crate::curve::curve::CurveKind;
|
||||
let e_form = eval_bound(rational, p + q, size);
|
||||
let circle_centre = |rev: &crate::surface::surf_of_revolution::SurfOfRevolution| match rev
|
||||
.gen_curve
|
||||
.as_ref()
|
||||
.map(crate::curve::curve::Curve::kind)
|
||||
{
|
||||
Some(CurveKind::Ellipse(e)) => Some(e.position.origin().length()),
|
||||
_ => None,
|
||||
};
|
||||
let (rev, generator) = match surface.kind() {
|
||||
SurfaceKind::Torus(t) => {
|
||||
let Some(c) = circle_centre(&t.base) else {
|
||||
return Ok(None);
|
||||
};
|
||||
(
|
||||
&t.base,
|
||||
Generator::Torus {
|
||||
major: t.major_radius,
|
||||
minor: t.minor_radius,
|
||||
c,
|
||||
},
|
||||
)
|
||||
}
|
||||
SurfaceKind::Sphere(sp) => {
|
||||
let Some(c) = circle_centre(&sp.base) else {
|
||||
return Ok(None);
|
||||
};
|
||||
(
|
||||
&sp.base,
|
||||
Generator::Sphere {
|
||||
radius: sp.radius,
|
||||
c,
|
||||
},
|
||||
)
|
||||
}
|
||||
SurfaceKind::Cone(c) => {
|
||||
let a = c.semi_angle_deg * PI / 180.0;
|
||||
let d = c.rev.anal_uv_domain;
|
||||
(
|
||||
&c.rev,
|
||||
Generator::Cone {
|
||||
r0: c.base_radius,
|
||||
t: c.tan_semi_angle(),
|
||||
a,
|
||||
v0: d.v_min(),
|
||||
v1: d.v_max(),
|
||||
},
|
||||
)
|
||||
}
|
||||
SurfaceKind::SurfOfRevolution(rev) => {
|
||||
let Some(CurveKind::BSpline(g)) =
|
||||
rev.gen_curve.as_ref().map(crate::curve::curve::Curve::kind)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let pts = g.control_points()?;
|
||||
let gsize = pts.iter().fold(0.0_f64, |m, c| {
|
||||
let w = if c.w == NOW { 1.0 } else { c.w };
|
||||
m.max((Vec3::new(c.x, c.y, c.z) * (1.0 / w)).length())
|
||||
});
|
||||
let rational = pts.iter().any(|c| c.w != NOW);
|
||||
(
|
||||
&**rev,
|
||||
Generator::Spline {
|
||||
e_gen: eval_bound(rational, g.degree()?, gsize),
|
||||
},
|
||||
)
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let Some(pc) = rev.polar_curve() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let e_polar = {
|
||||
let pts = pc.base().control_points()?;
|
||||
let psize = pts.iter().fold(0.0_f64, |m, c| {
|
||||
let w = if c.w == NOW { 1.0 } else { c.w };
|
||||
m.max((Vec3::new(c.x, c.y, c.z) * (1.0 / w)).length())
|
||||
});
|
||||
eval_bound(pts.iter().any(|c| c.w != NOW), pc.base().degree()?, psize)
|
||||
};
|
||||
let origin = rev.position.origin();
|
||||
let reach = net
|
||||
.iter()
|
||||
.flatten()
|
||||
.fold(0.0_f64, |m, x| m.max((*x - origin).length()));
|
||||
Ok(Some(Self {
|
||||
e_form,
|
||||
origin,
|
||||
reach,
|
||||
generator,
|
||||
polar: (
|
||||
pc.position.origin(),
|
||||
pc.position.x_axis(),
|
||||
pc.position.y_axis(),
|
||||
pc.radius_x,
|
||||
e_polar,
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
/// The bound at the STEP parameters `step` (degrees, and the generator's), the STEP
|
||||
/// point `s` and the form's point `f` ([`evaluates_as`] items 1 to 4).
|
||||
fn at(&self, step: Vec2, s: Vec3, f: Vec3) -> f64 {
|
||||
let u = crate::nurbs::math::UNIT;
|
||||
let rad = |deg: f64| deg * PI / 180.0;
|
||||
let dc = |deg: f64| 2.0 * u + gamma(3) * rad(deg).abs();
|
||||
let o = max_coord(self.origin);
|
||||
let size_f = f.length();
|
||||
// item 2 (per coordinate) and item 3 (as a length)
|
||||
let (coord, generator) = match self.generator {
|
||||
Generator::Torus { major, minor, c } => {
|
||||
let (m, h) = (major + minor, minor);
|
||||
let dm = gamma(3) * (major + minor) + 2.0 * minor * dc(step.y);
|
||||
let dh = gamma(1) * minor + 2.0 * minor * dc(step.y);
|
||||
(
|
||||
gamma(3) * (o + m + h) + m * (gamma(2) + 2.0 * dc(step.x)) + dm + dh,
|
||||
self.e_form + gamma(4) * (size_f + c) + gamma(4) * minor * rad(step.y).abs(),
|
||||
)
|
||||
}
|
||||
Generator::Sphere { radius, c } => {
|
||||
let dm = gamma(1) * radius + 2.0 * radius * dc(step.y);
|
||||
(
|
||||
gamma(3) * (o + 2.0 * radius)
|
||||
+ radius * (gamma(2) + 2.0 * dc(step.x))
|
||||
+ 2.0 * dm,
|
||||
self.e_form + gamma(4) * (size_f + c) + gamma(4) * radius * rad(step.y).abs(),
|
||||
)
|
||||
}
|
||||
Generator::Cone { r0, t, a, v0, v1 } => {
|
||||
let v = step.y;
|
||||
let m = r0.abs() + (v * t).abs();
|
||||
let dt = (1.0 + t * t) * gamma(3) * a.abs() + 2.0 * u * t.abs();
|
||||
let dm = gamma(2) * m + v.abs() * dt;
|
||||
(
|
||||
gamma(3) * (o + m + v.abs()) + m * (gamma(2) + 2.0 * dc(step.x)) + dm,
|
||||
gamma(6) * 2.0 * (v0.abs() + v1.abs()) * (1.0 + t.abs()),
|
||||
)
|
||||
}
|
||||
Generator::Spline { e_gen } => (
|
||||
gamma(8) * (o + 3.0 * self.reach) + 2.0 * self.reach * dc(step.x) + e_gen,
|
||||
0.0,
|
||||
),
|
||||
};
|
||||
let e_step = 3.0_f64.sqrt() * coord;
|
||||
// item 4: the polar converter's inversion of its own circle's point
|
||||
let (c, x, y, rc, e_polar) = self.polar;
|
||||
let z = x.cross(y);
|
||||
let along = |v: Vec3| v - z * v.dot(z);
|
||||
let lever = along(s - c).length();
|
||||
let sweep = if rc > 0.0 {
|
||||
lever
|
||||
* ((e_polar + gamma(4) * (rc + 2.0 * c.length())) / rc
|
||||
+ gamma(4) * rad(step.x).abs()
|
||||
+ gamma(1) * 2.0 * PI)
|
||||
} else {
|
||||
// a circle of no radius has no angle: any, at the STEP point's lever
|
||||
lever * PI
|
||||
};
|
||||
self.e_form + e_step + generator + sweep
|
||||
}
|
||||
}
|
||||
|
||||
const OP_DERIVE: &str = "surface::implicit::AnalyticSupport::derive";
|
||||
|
||||
@@ -39,11 +39,62 @@ use crate::geom::vector3d::{Point3, Vec3};
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::EFF_ZERO;
|
||||
use crate::nurbs::b_spline_curve::BSplineCurve;
|
||||
use crate::surface::implicit::{check_exact_net, net_miss, ClassSurface, ExactNetCheck, LeaderMap};
|
||||
use crate::surface::implicit::{
|
||||
check_exact_net, lies_on, AnalyticSupport, ClassSurface, ExactNetCheck, Implicit, LeaderMap,
|
||||
};
|
||||
use crate::surface::procedural_blend::chamfer::ChamferDefinition;
|
||||
use crate::surface::surface::Surface;
|
||||
|
||||
const OP_TRANSFORM: &str = "surface::procedural_blend::exact::ExactRound::transform";
|
||||
const OP_CIRCLE: &str = "surface::procedural_blend::exact::ExactRound::supports_distance";
|
||||
|
||||
/// The torus a round of the circle class is: a surface of revolution's, about its
|
||||
/// placement's axis, of the circle its generating arc is on (the centre of a rational quadratic
|
||||
/// arc from its first span's control points: the midpoint of the chord pushed away from the
|
||||
/// middle point by `a^2 / h`, `a` the half chord, `h` the middle point's distance from the
|
||||
/// chord's midpoint); or a torus label's own. `None` for any other surface.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of reading the arc's control polygon.
|
||||
fn own_torus(net: &Surface) -> Result<Option<Implicit>> {
|
||||
use crate::curve::curve::{Curve, CurveKind};
|
||||
use crate::surface::surface::SurfaceKind;
|
||||
match net.kind() {
|
||||
SurfaceKind::Torus(_) => Ok(AnalyticSupport::of(net)
|
||||
.as_ref()
|
||||
.map(AnalyticSupport::implicit)),
|
||||
SurfaceKind::SurfOfRevolution(rev) => {
|
||||
let Some(CurveKind::BSpline(arc)) = rev.gen_curve().map(Curve::kind) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (points, _) = arc.control_polygon()?;
|
||||
let [p0, p1, p2, ..] = points.as_slice() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mid = (*p0 + *p2) * 0.5;
|
||||
let (a, h) = ((*p0 - mid).length(), (mid - *p1).length());
|
||||
if h.partial_cmp(&0.0) != Some(core::cmp::Ordering::Greater) {
|
||||
return Ok(None);
|
||||
}
|
||||
let centre = mid + (mid - *p1) * (a * a / (h * h));
|
||||
let position = rev.position();
|
||||
let (o, axis) = (
|
||||
position.origin(),
|
||||
position.x_axis().cross(position.y_axis()),
|
||||
);
|
||||
let along = (centre - o).dot(axis);
|
||||
let across = (centre - o) - axis * along;
|
||||
Ok(Some(Implicit::Torus {
|
||||
centre: o + axis * along,
|
||||
axis,
|
||||
major: across.length(),
|
||||
minor: (*p0 - centre).length(),
|
||||
}))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// The exact class of a round whose net is its definition.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -263,7 +314,8 @@ impl ExactRound {
|
||||
|
||||
/// Re-certifies `net` against this definition: the supports classified again are of
|
||||
/// this class, and the net lies on the class's surface derived from them
|
||||
/// ([`check_exact_net`]). Returns the distance proved, 0.
|
||||
/// ([`check_exact_net`]). A round of the circle class is certified on its own torus
|
||||
/// instead ([`ExactRound::supports_distance`]). Returns the distance proved, 0.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -271,6 +323,10 @@ impl ExactRound {
|
||||
/// surface from them, a net off it; those of reading the supports' quadrics and the
|
||||
/// net's NURBS form.
|
||||
pub fn recertify(&self, k: &Kernel, net: &Surface) -> Result<f64> {
|
||||
if self.class == ExactClass::Circle {
|
||||
self.supports_distance(k, net)?;
|
||||
return Ok(0.0);
|
||||
}
|
||||
let [a, b] = &self.supports;
|
||||
let blend = classify_supports(k, [a, b], &self.inputs)?;
|
||||
// the chord (#2486): a line spine between planes, of sizes that give its offsets
|
||||
@@ -302,19 +358,72 @@ impl ExactRound {
|
||||
Ok(0.0)
|
||||
}
|
||||
|
||||
/// How far `net` strays from the surface of the class its supports give
|
||||
/// ([`net_miss`]); `None` where they give it none.
|
||||
/// A round of the circle class: the torus `net` is ([`own_torus`]: its revolution's
|
||||
/// axis and generating circle, or its torus label), proved to hold its form to rounding
|
||||
/// ([`lies_on`]),
|
||||
/// and how far that torus is from the class's torus derived from the supports
|
||||
/// ([`class_surface`]): returned, refused past the tolerance the class was read within.
|
||||
///
|
||||
/// The class is read within the body's tolerance. Supports only that close to it (a plane
|
||||
/// 1e-13 off square to a cylinder's axis, read from a record) have no torus touching both
|
||||
/// exactly, so the round the solver builds, tangent to the supports as they are, is on a
|
||||
/// torus of its own. Its definition is that torus, at bound 0, a stated distance from the
|
||||
/// supports' class.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of reading the supports' quadrics and of the evaluations.
|
||||
pub fn miss(&self, k: &Kernel, net: &Surface) -> Result<Option<f64>> {
|
||||
/// `InvalidInput` for supports of another class or with no torus, a net of no torus, a
|
||||
/// net off its torus, or a torus further from the class's than the tolerance; those of
|
||||
/// reading the supports' quadrics, the net's NURBS form and [`lies_on`].
|
||||
pub fn supports_distance(&self, k: &Kernel, net: &Surface) -> Result<f64> {
|
||||
let refused = |why| SolidError::invalid_input(OP_CIRCLE, why);
|
||||
let [a, b] = &self.supports;
|
||||
let blend = classify_supports(k, [a, b], &self.inputs)?;
|
||||
match class_surface(&blend, self.leader.as_ref(), &self.map) {
|
||||
Some(surface) => net_miss(k, net, &surface).map(Some),
|
||||
None => Ok(None),
|
||||
if ExactClass::of_spine(blend.spine) != Some(ExactClass::Circle) {
|
||||
return Err(refused(
|
||||
"the supports classified again are not of the class the definition names",
|
||||
));
|
||||
}
|
||||
let Some(ClassSurface::Implicit(Implicit::Torus {
|
||||
centre,
|
||||
axis,
|
||||
major,
|
||||
minor,
|
||||
})) = class_surface(&blend, self.leader.as_ref(), &self.map)
|
||||
else {
|
||||
return Err(refused("the supports give the class no torus"));
|
||||
};
|
||||
let form = crate::blend::fillet_section::station::as_b_spline(k, net)?
|
||||
.ok_or_else(|| refused("the net has no NURBS form to check"))?;
|
||||
let own = own_torus(net)?.ok_or_else(|| refused("the net is no torus"))?;
|
||||
if !lies_on(&form, &own)? {
|
||||
return Err(refused("the net is off its own torus"));
|
||||
}
|
||||
let Implicit::Torus {
|
||||
centre: c,
|
||||
axis: d,
|
||||
major: big_r,
|
||||
minor: r,
|
||||
} = own
|
||||
else {
|
||||
return Err(refused("the net's own surface is no torus"));
|
||||
};
|
||||
// every point of the net's torus moves to the class's torus by at most: the turn of
|
||||
// its axis (an angle, over the torus's reach from its centre), the move of its
|
||||
// centre, and the change of its radii
|
||||
let turn = d.cross(axis).length().atan2(d.dot(axis).abs());
|
||||
let distance =
|
||||
turn * (big_r + r) + (c - centre).length() + (big_r - major).abs() + (r - minor).abs();
|
||||
let within = matches!(
|
||||
distance.partial_cmp(&self.inputs.tol),
|
||||
Some(core::cmp::Ordering::Less | core::cmp::Ordering::Equal)
|
||||
);
|
||||
if !within {
|
||||
return Err(refused(
|
||||
"the net's torus is further from the class's than the tolerance",
|
||||
));
|
||||
}
|
||||
Ok(distance)
|
||||
}
|
||||
|
||||
/// Moves the definition with its net (`Transform`): the supports, the leader and the
|
||||
|
||||
+72
-7
@@ -110,6 +110,12 @@
|
||||
//! WR-493, the SolidCraft fork's tangency-labs/smlib@7ea6a96): `IsNurbSurfaceSphere`
|
||||
//! tests the half meridian within `180 + 360 * SM_EFF_ZERO` degrees and the sphere it
|
||||
//! finds keeps the tested NURB (`SetFromGwNurb`). Off by default (upstream).
|
||||
//! - **Far spheres** (#3131, by default): `IsNurbSurfaceSphere` and
|
||||
//! `RebuildSTEPFromNURBParameters` add to their fixed half-meridian tolerances, and
|
||||
//! `IsNurbSurfaceSphere` to its generator's STEP interval test, the angles' rounding at
|
||||
//! the points' size ([`meridian_rounding`]): upstream's fixed degrees refused a sphere of
|
||||
//! radius 2.5 at 1e5 (180 + 6.7e-10 degrees). Near the origin the default half-meridian
|
||||
//! test widens from `1e-12` to about `1e-12 + 8e-12` degrees for radius 2.5.
|
||||
//! - **Parent calls.** `Transform` finishes in `SmSurfOfRevolution::Transform` (status
|
||||
//! ignored), its polar-converter rebuild given the sphere's iso circles as upstream's
|
||||
//! virtual call does; `ReadFromDB` reads the parent record with
|
||||
@@ -133,12 +139,15 @@ use crate::geom::tol::{are_same, are_same_to_tol, is_zero};
|
||||
use crate::geom::vector2d::Point2;
|
||||
use crate::geom::vector3d::{asin, sqrt, Point3, Vec3};
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::{max2, min2, EFF_ZERO, EFF_ZERO_PARAM, EFF_ZERO_SQRT, PI, UNDEF_DOUBLE};
|
||||
use crate::math::{
|
||||
max2, min2, rad_to_deg, EFF_ZERO, EFF_ZERO_PARAM, EFF_ZERO_SQRT, PI, UNDEF_DOUBLE,
|
||||
};
|
||||
use crate::nurbs::b_spline_curve::BSplineCurve;
|
||||
use crate::nurbs::b_spline_surface::p2::EditNotify;
|
||||
use crate::nurbs::b_spline_surface::{
|
||||
BSplineSurface, BoundingBoxRequest, IsoCurveOptions, LocationType, SurfParamType,
|
||||
};
|
||||
use crate::nurbs::math::gamma;
|
||||
use crate::nurbs::nurbs_crv::to_euclid;
|
||||
use crate::nurbs::nurbs_srf::copy_nurb_surface;
|
||||
use crate::surface::cone::Cone;
|
||||
@@ -2029,7 +2038,11 @@ impl Sphere {
|
||||
/// (WR-493) the half turn is tested within `180 + 360 * SM_EFF_ZERO` degrees and the
|
||||
/// sphere then keeps a copy of `test`'s NURB, its analytic data rebuilt from it
|
||||
/// (`SetFromGwNurb`); by default `180 + SM_EFF_ZERO` and the NURB made from the
|
||||
/// fitted circles, as upstream.
|
||||
/// fitted circles, as upstream. Not upstream: in both modes the half-meridian test, and
|
||||
/// the test of the generator's STEP interval against the sphere's (within `SM_EFF_ZERO`
|
||||
/// upstream), also allow the angles' rounding at the points' size,
|
||||
/// [`meridian_rounding`] of the corner points' size and the circle's radius: `4
|
||||
/// gamma_K (1 + |corners| + 2 r) / r` radians (#3131).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -2109,14 +2122,18 @@ impl Sphere {
|
||||
} else {
|
||||
EFF_ZERO
|
||||
};
|
||||
// not upstream: the angles' rounding at the points' size (`meridian_rounding`)
|
||||
let corners = min_pnt.max_dimension().max(max_pnt.max_dimension());
|
||||
let (p, q) = (test.degree(SurfParamType::U), test.degree(SurfParamType::V));
|
||||
let rounding = |radius: Option<f64>| meridian_rounding(p, q, corners, radius);
|
||||
let is_sphere_with_swap_uv = iso_u.parallel_planes
|
||||
&& !iso_u.same_plane // [ 080625 ]
|
||||
&& iso_v.coaxial_centers
|
||||
&& angles_v.length() <= 180.0 + half_meridian_tol;
|
||||
&& angles_v.length() <= 180.0 + half_meridian_tol + rounding(iso_v.radius);
|
||||
let is_sphere_no_swap_uv = iso_v.parallel_planes
|
||||
&& !iso_v.same_plane // [ 080625 ]
|
||||
&& iso_u.coaxial_centers
|
||||
&& angles_u.length() <= 180.0 + half_meridian_tol;
|
||||
&& angles_u.length() <= 180.0 + half_meridian_tol + rounding(iso_u.radius);
|
||||
|
||||
// quit when not a properly oriented sphere
|
||||
if !is_sphere_with_swap_uv && !is_sphere_no_swap_uv {
|
||||
@@ -2224,9 +2241,10 @@ impl Sphere {
|
||||
// If pCircle's STEP interval does not match the v-interval of sAnalUVDomain,
|
||||
// then the Sphere constructor will complain and the resulting bad surface can
|
||||
// lead to crashes. [B414]
|
||||
// (not upstream: its angles to the same rounding as the half meridian's)
|
||||
if !circle
|
||||
.step_interval()
|
||||
.are_equal(&anal_uv_domain.v_interval(), EFF_ZERO)
|
||||
.are_equal(&anal_uv_domain.v_interval(), EFF_ZERO + rounding(Some(rad)))
|
||||
{
|
||||
// upstream: WARN("Bad result in IsNurbSurfaceSphere(): GenCurve STEP
|
||||
// interval does not match sAnalUVDomain VInterval")
|
||||
@@ -2333,16 +2351,23 @@ impl Sphere {
|
||||
|
||||
// see if we have a U or a V oriented sphere
|
||||
// Tol: compensate for using degrees. [B279]
|
||||
// (not upstream: and the angles' rounding at the points' size, `meridian_rounding`)
|
||||
let corners = nurb_min_point
|
||||
.max_dimension()
|
||||
.max(nurb_max_point.max_dimension());
|
||||
let (p, q) = (srf.degree(SurfParamType::U), srf.degree(SurfParamType::V));
|
||||
let angles_u = iso_u.angles.unwrap_or(Extent1d::new());
|
||||
let angles_v = iso_v.angles.unwrap_or(Extent1d::new());
|
||||
let is_sphere_with_swap_uv = iso_u.parallel_planes
|
||||
&& !iso_v.same_plane // [ 080625 ]
|
||||
&& iso_v.coaxial_centers
|
||||
&& angles_v.length() <= 180.0 + 360.0 * EFF_ZERO;
|
||||
&& angles_v.length()
|
||||
<= 180.0 + 360.0 * EFF_ZERO + meridian_rounding(p, q, corners, iso_v.radius);
|
||||
let is_sphere_no_swap_uv = iso_v.parallel_planes
|
||||
&& !iso_v.same_plane // [ 080625 ]
|
||||
&& iso_u.coaxial_centers
|
||||
&& angles_u.length() <= 180.0 + 360.0 * EFF_ZERO;
|
||||
&& angles_u.length()
|
||||
<= 180.0 + 360.0 * EFF_ZERO + meridian_rounding(p, q, corners, iso_u.radius);
|
||||
|
||||
// quit when not a properly oriented sphere
|
||||
if !is_sphere_with_swap_uv && !is_sphere_no_swap_uv {
|
||||
@@ -2985,6 +3010,34 @@ fn convert_uv_from_nurbs_to_step(
|
||||
Ok(Point2::new(step_x, polar_param))
|
||||
}
|
||||
|
||||
/// Not upstream: the rounding, in degrees, of the length of the half meridian (or of a
|
||||
/// circle's STEP interval) that [`Sphere::is_nurb_surface_sphere`] and
|
||||
/// [`Sphere::rebuild_step_from_nurb_parameters`] read off a form of degrees `(p, q)` whose
|
||||
/// corner points are at most `corners` from the origin, its circle of radius `radius`; 0
|
||||
/// without a radius.
|
||||
///
|
||||
/// The angles are read off points of the form: an evaluation is within `gamma_K |P|` of
|
||||
/// the exact point (`K = 16 (p + q + 2)`, a few ulps for each of the rational evaluation's
|
||||
/// operations), `|P| <= 1 + corners + 2 r` (a point of the sphere is at most its
|
||||
/// diameter from a corner). The circle's centre is fitted to such points; its error is taken
|
||||
/// to be the same again, which is measured, not derived (the fit's conditioning is not
|
||||
/// bounded here: `far_sphere_recognised.rs` measures the excess over this allowance on
|
||||
/// spheres of radius 1e-3 to 1e3, at 0 to 1e7 from the origin, turned off the axes: at most
|
||||
/// 0.0055 of this allowance). An
|
||||
/// angle at radius `r` is then known to `2 gamma_K |P| / r` radians at each end, and the half
|
||||
/// turn's length to twice that: `4 gamma_K |P| / r`. As a position, `r` times that is
|
||||
/// `4 gamma_K |P|`, below the iso-circle tests' `100 SM_EFF_ZERO (1 + |corners|)` for every
|
||||
/// size, so it admits no form those tests refuse.
|
||||
pub(crate) fn meridian_rounding(p: usize, q: usize, corners: f64, radius: Option<f64>) -> f64 {
|
||||
match radius {
|
||||
Some(r) if r > 0.0 => {
|
||||
let size = 1.0 + corners + 2.0 * r;
|
||||
rad_to_deg(4.0 * gamma(16 * (p + q + 2)) * size / r)
|
||||
}
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -3001,6 +3054,18 @@ mod tests {
|
||||
Sphere::create_canonical(k, &frame(), radius).expect("sphere")
|
||||
}
|
||||
|
||||
/// `meridian_rounding` for radius 2.5 against numbers recomputed independently of it:
|
||||
/// 1.8e-11 degrees with corners at 12.5 (the origin) and 9.77e-8 degrees with corners at
|
||||
/// 100012.5 (1e5 out), so a doubled or halved allowance fails here (#3131).
|
||||
#[test]
|
||||
fn the_meridian_allowance_is_its_closed_form() {
|
||||
for (corners, degrees) in [(12.5, 1.8e-11), (100_012.5, 9.77e-8)] {
|
||||
let got = meridian_rounding(2, 2, corners, Some(2.5));
|
||||
assert!((got / degrees - 1.0).abs() < 0.02, "{corners}: {got:e}");
|
||||
}
|
||||
assert_eq!(meridian_rounding(2, 2, 1.0, None), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_with_domain_keeps_the_shape() {
|
||||
let k = Kernel::default();
|
||||
|
||||
+99
-6
@@ -1061,7 +1061,6 @@ impl Torus {
|
||||
let ref_frame = ref_iso.reference_frame.ok_or_else(missing)?;
|
||||
let other_frame = other_iso.reference_frame.ok_or_else(missing)?;
|
||||
let min_rad = other_iso.radius.ok_or_else(missing)?;
|
||||
let sweep_ivl = ref_iso.angles.ok_or_else(missing)?;
|
||||
let common_point = other_iso.common_line_point.ok_or_else(missing)?;
|
||||
let common_vec = other_iso.common_line_vec.ok_or_else(missing)?;
|
||||
|
||||
@@ -1069,6 +1068,17 @@ impl Torus {
|
||||
let torus_origin =
|
||||
common_point + common_vec.dot(other_frame.origin() - common_point) * common_vec;
|
||||
|
||||
// not upstream (#3199): the x axis and the sweep interval from the form's own first
|
||||
// sweep row's ends about the axis (`sweep_frame`), not from `is_arc`'s fit of it
|
||||
let Some((torus_x_axis, torus_y_axis, sweep_ivl)) = sweep_frame(
|
||||
torus_origin,
|
||||
ref_frame.z_axis(),
|
||||
fitted_sides(&ref_frame, ref_iso.angles.ok_or_else(missing)?),
|
||||
meridians(test, swap_uv)?,
|
||||
) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// get torus orientation and size
|
||||
// not upstream (WR-501, #1470, opt-in; tangency-labs/smlib@6136a04): the major
|
||||
// radius runs from the torus origin, not from the first sweep circle's centre,
|
||||
@@ -1079,9 +1089,7 @@ impl Torus {
|
||||
other_frame.origin() - ref_frame.origin()
|
||||
};
|
||||
let maj_rad = major_vec.length();
|
||||
let torus_x_axis = ref_frame.x_axis(); // the frame where all circles are parallel
|
||||
let torus_y_axis = ref_frame.y_axis();
|
||||
let torus_z_axis = ref_frame.z_axis();
|
||||
let torus_z_axis = ref_frame.z_axis(); // the frame where all circles are parallel
|
||||
|
||||
// torus must have positive radii
|
||||
if min_rad <= EFF_ZERO || maj_rad <= EFF_ZERO {
|
||||
@@ -1294,7 +1302,6 @@ impl Torus {
|
||||
let mut ref_frame = ref_iso.reference_frame.ok_or_else(missing)?;
|
||||
let other_frame = other_iso.reference_frame.ok_or_else(missing)?;
|
||||
let min_rad = other_iso.radius.ok_or_else(missing)?;
|
||||
let sweep_ivl = ref_iso.angles.ok_or_else(missing)?;
|
||||
let common_point = other_iso.common_line_point.ok_or_else(missing)?;
|
||||
let common_vec = other_iso.common_line_vec.ok_or_else(missing)?;
|
||||
|
||||
@@ -1302,8 +1309,18 @@ impl Torus {
|
||||
let torus_origin =
|
||||
common_point + common_vec.dot(other_frame.origin() - common_point) * common_vec;
|
||||
|
||||
// not upstream (#3199): the x axis and the sweep interval from the form's own first
|
||||
// sweep row's ends about the axis (`sweep_frame`), not from `is_arc`'s fit of it
|
||||
let (x_axis, y_axis, sweep_ivl) = sweep_frame(
|
||||
torus_origin,
|
||||
ref_frame.z_axis(),
|
||||
fitted_sides(&ref_frame, ref_iso.angles.ok_or_else(missing)?),
|
||||
meridians(srf, swap_uv)?,
|
||||
)
|
||||
.ok_or_else(|| SolidError::sm_err(OP_REBUILD))?;
|
||||
|
||||
// Install the origin into pRefFrame. // [B328]
|
||||
let _ = ref_frame.set_canonical(torus_origin, ref_frame.x_axis(), ref_frame.y_axis());
|
||||
let _ = ref_frame.set_canonical(torus_origin, x_axis, y_axis);
|
||||
|
||||
// get torus orientation and size
|
||||
let major_vec = other_frame.origin() - ref_frame.origin();
|
||||
@@ -1587,6 +1604,82 @@ impl Revolved for Torus {
|
||||
|
||||
/// `SmTorus::CreateIsoParametricCurve` of the surface of revolution `rev` of a torus
|
||||
/// of `shape` (also called from the base class's rebuilds, where it is virtual).
|
||||
/// The control points, Euclidean, of the form's first and last meridians: the columns of its
|
||||
/// net at the sweep's two ends (the sweep is along u, or along v with `swap_uv`). A clamped
|
||||
/// end's column is that boundary curve's own control polygon, and a meridian lies in a plane
|
||||
/// through the axis, so every one of its control points has the meridian's angle about the
|
||||
/// axis.
|
||||
fn meridians(srf: &BSplineSurface, swap_uv: bool) -> Result<[Vec<Point3>; 2]> {
|
||||
let nurb = srf
|
||||
.gw_nurb()
|
||||
.ok_or_else(|| SolidError::null_pointer(OP_NURB_TORUS))?;
|
||||
let (n, m) = (nurb.n(), nurb.m());
|
||||
let column = |end: bool| -> Result<Vec<Point3>> {
|
||||
if swap_uv {
|
||||
let j = if end { m } else { 0 };
|
||||
(0..=n).map(|i| Ok(nurb.cpt(i, j)?.to_euclid())).collect()
|
||||
} else {
|
||||
let i = if end { n } else { 0 };
|
||||
(0..=m).map(|j| Ok(nurb.cpt(i, j)?.to_euclid())).collect()
|
||||
}
|
||||
};
|
||||
Ok([column(false)?, column(true)?])
|
||||
}
|
||||
|
||||
/// The directions, about the axis, of the fitted first sweep row's ends (`is_arc`'s frame and
|
||||
/// angles): [`sweep_frame`] reads only their sides from them.
|
||||
fn fitted_sides(frame: &Axis2Placement, angles: Extent1d) -> [Vec3; 2] {
|
||||
let at = |deg: f64| {
|
||||
let a = deg * PI / 180.0;
|
||||
a.cos() * frame.x_axis() + a.sin() * frame.y_axis()
|
||||
};
|
||||
[at(angles.min()), at(angles.max())]
|
||||
}
|
||||
|
||||
/// Not upstream (#3199): a recognised torus's x axis, y axis and sweep interval (degrees) from
|
||||
/// its form's first and last meridians (`meridians`: their control points, exact data), about
|
||||
/// the axis through `origin` along the unit `z`. A meridian lies in a plane through the axis,
|
||||
/// so its control points share its angle on the side the tube's centre is on, and the
|
||||
/// opposite angle past the axis (a spindle torus's tube crosses it); `sides` (the fitted
|
||||
/// row's end directions, [`fitted_sides`]) says which side that is, and nothing else. Each
|
||||
/// meridian's angle is read at its control point farthest from the axis on that side: x is
|
||||
/// the first meridian's direction square to the axis (so the interval starts at 0), y `z x
|
||||
/// x`, and the interval's end the last meridian's angle from x about z (`atan2` in that
|
||||
/// frame), a closed sweep (within `SM_EFF_ZERO_SQRT` of 0) at 360, as `is_arc` maps its end.
|
||||
/// `is_arc` fitted the first sweep row's circle through three samples, so on a short arc its
|
||||
/// centre, and with it the x axis and the end angle, was off the axis by its conditioning
|
||||
/// (1e-14 rad on #3185's grooves); the converter centres the sweep circle on the axis, so its
|
||||
/// ends and its inside now agree to rounding. `None` when a meridian has no control point off
|
||||
/// the axis on its side.
|
||||
fn sweep_frame(
|
||||
origin: Point3,
|
||||
z: Vec3,
|
||||
sides: [Vec3; 2],
|
||||
[first, last]: [Vec<Point3>; 2],
|
||||
) -> Option<(Vec3, Vec3, Extent1d)> {
|
||||
let radial = |p: &Point3| {
|
||||
let s = *p - origin;
|
||||
s - s.dot(z) * z
|
||||
};
|
||||
let farthest = |ps: &[Point3], side: Vec3| {
|
||||
ps.iter()
|
||||
.map(radial)
|
||||
.filter(|r| r.dot(side) > 0.0)
|
||||
.max_by(|a, b| a.length().total_cmp(&b.length()))
|
||||
};
|
||||
let [first_side, last_side] = sides;
|
||||
let x = farthest(&first, first_side)?.unitize().ok()?;
|
||||
let y = z.cross(x);
|
||||
let e = farthest(&last, last_side)?;
|
||||
let mut end_deg = e.dot(y).atan2(e.dot(x)) * 180.0 / PI;
|
||||
if end_deg.abs() < EFF_ZERO_SQRT {
|
||||
end_deg = 360.0;
|
||||
} else if end_deg < 0.0 {
|
||||
end_deg += 360.0;
|
||||
}
|
||||
Some((x, y, Extent1d::from_min_max(0.0, end_deg)))
|
||||
}
|
||||
|
||||
fn iso_curve(
|
||||
rev: &SurfOfRevolution,
|
||||
shape: Shape,
|
||||
|
||||
+9
-4
@@ -1623,10 +1623,15 @@ pub(crate) fn ignore_read<T>(r: Result<T>) -> Result<Option<T>> {
|
||||
| SolidError::Cancelled { .. }
|
||||
| SolidError::Unsupported { .. }),
|
||||
) => Err(e),
|
||||
Err(
|
||||
e
|
||||
@ (SolidError::InvalidInput { reason, .. } | SolidError::PortInvariant { reason, .. }),
|
||||
) if reason == NESTING_LIMIT_REASON => Err(e),
|
||||
Err(e)
|
||||
if matches!(
|
||||
&e,
|
||||
SolidError::InvalidInput { reason, .. } | SolidError::PortInvariant { reason, .. }
|
||||
if *reason == NESTING_LIMIT_REASON
|
||||
) =>
|
||||
{
|
||||
Err(e)
|
||||
}
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ pub mod directed;
|
||||
pub mod extrude;
|
||||
pub mod rotational_sweep_geometry;
|
||||
pub mod screw;
|
||||
pub mod section_blend;
|
||||
pub mod segments;
|
||||
pub mod topology_sweep;
|
||||
pub mod trans_sweep_geometry;
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
//! A multi-section blend: one B-spline surface through ordered cross-sections that leaves the
|
||||
//! first and the last of them with the continuity their frames ask for.
|
||||
//!
|
||||
//! Upstream: none (not in SMLib). SolidCraft's `loft_blend` (`crates/kernel/src/loft_blend.rs`
|
||||
//! at 52afe21) and the Hermite rows of its `bridge::section`, moved into the kernel so no
|
||||
//! application builds the surface.
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |--------------------|----------|
|
||||
//! | [`section_blend`] | (none; SolidCraft's `loft_blend`) |
|
||||
//! | [`SectionFrame`] | (none; SolidCraft's `bridge::Frame`) |
|
||||
//! | [`EndOrder`] | (none; SolidCraft's `Continuity`) |
|
||||
//! | `end_rows` (crate) | (none; the rows of SolidCraft's `bridge::section`) |
|
||||
//! | `third_derivative` (crate) | (none; SolidCraft's `bridge::third_derivative`) |
|
||||
//!
|
||||
//! **The surface.** The sections run along u, each sampled at the same stations along v. Section
|
||||
//! `i` sits at `u = i / (n - 1)` and the surface passes through every station of it: each
|
||||
//! control row is interpolated through its stations along v (degree `min(3, stations - 1)`, the
|
||||
//! kernel's global interpolation at uniform parameters with averaged knots). Along u the knots are
|
||||
//! clamped and repeated `degree` times at every interior section, so every span is one Bezier.
|
||||
//! A span's end rows are the Hermite rows (`end_rows`) of its two sections: position, then
|
||||
//! tangent, curvature and change of curvature as the section holds them, written as the Bezier's
|
||||
//! leading control rows at the span's parameter interval. The u degree is the largest
|
||||
//! `c + c' - 1` over the spans (`c`, `c'` the conditions at its two ends); a span with fewer is
|
||||
//! degree-elevated exactly (by its own row elevation: the rows are vectors of points before any
|
||||
//! surface exists, so the kernel's surface and single-curve elevations do not fit them).
|
||||
//!
|
||||
//! **What each section holds (the definition, SolidCraft's).**
|
||||
//! - The first and the last section hold what their [`EndOrder`] asks (G0 to G3) from the
|
||||
//! caller's frames: the tangent `leave * speed`, the curvature `bend * speed^2` and the change
|
||||
//! of curvature from `bend_change` (`third_derivative`: only its part square to the tangent
|
||||
//! holds the condition, the rest a reparametrisation). The speed is the station's chord to the
|
||||
//! neighbouring section `c`, over 3, times `1 + crown`, over the span's interval `1 / (n - 1)`:
|
||||
//! `d_u = leave * c / 3 * (1 + crown) * (n - 1)` for `n` sections, so the Bezier's first control
|
||||
//! offset is `c (1 + crown) / (3 m)` at span degree `m`. The last section's frames point back
|
||||
//! into the blend, so its odd derivatives turn round.
|
||||
//! - An interior section holds its position and the run's derivatives from finite differences of
|
||||
//! the neighbouring sections' stations: the central first and second differences, and a third
|
||||
//! (central, or one-sided at the second and the last-but-one section) where four sections
|
||||
//! reach. So the joint is C3 in u with four or more sections and C2 with three, at every
|
||||
//! station.
|
||||
//!
|
||||
//! This is SolidCraft's definition of the blend, not an approximation of another surface: the
|
||||
//! finite differences are the conditions the surface is built to, not estimates of a curve the
|
||||
//! sections lie on, and no error against anything is claimed. What is exact: the surface passes
|
||||
//! through every station; at every station of an end it holds the frames' conditions to rounding;
|
||||
//! at every station of an interior section the spans on either side agree in their u derivatives
|
||||
//! up to the order held. Between stations the end and joint curves are the v interpolants of the
|
||||
//! stations' rows (the interpolation is linear and commutes with evaluation in u), not conditions
|
||||
//! of their own.
|
||||
//!
|
||||
//! **Refused**, naming why: fewer than 2 or more than [`MAX_SECTIONS`] sections; sections of
|
||||
//! different station counts, or a count outside [`MIN_STATIONS`] ..= [`MAX_STATIONS`]; a frame
|
||||
//! that is not finite, or a `leave` of no length at an end that uses it; a crown outside
|
||||
//! `-0.9 ..= 3`; two neighbouring sections that meet at a station (no room for a blend there).
|
||||
//!
|
||||
//! **The two floors.** Two tests guard divisions, and both refuse; neither adjusts an input or a
|
||||
//! result to get past them. A tangent's length (an end's `leave`, and the first derivative in
|
||||
//! `third_derivative`) is divided by: a length that is not finite and positive is refused,
|
||||
//! never replaced by a default direction. Two neighbouring sections meeting at a station
|
||||
//! (distance not positive) leave the span there no room: a speed of zero, and no tangent to
|
||||
//! divide by at the next section; the blend is refused, never nudged apart. Both are exact tests,
|
||||
//! not SolidCraft's `1e-12` and `1e-9` floors: a chord of `1e-12` is not refused, its speed is
|
||||
//! about `1e-12` and the end rows collapse onto the section, a valid but degenerate surface.
|
||||
//!
|
||||
//! Deviations: no upstream counterpart. SolidCraft's `1e-12` floor on a tangent's length and its
|
||||
//! `1e-9` floor on the distance between neighbouring sections are exact tests here (a length that
|
||||
//! is not positive): what the floors protect is the division, and an exact test protects it
|
||||
//! without a scale-dependent constant. Its one-sided third difference at the last-but-one section
|
||||
//! has its sign turned (`f(i-2) - 3 f(i-1) + 3 f(i) - f(i+1)`, the negative of the change of
|
||||
//! curvature it estimates); here it is the backward difference, exact on a cubic like the other
|
||||
//! two. Its batched row solves are one solve per row. No
|
||||
//! recursion.
|
||||
|
||||
use crate::error::{Result, SolidError};
|
||||
use crate::math::{Point3, Vec3};
|
||||
use crate::nurbs::b_spline_curve::KnotType;
|
||||
use crate::nurbs::b_spline_surface::{BSplineSurface, BSplineSurfaceForm, CanonicalSurfaceData};
|
||||
use crate::nurbs::crv_fit::p1::{interp_given_params, interp_knot_vector};
|
||||
use crate::nurbs::knots::KnotVector;
|
||||
use crate::nurbs::math::NOW;
|
||||
|
||||
const OP: &str = "sweep::section_blend";
|
||||
|
||||
/// Most cross-sections one blend runs through.
|
||||
pub const MAX_SECTIONS: usize = 50;
|
||||
/// Fewest stations a cross-section is sampled at (a straight line across it).
|
||||
pub const MIN_STATIONS: usize = 2;
|
||||
/// Most stations a cross-section is sampled at.
|
||||
pub const MAX_STATIONS: usize = 257;
|
||||
|
||||
/// One station of a cross-section: its point and, at the first and last section, how the blend
|
||||
/// leaves it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SectionFrame {
|
||||
/// The point.
|
||||
pub p: Point3,
|
||||
/// The direction the blend leaves in, towards the next section (at the last section, back
|
||||
/// into the blend, towards the section before). Its length is ignored.
|
||||
pub leave: Vec3,
|
||||
/// The curvature vector the blend takes along `leave` (zero for a straight start).
|
||||
pub bend: Vec3,
|
||||
/// The arc-length derivative of `bend` along `leave`.
|
||||
pub bend_change: Vec3,
|
||||
}
|
||||
|
||||
/// The continuity a blend holds at its first or last section.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum EndOrder {
|
||||
/// Position.
|
||||
G0,
|
||||
/// Position and tangent direction.
|
||||
G1,
|
||||
/// And curvature.
|
||||
G2,
|
||||
/// And change of curvature.
|
||||
G3,
|
||||
}
|
||||
|
||||
impl EndOrder {
|
||||
/// The conditions held: 1 to 4 (position, tangent, curvature, change of curvature).
|
||||
#[must_use]
|
||||
pub const fn conditions(self) -> usize {
|
||||
match self {
|
||||
Self::G0 => 1,
|
||||
Self::G1 => 2,
|
||||
Self::G2 => 3,
|
||||
Self::G3 => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid(reason: &'static str) -> SolidError {
|
||||
SolidError::invalid_input(OP, reason)
|
||||
}
|
||||
|
||||
/// The derivatives a section holds along the run at each station: `d[k]` is the `(k + 1)`-th,
|
||||
/// as many as its conditions less one.
|
||||
struct Held {
|
||||
p: Vec<Point3>,
|
||||
d: [Vec<Vec3>; 3],
|
||||
}
|
||||
|
||||
/// The Bezier control rows at one end of a span of degree `m` over the parameter interval
|
||||
/// `delta`, from the end's position `p` and its derivatives `d` (the first `conditions - 1` of
|
||||
/// them used), as the span leaves the end (`backward` false: rows `0, 1, ..`) or arrives at it
|
||||
/// (`backward` true: rows `m, m - 1, ..`, the odd derivatives read the other way): the leading
|
||||
/// rows of a Bezier whose derivatives at that end are `d`. Row `k` comes back at index `k` from
|
||||
/// the end.
|
||||
///
|
||||
/// # Errors
|
||||
/// `InvalidInput` for more conditions than the degree carries or than `d` gives.
|
||||
pub(crate) fn end_rows(
|
||||
p: Point3,
|
||||
d: &[Vec3],
|
||||
conditions: usize,
|
||||
m: usize,
|
||||
delta: f64,
|
||||
backward: bool,
|
||||
) -> Result<Vec<Point3>> {
|
||||
if conditions == 0 || conditions > 4 || conditions > m + 1 || d.len() + 1 < conditions {
|
||||
return Err(invalid(
|
||||
"a span's end carries more conditions than its degree or its data",
|
||||
));
|
||||
}
|
||||
#[allow(clippy::cast_precision_loss)] // a degree, at most 32
|
||||
let k = m as f64;
|
||||
let sign = if backward { -1.0 } else { 1.0 };
|
||||
let one = delta / k;
|
||||
let two = if m >= 2 {
|
||||
delta * delta / (k * (k - 1.0))
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let three = if m >= 3 {
|
||||
delta * delta * delta / (k * (k - 1.0) * (k - 2.0))
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let mut rows = vec![p];
|
||||
if conditions >= 2 {
|
||||
let d1 = *d.first().ok_or_else(|| invalid("a missing tangent"))?;
|
||||
let q1 = p + d1 * (sign * one);
|
||||
rows.push(q1);
|
||||
if conditions >= 3 {
|
||||
let d2 = *d.get(1).ok_or_else(|| invalid("a missing curvature"))?;
|
||||
let q2 = d2 * two + q1 * 2.0 - p;
|
||||
rows.push(q2);
|
||||
if conditions >= 4 {
|
||||
let d3 = *d
|
||||
.get(2)
|
||||
.ok_or_else(|| invalid("a missing change of curvature"))?;
|
||||
rows.push(d3 * (sign * three) + q2 * 3.0 - q1 * 3.0 + p);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// The third derivative a curve needs at an end so that the arc-length derivative of its
|
||||
/// curvature vector is `target`, from its first two derivatives `d1`, `d2` there: only its part
|
||||
/// square to the tangent holds the condition, the rest is a reparametrisation and is left zero
|
||||
/// (SolidCraft's `bridge::third_derivative`).
|
||||
///
|
||||
/// # Errors
|
||||
/// `InvalidInput` for a tangent of no length or a result that is not finite.
|
||||
pub(crate) fn third_derivative(d1: Vec3, d2: Vec3, target: Vec3) -> Result<Vec3> {
|
||||
let sigma = d1.length();
|
||||
// the floor on the tangent's length, divided by below: refused, never adjusted
|
||||
if !(sigma.is_finite() && sigma > 0.0) {
|
||||
return Err(invalid(
|
||||
"no room for the change of curvature: the tangent has no length",
|
||||
));
|
||||
}
|
||||
let t = d1 * (1.0 / sigma);
|
||||
let tp = d2 * (1.0 / sigma) - d1 * (d1.dot(d2) / (sigma * sigma * sigma));
|
||||
let nn = d2 - t * d2.dot(t);
|
||||
let r = tp * (-d2.dot(t)) - t * d2.dot(tp);
|
||||
let x = (target * sigma.powi(5) - r * (sigma * sigma) + nn * (2.0 * d1.dot(d2)))
|
||||
* (1.0 / (sigma * sigma));
|
||||
if !(x.x.is_finite() && x.y.is_finite() && x.z.is_finite()) {
|
||||
return Err(invalid("the change of curvature is not finite"));
|
||||
}
|
||||
Ok(x - t * x.dot(t))
|
||||
}
|
||||
|
||||
fn finite(v: Vec3) -> bool {
|
||||
v.x.is_finite() && v.y.is_finite() && v.z.is_finite()
|
||||
}
|
||||
|
||||
/// One exact degree elevation of a Bezier net of rows: the same curve in one row more.
|
||||
fn elevate(rows: &[Vec<Point3>]) -> Vec<Vec<Point3>> {
|
||||
let degree = rows.len().saturating_sub(1);
|
||||
#[allow(clippy::cast_precision_loss)] // a degree, at most 32
|
||||
let up = degree as f64 + 1.0;
|
||||
let mut out: Vec<Vec<Point3>> = Vec::new();
|
||||
if let Some(first) = rows.first() {
|
||||
out.push(first.clone());
|
||||
}
|
||||
for i in 1..=degree {
|
||||
if let (Some(before), Some(here)) = (rows.get(i - 1), rows.get(i)) {
|
||||
#[allow(clippy::cast_precision_loss)] // a row index, at most 32
|
||||
let a = i as f64 / up;
|
||||
out.push(
|
||||
before
|
||||
.iter()
|
||||
.zip(here)
|
||||
.map(|(x, y)| *x * a + *y * (1.0 - a))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(last) = rows.last() {
|
||||
out.push(last.clone());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The blend surface through `sections` (each a cross-section of the same stations, in order),
|
||||
/// holding `orders[0]` at the first section and `orders[1]` at the last, `crown` scaling how fast
|
||||
/// it leaves each (`-0.9 ..= 3`, 0 the plain blend). See the module's notes for the definition.
|
||||
///
|
||||
/// # Errors
|
||||
/// `InvalidInput` for the refusals the module names; those of the interpolation and the surface's
|
||||
/// construction.
|
||||
#[allow(clippy::too_many_lines)] // one construction, step by step
|
||||
pub fn section_blend(
|
||||
sections: &[Vec<SectionFrame>],
|
||||
orders: [EndOrder; 2],
|
||||
crown: [f64; 2],
|
||||
) -> Result<BSplineSurface> {
|
||||
let run = sections.len();
|
||||
if !(2..=MAX_SECTIONS).contains(&run) {
|
||||
return Err(invalid("a blend runs through 2 to 50 cross-sections"));
|
||||
}
|
||||
let samples = sections.first().map_or(0, Vec::len);
|
||||
if !(MIN_STATIONS..=MAX_STATIONS).contains(&samples) {
|
||||
return Err(invalid("a blend's cross-sections take 2 to 257 stations"));
|
||||
}
|
||||
if crown
|
||||
.iter()
|
||||
.any(|c| !(c.is_finite() && (-0.9..=3.0).contains(c)))
|
||||
{
|
||||
return Err(invalid("a blend's crown is between -0.9 and 3"));
|
||||
}
|
||||
for section in sections {
|
||||
if section.len() != samples {
|
||||
return Err(invalid(
|
||||
"every cross-section of a blend has the same stations",
|
||||
));
|
||||
}
|
||||
if section
|
||||
.iter()
|
||||
.any(|f| !(finite(f.p) && finite(f.leave) && finite(f.bend) && finite(f.bend_change)))
|
||||
{
|
||||
return Err(invalid("a blend's frame is not finite"));
|
||||
}
|
||||
}
|
||||
let point = |i: usize, s: usize| -> Result<Point3> {
|
||||
sections
|
||||
.get(i)
|
||||
.and_then(|sec| sec.get(s))
|
||||
.map(|f| f.p)
|
||||
.ok_or_else(|| invalid("a cross-section is missing a station"))
|
||||
};
|
||||
let frame = |i: usize, s: usize| -> Result<SectionFrame> {
|
||||
sections
|
||||
.get(i)
|
||||
.and_then(|sec| sec.get(s))
|
||||
.copied()
|
||||
.ok_or_else(|| invalid("a cross-section is missing a station"))
|
||||
};
|
||||
for i in 0..run - 1 {
|
||||
for s in 0..samples {
|
||||
// the floor on coincident sections (a zero speed, no tangent to divide by next):
|
||||
// refused, never nudged apart
|
||||
if point(i, s)?.distance(point(i + 1, s)?) <= 0.0 {
|
||||
return Err(invalid(
|
||||
"two neighbouring cross-sections meet at a station: there is no room for a blend",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// the conditions at each section: the ends' orders; an interior one four where a fourth
|
||||
// section reaches for the third difference, else three
|
||||
let mut conditions = vec![3usize; run];
|
||||
if let Some(c) = conditions.first_mut() {
|
||||
*c = orders[0].conditions();
|
||||
}
|
||||
if let Some(c) = conditions.last_mut() {
|
||||
*c = orders[1].conditions();
|
||||
}
|
||||
for (i, c) in conditions.iter_mut().enumerate().take(run - 1).skip(1) {
|
||||
if i >= 2 || i + 2 < run {
|
||||
*c = 4;
|
||||
}
|
||||
}
|
||||
let degree = conditions
|
||||
.windows(2)
|
||||
.map(|w| match w {
|
||||
[a, b] => a + b - 1,
|
||||
_ => 0,
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(1)
|
||||
.max(1);
|
||||
#[allow(clippy::cast_precision_loss)] // at most 50 sections
|
||||
let step = 1.0 / (run - 1) as f64;
|
||||
|
||||
// what each section holds along the run
|
||||
let mut held: Vec<Held> = Vec::new();
|
||||
for (i, &c) in conditions.iter().enumerate() {
|
||||
let p: Vec<Point3> = (0..samples).map(|s| point(i, s)).collect::<Result<_>>()?;
|
||||
let mut d: [Vec<Vec3>; 3] = [Vec::new(), Vec::new(), Vec::new()];
|
||||
let first = i == 0;
|
||||
let last = i + 1 == run;
|
||||
for s in 0..samples {
|
||||
let (d1, d2, d3) = if first || last {
|
||||
let f = frame(i, s)?;
|
||||
let (other, crown_here, sign) = if first {
|
||||
(point(1, s)?, crown[0], 1.0)
|
||||
} else {
|
||||
(point(i - 1, s)?, crown[1], -1.0)
|
||||
};
|
||||
let length = f.leave.length();
|
||||
// the floor on the tangent's length, divided by below: refused, never defaulted
|
||||
if c >= 2 && !(length.is_finite() && length > 0.0) {
|
||||
return Err(invalid("a blend's leave direction has no length"));
|
||||
}
|
||||
let leave = if length > 0.0 {
|
||||
f.leave * (1.0 / length)
|
||||
} else {
|
||||
f.leave
|
||||
};
|
||||
// a third of the station's chord to its neighbour, the crown, over the interval
|
||||
let speed = f.p.distance(other) / 3.0 * (1.0 + crown_here) / step;
|
||||
let d1 = leave * (sign * speed);
|
||||
let d2 = f.bend * (speed * speed);
|
||||
let d3 = if c >= 4 {
|
||||
// the last section's frames point back into the blend: read it backwards
|
||||
if first {
|
||||
third_derivative(d1, d2, f.bend_change)?
|
||||
} else {
|
||||
third_derivative(-d1, d2, f.bend_change)? * -1.0
|
||||
}
|
||||
} else {
|
||||
Vec3::ZERO
|
||||
};
|
||||
(d1, d2, d3)
|
||||
} else {
|
||||
let before = point(i - 1, s)?;
|
||||
let here = point(i, s)?;
|
||||
let after = point(i + 1, s)?;
|
||||
let d1 = (after - before) * (1.0 / (2.0 * step));
|
||||
let d2 = (after - here * 2.0 + before) * (1.0 / (step * step));
|
||||
let d3 = if c >= 4 {
|
||||
let cube = step * step * step;
|
||||
match (i.checked_sub(2), i + 2 < run) {
|
||||
(Some(k), true) => {
|
||||
(point(i + 2, s)? - point(i + 1, s)? * 2.0 + point(i - 1, s)? * 2.0
|
||||
- point(k, s)?)
|
||||
* (1.0 / (2.0 * cube))
|
||||
}
|
||||
(None, true) => {
|
||||
(point(i + 2, s)? - point(i + 1, s)? * 3.0 + here * 3.0 - before)
|
||||
* (1.0 / cube)
|
||||
}
|
||||
// (the backward stencil f(i+1) - 3 f(i) + 3 f(i-1) - f(i-2): SolidCraft's
|
||||
// has its sign turned, see Deviations)
|
||||
(Some(k), false) => {
|
||||
(after - here * 3.0 + before * 3.0 - point(k, s)?) * (1.0 / cube)
|
||||
}
|
||||
(None, false) => {
|
||||
return Err(invalid(
|
||||
"no fourth section for an interior change of curvature",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Vec3::ZERO
|
||||
};
|
||||
(d1, d2, d3)
|
||||
};
|
||||
if !(finite(d1) && finite(d2) && finite(d3)) {
|
||||
return Err(invalid(
|
||||
"a section's derivatives along the run are not finite",
|
||||
));
|
||||
}
|
||||
let [a, b, e] = &mut d;
|
||||
a.push(d1);
|
||||
b.push(d2);
|
||||
e.push(d3);
|
||||
}
|
||||
held.push(Held { p, d });
|
||||
}
|
||||
|
||||
// the spans' control rows, each elevated to the run's degree; a span's first row is the one
|
||||
// before's last
|
||||
let mut rows: Vec<Vec<Point3>> = Vec::new();
|
||||
for j in 0..run - 1 {
|
||||
let (Some(left), Some(right)) = (held.get(j), held.get(j + 1)) else {
|
||||
return Err(invalid("a missing section"));
|
||||
};
|
||||
let (Some(&kl), Some(&kr)) = (conditions.get(j), conditions.get(j + 1)) else {
|
||||
return Err(invalid("a missing section"));
|
||||
};
|
||||
let m = kl + kr - 1;
|
||||
let mut span = vec![vec![Point3::ZERO; samples]; m + 1];
|
||||
for s in 0..samples {
|
||||
let take = |h: &Held| -> Result<(Point3, Vec<Vec3>)> {
|
||||
let p = *h.p.get(s).ok_or_else(|| invalid("a missing station"))?;
|
||||
let d =
|
||||
h.d.iter()
|
||||
.map(|v| {
|
||||
v.get(s)
|
||||
.copied()
|
||||
.ok_or_else(|| invalid("a missing station"))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok((p, d))
|
||||
};
|
||||
let (pl, dl) = take(left)?;
|
||||
let (pr, dr) = take(right)?;
|
||||
for (k, q) in end_rows(pl, &dl, kl, m, step, false)?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
if let Some(slot) = span.get_mut(k).and_then(|r| r.get_mut(s)) {
|
||||
*slot = q;
|
||||
}
|
||||
}
|
||||
for (k, q) in end_rows(pr, &dr, kr, m, step, true)?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
if let Some(slot) = span.get_mut(m - k).and_then(|r| r.get_mut(s)) {
|
||||
*slot = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
while span.len() < degree + 1 {
|
||||
span = elevate(&span);
|
||||
}
|
||||
rows.extend(span.into_iter().skip(usize::from(j > 0)));
|
||||
}
|
||||
|
||||
// each row interpolated through its stations along v
|
||||
#[allow(clippy::cast_precision_loss)] // at most 257 stations
|
||||
let params: Vec<f64> = (0..samples)
|
||||
.map(|s| s as f64 / (samples - 1) as f64)
|
||||
.collect();
|
||||
let degree_v = 3.min(samples - 1);
|
||||
let knots_v = interp_knot_vector(¶ms, degree_v)?;
|
||||
let kv = KnotVector::from_slice(&knots_v)?;
|
||||
let count_u = rows.len();
|
||||
let mut net = vec![Point3::ZERO; count_u * samples];
|
||||
for (i, row) in rows.iter().enumerate() {
|
||||
let curve = interp_given_params(row, ¶ms, &kv, degree_v)?;
|
||||
for (s, c) in curve.cpts().iter().take(samples).enumerate() {
|
||||
// (exact tests: NOW is the stored "no weight" mark of a polynomial row, assigned,
|
||||
// never computed, and 1.0 an assigned unit weight; any other weight is a weighted
|
||||
// point)
|
||||
let q = if c.w == NOW || c.w == 1.0 {
|
||||
Point3::new(c.x, c.y, c.z)
|
||||
} else {
|
||||
Point3::new(c.x / c.w, c.y / c.w, c.z / c.w)
|
||||
};
|
||||
if let Some(slot) = net.get_mut(i * samples + s) {
|
||||
*slot = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the run's knots: clamped, every interior section `degree` times
|
||||
let mut u_knots = vec![0.0];
|
||||
let mut u_mults = vec![degree + 1];
|
||||
for i in 1..run - 1 {
|
||||
#[allow(clippy::cast_precision_loss)] // at most 50 sections
|
||||
u_knots.push(i as f64 * step);
|
||||
u_mults.push(degree);
|
||||
}
|
||||
u_knots.push(1.0);
|
||||
u_mults.push(degree + 1);
|
||||
let (v_knots, v_mults) = unique(&knots_v);
|
||||
BSplineSurface::create_canonical(&CanonicalSurfaceData {
|
||||
u_degree: degree,
|
||||
v_degree: degree_v,
|
||||
control_points: &net,
|
||||
form: BSplineSurfaceForm::Unspecified,
|
||||
u_multiplicities: &u_mults,
|
||||
v_multiplicities: &v_mults,
|
||||
u_knots: &u_knots,
|
||||
v_knots: &v_knots,
|
||||
knot_type: KnotType::Unspecified,
|
||||
weights: None,
|
||||
uv_domain: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// A knot vector as its distinct knots and their multiplicities.
|
||||
fn unique(knots: &[f64]) -> (Vec<f64>, Vec<usize>) {
|
||||
let mut values: Vec<f64> = Vec::new();
|
||||
let mut mults: Vec<usize> = Vec::new();
|
||||
for &u in knots {
|
||||
match (values.last(), mults.last_mut()) {
|
||||
(Some(&last), Some(m)) if last == u => *m += 1,
|
||||
_ => {
|
||||
values.push(u);
|
||||
mults.push(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
(values, mults)
|
||||
}
|
||||
+18
-12
@@ -64,28 +64,33 @@ pub use p8::{
|
||||
};
|
||||
pub use p9::{make_vertex_loop, orient_sheet_surfaces};
|
||||
|
||||
/// The relative accuracy [`is_outward_solid`] measures the volume's sign at: the sign of a
|
||||
/// solid's volume is its whole volume away from zero.
|
||||
const SIGN_ACCURACY: f64 = 1e-3;
|
||||
|
||||
/// Whether `b` is a valid closed solid facing out (not upstream): one closed manifold solid
|
||||
/// ([`is_manifold_solid`]), passed by the kernel's validity check (its pointer check and
|
||||
/// `AssertValid`, [`validation::check_brep`] at `Level0`), and its integrated volume positive.
|
||||
/// `AssertValid`, [`validation::check_brep`] at `Level0`), and its volume proved positive by
|
||||
/// its certified enclosure ([`crate::mass::enclosure::volume_sign`]; an undecided sign is
|
||||
/// not).
|
||||
///
|
||||
/// The two guard the faceuse orientation together. A body with every face flipped
|
||||
/// (`FlipFaceOrientation`, which leaves a face invalid) is still manifold and keeps a
|
||||
/// positive volume (the integrator reads the loops and surfaces, not the uses' orientation):
|
||||
/// the pointer check is what refuses it. A valid body turned inside out (its loops and
|
||||
/// surfaces reversed with its uses) has a negative volume. The sign's margin is the whole
|
||||
/// volume, so it is measured at a relative accuracy of `1e-3`.
|
||||
/// surfaces reversed with its uses) has a negative volume.
|
||||
///
|
||||
/// A sign the enclosure leaves undecided (its interval still holds 0 when refinement ends:
|
||||
/// a gap between faces as large as the volume, or the cell budget spent) is "not a solid" on
|
||||
/// purpose: a solid is what is proved one, so ops' `finish` refuses the body as `NotSolid`.
|
||||
/// A body the enclosure cannot read at all is an error, not `false`: a face with no NURBS form
|
||||
/// (an offset surface, as the offset of a round keeps; a curve-bounded or in-volume surface),
|
||||
/// a face without trim loops, or weights not proved positive.
|
||||
///
|
||||
/// A region the kernel is unsure of is [`crate::SolidError::UnsureRegion`], not "not a solid":
|
||||
/// the volume's sign would read it as void.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of the manifold test, the validity check and the volume;
|
||||
/// [`crate::SolidError::UnsureRegion`].
|
||||
/// Those of the manifold test, the validity check and the volume
|
||||
/// ([`crate::mass::enclosure::volume_sign`]: [`crate::SolidError::Unsupported`] for a body
|
||||
/// it cannot read); [`crate::SolidError::UnsureRegion`].
|
||||
pub fn is_outward_solid(
|
||||
k: &mut crate::kernel::Kernel,
|
||||
b: &mut crate::topology::Brep,
|
||||
@@ -123,9 +128,10 @@ fn is_valid_and_outward(
|
||||
return Ok(false);
|
||||
}
|
||||
crate::mass::integrator::properties::no_unsure_region(b)?;
|
||||
let volume = crate::mass::integrator::properties::brep_volume_default(k, b, SIGN_ACCURACY)?;
|
||||
// (false for a NaN too)
|
||||
Ok(volume > 0.0)
|
||||
Ok(matches!(
|
||||
crate::mass::enclosure::volume_sign(k, b)?,
|
||||
crate::mass::enclosure::VolumeSign::Positive(_)
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -34,6 +34,7 @@ use crate::error::{EntityRef, Failure, Result, SolidError};
|
||||
use crate::geom::attribute::ObjectKey;
|
||||
use crate::geom::axis2_placement::Axis2Placement;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::mass::enclosure::VolumeSign;
|
||||
use crate::math::Vec3;
|
||||
use crate::nurbs::b_spline_surface::p1::SurfParamType;
|
||||
use crate::nurbs::knots::Side;
|
||||
@@ -311,8 +312,12 @@ fn local_operation_on(
|
||||
/// - the body's pointer check failed, so its geometry was not checked (`AssertValid` is
|
||||
/// skipped then), even where the input's pointer check failed alike;
|
||||
/// - the input was a manifold solid and the body is not;
|
||||
/// - the body is a manifold solid whose volume is not positive (the input's volume is
|
||||
/// not measured).
|
||||
/// - the body is a manifold solid whose volume is not proved positive: its certified
|
||||
/// enclosure lies below 0, or still holds 0 after its budget
|
||||
/// ([`validation::volume_sign`]; the input's volume is not measured). A face whose net
|
||||
/// is certified within ε > 0 of its definition (a Gⁿ or law round, a sweep, a Mixed
|
||||
/// corner) is read on its net, so the gate decides on the body as stored; widening the
|
||||
/// enclosure by ε needs bounds on the definition's `S_u` and `S_v` (#3170).
|
||||
///
|
||||
/// The owners the check tests the geometry against are the identities the body's naming
|
||||
/// scheme names its entities by ([`validation::standing`]), as `move_faces` sets them.
|
||||
@@ -357,10 +362,12 @@ fn refuse_unsound(k: &mut Kernel, b: &Brep, input: &validation::Standing) -> Res
|
||||
Unsoundness::GeometryUnchecked
|
||||
} else if let Some(lost) = validation::lost_manifold(found.manifold, || Ok(input.manifold))? {
|
||||
lost
|
||||
} else if found.manifold
|
||||
&& validation::volume(k, &mut copy)?.partial_cmp(&0.0) != Some(std::cmp::Ordering::Greater)
|
||||
{
|
||||
Unsoundness::VolumeNotPositive
|
||||
} else if found.manifold {
|
||||
match validation::volume_sign(k, &mut copy)? {
|
||||
VolumeSign::Positive(_) => return Ok(()),
|
||||
VolumeSign::Negative(_) => Unsoundness::VolumeNotPositive,
|
||||
VolumeSign::Undecided(_) => Unsoundness::VolumeUndecided,
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -2085,6 +2092,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A body whose volume's sign is not proved is refused as such: the quarter shell R 10,
|
||||
/// t 1e-6 passes the check, but its faces' boundaries lie apart by about its thickness,
|
||||
/// so the strips between them hold 0 in its enclosure whatever its cells become.
|
||||
#[test]
|
||||
fn the_gate_refuses_a_body_whose_sign_it_cannot_prove() {
|
||||
let mut k = Kernel::default();
|
||||
let b = crate::primitive::api_primitives::create_cylindrical_box(
|
||||
&mut k,
|
||||
Point3::ZERO,
|
||||
10.0,
|
||||
10.0,
|
||||
10.0 + 1e-6,
|
||||
0.0,
|
||||
90.0,
|
||||
)
|
||||
.unwrap();
|
||||
let input =
|
||||
validation::standing(&mut k, &mut b.clone_standing_for_self().unwrap()).unwrap();
|
||||
let e = refuse_unsound(&mut k, &b, &input).unwrap_err();
|
||||
assert!(
|
||||
matches!(
|
||||
e.failure().map(|f| &f.reason),
|
||||
Some(crate::error::Reason::Offset(OffsetFailure::Unsound {
|
||||
what: crate::error::offset::Unsoundness::VolumeUndecided
|
||||
}))
|
||||
),
|
||||
"{e}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A body whose pointer check fails is not checked further (`AssertValid` is skipped):
|
||||
/// the gate refuses it even where its input failed the pointer check alike, rather
|
||||
/// than finding nothing beyond the input's failures.
|
||||
|
||||
+56
-1
@@ -95,7 +95,7 @@ use crate::geom::extent1d::Extent1d;
|
||||
use crate::geom::extent2d::Extent2d;
|
||||
use crate::geom::object::{NotifyOperation, ObjectRef};
|
||||
use crate::heal::stitch::p1::{DefaultStitchCallback, Stitch, StitchCallback};
|
||||
use crate::heal::stitch::p2::MinGapRequest;
|
||||
use crate::heal::stitch::p2::{ClosedGap, MinGapRequest};
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::{Point2, Vec3, EFF_ZERO};
|
||||
use crate::nurbs::b_spline_curve_n_lib::TrimMode;
|
||||
@@ -1900,6 +1900,61 @@ pub(crate) fn stitch_faces_with_callback<'a, C: StitchCallback>(
|
||||
)
|
||||
}
|
||||
|
||||
/// [`stitch_faces_with_callback`] that additionally returns each gap the stitch
|
||||
/// closed, with the tolerance growth its closure applied (not upstream,
|
||||
/// solidcore#2975). The caller supplies the rollback boundary; certificate errors
|
||||
/// propagate regardless of the legacy edit-status flags.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`stitch_faces`] and geometry enclosure failures (unsupported curve
|
||||
/// kinds, invalid data or non-finite bounds).
|
||||
pub(crate) fn stitch_faces_with_gap_report(
|
||||
k: &mut Kernel,
|
||||
b: &mut Brep,
|
||||
xsect_tol_3d: f64,
|
||||
faces: Option<&[Option<FaceId>]>,
|
||||
allow_spine_edges: bool,
|
||||
identity: Option<crate::query::brep::BrepIdentity<'_>>,
|
||||
) -> Result<(StitchOutcome, Vec<ClosedGap>)> {
|
||||
// lock brep bounding box, until goes out of scope
|
||||
let mut lockit = LockBrepBox::new(k, b)?;
|
||||
let b = lockit.brep();
|
||||
|
||||
// construct Stitch manager with the default keep/delete logic for glue pairs,
|
||||
// recording each closure
|
||||
let mut callback = DefaultStitchCallback::new();
|
||||
let mut stitch = Stitch::new(&mut callback, xsect_tol_3d, true, true);
|
||||
stitch.query_identity = identity;
|
||||
stitch.fast_edge_compare = true;
|
||||
stitch.ignore_problems = true;
|
||||
stitch.squeeze_small_edges = true;
|
||||
stitch.making_manifold_solid = !allow_spine_edges;
|
||||
stitch.remove_laminar_slivers = true;
|
||||
stitch.report_gaps = Some(Vec::new());
|
||||
|
||||
let mut lists = None;
|
||||
if let Some(faces) = faces {
|
||||
let mut vertices = Vec::new();
|
||||
let mut edges = Vec::new();
|
||||
for f in faces.iter().flatten() {
|
||||
vertices.extend(face_vertices(b, *f)?);
|
||||
edges.extend(face_edges(b, *f)?);
|
||||
}
|
||||
lists = Some((vertices, edges));
|
||||
}
|
||||
// pass the stitch call along (SER: its status is returned as is)
|
||||
let outcome = do_stitching(
|
||||
k,
|
||||
b,
|
||||
&mut stitch,
|
||||
lists.as_ref().map(|(v, _)| v.as_slice()),
|
||||
lists.as_ref().map(|(_, e)| e.as_slice()),
|
||||
)?;
|
||||
let gaps = stitch.take_gaps();
|
||||
Ok((outcome, gaps))
|
||||
}
|
||||
|
||||
// ---- ShrinkGeometry ------------------------------------------------------------------------
|
||||
|
||||
/// `SmBrep::ShrinkGeometry`: shrinks each face's UV domain to its trim curves (snapped
|
||||
|
||||
@@ -78,6 +78,7 @@ use crate::geom::extent1d::Extent1d;
|
||||
use crate::geom::extent3d::Extent3d;
|
||||
use crate::geom::object::{NotifyOperation, ObjectRef};
|
||||
use crate::geom::tol::ZONE_TOL_3D;
|
||||
use crate::heal::stitch::p2::ClosedGap;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::{max2, Point3, Vec3};
|
||||
use crate::nurbs::b_spline_curve::BSplineCurve;
|
||||
@@ -2662,6 +2663,103 @@ fn stitch_faces(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// [`stitch_faces`] that also returns what the stitch closed: each gap, the
|
||||
/// entities, and the tolerance growth its closure applied (`solidcore#2975`).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`stitch_faces`].
|
||||
fn stitch_faces_reported(
|
||||
k: &mut Kernel,
|
||||
b: &mut Brep,
|
||||
tolerance: f64,
|
||||
allow_spine: bool,
|
||||
lamina: &mut usize,
|
||||
gaps: &mut Vec<ClosedGap>,
|
||||
) -> Result<()> {
|
||||
let (out, pass_gaps) = crate::topology::brep::p4::stitch_faces_with_gap_report(
|
||||
k,
|
||||
b,
|
||||
tolerance,
|
||||
None,
|
||||
allow_spine,
|
||||
None,
|
||||
)?;
|
||||
gaps.extend(pass_gaps);
|
||||
*lamina = usize::try_from(out.lamina_edges)
|
||||
.map_err(|_| SolidError::invalid_input(OP, "lamina edge count"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// [`stitch_and_orient`] that additionally reports what the stitch closed
|
||||
/// (`solidcore#2975`): for every gap it closed, the entities it glued, the gap it
|
||||
/// enclosed, and the tolerance growth its closure applied. Geometry bounds are
|
||||
/// independent of the stitcher's sampled acceptance test and do not feed back
|
||||
/// into the construction. A caller decides whether the returned bounds suffice.
|
||||
/// On any error the B-rep is restored, including when certification fails after
|
||||
/// earlier closures. Errors are never suppressed by legacy status flags.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// As [`stitch_and_orient`], plus [`SolidError::Unsupported`] for curve kinds
|
||||
/// without a proved enclosure and [`SolidError::InvalidInput`] for invalid data
|
||||
/// or a non-finite enclosure. No partial report or partial edit is returned.
|
||||
pub fn stitch_and_orient_reported(
|
||||
k: &mut Kernel,
|
||||
b: &mut Brep,
|
||||
options: &StitchAndOrientOptions,
|
||||
) -> Result<Vec<ClosedGap>> {
|
||||
k.check_cancelled(OP)?;
|
||||
b.with_rollback(
|
||||
|b| stitch_and_orient_reported_inner(k, b, options),
|
||||
Result::is_ok,
|
||||
)
|
||||
}
|
||||
|
||||
fn stitch_and_orient_reported_inner(
|
||||
k: &mut Kernel,
|
||||
b: &mut Brep,
|
||||
options: &StitchAndOrientOptions,
|
||||
) -> Result<Vec<ClosedGap>> {
|
||||
let mut lamina = 0;
|
||||
let mut gaps = Vec::new();
|
||||
let mut tolerance = initial_stitch_tolerance(b.tolerance(), options.stitch_tolerance);
|
||||
stitch_faces_reported(
|
||||
k,
|
||||
b,
|
||||
tolerance,
|
||||
options.allow_spine_edges,
|
||||
&mut lamina,
|
||||
&mut gaps,
|
||||
)?;
|
||||
if lamina > 1 {
|
||||
tolerance = if options.stitch_tolerance <= 0.0 {
|
||||
tolerance * 20.0
|
||||
} else {
|
||||
tolerance * 10.0
|
||||
};
|
||||
stitch_faces_reported(
|
||||
k,
|
||||
b,
|
||||
tolerance,
|
||||
options.allow_spine_edges,
|
||||
&mut lamina,
|
||||
&mut gaps,
|
||||
)?;
|
||||
}
|
||||
plain_edit(k, b, |k, b, env| {
|
||||
orient_trimmed_surfaces(
|
||||
k,
|
||||
b,
|
||||
Some(env),
|
||||
true,
|
||||
options.just_unify.unwrap_or(lamina > 0),
|
||||
options.faces_to_swap.as_deref(),
|
||||
)
|
||||
})?;
|
||||
Ok(gaps)
|
||||
}
|
||||
|
||||
/// `SmBrep::OrientTrimmedSurfaces(bNormalsOutward, bJustUnifyNormals, pFacesToSwapUV)`
|
||||
/// ([`crate::topology::brep::p3::orient_trimmed_surfaces`]), notifying as
|
||||
/// [`with_p3_notifications`].
|
||||
|
||||
@@ -34,7 +34,7 @@ pub use coincidence::{contains_coincident_topology, CoincidenceResult};
|
||||
pub(crate) use pointers::validate_geometry;
|
||||
pub use pointers::validate_pointers;
|
||||
pub(crate) use soundness::lost_manifold;
|
||||
pub use soundness::{soundness, standing, volume, Soundness, Standing};
|
||||
pub use soundness::{soundness, standing, volume_sign, Soundness, Standing};
|
||||
|
||||
use crate::error::{EntityRef, Result, SolidError};
|
||||
use crate::geom::assert_array::{AssertArray, AssertReport, ObjectKey, ReportSpec};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! (not upstream) A body's soundness: its check at `SM_LEVEL_2`, and for a closed body,
|
||||
//! whether it is a manifold solid of positive volume. The local operation measures its
|
||||
//! input's [`standing`] (the check with the body's own owner naming, and the manifold
|
||||
//! test) and its result's, and the result's [`volume`] only to test it is positive
|
||||
//! test) and its result's, and the result's [`volume_sign`] only to test it is positive
|
||||
//! ([`crate::topology::brep::local_operation::local_operation`]); the goldens soundness
|
||||
//! probe (`goldens::soundness`) measures result bodies with [`soundness`] (the geometry's
|
||||
//! recorded owners).
|
||||
@@ -15,10 +15,7 @@ use crate::kernel::Kernel;
|
||||
use crate::topology::Brep;
|
||||
|
||||
use super::assert_valid::{check_brep_with_keys, BrepCheck, CheckLevel};
|
||||
|
||||
/// The relative accuracy the volume is integrated at: its sign is a verdict on the body,
|
||||
/// which a closed solid's whole volume holds away from zero.
|
||||
const VOLUME_ACCURACY: f64 = 1e-6;
|
||||
use crate::mass::enclosure::VolumeSign;
|
||||
|
||||
/// What [`soundness`] measured of one body.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -28,16 +25,17 @@ pub struct Soundness {
|
||||
pub check: BrepCheck,
|
||||
/// Whether the body is a manifold solid ([`crate::topology::brep::is_manifold_solid`]).
|
||||
pub manifold: bool,
|
||||
/// Its volume, when it is a manifold solid.
|
||||
pub volume: Option<f64>,
|
||||
/// Its volume's sign, when it is a manifold solid ([`volume_sign`]).
|
||||
pub volume: Option<VolumeSign>,
|
||||
}
|
||||
|
||||
impl Soundness {
|
||||
/// Whether the check is clean and, when the body is a manifold solid, its volume is
|
||||
/// positive (a NaN volume is not).
|
||||
/// proved positive (an undecided sign is not).
|
||||
#[must_use]
|
||||
pub fn sound(&self) -> bool {
|
||||
self.check.is_valid() && (!self.manifold || self.volume.is_some_and(|v| v > 0.0))
|
||||
self.check.is_valid()
|
||||
&& (!self.manifold || matches!(self.volume, Some(VolumeSign::Positive(_))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +97,11 @@ pub fn soundness(k: &mut Kernel, b: &mut Brep) -> Result<Soundness> {
|
||||
};
|
||||
let check = check_brep_with_keys(k, b, CheckLevel::Level2, &keys)?;
|
||||
let manifold = crate::topology::brep::p2::is_manifold_solid(b)?;
|
||||
let volume = if manifold { Some(volume(k, b)?) } else { None };
|
||||
let volume = if manifold {
|
||||
Some(volume_sign(k, b)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Soundness {
|
||||
check,
|
||||
manifold,
|
||||
@@ -111,7 +113,7 @@ pub fn soundness(k: &mut Kernel, b: &mut Brep) -> Result<Soundness> {
|
||||
/// geometry's owner the identity `b`'s naming scheme names the entity by
|
||||
/// ([`Brep::owner_identity`], as the operations that bind geometry set it), so a curve or
|
||||
/// surface owned under another entity's identity fails the owner tests; then the manifold
|
||||
/// test. Its volume is not measured ([`volume`]).
|
||||
/// test. Its volume is not measured ([`volume_sign`]).
|
||||
///
|
||||
/// As for [`soundness`], the check edits what it measures: measure a private copy.
|
||||
///
|
||||
@@ -151,11 +153,15 @@ pub(crate) fn lost_manifold(
|
||||
Ok(Some(crate::error::offset::Unsoundness::NotManifoldSolid))
|
||||
}
|
||||
|
||||
/// The volume of the manifold solid `b`, integrated at [`VOLUME_ACCURACY`].
|
||||
/// The sign of the manifold solid `b`'s volume, from its certified enclosure
|
||||
/// ([`crate::mass::enclosure::volume_sign`]): positive or negative where the enclosure
|
||||
/// proves it, undecided where it still holds 0 after its budget. A face whose net is
|
||||
/// certified within ε > 0 of its definition is read on its net: the sign is the stored
|
||||
/// body's (#3170 widens it by ε).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`crate::mass::integrator::properties::brep_volume_default`].
|
||||
pub fn volume(k: &mut Kernel, b: &mut Brep) -> Result<f64> {
|
||||
crate::mass::integrator::properties::brep_volume_default(k, b, VOLUME_ACCURACY)
|
||||
/// Those of [`crate::mass::enclosure::volume_sign`].
|
||||
pub fn volume_sign(k: &Kernel, b: &mut Brep) -> Result<VolumeSign> {
|
||||
crate::mass::enclosure::volume_sign(k, b)
|
||||
}
|
||||
|
||||
+45
-12
@@ -107,6 +107,10 @@
|
||||
//! `Kernel::fixes.imprint_extends_edge_to_proto_vertex`, [`ProtoTopologyManager::imprint_edge_definition`]
|
||||
//! first fits each end of the definition's interval to its proto vertex (`imprint_ends`: the SMLib
|
||||
//! fork's thread-groove fixes, tangency-labs/smlib b43876f, a8caccd).
|
||||
//! - **Not upstream: one trim interval** (WR-767, tangency-labs/solidcore#3214).
|
||||
//! [`ProtoTopologyManager::create_proto_edge`] trims the 3D curve and its uv curves to the
|
||||
//! one interval none of their trims snaps (`exact_pieces::common_trim_interval`); upstream
|
||||
//! trims them in turn with one interval, and the edge can end past its 3D curve.
|
||||
//! - `UpdateEdges` trims a definition at the split point; its alternative of splitting
|
||||
//! the proto edge (`SmProtoEdge::Split`) sits behind `static constexpr SmBoolean
|
||||
//! cbiDoSplit = FALSE` and is not ported.
|
||||
@@ -140,6 +144,7 @@ use crate::math::{max2, max3, max4, min2, Point2, Point3, Vec3, EFF_ZERO};
|
||||
use crate::nurbs::b_spline_curve_n_lib::TrimMode;
|
||||
use crate::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use crate::query::face::p2::proto_vertex_containment_radius;
|
||||
use crate::surface::surface::exact_pieces::{common_trim_interval, trim_unsnapped};
|
||||
use crate::topology::brep::p5::{make_edge_in_face_in, EdgeInFace};
|
||||
use crate::topology::VertexId;
|
||||
|
||||
@@ -2211,26 +2216,54 @@ impl<O: ClassObject> ProtoTopologyManager<O> {
|
||||
let p3d_curve = cc1.curve();
|
||||
let _ = cc2.curve() == p3d_curve;
|
||||
|
||||
// Create a copy of the input curve and trim it to this CurveInterval.
|
||||
// Create a copy of the input curve and of its uv curves (copy statuses ignored), and
|
||||
// trim them to this CurveInterval.
|
||||
let mut copy = curve_of(brep, p3d_curve)?.copy(k)?;
|
||||
let mut domain = ci1.interval;
|
||||
copy.trim(k, &mut domain, TrimMode::Notify)?;
|
||||
|
||||
// See about uv curves (copy statuses ignored; the trims' passed on with
|
||||
// `ssi_proto_topology_statuses_pass_on`, fork bcc6f01).
|
||||
let pass = k.fixes.ssi_proto_topology_statuses_pass_on;
|
||||
let mut uv1 = None;
|
||||
if let Some(c) = cc1.uv_curve1() {
|
||||
uv1 = ignore_soft(curve_of(brep, Some(c))?.copy(k))?;
|
||||
if let Some(u) = uv1.as_mut() {
|
||||
ignore_soft(passed_on(u.trim(k, &mut domain, TrimMode::Notify), pass)?)?;
|
||||
}
|
||||
}
|
||||
let mut uv2 = None;
|
||||
if let Some(c) = cc2.uv_curve1() {
|
||||
uv2 = ignore_soft(curve_of(brep, Some(c))?.copy(k))?;
|
||||
if let Some(u) = uv2.as_mut() {
|
||||
ignore_soft(passed_on(u.trim(k, &mut domain, TrimMode::Notify), pass)?)?;
|
||||
}
|
||||
|
||||
// The uv curves' trim statuses are ignored, and passed on with
|
||||
// `ssi_proto_topology_statuses_pass_on` (fork bcc6f01). Not upstream (WR-767): the
|
||||
// 3D curve and the uv curves whose trims accept the interval are trimmed to the one
|
||||
// interval none of their trims snaps ([`common_trim_interval`]). Upstream trims them
|
||||
// in turn with one interval, each trim snapping it onto a knot of its own curve, and
|
||||
// the edge takes the interval the last trim left: a uv curve's knot a few ulps from
|
||||
// the 3D curve's trim parameter leaves the edge's interval outside its 3D curve's
|
||||
// (`SmEdge` test 2). A uv curve whose trim refuses the interval is trimmed alone, as
|
||||
// upstream.
|
||||
let pass = k.fixes.ssi_proto_topology_statuses_pass_on;
|
||||
let mut joins = [false; 2];
|
||||
for (u, join) in [&uv1, &uv2].into_iter().zip(&mut joins) {
|
||||
if let Some(u) = u {
|
||||
let mut probe = ci1.interval;
|
||||
let trimmed = u.copy(k)?.trim(k, &mut probe, TrimMode::IntervalOnly);
|
||||
*join = ignore_soft(passed_on(trimmed, pass)?)?.is_some();
|
||||
}
|
||||
}
|
||||
let joined: Vec<&Curve> = std::iter::once(©)
|
||||
.chain(
|
||||
[&uv1, &uv2]
|
||||
.into_iter()
|
||||
.zip(joins)
|
||||
.filter_map(|(u, join)| u.as_ref().filter(|_| join)),
|
||||
)
|
||||
.collect();
|
||||
let domain = common_trim_interval(k, &joined, ci1.interval, OP_CREATE_PE)?;
|
||||
trim_unsnapped(k, &mut copy, domain, OP_CREATE_PE)?;
|
||||
for (u, join) in [&mut uv1, &mut uv2].into_iter().zip(joins) {
|
||||
if let Some(u) = u.as_mut() {
|
||||
let trimmed = if join {
|
||||
trim_unsnapped(k, u, domain, OP_CREATE_PE)
|
||||
} else {
|
||||
u.trim(k, &mut domain.clone(), TrimMode::Notify)
|
||||
};
|
||||
ignore_soft(passed_on(trimmed, pass)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
//! tangency-labs/solidcore#3091 (from tangency-labs/solidcraft#12858): a cut whose flat face is
|
||||
//! exactly tangent to a cylindrical boss comes back with no faces, or fails with `SM_ERR`.
|
||||
//!
|
||||
//! The part is a 43.5 x 36 x 10 plate with a boss on top: a cylinder of radius
|
||||
//! `R = sqrt(12.68)` (3.5609) at (27.2, 16), z 10..20. The tool is a D-shaped prism swept along
|
||||
//! +y through the whole part: its profile, in the XZ plane, is the chord `x = 27.2 - R` from
|
||||
//! z = 12.5435 to z = 17.6134 closed by an arc of radius 2.56398 about (23.2544, 15.0785) that
|
||||
//! bulges towards +x, into the boss. The chord's face, the plane `x = 27.2 - R`, touches the boss
|
||||
//! cylinder along the line `y = 16` without crossing it; the arc face cuts a notch out of the
|
||||
//! boss.
|
||||
//!
|
||||
//! In SolidCraft 0.40.0-rc.2 (solidcore 8ddce9016) the difference returns a body with no faces
|
||||
//! and the application deletes the part. The chord moved 0.001 mm either way (crossing the
|
||||
//! cylinder, or clear of it) cuts normally.
|
||||
//!
|
||||
//! The expected volume is the plate plus the boss less the notch: the notch is the integral over
|
||||
//! the D of the boss's chord along y, `2 sqrt(R^2 - (x - 27.2)^2)`, which lies wholly in the boss
|
||||
//! (the D's z range is inside 10..20 and its x range starts at the silhouette). With
|
||||
//! `x = v0 + r cos t` it is `int_0^a 4 r^2 sin^2 t sqrt(R^2 - (x - 27.2)^2) dt`,
|
||||
//! `a = acos((xch - v0) / r)`: 36.5850571 (Simpson, 4e5 panels), so `V = 16058.3539 - 36.5851 =
|
||||
//! 16021.7689`.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing
|
||||
)] // test helpers
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use solidcore::boolean::api_brep::{boolean_difference, boolean_union};
|
||||
use solidcore::curve::curve::Curve;
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::mass::integrator::properties::{brep_volume, PropertyAttributes};
|
||||
use solidcore::math::{Point3, Vec3};
|
||||
use solidcore::primitive::api_curves::{create_arc, create_line_segment};
|
||||
use solidcore::primitive::api_primitives::{create_box, create_cylinder};
|
||||
use solidcore::sweep::api_brep::create_linear_sweep;
|
||||
use solidcore::topology::brep::p1::faces;
|
||||
use solidcore::topology::brep::p2::is_manifold_solid;
|
||||
use solidcore::topology::proto_topology::p1::api_transform::{
|
||||
api_rotate, api_translate, TransformObject,
|
||||
};
|
||||
use solidcore::{Brep, Kernel};
|
||||
|
||||
/// The boss: centre and radius (`R^2 = 12.68`).
|
||||
const BOSS_X: f64 = 27.2;
|
||||
const BOSS_Y: f64 = 16.0;
|
||||
const BOSS_R: f64 = 3.560_898_762_952_973_6;
|
||||
/// The D's arc: centre (x, z) and radius, from the design's sketch.
|
||||
const ARC_X: f64 = 23.254_420_118_496_817;
|
||||
const ARC_Z: f64 = 15.078_456_991_201_652;
|
||||
const ARC_R: f64 = 2.563_975_512_604_723_5;
|
||||
|
||||
/// Volume of the plate and the boss.
|
||||
fn part_volume() -> f64 {
|
||||
43.5 * 36.0 * 10.0 + PI * BOSS_R * BOSS_R * 10.0
|
||||
}
|
||||
|
||||
/// Volume of the notch the D cuts out of the boss when its chord is at `x = xch`
|
||||
/// (the module doc's integral, Simpson with 4e5 panels in t).
|
||||
fn notch_volume(xch: f64) -> f64 {
|
||||
let a = ((xch - ARC_X) / ARC_R).acos();
|
||||
let n = 400_000;
|
||||
let mut s = 0.0;
|
||||
for i in 0..=n {
|
||||
let t = a * f64::from(i) / f64::from(n);
|
||||
let x = ARC_X + ARC_R * t.cos();
|
||||
let g = (BOSS_R * BOSS_R - (x - BOSS_X).powi(2)).max(0.0);
|
||||
let w = if i == 0 || i == n {
|
||||
1.0
|
||||
} else if i % 2 == 1 {
|
||||
4.0
|
||||
} else {
|
||||
2.0
|
||||
};
|
||||
s += w * 4.0 * ARC_R * ARC_R * t.sin() * t.sin() * g.sqrt();
|
||||
}
|
||||
s * a / f64::from(n) / 3.0
|
||||
}
|
||||
|
||||
/// The plate with its boss.
|
||||
fn part(k: &mut Kernel) -> Brep {
|
||||
let plate = create_box(k, Point3::new(0.0, 0.0, 0.0), 43.5, 36.0, 10.0).unwrap();
|
||||
let boss = create_cylinder(k, Point3::new(BOSS_X, BOSS_Y, 10.0), BOSS_R, 10.0).unwrap();
|
||||
boolean_union(k, plate, boss).unwrap()
|
||||
}
|
||||
|
||||
/// The D prism: chord at `x = xch`, swept along +y from `y0` by `length`.
|
||||
fn tool(k: &mut Kernel, xch: f64, y0: f64, length: f64) -> Brep {
|
||||
// The profile in the XY plane with u = x and v = z, then turned 90 degrees about X
|
||||
// (u stays x, v becomes z) and moved to y = y0.
|
||||
let h = (ARC_R * ARC_R - (xch - ARC_X).powi(2)).sqrt();
|
||||
let lo = Vec3::new(xch, ARC_Z - h, 0.0);
|
||||
let hi = Vec3::new(xch, ARC_Z + h, 0.0);
|
||||
let start = (ARC_Z - h - ARC_Z).atan2(xch - ARC_X).to_degrees();
|
||||
let end = (ARC_Z + h - ARC_Z).atan2(xch - ARC_X).to_degrees();
|
||||
let chord = Curve::from(create_line_segment(lo, hi).unwrap());
|
||||
let arc = Curve::from(create_arc(Vec3::new(ARC_X, ARC_Z, 0.0), ARC_R, start, end).unwrap());
|
||||
let mut curves = [chord, arc];
|
||||
for c in &mut curves {
|
||||
api_rotate(
|
||||
k,
|
||||
Some(TransformObject::Curve(c)),
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::X,
|
||||
90.0,
|
||||
)
|
||||
.unwrap();
|
||||
api_translate(k, Some(TransformObject::Curve(c)), Vec3::new(0.0, y0, 0.0)).unwrap();
|
||||
}
|
||||
let refs: Vec<Option<&Curve>> = curves.iter().map(Some).collect();
|
||||
create_linear_sweep(k, &refs, Vec3::new(0.0, 1.0, 0.0), length, true).unwrap()
|
||||
}
|
||||
|
||||
fn volume(k: &Kernel, b: &mut Brep) -> f64 {
|
||||
let store = AttributeStore::new();
|
||||
let key_of = |_| ObjectKey(0);
|
||||
let attrs = PropertyAttributes {
|
||||
store: &store,
|
||||
key_of: &key_of,
|
||||
};
|
||||
brep_volume(k, b, 1e-7, &attrs).unwrap()
|
||||
}
|
||||
|
||||
/// SolidCraft's fix set (`tests/kernel_cases/fixes.json`), on `k`.
|
||||
fn set_solidcraft_fixes(k: &mut Kernel) {
|
||||
let names: Vec<String> = serde_json::from_str(include_str!("kernel_cases/fixes.json")).unwrap();
|
||||
for name in &names {
|
||||
*k.fixes
|
||||
.flag_mut(name)
|
||||
.unwrap_or_else(|| panic!("unknown fix {name}")) = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cuts the D prism with its chord at `xch` out of the part and checks the result: a closed
|
||||
/// manifold solid of the expected volume.
|
||||
fn cut(xch: f64, y0: f64, length: f64, solidcraft_fixes: bool) -> Result<(), String> {
|
||||
let mut k = Kernel::default();
|
||||
if solidcraft_fixes {
|
||||
set_solidcraft_fixes(&mut k);
|
||||
}
|
||||
let part = part(&mut k);
|
||||
let tool = tool(&mut k, xch, y0, length);
|
||||
let mut result =
|
||||
boolean_difference(&mut k, part, tool).map_err(|e| format!("difference: {e}"))?;
|
||||
result.refresh_cached_topology();
|
||||
let n = faces(&result, None).map_err(|e| e.to_string())?.len();
|
||||
if n == 0 {
|
||||
return Err("the result has no faces".into());
|
||||
}
|
||||
if !is_manifold_solid(&result).map_err(|e| e.to_string())? {
|
||||
return Err(format!(
|
||||
"the result ({n} faces) is not a closed manifold solid"
|
||||
));
|
||||
}
|
||||
let want = part_volume() - notch_volume(xch);
|
||||
let v = volume(&k, &mut result);
|
||||
// 0.02 mm^3 on 16022: the integrator's 1e-7 relative accuracy is 1.6e-3.
|
||||
if (v - want).abs() > 0.02 {
|
||||
return Err(format!("volume {v} vs {want} ({n} faces)"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The chord exactly on the boss's silhouette, `x = 27.2 - R`.
|
||||
fn tangent_chord() -> f64 {
|
||||
BOSS_X - BOSS_R
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chord_tangent_to_the_boss_cuts_the_notch() {
|
||||
let runs = [
|
||||
// SolidCraft's through-all tool starts on the sketch plane, the plate's front face,
|
||||
// and runs well past the part (its reach: about 88 mm here).
|
||||
("from the front face, SolidCraft's fixes", 0.0, 88.0, true),
|
||||
("through both faces, SolidCraft's fixes", -2.0, 40.0, true),
|
||||
("from the front face, no fixes", 0.0, 88.0, false),
|
||||
("through both faces, no fixes", -2.0, 40.0, false),
|
||||
];
|
||||
let failures: Vec<String> = runs
|
||||
.iter()
|
||||
.filter_map(|(what, y0, len, fixes)| {
|
||||
cut(tangent_chord(), *y0, *len, *fixes)
|
||||
.err()
|
||||
.map(|e| format!("{what}: {e}"))
|
||||
})
|
||||
.collect();
|
||||
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chord_a_hair_into_or_clear_of_the_boss_cuts_the_notch() {
|
||||
let failures: Vec<String> = [-0.001, 0.001]
|
||||
.iter()
|
||||
.flat_map(|d| [(d, true), (d, false)])
|
||||
.filter_map(|(d, fixes)| {
|
||||
cut(tangent_chord() + d, -2.0, 40.0, fixes)
|
||||
.err()
|
||||
.map(|e| format!("chord moved {d} (fixes {fixes}): {e}"))
|
||||
})
|
||||
.collect();
|
||||
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//! #3144: whether two bodies meet, decided by a certified bracket of their boundary distance
|
||||
//! (`query::contact`), against closed forms: boxes touching on a face, apart by 1e-9,
|
||||
//! overlapping in a face, an edge resting on a face, interpenetrating, one inside the other,
|
||||
//! far apart; spheres apart and crossing; a cylinder crossing a box's face and one against a
|
||||
//! side along a line; a peg through a hole that it clears.
|
||||
|
||||
#![allow(clippy::unwrap_used, clippy::panic, clippy::float_cmp)] // test
|
||||
|
||||
use solidcore::boolean::api_brep::boolean_difference;
|
||||
use solidcore::geom::certified::Verdict;
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::primitive::api_primitives::{create_box, create_cylinder, create_sphere};
|
||||
use solidcore::query::contact::{boundary_distance, meets};
|
||||
use solidcore::topology::brep::mirror::transform_matrix;
|
||||
use solidcore::{Brep, Point3};
|
||||
|
||||
fn k() -> Kernel {
|
||||
Kernel {
|
||||
fixes: solidcore::kernel::UpstreamFixes::all(),
|
||||
..Kernel::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn cube(k: &mut Kernel, at: [f64; 3], size: f64) -> Brep {
|
||||
create_box(k, Point3::new(at[0], at[1], at[2]), size, size, size).unwrap()
|
||||
}
|
||||
|
||||
/// `b` turned by `angle` about the x axis, then moved by `by`.
|
||||
fn turned_about_x(k: &mut Kernel, b: &mut Brep, angle: f64, by: [f64; 3]) {
|
||||
let (c, s) = (angle.cos(), angle.sin());
|
||||
// row vectors (`p' = p A + t`): A's rows are the images of x, y and z, t the fourth row
|
||||
let m = [
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, c, s, 0.0],
|
||||
[0.0, -s, c, 0.0],
|
||||
[by[0], by[1], by[2], 1.0],
|
||||
];
|
||||
transform_matrix(k, b, &m, true).unwrap();
|
||||
}
|
||||
|
||||
/// Asserts the verdict at `tol`, the bracket shown on failure.
|
||||
fn verdict_is(k: &Kernel, a: &mut Brep, b: &mut Brep, tol: f64, want: Verdict) {
|
||||
let c = meets(k, a, b, tol).unwrap();
|
||||
assert!(c.lower <= c.upper, "{c:?}");
|
||||
assert_eq!(c.verdict, want, "at {tol}: {c:?}");
|
||||
}
|
||||
|
||||
/// The bracket of the boundary distance, asserted to hold `exact`.
|
||||
fn holds(k: &Kernel, a: &mut Brep, b: &mut Brep, exact: f64) {
|
||||
let c = boundary_distance(k, a, b, 1e-6).unwrap();
|
||||
assert_eq!(c.verdict, Verdict::Yes, "{c:?}");
|
||||
assert!(c.lower <= exact && exact <= c.upper, "{exact} not in {c:?}");
|
||||
assert!(c.upper - c.lower <= 1e-6 * c.upper.max(1.0), "{c:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_touching_on_a_face_meet_at_any_tolerance() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = cube(&mut k, [1.0, 0.0, 0.0], 1.0);
|
||||
let c = meets(&k, &mut a, &mut b, 1e-12).unwrap();
|
||||
assert_eq!(c.verdict, Verdict::Yes, "{c:?}");
|
||||
let [p, q] = c.witness.unwrap();
|
||||
assert!(
|
||||
p.distance(q) <= 1e-12 && (p.x - 1.0).abs() <= 1e-12,
|
||||
"{c:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_apart_by_a_nanometre_meet_only_at_a_tolerance_past_it() {
|
||||
let mut k = k();
|
||||
// the gap as the doubles have it
|
||||
let gap = (1.0 + 1e-9) - 1.0;
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = cube(&mut k, [1.0 + 1e-9, 0.0, 0.0], 1.0);
|
||||
verdict_is(&k, &mut a, &mut b, 1e-8, Verdict::Yes);
|
||||
verdict_is(&k, &mut a, &mut b, 1e-10, Verdict::No);
|
||||
holds(&k, &mut a, &mut b, gap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_overlapping_in_a_face_meet() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = cube(&mut k, [1.0, 0.5, 0.5], 1.0);
|
||||
verdict_is(&k, &mut a, &mut b, 1e-12, Verdict::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_edge_resting_on_a_face_meets_it() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
// a cube centred on the x axis, turned 45 degrees about it (either way: it is square), is
|
||||
// a diamond whose lowest edge is at z = -sqrt(2)/2; moved onto a's top face
|
||||
let mut b = cube(&mut k, [0.0, -0.5, -0.5], 1.0);
|
||||
let half = std::f64::consts::FRAC_1_SQRT_2;
|
||||
turned_about_x(
|
||||
&mut k,
|
||||
&mut b,
|
||||
std::f64::consts::FRAC_PI_4,
|
||||
[0.0, 0.5, 1.0 + half],
|
||||
);
|
||||
let c = meets(&k, &mut a, &mut b, 1e-12).unwrap();
|
||||
assert_eq!(c.verdict, Verdict::Yes, "{c:?}");
|
||||
let [p, _] = c.witness.unwrap();
|
||||
assert!((p.z - 1.0).abs() <= 1e-9, "{c:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interpenetrating_boxes_meet() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = cube(&mut k, [0.5; 3], 1.0);
|
||||
verdict_is(&k, &mut a, &mut b, 1e-12, Verdict::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_inside_another_meets_it_and_their_walls_are_one_apart() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 3.0);
|
||||
let mut b = cube(&mut k, [1.0; 3], 1.0);
|
||||
verdict_is(&k, &mut a, &mut b, 1e-3, Verdict::Yes);
|
||||
verdict_is(&k, &mut b, &mut a, 1e-3, Verdict::Yes);
|
||||
holds(&k, &mut a, &mut b, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_far_apart_do_not_meet_and_their_gap_is_bracketed() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = cube(&mut k, [5.0, 0.0, 0.0], 1.0);
|
||||
verdict_is(&k, &mut a, &mut b, 0.1, Verdict::No);
|
||||
holds(&k, &mut a, &mut b, 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spheres_apart_are_their_centres_gap_less_their_radii() {
|
||||
let mut k = k();
|
||||
let mut a = create_sphere(&mut k, Point3::new(0.0, 0.0, 0.0), 1.0).unwrap();
|
||||
let mut b = create_sphere(&mut k, Point3::new(3.0, 0.0, 0.0), 1.0).unwrap();
|
||||
verdict_is(&k, &mut a, &mut b, 0.5, Verdict::No);
|
||||
verdict_is(&k, &mut a, &mut b, 2.0, Verdict::Yes);
|
||||
holds(&k, &mut a, &mut b, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crossing_spheres_meet_where_their_surfaces_cross() {
|
||||
let mut k = k();
|
||||
let mut a = create_sphere(&mut k, Point3::new(0.0, 0.0, 0.0), 1.0).unwrap();
|
||||
let mut b = create_sphere(&mut k, Point3::new(1.5, 0.0, 0.0), 1.0).unwrap();
|
||||
let c = meets(&k, &mut a, &mut b, 1e-9).unwrap();
|
||||
assert_eq!(c.verdict, Verdict::Yes, "{c:?}");
|
||||
// the witness is on the circle where they cross: x = 0.75, |p| = 1
|
||||
let [p, _] = c.witness.unwrap();
|
||||
assert!((p.x - 0.75).abs() <= 1e-6, "{c:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cylinder_crossing_a_box_face_meets_the_box() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = create_cylinder(&mut k, Point3::new(0.5, 0.5, 0.5), 0.3, 1.0).unwrap();
|
||||
verdict_is(&k, &mut a, &mut b, 1e-9, Verdict::Yes);
|
||||
}
|
||||
|
||||
/// A cylinder touching a box side along a line meets it; moved 0.25 off, its bracket holds
|
||||
/// 0.25 and the verdicts either side of it are exact.
|
||||
///
|
||||
/// The bracket is not 1e-6 wide here: trimmed rim pieces stall `lower` at 0.25 - 0.0013 (the
|
||||
/// end caps' pieces at the rim straddle the circular trim, and each hull carries a sliver of
|
||||
/// the cap's plane outside the disk toward the box; their subdivision runs out of pairs
|
||||
/// first). The bracket contains the truth; verdicts outside [0.2487, 0.25] are exact. The
|
||||
/// width asserted is what the pair budget certifies, not a target.
|
||||
#[test]
|
||||
fn a_cylinder_against_a_box_side_meets_it_along_a_line() {
|
||||
let mut k = k();
|
||||
let mut a = cube(&mut k, [0.0; 3], 1.0);
|
||||
let mut b = create_cylinder(&mut k, Point3::new(1.5, 0.5, 0.0), 0.5, 1.0).unwrap();
|
||||
verdict_is(&k, &mut a, &mut b, 1e-9, Verdict::Yes);
|
||||
let mut far = create_cylinder(&mut k, Point3::new(1.75, 0.5, 0.0), 0.5, 1.0).unwrap();
|
||||
verdict_is(&k, &mut a, &mut far, 0.24, Verdict::No);
|
||||
verdict_is(&k, &mut a, &mut far, 0.26, Verdict::Yes);
|
||||
let c = boundary_distance(&k, &mut a, &mut far, 1e-6).unwrap();
|
||||
assert!(c.lower <= 0.25 && 0.25 <= c.upper, "{c:?}");
|
||||
assert!(c.lower >= 0.2487 && c.upper - 0.25 <= 1e-12, "{c:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_peg_through_a_hole_it_clears_does_not_meet_the_plate() {
|
||||
let mut k = k();
|
||||
// a 4 x 4 x 1 plate with a hole of radius 1 through its middle, and a peg of radius 0.75
|
||||
// down the hole's axis: the peg clears the hole's wall by 0.25
|
||||
let plate = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 4.0, 4.0, 1.0).unwrap();
|
||||
let hole = create_cylinder(&mut k, Point3::new(2.0, 2.0, -1.0), 1.0, 3.0).unwrap();
|
||||
let mut plate = boolean_difference(&mut k, plate, hole).unwrap();
|
||||
let mut peg = create_cylinder(&mut k, Point3::new(2.0, 2.0, -2.0), 0.75, 5.0).unwrap();
|
||||
verdict_is(&k, &mut plate, &mut peg, 0.1, Verdict::No);
|
||||
holds(&k, &mut plate, &mut peg, 0.25);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
//! A cylinder wall of a short arc far from the origin, extended 1500 mm along its rulings and
|
||||
//! round its axis. The iso that certifies the turn is interpolated between rulings 3 m long.
|
||||
//! At 500 mm the certificate took its rounding at the iso's own size and refused; at 1500 mm a
|
||||
//! weight ratio per row, differing by the continuation's rounding, took the iso out of its
|
||||
//! plane by 1.8e-10. Every point of the result stays on the cylinder, radius 80 about its axis.
|
||||
//! A cylinder wall of a short arc far from the origin, extended 500 and 1500 mm along its
|
||||
//! rulings and round its axis. The iso that certifies the turn is interpolated between rulings
|
||||
//! up to 3 m long: a weight ratio per row, differing by the continuation's rounding, took it out
|
||||
//! of its plane by 1.8e-10, and the certificate refused the extension as not exact. Every point
|
||||
//! of the result stays on the cylinder, radius 80 about its axis.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
@@ -16,9 +16,11 @@ use solidcore::kernel::UpstreamFixes;
|
||||
use solidcore::math::{Point2, Point3, Vec3};
|
||||
use solidcore::primitive::api_curves as curves;
|
||||
use solidcore::primitive::api_primitives::create_box;
|
||||
use solidcore::surface::cone::p1::Cone;
|
||||
use solidcore::surface::extend::Continuity;
|
||||
use solidcore::surface::implicit::AnalyticSupport;
|
||||
use solidcore::surface::side::BoxSide;
|
||||
use solidcore::surface::surface::Surface;
|
||||
use solidcore::sweep::api_brep::create_linear_sweep;
|
||||
use solidcore::topology::brep::p1 as bp1;
|
||||
use solidcore::{Brep, Kernel};
|
||||
@@ -108,3 +110,46 @@ fn a_short_arc_extends_along_long_rulings_on_its_cylinder() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A cylinder of radius 1, 60 degrees round and 1 high at the origin, extended along its
|
||||
/// rulings and round its axis by up to 1e5: the frame's size stays the source's (about 1)
|
||||
/// while the rows grow to the extension, so this is where a limit at the iso's own size
|
||||
/// could fall under its rounding. Every point stays on the cylinder.
|
||||
#[test]
|
||||
fn a_small_arc_extends_far_beyond_its_size_on_its_cylinder() {
|
||||
let k = Kernel::default();
|
||||
let wall = Surface::from(Cone::new_cylinder_bounded(
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
1.0,
|
||||
0.0,
|
||||
60.0,
|
||||
1.0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
for d in [1e2, 1e3, 1e4, 1e5] {
|
||||
let grown = wall
|
||||
.extended_sides_keeping_parameters(&k, &BoxSide::ALL, Continuity::Ruled, d)
|
||||
.unwrap_or_else(|e| panic!("{d}: {e}"))
|
||||
.surface;
|
||||
let dom = grown.natural_uv_domain_with_nurb(&k).unwrap();
|
||||
let (u, v) = (dom.u_interval(), dom.v_interval());
|
||||
for i in 0..=8 {
|
||||
for j in 0..=8 {
|
||||
let uv = Point2::new(
|
||||
u.min() + u.length() * f64::from(i) / 8.0,
|
||||
v.min() + v.length() * f64::from(j) / 8.0,
|
||||
);
|
||||
let x = grown.evaluate_point(&k, uv).unwrap();
|
||||
let r = x.x.hypot(x.y);
|
||||
assert!(
|
||||
(r - 1.0).abs() < 1e-9 * (1.0 + x.z.abs()),
|
||||
"{d} {i} {j} {r}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
//! A sphere's NURBS form is recognised as its sphere far from the origin, at any radius and
|
||||
//! orientation, and the half-meridian test allows the angles' rounding at the points' size,
|
||||
//! no more (#3131). A sphere of radius 2.5 at 1e5 reads 180 + 6.7e-10 degrees, past the fixed
|
||||
//! half-meridian tolerance, and was not recognised.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing
|
||||
)] // test helpers
|
||||
|
||||
use solidcore::blend::fillet_section::station::as_b_spline;
|
||||
use solidcore::curve::curve::Curve;
|
||||
use solidcore::geom::axis2_placement::Axis2Placement;
|
||||
use solidcore::geom::extent2d::Extent2d;
|
||||
use solidcore::math::{Point3, Vec3};
|
||||
use solidcore::nurbs::b_spline_curve::{BSplineCurve, NurbCircleParam};
|
||||
use solidcore::nurbs::b_spline_surface::BSplineSurface;
|
||||
use solidcore::nurbs::math::gamma;
|
||||
use solidcore::nurbs::srf_basic::Surface as Net;
|
||||
use solidcore::primitive::api_primitives::create_sphere;
|
||||
use solidcore::surface::implicit::AnalyticSupport;
|
||||
use solidcore::surface::sphere::Sphere;
|
||||
use solidcore::surface::surf_of_revolution::SurfOfRevolution;
|
||||
use solidcore::surface::surface::Surface;
|
||||
use solidcore::topology::brep::p1 as bp1;
|
||||
use solidcore::Kernel;
|
||||
|
||||
/// The allowance the recogniser adds, as a position: `4 gamma_K (1 + |corners| + 2 r)`,
|
||||
/// `K = 16 (2 + 2 + 2)` for a form of degrees (2, 2) (written out so that a change to the
|
||||
/// allowance fails). In degrees it is this over `r`, turned to degrees.
|
||||
fn allowance_position(form: &BSplineSurface, r: f64) -> f64 {
|
||||
let d = form.natural_uv_domain();
|
||||
let corners = form
|
||||
.evaluate_point(d.min())
|
||||
.unwrap()
|
||||
.max_dimension()
|
||||
.max(form.evaluate_point(d.max()).unwrap().max_dimension());
|
||||
4.0 * gamma(16 * 6) * (1.0 + corners + 2.0 * r)
|
||||
}
|
||||
|
||||
/// The NURBS form of a sphere of radius `r` at `c`, turned by `turn` degrees about the axis
|
||||
/// (1, 1, 1) when it is not 0, and its label.
|
||||
fn form_at(k: &mut Kernel, c: Point3, r: f64, turn: f64) -> (BSplineSurface, AnalyticSupport) {
|
||||
let b = create_sphere(k, Point3::new(0.0, 0.0, 0.0), r).unwrap();
|
||||
let f = bp1::faces(&b, None).unwrap()[0];
|
||||
let mut s = b
|
||||
.face(f)
|
||||
.unwrap()
|
||||
.surface
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.copy(k)
|
||||
.unwrap();
|
||||
// turned about (1, 1, 1) by `turn`, then moved to `c`
|
||||
let a = turn.to_radians();
|
||||
let n = Vec3::new(1.0, 1.0, 1.0) * (1.0 / 3.0_f64.sqrt());
|
||||
let rot = |v: Vec3| v * a.cos() + n.cross(v) * a.sin() + n * (n.dot(v) * (1.0 - a.cos()));
|
||||
let mut place = Axis2Placement::default();
|
||||
place
|
||||
.set_canonical(
|
||||
c,
|
||||
rot(Vec3::new(1.0, 0.0, 0.0)),
|
||||
rot(Vec3::new(0.0, 1.0, 0.0)),
|
||||
)
|
||||
.unwrap();
|
||||
s.transform(k, &place, None).unwrap();
|
||||
let form = as_b_spline(k, &s).unwrap().unwrap().into_owned();
|
||||
(form, AnalyticSupport::of(&s).unwrap())
|
||||
}
|
||||
|
||||
/// Recognised at radii 1e-3 to 1e3, 0 to 1e7 from the origin (at most 1e9 radii out), on the
|
||||
/// axes and turned off them, with its radius and centre within the allowance as a position.
|
||||
#[test]
|
||||
fn a_far_sphere_is_recognised_on_its_own_numbers() {
|
||||
let mut k = Kernel::default();
|
||||
for r in [1e-3, 1.0, 2.5, 1e3] {
|
||||
for off in [0.0, 1e3, 1e5, 1e6, 1e7] {
|
||||
// a sphere of radius 1e-3 at 1e7, ten orders apart: upstream's iso-circle test
|
||||
// fails with a status there (1001), before any angle is read; unchanged here
|
||||
if off > 1e9 * r {
|
||||
continue;
|
||||
}
|
||||
for turn in [0.0, 37.0] {
|
||||
let c = Point3::new(off, off, off + 10.0);
|
||||
let (form, _) = form_at(&mut k, c, r, turn);
|
||||
let found = Sphere::is_nurb_surface_sphere(&k, &form, 1.0)
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| panic!("not recognised: r {r} at {off}, turned {turn}"));
|
||||
let AnalyticSupport::Sphere { placement, radius } =
|
||||
AnalyticSupport::of(&Surface::from(found)).unwrap()
|
||||
else {
|
||||
panic!("not a sphere");
|
||||
};
|
||||
let tol = allowance_position(&form, r);
|
||||
assert!((radius - r).abs() <= tol, "r {r} at {off}: radius {radius}");
|
||||
assert!(
|
||||
(placement.origin() - c).length() <= tol,
|
||||
"r {r} at {off}: centre {:e} past {tol:e}",
|
||||
(placement.origin() - c).length()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A half meridian of `180 + extra` degrees, radius `r`, at `c`: a circle arc from -90 to
|
||||
/// `90 + extra` degrees in the plane y = c.y, turned a quarter turn about the vertical.
|
||||
fn long_meridian(k: &Kernel, c: Point3, r: f64, extra: f64) -> BSplineSurface {
|
||||
let mut frame = Axis2Placement::default();
|
||||
frame
|
||||
.set_canonical(c, Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 1.0))
|
||||
.unwrap();
|
||||
let generator = BSplineCurve::create_circle_segment(
|
||||
3,
|
||||
&frame,
|
||||
r,
|
||||
-90.0,
|
||||
90.0 + extra,
|
||||
NurbCircleParam::Quadratic,
|
||||
)
|
||||
.unwrap();
|
||||
let mut sor = SurfOfRevolution::new(
|
||||
k,
|
||||
Curve::from(generator),
|
||||
c,
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Extent2d::from_bounds(0.0, -90.0, 90.0, 90.0 + extra),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
sor.make_nurb(k).unwrap();
|
||||
as_b_spline(k, &Surface::from(sor))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// The half meridian is allowed `1e-12` degrees (upstream) plus the angles' rounding at the
|
||||
/// points' size. A meridian `d` past 180 degrees also folds the generator's STEP interval
|
||||
/// against the sphere's latitudes by `2 d` (a point past the pole reads latitude `90 - d`),
|
||||
/// held to the same allowance. So a meridian 0.4 of the allowance past is recognised (both
|
||||
/// tests pass; halve the allowance and the fold, 0.8 of it, is refused) and one twice the
|
||||
/// allowance past is refused, at the origin and at 1e5. A doubled allowance is caught by
|
||||
/// `sphere.rs`'s own unit test of `meridian_rounding` against its closed form.
|
||||
#[test]
|
||||
fn the_half_meridian_is_allowed_its_rounding_and_no_more() {
|
||||
let k = Kernel::default();
|
||||
let r = 2.5;
|
||||
for off in [0.0, 1e5] {
|
||||
let c = Point3::new(off, off, off + 10.0);
|
||||
let exact = long_meridian(&k, c, r, 0.0);
|
||||
let allowed = 1e-12 + (allowance_position(&exact, r) / r).to_degrees();
|
||||
let over = long_meridian(&k, c, r, 2.0 * allowed);
|
||||
assert!(
|
||||
Sphere::is_nurb_surface_sphere(&k, &over, 1.0)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"at {off}: 180 + {:e} degrees recognised",
|
||||
2.0 * allowed
|
||||
);
|
||||
let under = long_meridian(&k, c, r, 0.4 * allowed);
|
||||
assert!(
|
||||
Sphere::is_nurb_surface_sphere(&k, &under, 1.0)
|
||||
.unwrap()
|
||||
.is_some(),
|
||||
"at {off}: 180 + {:e} degrees refused",
|
||||
0.4 * allowed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A form or a label 1e-3 off the sphere is refused at every offset to 1e6: the label by
|
||||
/// `lies_on` (`AnalyticSupport::build`), the form by the iso-circle tests. Neither reaches
|
||||
/// the angle allowance; they show the allowance did not open what those tests close. At 1e7
|
||||
/// the iso-circle tests' own tolerance, `100 SM_EFF_ZERO (1 + |corners|)` (about 2e-3),
|
||||
/// admits a 1e-3 move: upstream's, unchanged here.
|
||||
#[test]
|
||||
fn a_form_or_label_1e3_off_is_refused_to_1e6() {
|
||||
let mut k = Kernel::default();
|
||||
for off in [0.0, 1e3, 1e4, 1e5, 1e6] {
|
||||
let c = Point3::new(off, off, off + 10.0);
|
||||
let (form, support) = form_at(&mut k, c, 2.5, 0.0);
|
||||
let AnalyticSupport::Sphere { placement, radius } = support else {
|
||||
panic!("a sphere");
|
||||
};
|
||||
let off_label = AnalyticSupport::Sphere {
|
||||
placement,
|
||||
radius: radius + 1e-3,
|
||||
};
|
||||
assert!(off_label.build(&k, form.copy()).is_err(), "label at {off}");
|
||||
let net = form.gw_nurb().unwrap();
|
||||
let mut cpts = net.cpts();
|
||||
let (i, j) = (cpts.len() / 2, cpts[0].len() / 2);
|
||||
let w = cpts[i][j].w;
|
||||
cpts[i][j].x += 1e-3
|
||||
* if w == solidcore::nurbs::math::NOW {
|
||||
1.0
|
||||
} else {
|
||||
w
|
||||
};
|
||||
let (p, q) = net.degrees();
|
||||
let moved = Net::new(cpts, p, q, net.u_knots().to_vec(), net.v_knots().to_vec()).unwrap();
|
||||
let moved = BSplineSurface::from_gw_nurb(&moved);
|
||||
assert!(
|
||||
Sphere::is_nurb_surface_sphere(&k, &moved, 1.0)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"form at {off}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1363,3 +1363,82 @@ fn d_prism_asked_past_the_rails_names_the_setback_that_fits() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The least distance from the prism's axis of the top face's spring curve at the corner of
|
||||
/// [`d_prism_eight_case`] when every setback is `s`, by its closed form: the top is a plane,
|
||||
/// so the spring curve is the parabola (P, X, Q) itself, P on the chord's rail x = 7, `s`
|
||||
/// along the chord from V, Q on the arc's rail rho = 8, `s` along the arc from V, and X where
|
||||
/// the rails' tangents there cross. The least over 20000 even steps of the parabola's
|
||||
/// parameter (each a few millionths long).
|
||||
fn top_spring_least_rho(s: f64) -> f64 {
|
||||
let p = (7.0, 6.0 - s);
|
||||
let psi = 6.0f64.atan2(8.0) + s / 10.0;
|
||||
let q = (8.0 * psi.cos(), 8.0 * psi.sin());
|
||||
let along = (7.0 - q.0) / psi.sin();
|
||||
let x = (7.0, q.1 - along * psi.cos());
|
||||
(0..=20000)
|
||||
.map(|i| {
|
||||
let t = f64::from(i) / 20000.0;
|
||||
let (a, b, c) = ((1.0 - t) * (1.0 - t), 2.0 * t * (1.0 - t), t * t);
|
||||
(a * p.0 + b * x.0 + c * q.0).hypot(a * p.1 + b * x.1 + c * q.1)
|
||||
})
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
}
|
||||
|
||||
/// #2343 (d), "a thin flat strip beside a cylinder": the bored D prism (bore R 6.9) asked 8.5
|
||||
/// at V. By the closed form above its top face's spring curve dips into the bore from asked
|
||||
/// 7.8068; main built asked 7.8 (the spring curve 6e-4 clear of the bore) and refused 8 to
|
||||
/// 9.5 late, "a round kept round a feature does not close on the body", naming nothing. The
|
||||
/// corner checks what it takes out of the top: refused as `SetbackTooLong` at V, naming the
|
||||
/// largest setback on 8.5's grid of thousandths whose spring curve stays on the face, 0.918 x
|
||||
/// 8.5 = 7.803 (3.3e-4 clear of the bore; the next, 7.8115, 4.1e-4 into it). Asked that, the
|
||||
/// corner builds, graded as the corners above.
|
||||
#[test]
|
||||
fn bored_d_prism_spring_curve_leaving_the_top_names_the_setback_that_keeps_it() {
|
||||
let (bore, asked, step) = (6.9, 8.5, 8.5 / 1000.0);
|
||||
let (mut k, mut b, edges, case) = d_prism_eight_case(Some(bore), 14);
|
||||
let v = vertex_at(&b, case.v);
|
||||
let r = round_asked(
|
||||
&mut k,
|
||||
&mut b,
|
||||
&edges,
|
||||
&[1.0, 1.5, 2.0],
|
||||
case.v,
|
||||
Some(asked),
|
||||
);
|
||||
assert_eq!(r.event, Some(BlendCornerEvent::Setback), "{}", r.reason);
|
||||
assert!(!r.ok, "asked {asked}: built");
|
||||
let named = match r.refusal {
|
||||
Some(BlendRefusal::SetbackTooLong { vertex, largest }) => {
|
||||
assert_eq!(vertex, v);
|
||||
largest
|
||||
}
|
||||
other => panic!("{other:?}, {}", r.reason),
|
||||
};
|
||||
// on the grid, its spring curve clear of the bore, the next one's not
|
||||
let grid = (named / step).round();
|
||||
assert!((grid * step - named).abs() <= 1e-12, "{named} off the grid");
|
||||
assert!(
|
||||
top_spring_least_rho(named) > bore,
|
||||
"{named}: {}",
|
||||
top_spring_least_rho(named)
|
||||
);
|
||||
assert!(
|
||||
top_spring_least_rho(named + step) < bore,
|
||||
"{}: {}",
|
||||
named + step,
|
||||
top_spring_least_rho(named + step)
|
||||
);
|
||||
let (mut k, mut b, edges, case) = d_prism_eight_case(Some(bore), 14);
|
||||
let r = round_asked(
|
||||
&mut k,
|
||||
&mut b,
|
||||
&edges,
|
||||
&[1.0, 1.5, 2.0],
|
||||
case.v,
|
||||
Some(named),
|
||||
);
|
||||
assert!(r.ok, "asked {named}: {}", r.reason);
|
||||
graded(&mut k, &mut b, &case, 3);
|
||||
kept_definitions(&k, &b, &case, 3);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
//! Regression tests for tangency-labs/solidcore#2972: a body's volume sign is read from a
|
||||
//! certified enclosure `[lo, hi]` of its volume, not from a quadrature estimate.
|
||||
//!
|
||||
//! - A thin quarter shell (radius 1e4, thickness 1e-3, length 10) is a valid solid whose
|
||||
//! volume, 157.08, lies below the estimate's error at the sign test's accuracy (the
|
||||
//! estimate at 1e-3 reads -180.05): its enclosure holds the closed form and is positive,
|
||||
//! and `is_outward_solid` calls it a solid.
|
||||
//! - A box with one edge rounded (radius 3) is enclosed to a millionth of its volume,
|
||||
//! around the closed form `24000 - 40 * 9 * (1 - pi / 4)`, on the default kernel and with
|
||||
//! the rounds' definitions on.
|
||||
//! - A box, and the same box with its material outside (every face's material side
|
||||
//! swapped), decide positive and negative with enclosures holding +120 and -120; a
|
||||
//! sphere (seam and poles) holds `4/3 pi r^3`.
|
||||
//! - The same box with one side's surface moved out by 1e-3 and its trims kept: the faces
|
||||
//! no longer meet, and the closed surface the enclosure bounds (the faces and the strips
|
||||
//! across their gaps) holds 120 + 30e-3, which the faces alone do not.
|
||||
//! - Two unit cubes touching along an edge (their union: four face sides at that edge,
|
||||
//! paired by sense) are enclosed around 2.
|
||||
//! - A G2 round of the box edge (its net rational of degree 7 across, whose weights an
|
||||
//! interval proves positive only over small boxes) decides, around the round's volume:
|
||||
//! as a bare net, and as a procedural blend whose net is certified within a bound above 0
|
||||
//! of its definition, read on its net (#3170 widens by that bound).
|
||||
//! - The quarter shell R 10, t 1e-6 passes the check, but its faces' boundaries lie apart
|
||||
//! by about its thickness: the sign is undecided, early (the strips between them hold 0
|
||||
//! whatever the cells become), and `is_outward_solid` reads that as "not a solid". A box
|
||||
//! with an offset-surface face cannot be read at all: an error, not a sign.
|
||||
//! - `nurb_fillet.smb` (a sphere with a pocket) decides positive: its sphere face's loop
|
||||
//! closes along the pole, from trim ends a unit in the last place off the net's last
|
||||
//! knot, a segment whose 3D length is of the order of rounding.
|
||||
#![allow(clippy::unwrap_used, clippy::panic)]
|
||||
|
||||
use solidcore::blend::fillet_executive::api::fillet_edges;
|
||||
use solidcore::blend::fillet_section::section::GN_THUMBWEIGHT_EQUAL_AREA;
|
||||
use solidcore::boolean::api_brep::boolean_union;
|
||||
use solidcore::geom::axis2_placement::Axis2Placement;
|
||||
use solidcore::kernel::{KernelOptions, UpstreamFixes};
|
||||
use solidcore::mass::enclosure::{
|
||||
enclose_volume, volume_sign, VolumeEnclosure, VolumeSign, CELL_BUDGET,
|
||||
};
|
||||
use solidcore::math::{Point3, Vec3, PI};
|
||||
use solidcore::primitive::api_primitives::{create_box, create_cylindrical_box, create_sphere};
|
||||
use solidcore::query::api_queries::edge_closest_point;
|
||||
use solidcore::surface::offset_surface::OffsetSurface;
|
||||
use solidcore::surface::surface::Surface;
|
||||
use solidcore::topology::brep::is_outward_solid;
|
||||
use solidcore::topology::brep::p1::{faces, regions};
|
||||
use solidcore::topology::brep::validation::{check_brep, CheckLevel};
|
||||
use solidcore::SolidError;
|
||||
use solidcore::{Brep, Kernel};
|
||||
|
||||
/// The quarter shell between radii `r` and `r + t` about Z, length 10.
|
||||
fn quarter_shell(k: &mut Kernel, r: f64, t: f64) -> Brep {
|
||||
create_cylindrical_box(k, Point3::new(0.0, 0.0, 0.0), 10.0, r, r + t, 0.0, 90.0).unwrap()
|
||||
}
|
||||
|
||||
fn shell_volume(r: f64, t: f64) -> f64 {
|
||||
PI / 4.0 * ((r + t) * (r + t) - r * r) * 10.0
|
||||
}
|
||||
|
||||
/// The box 40 x 30 x 20 with its edge through (20, 30, 20) rounded at radius 3.
|
||||
fn rounded_box(k: &mut Kernel) -> Brep {
|
||||
rounded_box_by(k, 1, 1, 1.0)
|
||||
}
|
||||
|
||||
/// [`rounded_box`] with `fillet_edges`' section kind, order and thumbweight.
|
||||
fn rounded_box_by(k: &mut Kernel, kind: u32, order: u32, weight: f64) -> Brep {
|
||||
let mut b = create_box(k, Point3::new(0.0, 0.0, 0.0), 40.0, 30.0, 20.0).unwrap();
|
||||
let q = Point3::new(20.0, 30.0, 20.0);
|
||||
let e = b
|
||||
.edge_list()
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|&e| edge_closest_point(k, &b, e, q).is_ok_and(|c| c.closest.distance < 1e-9))
|
||||
.unwrap();
|
||||
fillet_edges(k, &mut b, &[e], 3.0, kind, order, weight).unwrap();
|
||||
b
|
||||
}
|
||||
|
||||
/// Every fix on but the G2 one, as SolidCraft runs: the round is an exact round, a
|
||||
/// definition with bound 0.
|
||||
fn definitions_kernel() -> Kernel {
|
||||
let mut k = Kernel::new(&KernelOptions::default());
|
||||
let mut f = UpstreamFixes::default();
|
||||
for &name in UpstreamFixes::NAMES {
|
||||
if name != "geometric_g2_continuity" {
|
||||
*f.flag_mut(name).unwrap() = true;
|
||||
}
|
||||
}
|
||||
k.fixes = f;
|
||||
k
|
||||
}
|
||||
|
||||
fn holds(e: &VolumeEnclosure, v: f64) -> bool {
|
||||
e.lo <= v && v <= e.hi
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_thin_shell_below_the_estimates_error_decides_positive() {
|
||||
for (r, t) in [(1e4, 1e-3), (10.0, 1e-4)] {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = quarter_shell(&mut k, r, t);
|
||||
let v = shell_volume(r, t);
|
||||
match volume_sign(&k, &mut b).unwrap() {
|
||||
VolumeSign::Positive(e) => assert!(holds(&e, v), "r {r} t {t}: {e:?} misses {v}"),
|
||||
other => panic!("r {r} t {t}: {other:?}, volume {v}"),
|
||||
}
|
||||
// the solid test reads the same sign: on the estimate (-180.05 at r 1e4) it was false
|
||||
let mut b = quarter_shell(&mut k, r, t);
|
||||
assert!(is_outward_solid(&mut k, &mut b).unwrap(), "r {r} t {t}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rounded_box_is_enclosed_around_its_closed_form() {
|
||||
let v = 24000.0 - 40.0 * 9.0 * (1.0 - PI / 4.0);
|
||||
for (name, mut k) in [
|
||||
("default", Kernel::default()),
|
||||
("definitions", definitions_kernel()),
|
||||
] {
|
||||
let mut b = rounded_box(&mut k);
|
||||
let e = enclose_volume(&k, &mut b, 1e-6 * v).unwrap();
|
||||
assert!(holds(&e, v), "{name}: {e:?} misses {v}");
|
||||
assert!(e.width() <= 1e-6 * v, "{name}: {e:?} wider than 1e-6 V");
|
||||
let mut b = rounded_box(&mut k);
|
||||
let s = volume_sign(&k, &mut b).unwrap();
|
||||
assert!(
|
||||
matches!(s, VolumeSign::Positive(e) if holds(&e, v)),
|
||||
"{name}: {s:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_and_its_inside_out_twin_decide_their_signs() {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = create_box(&mut k, Point3::new(1.0, 2.0, 3.0), 4.0, 5.0, 6.0).unwrap();
|
||||
let e = enclose_volume(&k, &mut b, 1e-9).unwrap();
|
||||
assert!(holds(&e, 120.0) && e.width() <= 1e-9, "{e:?}");
|
||||
// the material outside: every face's material side is the other one
|
||||
for r in regions(&b, None).unwrap() {
|
||||
let g = b.region_mut(r).unwrap();
|
||||
g.is_void = 1 - g.is_void;
|
||||
}
|
||||
match volume_sign(&k, &mut b).unwrap() {
|
||||
VolumeSign::Negative(e) => assert!(holds(&e, -120.0), "{e:?}"),
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sphere_is_enclosed_across_its_seam_and_poles() {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = create_sphere(&mut k, Point3::new(0.0, 0.0, 0.0), 2.0).unwrap();
|
||||
let v = 4.0 / 3.0 * PI * 8.0;
|
||||
let e = enclose_volume(&k, &mut b, 1e-6).unwrap();
|
||||
assert!(holds(&e, v) && e.width() <= 1e-6, "{e:?} misses {v}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gap_between_faces_is_closed_by_its_strips() {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = create_box(&mut k, Point3::new(1.0, 2.0, 3.0), 4.0, 5.0, 6.0).unwrap();
|
||||
// the side x = 5 (5 x 6) moved out by delta, its trims and its neighbours kept
|
||||
let delta = 1e-3;
|
||||
let side = faces(&b, None)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|&f| {
|
||||
let face = b.face(f).unwrap();
|
||||
let s = face.surface.as_ref().unwrap();
|
||||
s.evaluate_point(&k, face.uv_domain.mid())
|
||||
.is_ok_and(|p| (p.x - 5.0).abs() < 1e-12)
|
||||
})
|
||||
.unwrap();
|
||||
let mut shift = Axis2Placement::default();
|
||||
shift.translate(Vec3::new(delta, 0.0, 0.0));
|
||||
b.face_mut(side)
|
||||
.unwrap()
|
||||
.surface
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.transform(&k, &shift, None)
|
||||
.unwrap();
|
||||
let v = 120.0 + 30.0 * delta;
|
||||
let e = enclose_volume(&k, &mut b, 1.0).unwrap();
|
||||
assert!(holds(&e, v) && e.counts.strips > 0, "{e:?} misses {v}");
|
||||
assert!(matches!(
|
||||
volume_sign(&k, &mut b).unwrap(),
|
||||
VolumeSign::Positive(_)
|
||||
));
|
||||
}
|
||||
|
||||
/// `upstream/TestFiles/pt_TestFiles/Solids/nurb_fillet.smb`, read and healed; `None` where
|
||||
/// the upstream test files are not present.
|
||||
fn nurb_fillet(k: &mut Kernel) -> Option<Brep> {
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::geom::object::ObjectRef;
|
||||
use solidcore::topology::brep::p10::TopologyEnv;
|
||||
use solidcore::topology::brep::p7::read_from_file;
|
||||
use solidcore::topology::database_io::FileType;
|
||||
use solidcore::topology::topology::NotifyRecords;
|
||||
use solidcore::EntityRef;
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../upstream/TestFiles/pt_TestFiles/Solids/nurb_fillet.smb");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
assert!(
|
||||
std::env::var_os("SOLIDCORE_REQUIRE_UPSTREAM").is_none(),
|
||||
"SOLIDCORE_REQUIRE_UPSTREAM is set and the upstream test file is missing: {}",
|
||||
path.display()
|
||||
);
|
||||
return None;
|
||||
};
|
||||
let mut b = Brep::new_with_infinite_region().unwrap();
|
||||
let mut records = NotifyRecords::new();
|
||||
let mut attributes = AttributeStore::default();
|
||||
let key = |_: EntityRef| ObjectRef {
|
||||
key: ObjectKey(0),
|
||||
type_code: 0,
|
||||
};
|
||||
let mut env = TopologyEnv {
|
||||
all_attributes: &[],
|
||||
attributes: &mut attributes,
|
||||
key_of: &key,
|
||||
records: &mut records,
|
||||
next_key: 1 << 50,
|
||||
};
|
||||
read_from_file(k, &mut b, bytes, FileType::Ascii, false, &mut env).unwrap();
|
||||
b.use_arena_naming().unwrap();
|
||||
Some(b)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sphere_closed_along_its_pole_decides() {
|
||||
let mut k = Kernel::default();
|
||||
let Some(mut b) = nurb_fillet(&mut k) else {
|
||||
return;
|
||||
};
|
||||
let s = volume_sign(&k, &mut b).unwrap();
|
||||
assert!(matches!(s, VolumeSign::Positive(_)), "{s:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solids_touching_along_an_edge_pair_their_sides() {
|
||||
let mut k = Kernel::default();
|
||||
let a = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 1.0, 1.0, 1.0).unwrap();
|
||||
let c = create_box(&mut k, Point3::new(1.0, 1.0, 0.0), 1.0, 1.0, 1.0).unwrap();
|
||||
let mut b = boolean_union(&mut k, a, c).unwrap();
|
||||
let e = enclose_volume(&k, &mut b, 1e-9).unwrap();
|
||||
assert!(holds(&e, 2.0) && e.width() <= 1e-9, "{e:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_g2_round_decides_around_its_volume() {
|
||||
// the G^n section's flag and the one its plane rails need: the round a bare net
|
||||
let mut bare = Kernel::default();
|
||||
bare.fixes.fillet_gn_section_generator = true;
|
||||
bare.fixes.fillet_surface_plane_curve_on_plane_equation = true;
|
||||
let v = 24000.0 - 40.0 * 9.0 * (1.0 - PI / 4.0);
|
||||
for (name, mut k, certified) in [
|
||||
("bare net", bare, false),
|
||||
("definition", definitions_kernel(), true),
|
||||
] {
|
||||
let mut b = rounded_box_by(&mut k, 2, 2, GN_THUMBWEIGHT_EQUAL_AREA);
|
||||
// with the definitions on, the round is a procedural blend whose net is certified
|
||||
// within a bound above 0: it is read on its net
|
||||
let bounds: Vec<f64> = faces(&b, None)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter_map(|f| {
|
||||
let s = b.face(f).unwrap().surface.as_ref().unwrap();
|
||||
s.procedural_blend().map(|d| d.bound())
|
||||
})
|
||||
.collect();
|
||||
if certified {
|
||||
assert!(bounds.iter().any(|&e| e > 0.0), "{name}: {bounds:?}");
|
||||
} else {
|
||||
assert!(bounds.is_empty(), "{name}: {bounds:?}");
|
||||
}
|
||||
let e = enclose_volume(&k, &mut b, 1e-3 * v).unwrap();
|
||||
assert!(
|
||||
holds(&e, v) && e.width() <= 1e-3 * v,
|
||||
"{name}: {e:?} misses {v}"
|
||||
);
|
||||
let mut b = rounded_box_by(&mut k, 2, 2, GN_THUMBWEIGHT_EQUAL_AREA);
|
||||
let s = volume_sign(&k, &mut b).unwrap();
|
||||
assert!(matches!(s, VolumeSign::Positive(_)), "{name}: {s:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gap_as_large_as_the_volume_leaves_the_sign_undecided() {
|
||||
let mut k = Kernel::default();
|
||||
let shell = |k: &mut Kernel| quarter_shell(k, 10.0, 1e-6);
|
||||
let mut b = shell(&mut k);
|
||||
let c = check_brep(&mut k, &mut b, CheckLevel::Level2).unwrap();
|
||||
assert!(c.lines().is_empty(), "{:?}", c.lines());
|
||||
let v = shell_volume(10.0, 1e-6);
|
||||
let mut b = shell(&mut k);
|
||||
match volume_sign(&k, &mut b).unwrap() {
|
||||
// undecided long before the budget: the strips settle
|
||||
VolumeSign::Undecided(e) => {
|
||||
assert!(holds(&e, v) && e.counts.cells < CELL_BUDGET / 4, "{e:?}");
|
||||
}
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
// undecided is "not a solid" on purpose
|
||||
let mut b = shell(&mut k);
|
||||
assert!(!is_outward_solid(&mut k, &mut b).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_face_without_a_nurbs_form_is_an_error_not_a_sign() {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 4.0, 5.0, 6.0).unwrap();
|
||||
// one side's plane as an offset surface at distance 0: the same box
|
||||
let f = faces(&b, None).unwrap()[0];
|
||||
let base = b.face(f).unwrap().surface.as_ref().unwrap().clone();
|
||||
let off = OffsetSurface::new(&k, 0.0, base, true, 0).unwrap();
|
||||
b.face_mut(f).unwrap().surface = Some(Surface::from(off));
|
||||
let r = volume_sign(&k, &mut b);
|
||||
assert!(matches!(r, Err(SolidError::Unsupported { .. })), "{r:?}");
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
//! tangency-labs/solidcore#2975: the stitch reports, for every gap it closes, a
|
||||
//! certified bound on the gap and the tolerance growth its closure applied; an exact
|
||||
//! match's closure applies no growth.
|
||||
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::indexing_slicing
|
||||
)]
|
||||
|
||||
use solidcore::geom::extent2d::Extent2d;
|
||||
use solidcore::geom::tol::USE_DEFAULT;
|
||||
use solidcore::math::{Point2, Vec3};
|
||||
use solidcore::surface::plane::Plane;
|
||||
use solidcore::surface::surface::Surface;
|
||||
use solidcore::topology::brep::p2::ArenaNotifyState;
|
||||
use solidcore::topology::brep::p3::new_brep;
|
||||
use solidcore::topology::brep::p5::{stitch_and_orient_reported, StitchAndOrientOptions};
|
||||
use solidcore::topology::brep::p8::{create_face_from_surface, FaceBuildEnv};
|
||||
use solidcore::topology::brep_cache::{arena_identity, CacheObject};
|
||||
use solidcore::topology::topology_traverser::Marks;
|
||||
use solidcore::{Brep, Kernel};
|
||||
|
||||
/// Compare stored tolerance accounting independently of the reported bound.
|
||||
fn check_accounting(entry: &solidcore::heal::stitch::p2::ClosedGap) {
|
||||
let increase = (entry.tolerance_after - entry.tolerance_before).max(0.0);
|
||||
assert!(
|
||||
entry.growth.is_finite() && entry.growth >= increase,
|
||||
"{entry:?}"
|
||||
);
|
||||
if entry.tolerance_after <= entry.tolerance_before {
|
||||
assert_eq!(entry.growth, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds one square planar face on the plane `z = 0` over the 1 x 1 domain whose low
|
||||
/// corner is `x` on x and `y` on y.
|
||||
fn add_square(k: &mut Kernel, b: &mut Brep, x: f64, y: f64) {
|
||||
let surface = Surface::from(Plane::from_normal(
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
));
|
||||
let mut state = ArenaNotifyState::new(b);
|
||||
let mut env = state.env(None);
|
||||
let mut marks = Marks::default();
|
||||
let this_face = |face| arena_identity(CacheObject::Face(face));
|
||||
let mut fenv = FaceBuildEnv {
|
||||
env: &mut env,
|
||||
marks: &mut marks,
|
||||
this_face: &this_face,
|
||||
};
|
||||
let domain = Extent2d::from_points(Point2::new(x, y), Point2::new(x + 1.0, y + 1.0));
|
||||
create_face_from_surface(k, b, Some(surface), &domain, &mut fenv).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_known_gap_reports_its_bound_and_the_growth_its_closure_applied() {
|
||||
let gap = 1e-5;
|
||||
let mut k = Kernel::default();
|
||||
let mut b = new_brep(&mut k, USE_DEFAULT, USE_DEFAULT).unwrap();
|
||||
add_square(&mut k, &mut b, 0.0, 0.0);
|
||||
add_square(&mut k, &mut b, 1.0 + gap, 0.0);
|
||||
|
||||
let gaps = stitch_and_orient_reported(&mut k, &mut b, &StitchAndOrientOptions::default())
|
||||
.expect("the stitch runs");
|
||||
for entry in &gaps {
|
||||
check_accounting(entry);
|
||||
}
|
||||
// The two corners glue: each reports the gap it held (the known offset the two
|
||||
// squares put between them) and the growth the glue applied — the survivor's
|
||||
// tolerance rose by the same amount, the glue's rule keeping the closed distance
|
||||
// inside the surviving tolerance (solidcore#2975).
|
||||
let corners: Vec<_> = gaps
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
matches!(
|
||||
entry.kind,
|
||||
solidcore::heal::stitch::p2::ClosedGapKind::Vertex { .. }
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(corners.len(), 2, "{gaps:?}");
|
||||
for entry in corners {
|
||||
assert!(entry.gap >= (1.0 + gap) - 1.0, "{entry:?}");
|
||||
assert!(
|
||||
(entry.gap - gap).abs() <= 1e-9 * gap.max(1.0),
|
||||
"reported {entry:?} against the known {gap:.1e}"
|
||||
);
|
||||
assert!(
|
||||
(entry.growth - gap).abs() <= 1e-9 * gap.max(1.0),
|
||||
"growth {entry:?} against the known {gap:.1e}"
|
||||
);
|
||||
}
|
||||
// Gluing the vertices does not move the two underlying edge curves. Their
|
||||
// whole-curve gap is still the original horizontal separation.
|
||||
let shared = gaps
|
||||
.iter()
|
||||
.find(|entry| {
|
||||
matches!(
|
||||
entry.kind,
|
||||
solidcore::heal::stitch::p2::ClosedGapKind::Edge { .. }
|
||||
)
|
||||
})
|
||||
.expect("the shared edge closure");
|
||||
assert!(
|
||||
shared.gap >= (1.0 + gap) - 1.0 && shared.gap < gap + 1e-12,
|
||||
"{shared:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_exact_match_reports_no_growth() {
|
||||
let mut k = Kernel::default();
|
||||
let mut b = new_brep(&mut k, USE_DEFAULT, USE_DEFAULT).unwrap();
|
||||
add_square(&mut k, &mut b, 1.0, 0.0);
|
||||
add_square(&mut k, &mut b, 0.0, 0.0);
|
||||
|
||||
let gaps = stitch_and_orient_reported(&mut k, &mut b, &StitchAndOrientOptions::default())
|
||||
.expect("the stitch runs");
|
||||
assert_eq!(
|
||||
gaps.len(),
|
||||
3,
|
||||
"two endpoint glues and their shared edge: {gaps:?}"
|
||||
);
|
||||
for gap in gaps {
|
||||
// Independently enclosing the two analytic line evaluations adds a
|
||||
// conservative rounding floor, even though the geometric boundary agrees.
|
||||
assert!(
|
||||
gap.gap.is_finite() && (0.0..=1e-12).contains(&gap.gap),
|
||||
"{gap:?}"
|
||||
);
|
||||
check_accounting(&gap);
|
||||
assert!(
|
||||
gap.growth == 0.0,
|
||||
"the exact match's closure applied no growth: {gap:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_interior_vertex_split_reports_the_original_curve_gap() {
|
||||
use solidcore::heal::stitch::p2::ClosedGapKind;
|
||||
let mut k = Kernel::default();
|
||||
let mut b = new_brep(&mut k, USE_DEFAULT, USE_DEFAULT).unwrap();
|
||||
let gap = 1e-5;
|
||||
add_square(&mut k, &mut b, 0.0, 0.0);
|
||||
add_square(&mut k, &mut b, 0.5, 1.0 + gap);
|
||||
let records =
|
||||
stitch_and_orient_reported(&mut k, &mut b, &StitchAndOrientOptions::default()).unwrap();
|
||||
let splits: Vec<_> = records
|
||||
.iter()
|
||||
.filter(|r| matches!(r.kind, ClosedGapKind::Split { .. }))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
splits.len(),
|
||||
2,
|
||||
"both ends of the partial shared boundary: {records:?}"
|
||||
);
|
||||
for entry in splits {
|
||||
assert!(
|
||||
entry.gap >= (1.0 + gap) - 1.0 && entry.gap < gap + 1e-12,
|
||||
"{entry:?}"
|
||||
);
|
||||
check_accounting(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Two adjacent squares, with one shared edge represented by an exact cubic
|
||||
/// Hermite line. Stitching can compare it; this certificate does not support it.
|
||||
fn unsupported_shared_edge() -> (Kernel, Brep) {
|
||||
use solidcore::curve::hermite_curve::HermiteCurve;
|
||||
let mut k = Kernel::default();
|
||||
let mut b = new_brep(&mut k, USE_DEFAULT, USE_DEFAULT).unwrap();
|
||||
add_square(&mut k, &mut b, 0.0, 0.0);
|
||||
add_square(&mut k, &mut b, 1.0, 0.0);
|
||||
b.refresh_cached_topology();
|
||||
let ends: Vec<_> = b
|
||||
.edge_list()
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|e| {
|
||||
let data = b.edge(e).unwrap();
|
||||
let c = data.curve.as_ref().unwrap();
|
||||
let a = c.evaluate_point(&k, data.interval.min()).unwrap();
|
||||
let z = c.evaluate_point(&k, data.interval.max()).unwrap();
|
||||
(e, a, z)
|
||||
})
|
||||
.collect();
|
||||
let edge = ends
|
||||
.iter()
|
||||
.find(|(e, a, z)| {
|
||||
ends.iter()
|
||||
.any(|(other, p, q)| e != other && ((a == p && z == q) || (a == q && z == p)))
|
||||
})
|
||||
.expect("the exactly shared boundary")
|
||||
.0;
|
||||
let data = b.edge(edge).unwrap();
|
||||
let old = data.curve.as_ref().unwrap();
|
||||
let a = old.evaluate_point(&k, data.interval.min()).unwrap();
|
||||
let z = old.evaluate_point(&k, data.interval.max()).unwrap();
|
||||
let mut curve: solidcore::curve::curve::Curve = HermiteCurve::new(a, z - a, z, z - a, 3).into();
|
||||
curve.set_owner(
|
||||
old.owner(),
|
||||
Some(solidcore::topology::brep::p2::brep_ref(&b)),
|
||||
);
|
||||
b.edge_mut(edge).unwrap().curve = Some(curve);
|
||||
b.edge_mut(edge).unwrap().interval =
|
||||
solidcore::geom::extent1d::Extent1d::from_min_max(0.0, 1.0);
|
||||
(k, b)
|
||||
}
|
||||
|
||||
/// A deterministic binary record checks topology, geometry and stored tolerances;
|
||||
/// explicit id-list assertions below also cover the original arena handles.
|
||||
fn binary(k: &Kernel, b: &Brep) -> Vec<u8> {
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::topology::brep_data_write::FromBrepEnv;
|
||||
use solidcore::topology::topology::NotifyRecords;
|
||||
let attributes = AttributeStore::new();
|
||||
let records = NotifyRecords::new();
|
||||
let env = FromBrepEnv {
|
||||
attributes: &attributes,
|
||||
records: &records,
|
||||
key_of: &solidcore::topology::loopuse::notify_ref,
|
||||
curve_key: &|_| ObjectKey(0),
|
||||
uv_curve_key: &|_| ObjectKey(0),
|
||||
surface_key: &|_| ObjectKey(0),
|
||||
};
|
||||
solidcore::topology::api_files::write_brep_to_file(k, Some(b), &env, false).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn certificate_failure_propagates_and_rolls_back_earlier_closures_with_flags_off_or_on() {
|
||||
use solidcore::heal::stitch::p1::{DefaultStitchCallback, Stitch};
|
||||
use solidcore::heal::stitch::p2::{ClosedGapKind, MinGapRequest};
|
||||
use solidcore::SolidError;
|
||||
|
||||
// The unwrapped stitch demonstrates that failure really occurs AFTER the two
|
||||
// vertex glues. This is not merely a no-edit input-validation test.
|
||||
let (mut k, mut b) = unsupported_shared_edge();
|
||||
let mut callback = DefaultStitchCallback::new();
|
||||
let mut stitch = Stitch::new(&mut callback, b.tolerance() * 4.0, true, true);
|
||||
stitch.fast_edge_compare = true;
|
||||
stitch.report_gaps = Some(Vec::new());
|
||||
let failure = stitch
|
||||
.do_stitching(&mut k, &mut b, None, None, MinGapRequest::default())
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(
|
||||
failure,
|
||||
SolidError::Unsupported {
|
||||
operation: "heal::stitch::bounds",
|
||||
..
|
||||
}
|
||||
),
|
||||
"{failure:?}"
|
||||
);
|
||||
let records = stitch.report_gaps.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
records
|
||||
.iter()
|
||||
.filter(|r| matches!(r.kind, ClosedGapKind::Vertex { .. }))
|
||||
.count(),
|
||||
2,
|
||||
"{records:?}"
|
||||
);
|
||||
b.refresh_cached_topology();
|
||||
assert_eq!(
|
||||
b.vertex_list().len(),
|
||||
6,
|
||||
"the direct stitch has already edited the operand"
|
||||
);
|
||||
|
||||
for statuses in [false, true] {
|
||||
let (mut k, mut b) = unsupported_shared_edge();
|
||||
k.fixes.brep_edit_statuses_pass_on = statuses;
|
||||
let before = binary(&k, &b);
|
||||
let ids = (
|
||||
b.face_list().to_vec(),
|
||||
b.edge_list().to_vec(),
|
||||
b.vertex_list().to_vec(),
|
||||
);
|
||||
let result = stitch_and_orient_reported(&mut k, &mut b, &StitchAndOrientOptions::default());
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(SolidError::Unsupported {
|
||||
operation: "heal::stitch::bounds",
|
||||
..
|
||||
})
|
||||
),
|
||||
"flag={statuses}: {result:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
binary(&k, &b),
|
||||
before,
|
||||
"flag={statuses}: the full operand must be restored"
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
b.face_list().to_vec(),
|
||||
b.edge_list().to_vec(),
|
||||
b.vertex_list().to_vec()
|
||||
),
|
||||
ids
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user