diff --git a/coverage/glb-machine-manager/browser-production.report.json b/coverage/glb-machine-manager/browser-production.report.json index 09a9fac..943c190 100644 --- a/coverage/glb-machine-manager/browser-production.report.json +++ b/coverage/glb-machine-manager/browser-production.report.json @@ -4,10 +4,8 @@ "mode": "production-preview", "status": "verified", "checks": [ - "eight GLB parts rendered with studio PMREM lighting and calibrated exposure", - "dragging GLB root tree nodes updates component parent hierarchy", - "origin visibility, numeric translation/rotation and TransformControls mode update", - "internal mesh parent-relative position and rotation survive GLB export without moving its parent", + "eight GLB parts rendered with studio PMREM lighting", + "eight-part roots have unique single DOFs; fixed base cannot move; numeric input drives only bound X", "validated assembly exports a self-contained managed GLB", "validated hierarchy applied through LOAD_WORKSPACE with geometry and physical zero in userData", "same component assembly switches to BC hierarchy and applies BC geometry", diff --git a/coverage/glb-machine-manager/browser.report.json b/coverage/glb-machine-manager/browser.report.json index 0bfca30..291921a 100644 --- a/coverage/glb-machine-manager/browser.report.json +++ b/coverage/glb-machine-manager/browser.report.json @@ -4,10 +4,8 @@ "mode": "development-source", "status": "verified", "checks": [ - "eight GLB parts rendered with studio PMREM lighting and calibrated exposure", - "dragging GLB root tree nodes updates component parent hierarchy", - "origin visibility, numeric translation/rotation and TransformControls mode update", - "internal mesh parent-relative position and rotation survive GLB export without moving its parent", + "eight GLB parts rendered with studio PMREM lighting", + "eight-part roots have unique single DOFs; fixed base cannot move; numeric input drives only bound X", "validated assembly exports a self-contained managed GLB", "validated hierarchy applied through LOAD_WORKSPACE with geometry and physical zero in userData", "same component assembly switches to BC hierarchy and applies BC geometry", diff --git a/coverage/glb-machine-manager/joint-dof/browser.report.json b/coverage/glb-machine-manager/joint-dof/browser.report.json new file mode 100644 index 0000000..f472f44 --- /dev/null +++ b/coverage/glb-machine-manager/joint-dof/browser.report.json @@ -0,0 +1,2816 @@ +{ + "report_id": "GLB-MGR-E13", + "status": "verified", + "url": "http://127.0.0.1:5180/", + "machines": [ + { + "coordinates": "XYZAC", + "checks": [ + "fixed or single DOF", + "direction/sign and parent isolation", + "pivot and child inheritance", + "duplicate axis rejected", + "JSON on scene and real bed", + "GLB roundtrip restores calibration and bindings", + "single-file apply", + "WASM runtime axis mapping", + "live dialog follows actual joints" + ], + "nodeCount": 134, + "samples": [ + { + "tick": 90, + "q": [ + 1.3176405754337055, + 0.7018353223247848, + 0.6876461908165524, + 1.3420399371694114, + 2.013059905754117 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 468.9564860191593, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.805613362464655, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -203.57416017061038, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.84530647960638, + 18.940227031707764, + 1.3644995348447968 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 375.2376868322547 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.05334337917303, + 22.69260390003031, + -7.675989661932547 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.09287421014032093, + 0, + 0, + 0.9956778500553337 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.06093478253640957, + 0.9981417495913296 + ], + "world": [ + -18.516113758087158, + 9.155667875763083, + -8.404086062678426 + ] + } + } + }, + { + "tick": 185, + "q": [ + 2.7756064007857404, + 1.5863598370741907, + 1.5287161867475643, + 2.8933834409319528, + 4.340075161397929 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 470.4144518445113, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.791033704211138, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -202.689635655861, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.83072682135286, + 18.940227031707764, + 1.3733447799922907 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 374.39661683632374 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.038763720919512, + 22.684193200071, + -7.667144416785053 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.0793865985031779, + 0, + 0, + 0.9968439035165412 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.04065430860467508, + 0.9991732718562261 + ], + "world": [ + -18.516113758087158, + 8.982518845485506, + -8.410491855408743 + ] + } + } + }, + { + "tick": 285, + "q": [ + 4.227072194127217, + 2.5951545767221713, + 2.475714263388226, + 4.526376602787225, + 6.789564904180838 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 471.8659176378528, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.77651904627772, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -201.68084091621301, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.816212163419443, + 18.940227031707764, + 1.3834327273887705 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 373.44961875968306 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.024249062986094, + 22.674723219304592, + -7.657056469388573 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.06517344104900848, + 0, + 0, + 0.9978739512492705 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.019288488927989858, + 0.9998139597919579 + ], + "world": [ + -18.516113758087158, + 8.800140324963277, + -8.412168032198611 + ] + } + } + }, + { + "tick": 385, + "q": [ + 5.585008832367324, + 3.674954917558891, + 3.4941574868166656, + 6.159369764642497, + 9.239054646963744 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 473.22385427609294, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.76293967989532, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -200.6010405753763, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.802632797037045, + 18.940227031707764, + 1.3942307307971378 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.4311755362546 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.010669696603696, + 22.664538787070306, + -7.646258465980206 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.05094704850852057, + 0, + 0, + 0.9987013558858676 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.002086143827703748, + 0.9999978239995976 + ], + "world": [ + -18.516113758087158, + 8.617788107232247, + -8.408646242158976 + ] + } + } + }, + { + "tick": 480, + "q": [ + 6.781150234023014, + 4.757642080562299, + 4.534698666749545, + 7.710713268405529, + 11.566069902608293 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 474.4199956777486, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.750978265878764, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -199.5183534123729, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.790671383020488, + 18.940227031707764, + 1.4050576024271717 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 371.39063435632175 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.99870828258714, + 22.65413337527098, + -7.635431594350172 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.03742234792877148, + 0, + 0, + 0.9992995386146728 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.022391312837410866, + 0.9997492831252329 + ], + "world": [ + -18.516113758087158, + 8.444712771554135, + -8.400488482264445 + ] + } + } + }, + { + "tick": 575, + "q": [ + 7.879139286684113, + 5.88650032048199, + 5.65215194012964, + 9.262056772168728, + 13.893085158253092 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 475.5179847304097, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.73999837535215, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -198.3894951724532, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.779691492493875, + 18.940227031707764, + 1.4163461848263688 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 370.2731810829417 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.987728392060525, + 22.642958842537176, + -7.624143011950975 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.023890788755168254, + 0, + 0, + 0.9997145743724335 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.042687248528164794, + 0.9990884839758162 + ], + "world": [ + -18.516113758087158, + 8.271921726797634, + -8.387648088245902 + ] + } + } + }, + { + "tick": 665, + "q": [ + 8.823143354322687, + 6.989726307669262, + 6.786131038978194, + 10.731750617839126, + 16.09762592675869 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 476.4619887980483, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.730558334675766, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -197.28626918526592, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.77025145181749, + 18.940227031707764, + 1.4273784446982414 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 369.1392019840931 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.97828835138414, + 22.631619051548693, + -7.613110752079102 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + -0.011067337782832394, + 0, + 0, + 0.999938755141734 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.06189887515293137, + 0.9980824260825364 + ], + "world": [ + -18.516113758087158, + 8.10860198461624, + -8.37117255590632 + ] + } + } + }, + { + "tick": 765, + "q": [ + 9.756032839293665, + 8.243261817217851, + 8.136263943462358, + 12.364743779695123, + 18.547115669542684 + ], + "nodes": { + "X": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2", + "localMm": [ + 477.39487828301924, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.721229439826057, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -196.03273367571734, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.76092255696778, + 18.940227031707764, + 1.4399137997937275 + ] + }, + "Z": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 367.78906907960896 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.96895945653443, + 22.61811772250385, + -7.600575396983617 + ] + }, + "A": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0.003182984566223136, + 0, + 0, + 0.9999949342917949 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "d3a771e5-3f8b-45c8-82c6-ee2fe6e702bb:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.08321795014841916, + 0.9965313706919091 + ], + "world": [ + -18.516113758087158, + 7.927698878995702, + -8.347960475142578 + ] + } + } + } + ], + "liveSample": { + "q": { + "x": 6.372493692377477, + "y": 10.586274424672933, + "z": 4.017587828351905, + "a": 3.551850062143684, + "b": 0, + "c": 13.586416770239474 + }, + "joints": { + "X": { + "localMm": [ + 479.01133913610306, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -1.870506483129522, + 0.34002411365509033, + 1.488389492034912 + ] + }, + "Y": { + "localMm": [ + -3.9693117141723633, + -193.68972106826223, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -1.8744757948436943, + 0.1463343925868281, + 1.8940227031707764 + ] + }, + "Z": { + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 364.9077451947194 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -1.8952794848003593, + -0.7577145270909063, + 2.2589304483654957 + ] + }, + "A": { + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0.03099077617962636, + 0, + 0, + 0.999519670537696 + ], + "world": [ + -1.8515222072601318, + -0.20127654075622559, + 0.8832522630691528 + ] + }, + "C": { + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.11828626527457455, + 0.9929795362681917 + ], + "world": [ + -1.8516113758087158, + -0.8287856514100127, + 0.7576881690543336 + ] + } + } + } + }, + { + "coordinates": "XYZBC", + "checks": [ + "fixed or single DOF", + "direction/sign and parent isolation", + "pivot and child inheritance", + "duplicate axis rejected", + "JSON on scene and real bed", + "GLB roundtrip restores calibration and bindings", + "single-file apply", + "WASM runtime axis mapping", + "live dialog follows actual joints" + ], + "nodeCount": 134, + "samples": [ + { + "tick": 90, + "q": [ + 1.3329950123827954, + 0.7177480791808708, + 0.6399756098721868, + 1.3420399371694114, + 2.013059905754117 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 468.9718404561084, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.805459818095166, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -203.55824741375432, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.84515293523689, + 18.940227031707764, + 1.3646586624133574 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 375.2853574131991 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.05318983480354, + 22.693080605839754, + -7.675830534363986 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.09287421014032093, + 0, + 0.9956778500553337 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.06093478253640957, + 0.9981417495913296 + ], + "world": [ + -18.356217359868296, + 7.982795032364486, + -8.353592157363893 + ] + } + } + }, + { + "tick": 190, + "q": [ + 2.9236359338379474, + 1.7144867753147128, + 1.3375802533236587, + 2.9750330990247162, + 4.462549648537074 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 470.56248137756353, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.789553408880614, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -202.56150871762046, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.829246526022338, + 18.940227031707764, + 1.374626049374696 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 374.5877527697476 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.03728342558899, + 22.686104559405237, + -7.665863147402648 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.07867629953385517, + 0, + 0.9969002156142104 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.03958637766287092, + 0.9992161521430348 + ], + "world": [ + -18.38049683513355, + 7.978608934572765, + -8.353592157363893 + ] + } + } + }, + { + "tick": 290, + "q": [ + 4.468468779001359, + 2.8418226921376712, + 1.9513302101338241, + 4.6080262608799885, + 6.912039391319983 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 472.1073142227269, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.77410508042898, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -201.4341728007975, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.813798197570705, + 18.940227031707764, + 1.3858994085429255 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 373.97400281293744 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.021835097137355, + 22.679967059837136, + -7.654589788234418 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.06446241175020236, + 0, + 0.9979201358181663 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.018219885427730547, + 0.9998340041101824 + ], + "world": [ + -18.404885742068682, + 7.97511643512577, + -8.353592157363893 + ] + } + } + }, + { + "tick": 385, + "q": [ + 5.883201022542513, + 4.028643843077865, + 2.462666920306882, + 6.159369764642497, + 9.239054646963744 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 473.52204646626814, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.75995775799357, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -200.24735164985734, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.799650875135292, + 18.940227031707764, + 1.3977676200523272 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 373.4626661027644 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.007687774701942, + 22.674853692735404, + -7.642721576725016 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.05094704850852057, + 0, + 0.9987013558858676 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.002086143827703748, + 0.9999978239995976 + ], + "world": [ + -18.42813852233286, + 7.9724435963565465, + -8.353592157363893 + ] + } + } + }, + { + "tick": 485, + "q": [ + 7.306525593624857, + 5.394054208222688, + 2.932617076240679, + 7.792362926498329, + 11.688544389747493 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 474.94537103735047, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.745724512282745, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -198.88194128471247, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.78541762942447, + 18.940227031707764, + 1.4114217237037758 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.9927159468306 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.99345452899112, + 22.670154191176067, + -7.629067473073568 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.036710309904143014, + 0, + 0.9993259494012661 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.023459823406074685, + 0.999724780469983 + ], + "world": [ + -18.45268377577148, + 7.970311257848664, + -8.353592157363893 + ] + } + } + }, + { + "tick": 580, + "q": [ + 8.587327421007348, + 6.795681267784465, + 3.321716221091818, + 9.343706430261529, + 14.015559645392292 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 476.226172864733, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.73291649400892, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -197.48031422515072, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.772609611150642, + 18.940227031707764, + 1.4254379942993936 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.60361680197946 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.980646510717293, + 22.666263199727556, + -7.61505120247795 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.023178458441054848, + 0, + 0.9997313434439756 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.04375504124825429, + 0.9990422895780556 + ], + "world": [ + -18.476049125769173, + 7.968934204260025, + -8.353592157363893 + ] + } + } + }, + { + "tick": 680, + "q": [ + 9.851918867897375, + 8.37441842738914, + 3.6797705807240826, + 10.976699592117525, + 16.465049388176286 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 477.49076431162297, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.72027057954002, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -195.90157706554604, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.759963696681744, + 18.940227031707764, + 1.4412253658954404 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.2455624423472 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.968000596248395, + 22.662682656131235, + -7.599263830881903 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.008929861991525902, + 0, + 0.9999601279875174 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.06509877759870472, + 0.9978788248856443 + ], + "world": [ + -18.500674929339052, + 7.968168611407265, + -8.353592157363893 + ] + } + } + }, + { + "tick": 775, + "q": [ + 10.96679214504157, + 9.965826828848682, + 3.979894644738888, + 12.528043095880722, + 18.79206464382108 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 478.6056375887672, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.70912184676858, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -194.31016866408652, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.748814963910302, + 18.940227031707764, + 1.4571394499100356 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 371.94543837833237 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.956851863476953, + 22.659681415491086, + -7.583349746867308 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + 0.004608028999853688, + 0, + 0.9999893829780077 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.08534792697105023, + 0.9963512088424163 + ], + "world": [ + -18.524080696270122, + 7.968091595376016, + -8.353592157363893 + ] + } + } + } + ], + "liveSample": { + "q": { + "x": 7.813509101550231, + "y": 12.674945969352336, + "z": -2.6236155210805876, + "a": 0, + "b": 3.143601771679865, + "c": 12.906002952799778 + }, + "joints": { + "X": { + "localMm": [ + 480.4523545452758, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -1.869065467720349, + 0.34002411365509033, + 1.488389492034912 + ] + }, + "Y": { + "localMm": [ + -3.9693117141723633, + -191.60104952358284, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -1.8730347794345215, + 0.1484230641315075, + 1.8940227031707764 + ] + }, + "Z": { + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 371.54894854415187 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -1.8938384693911865, + -0.7556258555462269, + 2.2655716517149282 + ] + }, + "B": { + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + 0.02742965986214243, + 0, + 0.9996237360926596 + ], + "world": [ + -1.8515222072601318, + -0.20127654075622559, + 0.8832522630691528 + ] + }, + "C": { + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.11238816966575761, + 0.9936643796167702 + ], + "world": [ + -1.8563519135537172, + -0.8353592157363892, + 0.7969396403687783 + ] + } + } + } + } + ], + "browser_errors": [], + "latestSamples": [ + { + "tick": 90, + "q": [ + 1.3329950123827954, + 0.7177480791808708, + 0.6399756098721868, + 1.3420399371694114, + 2.013059905754117 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 468.9718404561084, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.805459818095166, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -203.55824741375432, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.84515293523689, + 18.940227031707764, + 1.3646586624133574 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 375.2853574131991 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.05318983480354, + 22.693080605839754, + -7.675830534363986 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.09287421014032093, + 0, + 0.9956778500553337 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.06093478253640957, + 0.9981417495913296 + ], + "world": [ + -18.356217359868296, + 7.982795032364486, + -8.353592157363893 + ] + } + } + }, + { + "tick": 190, + "q": [ + 2.9236359338379474, + 1.7144867753147128, + 1.3375802533236587, + 2.9750330990247162, + 4.462549648537074 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 470.56248137756353, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.789553408880614, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -202.56150871762046, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.829246526022338, + 18.940227031707764, + 1.374626049374696 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 374.5877527697476 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.03728342558899, + 22.686104559405237, + -7.665863147402648 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.07867629953385517, + 0, + 0.9969002156142104 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.03958637766287092, + 0.9992161521430348 + ], + "world": [ + -18.38049683513355, + 7.978608934572765, + -8.353592157363893 + ] + } + } + }, + { + "tick": 290, + "q": [ + 4.468468779001359, + 2.8418226921376712, + 1.9513302101338241, + 4.6080262608799885, + 6.912039391319983 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 472.1073142227269, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.77410508042898, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -201.4341728007975, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.813798197570705, + 18.940227031707764, + 1.3858994085429255 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 373.97400281293744 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.021835097137355, + 22.679967059837136, + -7.654589788234418 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.06446241175020236, + 0, + 0.9979201358181663 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + -0.018219885427730547, + 0.9998340041101824 + ], + "world": [ + -18.404885742068682, + 7.97511643512577, + -8.353592157363893 + ] + } + } + }, + { + "tick": 385, + "q": [ + 5.883201022542513, + 4.028643843077865, + 2.462666920306882, + 6.159369764642497, + 9.239054646963744 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 473.52204646626814, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.75995775799357, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -200.24735164985734, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.799650875135292, + 18.940227031707764, + 1.3977676200523272 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 373.4626661027644 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -19.007687774701942, + 22.674853692735404, + -7.642721576725016 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.05094704850852057, + 0, + 0.9987013558858676 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.002086143827703748, + 0.9999978239995976 + ], + "world": [ + -18.42813852233286, + 7.9724435963565465, + -8.353592157363893 + ] + } + } + }, + { + "tick": 485, + "q": [ + 7.306525593624857, + 5.394054208222688, + 2.932617076240679, + 7.792362926498329, + 11.688544389747493 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 474.94537103735047, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.745724512282745, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -198.88194128471247, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.78541762942447, + 18.940227031707764, + 1.4114217237037758 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.9927159468306 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.99345452899112, + 22.670154191176067, + -7.629067473073568 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.036710309904143014, + 0, + 0.9993259494012661 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.023459823406074685, + 0.999724780469983 + ], + "world": [ + -18.45268377577148, + 7.970311257848664, + -8.353592157363893 + ] + } + } + }, + { + "tick": 580, + "q": [ + 8.587327421007348, + 6.795681267784465, + 3.321716221091818, + 9.343706430261529, + 14.015559645392292 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 476.226172864733, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.73291649400892, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -197.48031422515072, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.772609611150642, + 18.940227031707764, + 1.4254379942993936 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.60361680197946 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.980646510717293, + 22.666263199727556, + -7.61505120247795 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.023178458441054848, + 0, + 0.9997313434439756 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.04375504124825429, + 0.9990422895780556 + ], + "world": [ + -18.476049125769173, + 7.968934204260025, + -8.353592157363893 + ] + } + } + }, + { + "tick": 680, + "q": [ + 9.851918867897375, + 8.37441842738914, + 3.6797705807240826, + 10.976699592117525, + 16.465049388176286 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 477.49076431162297, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.72027057954002, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -195.90157706554604, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.759963696681744, + 18.940227031707764, + 1.4412253658954404 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 372.2455624423472 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.968000596248395, + 22.662682656131235, + -7.599263830881903 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + -0.008929861991525902, + 0, + 0.9999601279875174 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.06509877759870472, + 0.9978788248856443 + ], + "world": [ + -18.500674929339052, + 7.968168611407265, + -8.353592157363893 + ] + } + } + }, + { + "tick": 775, + "q": [ + 10.96679214504157, + 9.965826828848682, + 3.979894644738888, + 12.528043095880722, + 18.79206464382108 + ], + "nodes": { + "X": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2", + "localMm": [ + 478.6056375887672, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.70912184676858, + 14.883894920349121, + 3.4002411365509015 + ] + }, + "Y": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1", + "localMm": [ + -3.9693117141723633, + -194.31016866408652, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.748814963910302, + 18.940227031707764, + 1.4571394499100356 + ] + }, + "Z": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/2/1/0", + "localMm": [ + -20.80368995666504, + -904.0489196777344, + 371.94543837833237 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "world": [ + -18.956851863476953, + 22.659681415491086, + -7.583349746867308 + ] + }, + "B": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3", + "localMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + 0.004608028999853688, + 0, + 0.9999893829780077 + ], + "world": [ + -18.51522207260132, + 8.832522630691528, + -2.0127654075622576 + ] + }, + "C": { + "id": "dc2b9596-17d1-4c20-8563-832afab6d873:scene/0/0/0/3/1", + "localMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0.08534792697105023, + 0.9963512088424163 + ], + "world": [ + -18.524080696270122, + 7.968091595376016, + -8.353592157363893 + ] + } + } + } + ] +} diff --git a/coverage/glb-machine-manager/joint-dof/controls.report.json b/coverage/glb-machine-manager/joint-dof/controls.report.json new file mode 100644 index 0000000..d2e99a4 --- /dev/null +++ b/coverage/glb-machine-manager/joint-dof/controls.report.json @@ -0,0 +1 @@ +{"schema":"axis-control-inventory.v1","status":"verified","documented_controls":115,"duplicate_command_entries_removed":["machine_menu","view_menu","program_pane_load","tool_probe_commands"],"contract":"linuxcnc-axis-web-control-panel-source-logic.md"} diff --git a/coverage/glb-machine-manager/joint-dof/math.report.json b/coverage/glb-machine-manager/joint-dof/math.report.json new file mode 100644 index 0000000..061e308 --- /dev/null +++ b/coverage/glb-machine-manager/joint-dof/math.report.json @@ -0,0 +1 @@ +{"report_id":"GLB-MGR-E13-MATH","status":"verified","checks":["parent-frame motion with nonidentity zero orientation","unit/sign/physical-zero conversion","100 updates without drift","child inheritance","rotation about own origin","malformed JSON rejected","missing live axis rejected atomically"]} diff --git a/coverage/glb-machine-manager/joint-dof/xyzac-live.png b/coverage/glb-machine-manager/joint-dof/xyzac-live.png new file mode 100644 index 0000000..cfa3c7d Binary files /dev/null and b/coverage/glb-machine-manager/joint-dof/xyzac-live.png differ diff --git a/coverage/glb-machine-manager/joint-dof/xyzac.json b/coverage/glb-machine-manager/joint-dof/xyzac.json new file mode 100644 index 0000000..a58d9c1 --- /dev/null +++ b/coverage/glb-machine-manager/joint-dof/xyzac.json @@ -0,0 +1,4108 @@ +{ + "schema": "linuxcnc.joint-model/1", + "coordinates": "XYZAC", + "units": { + "length": "mm", + "angle": "deg", + "gltfLength": "m" + }, + "controller": { + "source": "linuxcnc-wasm.visual.joints", + "axisOrder": [ + "X", + "Y", + "Z", + "A", + "C" + ] + }, + "calibration": { + "cOriginMm": [ + 0, + 0, + 0 + ], + "tiltOffsetMm": [ + 0, + 0, + 0 + ], + "physicalZero": { + "x": 5, + "y": 0, + "z": 7, + "a": 12, + "b": 0, + "c": 9 + } + }, + "nodes": [ + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene", + "parentId": null, + "name": "MILL700-U_1.glb", + "role": "base", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:source-scene", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene", + "name": "AuxScene", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "source-scene" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:source-scene", + "name": "MILL700-U_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0" + }, + "zero": { + "positionMm": [ + -2349.517822265625, + -1341.2762880325317, + 2486.2565994262695 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "mapl14805146fe3i-None348856022", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/0" + }, + "zero": { + "positionMm": [ + -868.2212829589844, + 1555.3476810455322, + -784.3674421310425 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "mapl14805146fe3i-None348856022_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/1" + }, + "zero": { + "positionMm": [ + -157.12809562683105, + 1193.3876276016235, + -794.0124273300171 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "MILL700-A-主轴-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2" + }, + "zero": { + "positionMm": [ + 472.6388454437256, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "X", + "direction": "x", + "sign": 1, + "dof": 1, + "type": "prismatic", + "unit": "mm", + "frame": "parent" + } + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2", + "name": "MILL700-", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0" + }, + "zero": { + "positionMm": [ + -31.702756881713867, + -564.4468069076538, + 187.18719482421875 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0", + "name": "平面004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/0" + }, + "zero": { + "positionMm": [ + 69.46694850921631, + 0.13110041618347168, + 468.2943820953369 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0", + "name": "立方体064", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1" + }, + "zero": { + "positionMm": [ + -322.7880001068115, + 0.024276018142700174, + 198.69959354400635 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体025", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/0" + }, + "zero": { + "positionMm": [ + 680.0000667572021, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体026", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/1" + }, + "zero": { + "positionMm": [ + 860.0003719329834, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体027", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/2" + }, + "zero": { + "positionMm": [ + 880.0003528594971, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体028", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/3" + }, + "zero": { + "positionMm": [ + 900.0003337860107, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/4", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体029", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/4" + }, + "zero": { + "positionMm": [ + 779.9999713897705, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/5", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体030", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/5" + }, + "zero": { + "positionMm": [ + 799.9999523162842, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/6", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体031", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/6" + }, + "zero": { + "positionMm": [ + 839.9999141693115, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/7", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体032", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/7" + }, + "zero": { + "positionMm": [ + 819.9999332427979, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/8", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体033", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/8" + }, + "zero": { + "positionMm": [ + 740.0000095367432, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/9", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体034", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/9" + }, + "zero": { + "positionMm": [ + 759.9999904632568, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/10", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体035", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/10" + }, + "zero": { + "positionMm": [ + 720.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/11", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体036", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/11" + }, + "zero": { + "positionMm": [ + 700.0000476837158, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/12", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体053", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/12" + }, + "zero": { + "positionMm": [ + -160.00008583068848, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/13", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体054", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/13" + }, + "zero": { + "positionMm": [ + -180.00006675720215, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/14", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体055", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/14" + }, + "zero": { + "positionMm": [ + -220.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/15", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体056", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/15" + }, + "zero": { + "positionMm": [ + -200.00004768371582, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/16", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体057", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/16" + }, + "zero": { + "positionMm": [ + -79.99992370605469, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/17", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体058", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/17" + }, + "zero": { + "positionMm": [ + -99.99990463256836, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/18", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体059", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/18" + }, + "zero": { + "positionMm": [ + -139.9998664855957, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/19", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体060", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/19" + }, + "zero": { + "positionMm": [ + -119.99988555908203, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/20", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体061", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/20" + }, + "zero": { + "positionMm": [ + -39.999961853027344, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/21", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体062", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/21" + }, + "zero": { + "positionMm": [ + -59.999942779541016, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1/22", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/0/1", + "name": "立方体063", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/22" + }, + "zero": { + "positionMm": [ + -19.999980926513672, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2", + "name": "MILL700-A-主轴-JT2", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1" + }, + "zero": { + "positionMm": [ + -3.9693117141723633, + -204.27599549293518, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "Y", + "direction": "y", + "sign": 1, + "dof": 1, + "type": "prismatic", + "unit": "mm", + "frame": "parent" + } + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1", + "name": "MILL700-A-主轴-JT3", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0" + }, + "zero": { + "positionMm": [ + -20.80368995666504, + -904.0489196777344, + 368.9253330230713 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "Z", + "direction": "z", + "sign": -1, + "dof": 1, + "type": "prismatic", + "unit": "mm", + "frame": "parent" + } + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0", + "name": "123", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0" + }, + "zero": { + "positionMm": [ + 0.059604644775390625, + 1.8161535263061523, + -702.0378112792969 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0", + "name": "_3028378857429004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0/0" + }, + "zero": { + "positionMm": [ + 0.5391836166381836, + -2.054750919342041, + 502.8970241546631 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0/0", + "name": "_3028378857429004_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0/0/0" + }, + "zero": { + "positionMm": [ + -0.5391836166381836, + 2.054750919342041, + -502.8970241546631 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0/0/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/0/0", + "name": "_3028378857429004_2", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0/0/1" + }, + "zero": { + "positionMm": [ + -0.49185752868652344, + 1.9202828407287598, + -286.04042530059814 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0", + "name": "MILL700-A-主轴-JT4", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/1" + }, + "zero": { + "positionMm": [ + 1.3986825942993164, + 0.9530782699584961, + -850.0750064849854 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0", + "name": "SHS25C100020_Track_TSXXX-0000058522006", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/2" + }, + "zero": { + "positionMm": [ + 0.07891654968261719, + 167.50484704971313, + -9.09280776977539 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1", + "name": "SHS25C100020_Track_TSXXX-0000058522002", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/1" + }, + "zero": { + "positionMm": [ + 3.1614303588867188, + 121.06472253799438, + -268.9887285232544 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1", + "name": "SHS25CQZSS1_Slide25-55_TSXXX-0000058522012", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/2" + }, + "zero": { + "positionMm": [ + -23.18131923675537, + -708.1259489059448, + 123.15058708190918 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2", + "name": "SHS25CQZSS1_Slide25-55_TSXXX-0000058522001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/2" + }, + "zero": { + "positionMm": [ + -7.143378257751465, + -71.1749792098999, + 10.328888893127441 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2", + "name": "SHS25CQZSS1_Slide25-55_TSXXX-0000058522001001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/3" + }, + "zero": { + "positionMm": [ + -7.143378257751465, + -71.1749792098999, + 10.328888893127441 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "MILL700-A-工作台-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3" + }, + "zero": { + "positionMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "A", + "direction": "x", + "sign": 1, + "dof": 1, + "type": "revolute", + "unit": "deg", + "frame": "parent" + } + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3", + "name": "MILL700-A-工作台-JT1001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3", + "name": "MILL700-A-工作台-JT2", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/1" + }, + "zero": { + "positionMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "C", + "direction": "z", + "sign": 1, + "dof": 1, + "type": "revolute", + "unit": "deg", + "frame": "parent" + } + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/1/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/1", + "name": "MILL700-A--工件托盘", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/1/0" + }, + "zero": { + "positionMm": [ + -0.039458274841308594, + 0.02866983413696289, + 60.18102169036865 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/1/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/1/0", + "name": "MILL700工件", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/1/0/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3", + "name": "托底A", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/2" + }, + "zero": { + "positionMm": [ + -24.169445037841797, + -631.3432455062866, + -370.8224296569824 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/2/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/2", + "name": "MILL700-A-工作台-JT1001_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/2/0" + }, + "zero": { + "positionMm": [ + 24.172663688659668, + 650.3862142562866, + 370.81682682037354 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "MILL700-A-门1-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4" + }, + "zero": { + "positionMm": [ + 1380.0005912780762, + 622.5010752677917, + -767.5008773803711 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4", + "name": "mapl14805146fe3i014", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4/0" + }, + "zero": { + "positionMm": [ + 69.13900375366211, + -430.00227212905884, + -264.99879360198975 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4", + "name": "mapl14805146fe3i016", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4/1" + }, + "zero": { + "positionMm": [ + -62.04628944396973, + 56.50210380554199, + 775.352954864502 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/4", + "name": "mapl14805146fe3i431", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4/2" + }, + "zero": { + "positionMm": [ + -6.041586399078369, + 54.02481555938721, + -313.0190372467041 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "MILL700-A-门2-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5" + }, + "zero": { + "positionMm": [ + 517.0004367828369, + -263.78095149993896, + -744.2798614501953 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5", + "name": "mapl14805146fe3i197", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0" + }, + "zero": { + "positionMm": [ + -78.32932472229004, + 165.7944917678833, + 817.5182342529297 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0", + "name": "_126_807_34061712252_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0", + "name": "mapl14805146fe3i001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/1" + }, + "zero": { + "positionMm": [ + 648.3297348022461, + -211.74824237823486, + -1078.8166522979736 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0", + "name": "mapl14805146fe3i196", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/2" + }, + "zero": { + "positionMm": [ + 101.07946395874023, + -114.5554780960083, + -789.954662322998 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/2/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/2", + "name": "_126_807_35301710201", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/2/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/2/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/5/0/2", + "name": "_126_807_35301710201_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/2/1" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0", + "name": "MILL700-A底座", + "role": "base", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6" + }, + "zero": { + "positionMm": [ + 459.7148895263672, + 906.7631363868713, + -2320.876121520996 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "SHS25C100020_Track_TSXXX-0000058522001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/0" + }, + "zero": { + "positionMm": [ + 39.36731815338135, + 667.2102808952332, + 1206.9398164749146 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "底座002", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/1" + }, + "zero": { + "positionMm": [ + 2.282381057739258, + 382.2411298751831, + 456.71534538269043 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "底座009", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/2" + }, + "zero": { + "positionMm": [ + 323.70173931121826, + -1000.0901222229004, + 292.11002588272095 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "底座011", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/3" + }, + "zero": { + "positionMm": [ + -3093.5568809509277, + -2605.290651321411, + -2075.2453804016113 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/4", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "底座012", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/4" + }, + "zero": { + "positionMm": [ + 618.5640096664429, + -0.025063753128051758, + 520.3256011009216 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/5", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "底座015", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/5" + }, + "zero": { + "positionMm": [ + 838.29665184021, + -237.21277713775635, + 275.1512825489044 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "显示器001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6" + }, + "zero": { + "positionMm": [ + -682.807445526123, + -1648.0319499969482, + 759.9776983261108 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6", + "name": "mapl14805146fe3i-None1061424004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/0" + }, + "zero": { + "positionMm": [ + -0.8423328399658203, + -13.741254806518555, + -113.4251356124878 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/0/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/0", + "name": "Mesh_66", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/0/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/0/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/0", + "name": "Mesh_66_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/0/1" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6", + "name": "mapl14805146fe3i-None1061424004001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/1" + }, + "zero": { + "positionMm": [ + -0.8423328399658203, + -13.741254806518555, + -113.4251356124878 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6", + "name": "mapl14805146fe3i-None1061424004_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/2" + }, + "zero": { + "positionMm": [ + -81.01844787597656, + -285.07375717163086, + -248.1238842010498 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6", + "name": "平面", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/3" + }, + "zero": { + "positionMm": [ + -59.470415115356445, + -12.278079986572266, + 561.9646310806274 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6/4", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/6", + "name": "平面001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/4" + }, + "zero": { + "positionMm": [ + 3.1015872955322266, + -22.612810134887695, + 530.0655364990234 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "移动柜", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7" + }, + "zero": { + "positionMm": [ + -467.17357635498047, + 1571.4397430419922, + 533.7636470794678 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/0" + }, + "zero": { + "positionMm": [ + -21.704435348510742, + 3.5028457641601562, + 45.71419954299927 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/1" + }, + "zero": { + "positionMm": [ + -21.704435348510742, + 3.5028457641601562, + 45.71419954299927 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/2" + }, + "zero": { + "positionMm": [ + 408.51664543151855, + -30.3189754486084, + -459.0965211391449 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910_1001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/3" + }, + "zero": { + "positionMm": [ + 408.51664543151855, + -30.3189754486084, + -459.0965211391449 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6", + "name": "立方体", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8" + }, + "zero": { + "positionMm": [ + 358.4332466125488, + 211.11465990543365, + 1708.8958024978638 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/0", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/0" + }, + "zero": { + "positionMm": [ + 19.999980926513672, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/1", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体002", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/1" + }, + "zero": { + "positionMm": [ + 59.999942779541016, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/2", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体003", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/2" + }, + "zero": { + "positionMm": [ + 39.999961853027344, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/3", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/3" + }, + "zero": { + "positionMm": [ + 119.99988555908203, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/4", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体005", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/4" + }, + "zero": { + "positionMm": [ + 139.9998664855957, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/5", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体006", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/5" + }, + "zero": { + "positionMm": [ + 99.99990463256836, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/6", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体007", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/6" + }, + "zero": { + "positionMm": [ + 79.99992370605469, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/7", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体008", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/7" + }, + "zero": { + "positionMm": [ + 240.00024795532227, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/8", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体009", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/8" + }, + "zero": { + "positionMm": [ + 260.00022888183594, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/9", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体010", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/9" + }, + "zero": { + "positionMm": [ + 300.0001907348633, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/10", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体011", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/10" + }, + "zero": { + "positionMm": [ + 280.0002098083496, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/11", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体012", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/11" + }, + "zero": { + "positionMm": [ + 200.00028610229492, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/12", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体013", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/12" + }, + "zero": { + "positionMm": [ + 220.0002670288086, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/13", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体014", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/13" + }, + "zero": { + "positionMm": [ + 180.00030517578125, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/14", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体015", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/14" + }, + "zero": { + "positionMm": [ + 160.00032424926758, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/15", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体016", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/15" + }, + "zero": { + "positionMm": [ + 320.00017166137695, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/16", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体017", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/16" + }, + "zero": { + "positionMm": [ + 340.0001525878906, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/17", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体018", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/17" + }, + "zero": { + "positionMm": [ + 380.0005912780762, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/18", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体019", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/18" + }, + "zero": { + "positionMm": [ + 360.0001335144043, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/19", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体020", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/19" + }, + "zero": { + "positionMm": [ + 440.000057220459, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/20", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体021", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/20" + }, + "zero": { + "positionMm": [ + 460.00051498413086, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/21", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体022", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/21" + }, + "zero": { + "positionMm": [ + 420.0005531311035, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/22", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体023", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/22" + }, + "zero": { + "positionMm": [ + 400.00009536743164, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/23", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体024", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/23" + }, + "zero": { + "positionMm": [ + -19.999980926513672, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/24", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体037", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/24" + }, + "zero": { + "positionMm": [ + -700.0000476837158, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/25", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体038", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/25" + }, + "zero": { + "positionMm": [ + -720.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/26", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体039", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/26" + }, + "zero": { + "positionMm": [ + -759.9999904632568, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/27", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体040", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/27" + }, + "zero": { + "positionMm": [ + -740.0000095367432, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/28", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体041", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/28" + }, + "zero": { + "positionMm": [ + -819.9999332427979, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/29", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体042", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/29" + }, + "zero": { + "positionMm": [ + -839.9999141693115, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/30", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体043", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/30" + }, + "zero": { + "positionMm": [ + -799.9999523162842, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/31", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体044", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/31" + }, + "zero": { + "positionMm": [ + -779.9999713897705, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/32", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体045", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/32" + }, + "zero": { + "positionMm": [ + -940.000057220459, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/33", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体046", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/33" + }, + "zero": { + "positionMm": [ + -959.9997997283936, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/34", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体047", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/34" + }, + "zero": { + "positionMm": [ + -999.9997615814209, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/35", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体048", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/35" + }, + "zero": { + "positionMm": [ + -979.9997806549072, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/36", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体049", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/36" + }, + "zero": { + "positionMm": [ + -900.0000953674316, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/37", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体050", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/37" + }, + "zero": { + "positionMm": [ + -920.0000762939453, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/38", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体051", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/38" + }, + "zero": { + "positionMm": [ + -880.000114440918, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/39", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体052", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/39" + }, + "zero": { + "positionMm": [ + -860.0001335144043, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/40", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体065", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/40" + }, + "zero": { + "positionMm": [ + -1020.0002193450928, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/41", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体066", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/41" + }, + "zero": { + "positionMm": [ + -1079.9999237060547, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/42", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体067", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/42" + }, + "zero": { + "positionMm": [ + -1099.9999046325684, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/43", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体068", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/43" + }, + "zero": { + "positionMm": [ + -1059.999942779541, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/44", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体069", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/44" + }, + "zero": { + "positionMm": [ + -1040.0002002716064, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/45", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体070", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/45" + }, + "zero": { + "positionMm": [ + -1160.0000858306885, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/46", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体071", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/46" + }, + "zero": { + "positionMm": [ + -1180.0000667572021, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/47", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体072", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/47" + }, + "zero": { + "positionMm": [ + -1220.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/48", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体073", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/48" + }, + "zero": { + "positionMm": [ + -1200.0000476837158, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/49", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体074", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/49" + }, + "zero": { + "positionMm": [ + -1140.0001049041748, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8/50", + "parentId": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/6/8", + "name": "立方体075", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/50" + }, + "zero": { + "positionMm": [ + -1119.999885559082, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + } + ], + "axisMap": { + "X": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2", + "Y": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1", + "Z": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/2/1/0", + "A": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3", + "C": "0528c73a-d8a4-4ba6-9b2b-b2a3ad77f11e:scene/0/3/1" + } +} diff --git a/coverage/glb-machine-manager/joint-dof/xyzbc-live.png b/coverage/glb-machine-manager/joint-dof/xyzbc-live.png new file mode 100644 index 0000000..fd7e51b Binary files /dev/null and b/coverage/glb-machine-manager/joint-dof/xyzbc-live.png differ diff --git a/coverage/glb-machine-manager/joint-dof/xyzbc.json b/coverage/glb-machine-manager/joint-dof/xyzbc.json new file mode 100644 index 0000000..d3e6242 --- /dev/null +++ b/coverage/glb-machine-manager/joint-dof/xyzbc.json @@ -0,0 +1,4108 @@ +{ + "schema": "linuxcnc.joint-model/1", + "coordinates": "XYZBC", + "units": { + "length": "mm", + "angle": "deg", + "gltfLength": "m" + }, + "controller": { + "source": "linuxcnc-wasm.visual.joints", + "axisOrder": [ + "X", + "Y", + "Z", + "B", + "C" + ] + }, + "calibration": { + "cOriginMm": [ + 0, + 0, + 0 + ], + "tiltOffsetMm": [ + 0, + 0, + 0 + ], + "physicalZero": { + "x": 5, + "y": 0, + "z": 7, + "a": 0, + "b": 12, + "c": 9 + } + }, + "nodes": [ + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene", + "parentId": null, + "name": "MILL700-U_1.glb", + "role": "base", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:source-scene", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene", + "name": "AuxScene", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "source-scene" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:source-scene", + "name": "MILL700-U_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0" + }, + "zero": { + "positionMm": [ + -2349.517822265625, + -1341.2762880325317, + 2486.2565994262695 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "mapl14805146fe3i-None348856022", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/0" + }, + "zero": { + "positionMm": [ + -868.2212829589844, + 1555.3476810455322, + -784.3674421310425 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "mapl14805146fe3i-None348856022_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/1" + }, + "zero": { + "positionMm": [ + -157.12809562683105, + 1193.3876276016235, + -794.0124273300171 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "MILL700-A-主轴-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2" + }, + "zero": { + "positionMm": [ + 472.6388454437256, + 1681.300401687622, + -997.8671073913574 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "X", + "direction": "x", + "sign": 1, + "dof": 1, + "type": "prismatic", + "unit": "mm", + "frame": "parent" + } + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2", + "name": "MILL700-", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0" + }, + "zero": { + "positionMm": [ + -31.702756881713867, + -564.4468069076538, + 187.18719482421875 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0", + "name": "平面004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/0" + }, + "zero": { + "positionMm": [ + 69.46694850921631, + 0.13110041618347168, + 468.2943820953369 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0", + "name": "立方体064", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1" + }, + "zero": { + "positionMm": [ + -322.7880001068115, + 0.024276018142700174, + 198.69959354400635 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体025", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/0" + }, + "zero": { + "positionMm": [ + 680.0000667572021, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体026", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/1" + }, + "zero": { + "positionMm": [ + 860.0003719329834, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体027", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/2" + }, + "zero": { + "positionMm": [ + 880.0003528594971, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体028", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/3" + }, + "zero": { + "positionMm": [ + 900.0003337860107, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/4", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体029", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/4" + }, + "zero": { + "positionMm": [ + 779.9999713897705, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/5", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体030", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/5" + }, + "zero": { + "positionMm": [ + 799.9999523162842, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/6", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体031", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/6" + }, + "zero": { + "positionMm": [ + 839.9999141693115, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/7", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体032", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/7" + }, + "zero": { + "positionMm": [ + 819.9999332427979, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/8", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体033", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/8" + }, + "zero": { + "positionMm": [ + 740.0000095367432, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/9", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体034", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/9" + }, + "zero": { + "positionMm": [ + 759.9999904632568, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/10", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体035", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/10" + }, + "zero": { + "positionMm": [ + 720.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/11", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体036", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/11" + }, + "zero": { + "positionMm": [ + 700.0000476837158, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/12", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体053", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/12" + }, + "zero": { + "positionMm": [ + -160.00008583068848, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/13", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体054", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/13" + }, + "zero": { + "positionMm": [ + -180.00006675720215, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/14", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体055", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/14" + }, + "zero": { + "positionMm": [ + -220.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/15", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体056", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/15" + }, + "zero": { + "positionMm": [ + -200.00004768371582, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/16", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体057", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/16" + }, + "zero": { + "positionMm": [ + -79.99992370605469, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/17", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体058", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/17" + }, + "zero": { + "positionMm": [ + -99.99990463256836, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/18", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体059", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/18" + }, + "zero": { + "positionMm": [ + -139.9998664855957, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/19", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体060", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/19" + }, + "zero": { + "positionMm": [ + -119.99988555908203, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/20", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体061", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/20" + }, + "zero": { + "positionMm": [ + -39.999961853027344, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/21", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体062", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/21" + }, + "zero": { + "positionMm": [ + -59.999942779541016, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1/22", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/0/1", + "name": "立方体063", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/0/1/22" + }, + "zero": { + "positionMm": [ + -19.999980926513672, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2", + "name": "MILL700-A-主轴-JT2", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1" + }, + "zero": { + "positionMm": [ + -3.9693117141723633, + -204.27599549293518, + 405.63321113586426 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "Y", + "direction": "y", + "sign": 1, + "dof": 1, + "type": "prismatic", + "unit": "mm", + "frame": "parent" + } + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1", + "name": "MILL700-A-主轴-JT3", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0" + }, + "zero": { + "positionMm": [ + -20.80368995666504, + -904.0489196777344, + 368.9253330230713 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "Z", + "direction": "z", + "sign": -1, + "dof": 1, + "type": "prismatic", + "unit": "mm", + "frame": "parent" + } + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0", + "name": "123", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0" + }, + "zero": { + "positionMm": [ + 0.059604644775390625, + 1.8161535263061523, + -702.0378112792969 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0", + "name": "_3028378857429004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0/0" + }, + "zero": { + "positionMm": [ + 0.5391836166381836, + -2.054750919342041, + 502.8970241546631 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0/0", + "name": "_3028378857429004_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0/0/0" + }, + "zero": { + "positionMm": [ + -0.5391836166381836, + 2.054750919342041, + -502.8970241546631 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0/0/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/0/0", + "name": "_3028378857429004_2", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/0/0/1" + }, + "zero": { + "positionMm": [ + -0.49185752868652344, + 1.9202828407287598, + -286.04042530059814 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0", + "name": "MILL700-A-主轴-JT4", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/1" + }, + "zero": { + "positionMm": [ + 1.3986825942993164, + 0.9530782699584961, + -850.0750064849854 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0", + "name": "SHS25C100020_Track_TSXXX-0000058522006", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/0/2" + }, + "zero": { + "positionMm": [ + 0.07891654968261719, + 167.50484704971313, + -9.09280776977539 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1", + "name": "SHS25C100020_Track_TSXXX-0000058522002", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/1" + }, + "zero": { + "positionMm": [ + 3.1614303588867188, + 121.06472253799438, + -268.9887285232544 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1", + "name": "SHS25CQZSS1_Slide25-55_TSXXX-0000058522012", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/1/2" + }, + "zero": { + "positionMm": [ + -23.18131923675537, + -708.1259489059448, + 123.15058708190918 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2", + "name": "SHS25CQZSS1_Slide25-55_TSXXX-0000058522001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/2" + }, + "zero": { + "positionMm": [ + -7.143378257751465, + -71.1749792098999, + 10.328888893127441 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2", + "name": "SHS25CQZSS1_Slide25-55_TSXXX-0000058522001001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/2/3" + }, + "zero": { + "positionMm": [ + -7.143378257751465, + -71.1749792098999, + 10.328888893127441 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "MILL700-A-工作台-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3" + }, + "zero": { + "positionMm": [ + 497.99561500549316, + 1139.9997472763062, + -1603.0043363571167 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "B", + "direction": "y", + "sign": 1, + "dof": 1, + "type": "revolute", + "unit": "deg", + "frame": "parent" + } + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3", + "name": "MILL700-A-工作台-JT1001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3", + "name": "MILL700-A-工作台-JT2", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/1" + }, + "zero": { + "positionMm": [ + -0.08916854858398438, + -634.0826749801636, + -86.4475965499878 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": { + "axis": "C", + "direction": "z", + "sign": 1, + "dof": 1, + "type": "revolute", + "unit": "deg", + "frame": "parent" + } + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/1/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/1", + "name": "MILL700-A--工件托盘", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/1/0" + }, + "zero": { + "positionMm": [ + -0.039458274841308594, + 0.02866983413696289, + 60.18102169036865 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/1/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/1/0", + "name": "MILL700工件", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/1/0/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3", + "name": "托底A", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/2" + }, + "zero": { + "positionMm": [ + -24.169445037841797, + -631.3432455062866, + -370.8224296569824 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/2/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/2", + "name": "MILL700-A-工作台-JT1001_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/3/2/0" + }, + "zero": { + "positionMm": [ + 24.172663688659668, + 650.3862142562866, + 370.81682682037354 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "MILL700-A-门1-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4" + }, + "zero": { + "positionMm": [ + 1380.0005912780762, + 622.5010752677917, + -767.5008773803711 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4", + "name": "mapl14805146fe3i014", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4/0" + }, + "zero": { + "positionMm": [ + 69.13900375366211, + -430.00227212905884, + -264.99879360198975 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4", + "name": "mapl14805146fe3i016", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4/1" + }, + "zero": { + "positionMm": [ + -62.04628944396973, + 56.50210380554199, + 775.352954864502 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/4", + "name": "mapl14805146fe3i431", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/4/2" + }, + "zero": { + "positionMm": [ + -6.041586399078369, + 54.02481555938721, + -313.0190372467041 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "MILL700-A-门2-JT1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5" + }, + "zero": { + "positionMm": [ + 517.0004367828369, + -263.78095149993896, + -744.2798614501953 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5", + "name": "mapl14805146fe3i197", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0" + }, + "zero": { + "positionMm": [ + -78.32932472229004, + 165.7944917678833, + 817.5182342529297 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0", + "name": "_126_807_34061712252_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0", + "name": "mapl14805146fe3i001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/1" + }, + "zero": { + "positionMm": [ + 648.3297348022461, + -211.74824237823486, + -1078.8166522979736 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0", + "name": "mapl14805146fe3i196", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/2" + }, + "zero": { + "positionMm": [ + 101.07946395874023, + -114.5554780960083, + -789.954662322998 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/2/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/2", + "name": "_126_807_35301710201", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/2/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/2/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/5/0/2", + "name": "_126_807_35301710201_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/5/0/2/1" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0", + "name": "MILL700-A底座", + "role": "base", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6" + }, + "zero": { + "positionMm": [ + 459.7148895263672, + 906.7631363868713, + -2320.876121520996 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "SHS25C100020_Track_TSXXX-0000058522001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/0" + }, + "zero": { + "positionMm": [ + 39.36731815338135, + 667.2102808952332, + 1206.9398164749146 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "底座002", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/1" + }, + "zero": { + "positionMm": [ + 2.282381057739258, + 382.2411298751831, + 456.71534538269043 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "底座009", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/2" + }, + "zero": { + "positionMm": [ + 323.70173931121826, + -1000.0901222229004, + 292.11002588272095 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "底座011", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/3" + }, + "zero": { + "positionMm": [ + -3093.5568809509277, + -2605.290651321411, + -2075.2453804016113 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/4", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "底座012", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/4" + }, + "zero": { + "positionMm": [ + 618.5640096664429, + -0.025063753128051758, + 520.3256011009216 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/5", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "底座015", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/5" + }, + "zero": { + "positionMm": [ + 838.29665184021, + -237.21277713775635, + 275.1512825489044 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "显示器001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6" + }, + "zero": { + "positionMm": [ + -682.807445526123, + -1648.0319499969482, + 759.9776983261108 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6", + "name": "mapl14805146fe3i-None1061424004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/0" + }, + "zero": { + "positionMm": [ + -0.8423328399658203, + -13.741254806518555, + -113.4251356124878 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/0/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/0", + "name": "Mesh_66", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/0/0" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/0/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/0", + "name": "Mesh_66_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/0/1" + }, + "zero": { + "positionMm": [ + 0, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6", + "name": "mapl14805146fe3i-None1061424004001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/1" + }, + "zero": { + "positionMm": [ + -0.8423328399658203, + -13.741254806518555, + -113.4251356124878 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6", + "name": "mapl14805146fe3i-None1061424004_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/2" + }, + "zero": { + "positionMm": [ + -81.01844787597656, + -285.07375717163086, + -248.1238842010498 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6", + "name": "平面", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/3" + }, + "zero": { + "positionMm": [ + -59.470415115356445, + -12.278079986572266, + 561.9646310806274 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6/4", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/6", + "name": "平面001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/6/4" + }, + "zero": { + "positionMm": [ + 3.1015872955322266, + -22.612810134887695, + 530.0655364990234 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "移动柜", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7" + }, + "zero": { + "positionMm": [ + -467.17357635498047, + 1571.4397430419922, + 533.7636470794678 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/0" + }, + "zero": { + "positionMm": [ + -21.704435348510742, + 3.5028457641601562, + 45.71419954299927 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/1" + }, + "zero": { + "positionMm": [ + -21.704435348510742, + 3.5028457641601562, + 45.71419954299927 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910_1", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/2" + }, + "zero": { + "positionMm": [ + 408.51664543151855, + -30.3189754486084, + -459.0965211391449 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/7", + "name": "mapl14805146fe3i-None3286791910_1001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/7/3" + }, + "zero": { + "positionMm": [ + 408.51664543151855, + -30.3189754486084, + -459.0965211391449 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6", + "name": "立方体", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8" + }, + "zero": { + "positionMm": [ + 358.4332466125488, + 211.11465990543365, + 1708.8958024978638 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/0", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体001", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/0" + }, + "zero": { + "positionMm": [ + 19.999980926513672, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/1", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体002", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/1" + }, + "zero": { + "positionMm": [ + 59.999942779541016, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/2", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体003", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/2" + }, + "zero": { + "positionMm": [ + 39.999961853027344, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/3", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体004", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/3" + }, + "zero": { + "positionMm": [ + 119.99988555908203, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/4", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体005", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/4" + }, + "zero": { + "positionMm": [ + 139.9998664855957, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/5", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体006", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/5" + }, + "zero": { + "positionMm": [ + 99.99990463256836, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/6", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体007", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/6" + }, + "zero": { + "positionMm": [ + 79.99992370605469, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/7", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体008", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/7" + }, + "zero": { + "positionMm": [ + 240.00024795532227, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/8", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体009", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/8" + }, + "zero": { + "positionMm": [ + 260.00022888183594, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/9", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体010", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/9" + }, + "zero": { + "positionMm": [ + 300.0001907348633, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/10", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体011", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/10" + }, + "zero": { + "positionMm": [ + 280.0002098083496, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/11", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体012", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/11" + }, + "zero": { + "positionMm": [ + 200.00028610229492, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/12", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体013", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/12" + }, + "zero": { + "positionMm": [ + 220.0002670288086, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/13", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体014", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/13" + }, + "zero": { + "positionMm": [ + 180.00030517578125, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/14", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体015", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/14" + }, + "zero": { + "positionMm": [ + 160.00032424926758, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/15", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体016", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/15" + }, + "zero": { + "positionMm": [ + 320.00017166137695, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/16", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体017", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/16" + }, + "zero": { + "positionMm": [ + 340.0001525878906, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/17", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体018", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/17" + }, + "zero": { + "positionMm": [ + 380.0005912780762, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/18", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体019", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/18" + }, + "zero": { + "positionMm": [ + 360.0001335144043, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/19", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体020", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/19" + }, + "zero": { + "positionMm": [ + 440.000057220459, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/20", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体021", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/20" + }, + "zero": { + "positionMm": [ + 460.00051498413086, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/21", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体022", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/21" + }, + "zero": { + "positionMm": [ + 420.0005531311035, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/22", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体023", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/22" + }, + "zero": { + "positionMm": [ + 400.00009536743164, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/23", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体024", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/23" + }, + "zero": { + "positionMm": [ + -19.999980926513672, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/24", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体037", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/24" + }, + "zero": { + "positionMm": [ + -700.0000476837158, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/25", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体038", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/25" + }, + "zero": { + "positionMm": [ + -720.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/26", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体039", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/26" + }, + "zero": { + "positionMm": [ + -759.9999904632568, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/27", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体040", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/27" + }, + "zero": { + "positionMm": [ + -740.0000095367432, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/28", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体041", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/28" + }, + "zero": { + "positionMm": [ + -819.9999332427979, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/29", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体042", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/29" + }, + "zero": { + "positionMm": [ + -839.9999141693115, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/30", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体043", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/30" + }, + "zero": { + "positionMm": [ + -799.9999523162842, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/31", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体044", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/31" + }, + "zero": { + "positionMm": [ + -779.9999713897705, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/32", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体045", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/32" + }, + "zero": { + "positionMm": [ + -940.000057220459, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/33", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体046", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/33" + }, + "zero": { + "positionMm": [ + -959.9997997283936, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/34", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体047", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/34" + }, + "zero": { + "positionMm": [ + -999.9997615814209, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/35", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体048", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/35" + }, + "zero": { + "positionMm": [ + -979.9997806549072, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/36", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体049", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/36" + }, + "zero": { + "positionMm": [ + -900.0000953674316, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/37", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体050", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/37" + }, + "zero": { + "positionMm": [ + -920.0000762939453, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/38", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体051", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/38" + }, + "zero": { + "positionMm": [ + -880.000114440918, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/39", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体052", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/39" + }, + "zero": { + "positionMm": [ + -860.0001335144043, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/40", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体065", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/40" + }, + "zero": { + "positionMm": [ + -1020.0002193450928, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/41", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体066", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/41" + }, + "zero": { + "positionMm": [ + -1079.9999237060547, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/42", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体067", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/42" + }, + "zero": { + "positionMm": [ + -1099.9999046325684, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/43", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体068", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/43" + }, + "zero": { + "positionMm": [ + -1059.999942779541, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/44", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体069", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/44" + }, + "zero": { + "positionMm": [ + -1040.0002002716064, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/45", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体070", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/45" + }, + "zero": { + "positionMm": [ + -1160.0000858306885, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/46", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体071", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/46" + }, + "zero": { + "positionMm": [ + -1180.0000667572021, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/47", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体072", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/47" + }, + "zero": { + "positionMm": [ + -1220.0000286102295, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/48", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体073", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/48" + }, + "zero": { + "positionMm": [ + -1200.0000476837158, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/49", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体074", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/49" + }, + "zero": { + "positionMm": [ + -1140.0001049041748, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + }, + { + "id": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8/50", + "parentId": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/6/8", + "name": "立方体075", + "role": "", + "source": { + "file": "MILL700-U_1.glb", + "hash": "bf37130681cc6462412e52744519516e8ade2a3212dd69153c0b1b3e415bcf66", + "path": "scene/0/6/8/50" + }, + "zero": { + "positionMm": [ + -1119.999885559082, + 0, + 0 + ], + "quaternion": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + "joint": null + } + ], + "axisMap": { + "X": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2", + "Y": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1", + "Z": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/2/1/0", + "B": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3", + "C": "3330e412-f0f9-4360-b1cb-ddc0c957ebda:scene/0/3/1" + } +} diff --git a/coverage/glb-machine-manager/managed-bc-running.png b/coverage/glb-machine-manager/managed-bc-running.png index 460d7a8..bd76d48 100644 Binary files a/coverage/glb-machine-manager/managed-bc-running.png and b/coverage/glb-machine-manager/managed-bc-running.png differ diff --git a/coverage/glb-machine-manager/manager-ac-config.png b/coverage/glb-machine-manager/manager-ac-config.png index 5090862..84e78ee 100644 Binary files a/coverage/glb-machine-manager/manager-ac-config.png and b/coverage/glb-machine-manager/manager-ac-config.png differ diff --git a/docs/glb-machine-manager/01-项目功能内容.md b/docs/glb-machine-manager/01-项目功能内容.md index 98ee30f..5da36cf 100644 --- a/docs/glb-machine-manager/01-项目功能内容.md +++ b/docs/glb-machine-manager/01-项目功能内容.md @@ -8,12 +8,12 @@ 1. 对话框包含独立的 Three.js 三维预览,实时显示正在配置的机床。 2. 支持逐个导入自包含 `.glb` 机床部件,不允许依赖外部纹理或网格文件。 -3. 每个部件可绑定 `底座、X、Y、Z、刀具、A/B、C、工件` 角色,并编辑父级关系。可运行结构必须满足: +3. 每个部件可绑定 `底座、X、Y、Z、刀具、A/B、C、工件` 结构角色,并编辑父级关系。MGR-17 以独立的 XYZABC 自由度绑定驱动视觉层级,允许单 GLB 多关节。数控求解器仍按 TRT 的控制参考链运行: - 刀具链:底座 → X → Y → Z → 刀具。 - 转台链:底座 → A/B → C → 工件。 4. 每个部件可定义局部坐标原点。模型网格相对该原点偏移,运动关节围绕该原点运动。 5. 每个部件可独立显示或隐藏原点坐标轴辅助线。 -6. 每个部件可用数值和 Three.js 操纵器平移、旋转;变换只修改装配配置,不修改原 GLB 字节。Three.js 管理器通过 `camera.up.set(0, 0, 1)` 使用机床 Z-up 场景,机床 XY 为地面平面;导入 GLB 的节点、几何、材质、内部层级和原始变换保持不变。 +6. 每个运动节点只允许通过绑定的唯一自由度修改位移或转角,固定节点锁定;旧六分量自由编辑和操纵器由单值关节输入替代。变换只修改预览配置,不修改原 GLB 字节。Three.js 管理器通过 `camera.up.set(0, 0, 1)` 使用机床 Z-up 场景,机床 XY 为地面平面;导入 GLB 的节点、几何、材质、内部层级和原始变换保持不变。 7. 可定义 C 轴原点位置和 A/B 到 C 的运动学偏置。应用时写入底座场景节点及整机场景的 `userData.linuxcncMachineGeometry`,同时生成 URDF: - `C 原点 P = (x-rot-point, y-rot-point, z-rot-point)`。 - `A/B 偏置 D = (x-offset, y-offset, z-offset)`。 @@ -50,3 +50,5 @@ - 非法层级、重复角色、缺失部件、错误机型和外部资源 GLB 明确拒绝,已生效模型不改变。 15. 树中每个节点独立编辑相对父级的位置(mm)和 XYZ 欧拉姿态(deg)。数值输入回显节点当前局部值,旋转以自身原点为中心;子节点随动,父级和兄弟节点不变。源 GLB 保留,节点编辑保存在草稿并进入组合导出。 + +16. MGR-17:每个节点可配置一个 XYZABC 自由度、父坐标轴方向和正负向。配置以 `linuxcnc.joint-model/1` JSON 写入原 userData,包含层级、零姿态、轴索引、物理零点和 RTCP 标定。管理器实时跟随与正式机床共用驱动逻辑;详见 [07-单自由度JSON与实时驱动](07-单自由度JSON与实时驱动.md)。 diff --git a/docs/glb-machine-manager/02-项目程序开发详细步骤.md b/docs/glb-machine-manager/02-项目程序开发详细步骤.md index 7ddbce4..dc0663d 100644 --- a/docs/glb-machine-manager/02-项目程序开发详细步骤.md +++ b/docs/glb-machine-manager/02-项目程序开发详细步骤.md @@ -15,3 +15,5 @@ 13. `MGR-16`:将根节点部件变换和内部节点 `nodeTransforms[path]` 分开保存;编辑当前节点相对父级的位置和姿态,验证子节点继承、父兄节点不受影响、拖放换父级后保持世界姿态,并验证组合 GLB 与 AC/BC 应用结果。 每轮只推进任务矩阵中依赖已满足的任务。代码完成后必须同步更新 03、04、05;若改变数据契约或坐标定义,先更新 06。 + +14. `MGR-17`:定义单自由度 JSON/Schema;收敛 UI 输入;增加单文件层级应用和 GLB 恢复;接入实际 WASM 关节位置;验证每轴、反向、物理零点、导出往返和锁定行为。 diff --git a/docs/glb-machine-manager/03-推进台账.md b/docs/glb-machine-manager/03-推进台账.md index eef192e..b821719 100644 --- a/docs/glb-machine-manager/03-推进台账.md +++ b/docs/glb-machine-manager/03-推进台账.md @@ -82,3 +82,13 @@ - MILL700 选择 `MILL700-A-主轴-JT3`,输入 X=120 mm、RZ=25° 后,节点局部位置和四元数分别变为 `0.120 m` 和 `25°`;8 部件 AC/BC 生产回归、导出和应用继续通过。 - MILL700 补充断言:JT3 局部 Z 减少 44 mm 时 JT2 与兄弟滑轨不动、JT4 同步移动;RZ=25° 时 JT3 原点不动、JT4 绕该原点运动;父链预先旋转 20°,位移仍按父坐标系计算。切换选择和重开对话框保留输入;带编辑姿态的门节点换父级后世界姿态不变且能继续编辑。 - MGR-16 verified;验收证据 E12。 + +## R11 / 2026-09-21 / 单自由度关节与控制器映射 + +- 每个节点固定或只绑定一个 XYZABC 轴;父级 x/y/z 方向和 ±1 可配,平移/旋转类型及单位由数控轴确定。旧自由位置/姿态编辑改成单值关节输入。 +- 新增 `machine-joints.ts`:版本化节点与控制器映射、严格校验、零姿态驱动;预览与运行共用。 +- `makeJointAssembly` 保留已有模型层级,支持 MILL700 单文件配置完整五轴后应用;新配置不再要求八个独立 GLB。 +- 实际床身节点、控制底座和场景写入关节 JSON,兼容原 URDF 与机床几何 userData。导出显式 Scene extras;GLB 重导入恢复轴绑定、方向、物理零点和标定。 +- App 快照接入对话框实时跟随;运行模型通过实际 WASM 关节名映射驱动,并发布同 tick 的轴输入/模型变换诊断。 +- 大型关节模型运行关闭 MSAA 和实时阴影,减轻软件 WebGL 绘制开销。 +- 数学/JSON 校验、八部件 AC/BC 回归与完整 MILL700 AC/BC 实时/导出往返验证均通过;每机型 8 个不同 tick,五个轴均变化,浏览器 warning/error=0。MGR-17 verified;证据 E13。 diff --git a/docs/glb-machine-manager/04-任务矩阵.md b/docs/glb-machine-manager/04-任务矩阵.md index 7224f3a..95243d9 100644 --- a/docs/glb-machine-manager/04-任务矩阵.md +++ b/docs/glb-machine-manager/04-任务矩阵.md @@ -18,3 +18,5 @@ |MGR-14 层级运动验证|verified|MGR-12,MGR-13|模型删除;树节点轴绑定;XYZ mm、A/B/C deg 输入驱动局部层级;local/world 变换和 MILL700 父子联动验收| |MGR-15 关节局部坐标系|verified|MGR-12,MGR-14|选中树节点时局部 XYZ 辅助轴挂在节点 `(0,0,0)`,随关节和父链变换,不重建 WebGL| |MGR-16 部件节点相对父级变换|verified|MGR-05,MGR-14|编辑树中当前节点的“部件平移/部件旋转”后,该节点相对父级的位置和姿态改变;父节点与兄弟节点不被误改;子节点继承;预览、导出和 AC/BC 应用保持一致| + +|MGR-17 单自由度与运行轴映射|verified|MGR-16|每节点固定或唯一 XYZABC 自由度;单值编辑;JSON 保存到场景/底座;单 GLB 完整应用;导出往返与 AC/BC 实时轴映射验收| diff --git a/docs/glb-machine-manager/05-验收证据.md b/docs/glb-machine-manager/05-验收证据.md index e43c30c..5b5c3a6 100644 --- a/docs/glb-machine-manager/05-验收证据.md +++ b/docs/glb-machine-manager/05-验收证据.md @@ -103,3 +103,26 @@ - JT3 的父、兄弟、子节点编辑前后位置与四元数记录于 MILL700 报告的 `nodeEditing`;验证重选、重开对话框、编辑后换父级和再次输入。 - 线上 E12:`https://49.232.250.35:58015/` 已发布代码 `91ebd56`,资源 `assets/index-CJLtuazD.js`。以线上 URL 重跑 MILL700(含父/兄弟节点隔离、子节点继承、换父级和重开对话框),全部通过、浏览器错误 0。发布报告:`coverage/glb-machine-manager/parent-transform-deployment.report.json`(`GLB-MGR-E12-CLOUD`)。 + +## E13 / R11 / 单自由度、JSON 与实时轴映射 + +- 数学与错误边界:`node scripts/test_machine_joint_model.mjs`;`GLB-MGR-E13-MATH`,报告 `coverage/glb-machine-manager/joint-dof/math.report.json`。验证父级旋转、非单位零姿态旋转、正负号、mm/deg、物理零点、100 次更新无累积、错误 JSON、缺失轴值整帧拒绝。 +- 完整浏览器命令:`MACHINE_MANAGER_URL=http://127.0.0.1:5180/ node scripts/test_machine_joint_dof_browser.mjs`;`GLB-MGR-E13`,报告 `coverage/glb-machine-manager/joint-dof/browser.report.json`。 +- AC/BC 分别导入 MILL700,五个关节独立绑定;Z 轴改方向并反向,固定节点锁定,无六分量输入和自由操纵器;重复轴拒绝,子节点继承、父兄节点保持不变。 +- 导出 JSON 与 GLB 场景 extras、实际床身底座 extras 内容一致;GLB 重新导入管理器恢复方向和物理零点,标准“导入机床模型”也可载入;单 GLB 可应用到控制面板。 +- 同时五轴程序:`M428; G21 G90 G94 G17 G40 G49; G1 X20 Y10 Z10 A/B20 C30 F120; G1 X-10 Y-5 Z-5 A/B-10 C-20 F120; M2`。每种机型采样 8 个不同渲染 tick,逐轴校验实际 WASM 关节值减物理零点、方向符号及父级局部运动。 +- 示例:`coverage/glb-machine-manager/joint-dof/xyzac.json`、`xyzbc.json`;截图 `xyzac-live.png`、`xyzbc-live.png`;Schema 用 Python `jsonschema` 对两份实例验证。 +- 八个独立部件回归:`scripts/test_machine_manager_browser.mjs` 已更新单自由度 UI;生产和源模式均通过,旧 URDF 驱动兼容性检查保留。 +- 控件清单 115 项;双画布、XYZAC Three.js 契约、TypeScript 和生产构建通过。 + +- E13 最终状态:verified。XYZAC/XYZBC 各 8 个不同渲染 tick,五个轴均发生变化;浏览器 warning/error=0。此前 E01–E12 为历史验证记录,本次现行输入契约以 E13 为准。 + +|E13 文件|SHA-256| +|---|---| +|`joint-dof/browser.report.json`|`7b92b906fcdce8f30a9d22e0a8f9ac116b2fd3ebe3b5eef60aa770d134d7f6b7`| +|`joint-dof/math.report.json`|`ea994774e16ea5b506d028c8f7bcd3ff19fe8d3e2514aaa8447b870738fb07f4`| +|`joint-dof/controls.report.json`|`03a734e9a9b09610818a010dea90eb9ba196b01dfbd3c9e4d21501c8baa0f6c8`| +|`joint-dof/xyzac.json`|`99fa305279f05a331916138274c1bc1586ca4d4bdc93a1e34848fee160fe3fda`| +|`joint-dof/xyzbc.json`|`5e59370657eff67156f4a6338897e35e73c0b3bd3f955345a2698b05b88ea522`| +|`joint-dof/xyzac-live.png`|`6e4574082a11703c2b537e452aad039293100bb22a6420c2b7551d02ffef44fe`| +|`joint-dof/xyzbc-live.png`|`51fcd171ac07ad67ef6c1d733c098f24e1938474e1de2c1322701eb33ed49647`| diff --git a/docs/glb-machine-manager/06-决策记录.md b/docs/glb-machine-manager/06-决策记录.md index dfe7549..8231614 100644 --- a/docs/glb-machine-manager/06-决策记录.md +++ b/docs/glb-machine-manager/06-决策记录.md @@ -10,7 +10,7 @@ ## D03 / 编辑层级自由,应用层级受求解器限制 -UI 允许用户选择父级以建立层级和发现错误;应用时只接受底座→X→Y→Z→刀具及底座→A/B→C→工件。当前 WASM 只支持这两类 TRT,不能把任意树伪装成可执行机床。 +UI 允许用户选择父级以建立层级和发现错误;应用时只接受底座→X→Y→Z→刀具及底座→A/B→C→工件。当前 WASM 只支持这两类 TRT,不能把任意树伪装成可执行机床。MGR-17 起允许任意视觉层级按单自由度绑定五轴,数控仍使用所选 AC/BC TRT;八个独立文件不再是应用前提。 ## D04 / 原 GLB 不原地改写 @@ -55,3 +55,7 @@ GLB 树节点 ID 使用导入时的子节点索引路径,不使用可能重复 ## D14 / 部件编辑变换作用于当前树节点的父级局部空间 “部件平移”和“部件旋转”编辑当前选中的树节点。根节点的值保存为部件级 `translationMm`、`rotationDeg`;内部节点的值按稳定源路径保存为 `nodeTransforms[path]`,直接表示该节点相对当前父节点的位置和姿态。节点自身局部原点是旋转中心,子节点自然继承父节点变换,父节点和兄弟节点不因编辑子节点而改变。拖放改变父级后,先保持世界姿态,再重新计算局部值。导出和应用复用同一套局部变换,保证预览、组合 GLB 和运行机床一致。 + +## D15 / 单自由度约束与 JSON 运动契约(替代 D14 的自由编辑) + +每个运动节点只能绑定一个 CNC 轴,每轴只绑定一个节点。固定装配节点无自由度。输入为相对物理零点的单个关节量;保留导入零姿态,禁止通过其他数值或自由操纵器绕过约束。节点身份使用稳定 ID,实际绘制以父级方向和正负号驱动。以 `linuxcnc.joint-model/1` 扩展既有 userData,不更换数控 RTCP 求解器;URDF 坐标框架与视觉装配分离以避免双重运动。临时测试姿态不保存为零姿态。 diff --git a/docs/glb-machine-manager/07-单自由度JSON与实时驱动.md b/docs/glb-machine-manager/07-单自由度JSON与实时驱动.md new file mode 100644 index 0000000..ddaf4dd --- /dev/null +++ b/docs/glb-machine-manager/07-单自由度JSON与实时驱动.md @@ -0,0 +1,68 @@ +# 单自由度 JSON 与实时驱动 + +配置字段为 `userData.linuxcncJointModel`,版本 `linuxcnc.joint-model/1`。它与原 `linuxcncMachine`(URDF)和 `linuxcncMachineGeometry`(TRT 标定)一起保存在导出 GLB 的场景、运行底座及被标记为“床身底座”的实际模型节点中。GLB 将 userData 序列化为 extras。源 GLB 文件不覆盖。 + +## 对话框 + +1. 选中层级节点,设置“唯一自由度”:X/Y/Z 是平移,A/B/C 是旋转;固定零件选择“固定(无自由度)”。每个运动节点只有一个自由度,每个数控轴只能绑定一个节点。 +2. 设置父级坐标系中的 X/Y/Z 运动方向和正负向。AC 模型可用 XYZAC,BC 模型可用 XYZBC;另一倾转轴禁用。 +3. 当前节点只有一个“位移/转角”可编辑数值,单位 mm/deg。固定节点不能编辑;已导入的局部位置、姿态作为零姿态保留。旧版六分量自由编辑和自由操纵器已被单自由度输入替代(MGR-17)。 +4. 层级运动测试与节点输入共用对应轴值,所有输入均为相对物理零点的运动量,不写数控系统。配置 JSON 和 GLB 保存零姿态、自由度和标定,不将临时测试姿态烘焙进去。 +5. “跟随数控系统实时关节位置”使用当前机型的有效 WASM 快照,锁定手动关节输入。控制面板正式模型始终由 WASM 实际关节位置驱动。 +6. 绑定齐五个轴后可应用机床、导出 GLB;单个已有层级的 GLB 也可应用。可单独导出 JSON。未完成绑定的 JSON 是草稿,控制器读取时必须通过完整性校验。 +7. 重新导入本工具导出的 GLB 到管理器,可恢复层级、自由度、正负向、C 原点、倾转轴偏置和物理零点。GLB 机型必须与当前工作区相同。 + +## 字段契约 + +正式 JSON Schema:[joint-model.schema.json](joint-model.schema.json)。AC/BC 完整实例由验收程序生成,见 `coverage/glb-machine-manager/joint-dof/xyzac.json` 和 `xyzbc.json`。 + +| 字段 | 意义 | +|---|---| +| `schema` | 固定版本 `linuxcnc.joint-model/1` | +| `coordinates` | `XYZAC` 或 `XYZBC` | +| `units` | 设计长度 mm、角度 deg;GLB 节点实际长度 m | +| `controller.source` | `linuxcnc-wasm.visual.joints`,取实际运动学关节值 | +| `controller.axisOrder` | 实际快照数组的轴顺序,AC 为 `[X,Y,Z,A,C]`,BC 为 `[X,Y,Z,B,C]` | +| `calibration.cOriginMm` | C 轴原点,沿用 TRT rot-point 参数 | +| `calibration.tiltOffsetMm` | A/B 到 C 偏置,沿用 TRT offset 参数 | +| `calibration.physicalZero` | 每个数控轴对应模型零姿态的实际读数 | +| `axisMap` | 数控轴名到模型节点 ID,避免按名称或树排序推测关节 | +| `nodes[].id / parentId` | 唯一节点 ID 与直接父级;根为 null | +| `nodes[].source` | 源文件名、SHA-256 和导入路径,便于追溯 | +| `nodes[].zero` | 相对父级的零姿态:位置 mm、四元数 `[x,y,z,w]`、比例 | +| `nodes[].joint` | null 表示固定;对象表示唯一自由度 | +| `joint.axis / type / unit` | 数控轴 / prismatic 或 revolute / mm 或 deg | +| `joint.direction / frame` | 父级坐标系中的 x/y/z;frame 固定 parent | +| `joint.sign` | +1 或 −1,控制模型运动与数控轴正向的关系 | +| `joint.dof` | 固定为 1 | + +Z 轴关节的配置片段: + +```json +{ + "id": "part-id:scene/0/2/1", + "parentId": "part-id:scene/0/2", + "name": "主轴 Z 滑台", + "role": "z", + "source": { "file": "MILL700-U_1.glb", "hash": "源文件SHA256", "path": "scene/0/2/1" }, + "zero": { "positionMm": [-20.804, -904.049, 368.925], "quaternion": [0,0,0,1], "scale": [1,1,1] }, + "joint": { "axis": "Z", "direction": "z", "sign": -1, "dof": 1, "type": "prismatic", "unit": "mm", "frame": "parent" } +} +``` + +## 驱动规则 + +控制器先根据 `controller.axisOrder` 把快照数组转换为轴名 → 实际位置,然后通过 `axisMap` 找到运动节点。GLB 节点上的 `userData.linuxcncNodeId` 对应 `nodes[].id`,不依赖可重名的中文模型名称。 + +设实际位置 q、物理零点 q0、正负方向 s,则位移或转角 d = s × (q − q0)。设 e 为 `joint.direction` 对应的父级单位向量: + +- 平移:`p = zero.positionMm / 1000 + e * d / 1000`,四元数仍为零姿态。 +- 旋转:`Q = Quaternion(e, d * PI / 180) * zero.quaternion`,位置仍为零姿态,因此绕自身原点旋转。 +- 子节点继承父级世界矩阵;父级与兄弟节点不会因本节点运动改变。 +- 每次从零姿态求绝对变换,不进行增量累加,连续运行不产生累积漂移。 + +预览和运行复用 `machine-joints.ts` 的同一驱动函数。JSON 用于视觉关节映射;当前数控求解器仍为原生 AC/BC TRT,不根据任意视觉层级生成新的逆运动学。实际配置需要符合所选机床的机械结构、轴正向和标定;轴行程、速度和回零仍以控制器 INI 为准。 + +运行场景中的 URDF 控制坐标框架继续承担既有刀尖、刀长和刀路投影。导入模型的视觉装配位于固定底座下,仅由 JSON 关节绑定驱动,避免同一节点同时受到 URDF 和 JSON 两次运动。 + +读取时还要校验:节点 ID 唯一、父级有效且无环、每个节点最多一个自由度、轴绑定不重复、机型完整、轴索引一致、零姿态与标定为有限数值、四元数归一化。缺少实时轴值时整帧拒绝更新,避免只更新部分轴。 diff --git a/docs/glb-machine-manager/README.md b/docs/glb-machine-manager/README.md index 2900750..2199e61 100644 --- a/docs/glb-machine-manager/README.md +++ b/docs/glb-machine-manager/README.md @@ -11,7 +11,7 @@ 现有整机 GLB + 内嵌 URDF 导入能力继续保留。新管理器生成与现有 `linuxcnc.machine/1` 相同的严格结构,再通过已有配置、Worker、WASM 和 Three.js 投影链运行。 -当前状态:MGR-01 至 MGR-16 全部 verified,MILL700 实测覆盖照明、Z-up、132 节点树、拖放、节点原点、关节局部坐标系、节点相对父级平移旋转、模型删除、轴绑定、XYZ/AC 层级运动和 WebGL 资源降级。最新台账 R10,证据 `GLB-MGR-E01/E02/E05/E07/E08/E09/E10/E12` 位于 `coverage/glb-machine-manager/`。 +当前状态:MGR-17 单自由度/JSON/实时映射已 verified;MGR-01 至 MGR-16 已完成历史验收,MILL700 实测覆盖照明、Z-up、132 节点树、拖放、节点原点、关节局部坐标系、节点相对父级平移旋转、模型删除、轴绑定、XYZ/AC 层级运动和 WebGL 资源降级。最新台账 R11,证据 `GLB-MGR-E01/E02/E05/E07/E08/E09/E10/E12/E13` 位于 `coverage/glb-machine-manager/`。 文档: @@ -23,3 +23,6 @@ - [06-决策记录](06-决策记录.md) 相关旧专项:[GLB / URDF 五轴机床子项目](../glb-machine/README.md)。旧专项已完成整机导入、AC/BC 识别、几何参数同步和实际关节驱动,本专项不重复这些底层能力。 + +- [07-单自由度JSON与实时驱动](07-单自由度JSON与实时驱动.md) +- [关节 JSON Schema](joint-model.schema.json) diff --git a/docs/glb-machine-manager/joint-model.schema.json b/docs/glb-machine-manager/joint-model.schema.json new file mode 100644 index 0000000..4a7f19d --- /dev/null +++ b/docs/glb-machine-manager/joint-model.schema.json @@ -0,0 +1,475 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:linuxcnc:joint-model:1", + "title": "Web LinuxCNC single-DOF joint model", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "coordinates", + "units", + "controller", + "calibration", + "axisMap", + "nodes" + ], + "properties": { + "schema": { + "const": "linuxcnc.joint-model/1" + }, + "coordinates": { + "enum": [ + "XYZAC", + "XYZBC" + ] + }, + "units": { + "const": { + "length": "mm", + "angle": "deg", + "gltfLength": "m" + } + }, + "controller": { + "type": "object", + "additionalProperties": false, + "required": [ + "source", + "axisOrder" + ], + "properties": { + "source": { + "const": "linuxcnc-wasm.visual.joints" + }, + "axisOrder": { + "enum": [ + [ + "X", + "Y", + "Z", + "A", + "C" + ], + [ + "X", + "Y", + "Z", + "B", + "C" + ] + ] + } + } + }, + "calibration": { + "type": "object", + "additionalProperties": false, + "required": [ + "cOriginMm", + "tiltOffsetMm", + "physicalZero" + ], + "properties": { + "cOriginMm": { + "$ref": "#/$defs/vector3" + }, + "tiltOffsetMm": { + "$ref": "#/$defs/vector3" + }, + "physicalZero": { + "type": "object", + "required": [ + "x", + "y", + "z", + "a", + "b", + "c" + ], + "additionalProperties": false, + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + }, + "a": { + "type": "number" + }, + "b": { + "type": "number" + }, + "c": { + "type": "number" + } + } + } + } + }, + "axisMap": { + "type": "object", + "additionalProperties": false, + "properties": { + "X": { + "type": "string", + "minLength": 1 + }, + "Y": { + "type": "string", + "minLength": 1 + }, + "Z": { + "type": "string", + "minLength": 1 + }, + "A": { + "type": "string", + "minLength": 1 + }, + "B": { + "type": "string", + "minLength": 1 + }, + "C": { + "type": "string", + "minLength": 1 + } + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "maxItems": 12000, + "items": { + "$ref": "#/$defs/node" + } + } + }, + "$defs": { + "vector3": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 3, + "maxItems": 3 + }, + "node": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "parentId", + "name", + "role", + "source", + "zero", + "joint" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "parentId": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "role": { + "type": "string" + }, + "source": { + "type": "object", + "additionalProperties": false, + "required": [ + "file", + "hash", + "path" + ], + "properties": { + "file": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "path": { + "type": "string" + } + } + }, + "zero": { + "type": "object", + "additionalProperties": false, + "required": [ + "positionMm", + "quaternion", + "scale" + ], + "properties": { + "positionMm": { + "$ref": "#/$defs/vector3" + }, + "quaternion": { + "type": "array", + "items": { + "type": "number", + "minimum": -1, + "maximum": 1 + }, + "minItems": 4, + "maxItems": 4 + }, + "scale": { + "type": "array", + "items": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minItems": 3, + "maxItems": 3 + } + } + }, + "joint": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/dof" + } + ] + } + } + }, + "dof": { + "type": "object", + "additionalProperties": false, + "required": [ + "axis", + "direction", + "sign", + "dof", + "type", + "unit", + "frame" + ], + "properties": { + "axis": { + "enum": [ + "X", + "Y", + "Z", + "A", + "B", + "C" + ] + }, + "direction": { + "enum": [ + "x", + "y", + "z" + ] + }, + "sign": { + "enum": [ + -1, + 1 + ] + }, + "dof": { + "const": 1 + }, + "frame": { + "const": "parent" + }, + "type": { + "enum": [ + "prismatic", + "revolute" + ] + }, + "unit": { + "enum": [ + "mm", + "deg" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "axis": { + "enum": [ + "X", + "Y", + "Z" + ] + } + } + }, + "then": { + "properties": { + "type": { + "const": "prismatic" + }, + "unit": { + "const": "mm" + } + } + }, + "else": { + "properties": { + "type": { + "const": "revolute" + }, + "unit": { + "const": "deg" + } + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "coordinates": { + "const": "XYZAC" + } + } + }, + "then": { + "properties": { + "controller": { + "properties": { + "axisOrder": { + "const": [ + "X", + "Y", + "Z", + "A", + "C" + ] + } + } + }, + "axisMap": { + "propertyNames": { + "enum": [ + "X", + "Y", + "Z", + "A", + "C" + ] + } + }, + "nodes": { + "items": { + "properties": { + "joint": { + "anyOf": [ + { + "type": "null" + }, + { + "properties": { + "axis": { + "enum": [ + "X", + "Y", + "Z", + "A", + "C" + ] + } + } + } + ] + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "coordinates": { + "const": "XYZBC" + } + } + }, + "then": { + "properties": { + "controller": { + "properties": { + "axisOrder": { + "const": [ + "X", + "Y", + "Z", + "B", + "C" + ] + } + } + }, + "axisMap": { + "propertyNames": { + "enum": [ + "X", + "Y", + "Z", + "B", + "C" + ] + } + }, + "nodes": { + "items": { + "properties": { + "joint": { + "anyOf": [ + { + "type": "null" + }, + { + "properties": { + "axis": { + "enum": [ + "X", + "Y", + "Z", + "B", + "C" + ] + } + } + } + ] + } + } + } + } + } + } + } + ] +} diff --git a/docs/linuxcnc-axis-web-control-panel-source-logic.md b/docs/linuxcnc-axis-web-control-panel-source-logic.md index 9336ee9..73a1fdd 100644 --- a/docs/linuxcnc-axis-web-control-panel-source-logic.md +++ b/docs/linuxcnc-axis-web-control-panel-source-logic.md @@ -1071,3 +1071,7 @@ The document deliberately records ABI blockers and source boundaries. A `verified` Web slice means the implemented path matches this contract; it does not claim that an unavailable LinuxCNC host or official native oracle has been silently reproduced in the browser. + +### MGR-17 single-DOF joint controls + +`machine-joint-direction`, `machine-joint-sign`, `machine-joint-value`, `machine-joint-follow-live`, `machine-joint-live-state`, `machine-manager-export-json`: one CNC axis per moving node, one scalar input in mm/deg, parent-frame direction/sign, real WASM joint following and JSON export. `machine-tree-axis-role` now binds XYZABC directly; fixed nodes cannot move. Unrestricted translate/rotate gizmos and six-component inputs are retired. Runtime uses `linuxcnc.joint-model/1` with source order XYZAC/XYZBC and physical-zero subtraction. diff --git a/scripts/test_axis_control_inventory.mjs b/scripts/test_axis_control_inventory.mjs index 491a9a6..f124e24 100644 --- a/scripts/test_axis_control_inventory.mjs +++ b/scripts/test_axis_control_inventory.mjs @@ -13,7 +13,7 @@ const [contract, app, panel, preview] = await Promise.all([ const controlIds = [ "menu-file", "menu-file-edit-tool-data", "menu-file-quit", "menu-help", "menu-help-about", "about-close", "toolbar-estop", "toolbar-power", "toolbar-open", "toolbar-load-case", "toolbar-reload", "toolbar-run", "toolbar-step", "toolbar-pause-resume", "toolbar-stop", "toolbar-block-delete", "toolbar-optional-pause", "toolbar-zoom-in", "toolbar-zoom-out", "toolbar-view-z", "toolbar-view-z2", "toolbar-view-x", "toolbar-view-y", "toolbar-view-y2", "toolbar-view-p", "toolbar-rotate", "toolbar-clear-live-plot", - "workspace-case-select", "workspace-load-3d-chips", "workspace-rtcp-programs", "rtcp-program-scope", "rtcp-program-select", "rtcp-program-source", "rtcp-program-load", "rtcp-program-download", "machine-manager-settings", "machine-manager-import", "machine-manager-files", "machine-manager-delete", "machine-manager-translate", "machine-manager-rotate", "machine-manager-origin", "machine-manager-translation", "machine-manager-rotation", "machine-manager-role", "machine-manager-parent", "machine-manager-show-origin", "machine-tree-node", "machine-tree-origin", "machine-tree-axis-role", "machine-test-x", "machine-test-y", "machine-test-z", "machine-test-a", "machine-test-b", "machine-test-c", "machine-test-reset", "machine-test-state", "machine-manager-apply", "machine-manager-export", "manual-home-all", "manual-home-joint-select", + "workspace-case-select", "workspace-load-3d-chips", "workspace-rtcp-programs", "rtcp-program-scope", "rtcp-program-select", "rtcp-program-source", "rtcp-program-load", "rtcp-program-download", "machine-manager-settings", "machine-manager-import", "machine-manager-files", "machine-manager-delete", "machine-manager-role", "machine-manager-parent", "machine-manager-show-origin", "machine-tree-node", "machine-tree-origin", "machine-tree-axis-role", "machine-joint-direction", "machine-joint-sign", "machine-joint-value", "machine-joint-follow-live", "machine-joint-live-state", "machine-manager-export-json", "machine-test-x", "machine-test-y", "machine-test-z", "machine-test-a", "machine-test-b", "machine-test-c", "machine-test-reset", "machine-test-state", "machine-manager-apply", "machine-manager-export", "manual-home-all", "manual-home-joint-select", "switchkins-0", "switchkins-1", "switchkins-2", "tab-manual", "tab-mdi", "tab-diagnostics", "tab-preview", "tab-dro", "manual-axis-x", "manual-axis-y", "manual-axis-z", "manual-axis-a", "manual-axis-c", diff --git a/scripts/test_machine_joint_dof_browser.mjs b/scripts/test_machine_joint_dof_browser.mjs new file mode 100644 index 0000000..8f1d92f --- /dev/null +++ b/scripts/test_machine_joint_dof_browser.mjs @@ -0,0 +1,106 @@ +import assert from "node:assert/strict"; +import { readFile, mkdir, writeFile } from "node:fs/promises"; +import { pathToFileURL } from "node:url"; +import * as THREE from "../web/react/node_modules/three/build/three.module.js"; +const pw = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE ?? "/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.js")); +const browser = await (pw.default ?? pw).chromium.launch({ headless: true, executablePath: "/usr/bin/google-chrome", args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] }); +const output = new URL("../coverage/glb-machine-manager/joint-dof/", import.meta.url); await mkdir(output, { recursive: true }); +const bytes = await readFile(new URL("../MILL700-U_1.glb", import.meta.url)); +const report = { report_id: "GLB-MGR-E13", status: "running", url: process.env.MACHINE_MANAGER_URL ?? "http://127.0.0.1:5180/", machines: [], browser_errors: [] }; +const near = (a, b, label) => { assert.equal(a.length, b.length, label); a.forEach((v, i) => assert.ok(Math.abs(v - b[i]) < 2e-6, `${label} ${i}: ${v} vs ${b[i]}`)); }; +try { + for (const coordinates of (process.env.JOINT_TEST_COORDINATES ?? "XYZAC,XYZBC").split(",")) { + const page = await browser.newPage({ viewport: { width: 1280, height: 900 } }); + page.on("pageerror", (e) => report.browser_errors.push(e.message)); + page.on("console", (m) => { if (["error", "warning"].includes(m.type())) report.browser_errors.push(m.text()); }); + const c = (id) => page.locator(`[data-axis-control="${id}"]`); + const preview = page.locator("[data-machine-manager-preview]"); + const idle = () => page.waitForFunction(() => document.querySelector("main.axis-app")?.dataset.axisProgramLoading === "false" && document.querySelector("main.axis-app")?.dataset.axisStateSource === "linuxcnc-wasm", null, { timeout: 60000 }); + await page.goto(report.url); await idle(); + if (coordinates === "XYZBC") { await c("workspace-case-select").selectOption("linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"); await idle(); } + await c("machine-manager-settings").click(); await c("machine-manager-files").setInputFiles({ name: "MILL700-U_1.glb", mimeType: "model/gltf-binary", buffer: bytes }); + await page.waitForFunction(() => document.querySelectorAll('[data-axis-control="machine-tree-node"]').length >= 132); + assert.equal(await c("machine-joint-value").isDisabled(), true); + assert.equal(await c("machine-manager-apply").isDisabled(), true); + assert.equal(await c("machine-manager-translation").count(), 0); assert.equal(await c("machine-manager-rotation").count(), 0); + const choose = async (name) => { + const node = c("machine-tree-node").filter({ has: page.locator("span").filter({ hasText: new RegExp(`^${name}$`) }) }).first(); + const path = await node.locator("xpath=ancestor::li[1]").getAttribute("data-machine-tree-path"); await node.dispatchEvent("click"); + await page.waitForFunction((path) => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerSelectedJoint?.endsWith(`:${path}`), path); + return JSON.parse(await preview.getAttribute("data-machine-manager-selected-transform")); + }; + const names = { X: "MILL700-A-主轴-JT1", Y: "MILL700-A-主轴-JT2", Z: "MILL700-A-主轴-JT3", [coordinates[3]]: "MILL700-A-工作台-JT1", C: "MILL700-A-工作台-JT2" }; + for (const [axis, name] of Object.entries(names)) { await choose(name); await c("machine-tree-axis-role").selectOption(axis); } + await choose("MILL700-A底座"); await c("machine-manager-role").selectOption("base"); + assert.equal(await c("machine-manager-apply").isDisabled(), false); + const parent = await choose(names.Y), sibling = await choose("SHS25C100020_Track_TSXXX-0000058522002"), child = await choose("MILL700-A-主轴-JT4"), zeroZ = await choose(names.Z); + await c("machine-joint-direction").selectOption("x"); await c("machine-joint-sign").selectOption("-1"); await c("machine-joint-value").fill("30"); + const movedZ = await choose(names.Z); near(movedZ.local, [zeroZ.local[0] - .03, ...zeroZ.local.slice(1)], "single parent X translation"); near(movedZ.localQuaternion, zeroZ.localQuaternion, "locked rotation"); + assert.deepEqual(await choose(names.Y), parent); assert.deepEqual(await choose("SHS25C100020_Track_TSXXX-0000058522002"), sibling); + const movedChild = await choose("MILL700-A-主轴-JT4"); near(movedChild.world, [child.world[0] - .03, ...child.world.slice(1)], "child follows only configured DOF"); + await choose(names.Z); await c("machine-joint-value").fill("30"); near((await choose(names.Z)).local, movedZ.local, "absolute, no accumulated movement"); + await c("machine-joint-value").fill("0"); await c("machine-joint-direction").selectOption("z"); + await choose("MILL700-A-主轴-JT4"); await c("machine-tree-axis-role").selectOption("Z"); assert.match(await page.locator(".machine-manager-footer").textContent(), /重复|已绑定/); assert.equal(await c("machine-tree-axis-role").inputValue(), ""); + const rotaryZero = await choose(names[coordinates[3]]); await c("machine-joint-value").fill("25"); const rotaryMoved = await choose(names[coordinates[3]]); near(rotaryMoved.local, rotaryZero.local, "rotation pivot fixed"); + const axis = coordinates === "XYZAC" ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0); + near(rotaryMoved.localQuaternion, new THREE.Quaternion().setFromAxisAngle(axis, 25 * Math.PI / 180).multiply(new THREE.Quaternion(...rotaryZero.localQuaternion)).toArray(), "one rotational DOF"); + await c("machine-test-reset").click(); + const zeroFields = page.locator("fieldset.machine-zero"); await zeroFields.getByLabel("X", { exact: true }).fill("5"); await zeroFields.getByLabel("Z", { exact: true }).fill("7"); await zeroFields.getByLabel(coordinates[3], { exact: true }).fill("12"); await zeroFields.getByLabel("C", { exact: true }).fill("9"); + const jsonDownload = page.waitForEvent("download"); await c("machine-manager-export-json").click(); const json = JSON.parse(await readFile(await (await jsonDownload).path(), "utf8")); + assert.equal(json.schema, "linuxcnc.joint-model/1"); assert.equal(json.coordinates, coordinates); assert.deepEqual(Object.keys(json.axisMap).sort(), [...coordinates].sort()); assert.equal(json.nodes.filter((n) => n.joint).length, 5); assert.ok(json.nodes.every((n) => n.joint === null || n.joint.dof === 1)); + assert.equal(json.nodes.find((n) => n.id === json.axisMap.Z).joint.sign, -1); + const glbDownload = page.waitForEvent("download"); await c("machine-manager-export").click(); const glb = await readFile(await (await glbDownload).path()); + const gltf = JSON.parse(glb.toString("utf8", 20, 20 + glb.readUInt32LE(12))); + assert.deepEqual(gltf.scenes[0].extras.linuxcncJointModel, json); + const bed = gltf.nodes.find((n) => n.name === "MILL700-A底座"); assert.deepEqual(bed.extras.linuxcncJointModel, json); assert.ok(bed.extras.linuxcncMachineGeometry); + await writeFile(new URL(`${coordinates.toLowerCase()}.json`, output), JSON.stringify(json, null, 2) + "\n"); + await c("machine-manager-delete").click(); await c("machine-manager-files").setInputFiles({ name: `managed-${coordinates.toLowerCase()}.glb`, mimeType: "model/gltf-binary", buffer: glb }); + await page.waitForFunction(() => document.querySelectorAll('[data-axis-control="machine-tree-node"]').length >= 132); + await choose(names.Z); assert.equal(await c("machine-tree-axis-role").inputValue(), "Z"); assert.equal(await c("machine-joint-sign").inputValue(), "-1"); assert.equal(await zeroFields.getByLabel("Z", { exact: true }).inputValue(), "7"); + console.log(`${coordinates}: GLB export and editor roundtrip verified`); + // Standard machine-import route validates exported GLB, JSON hierarchy and zero restoration. + await page.keyboard.press("Escape"); await c("machine-glb-settings").click(); await c("machine-glb-file").setInputFiles({ name: `managed-${coordinates.toLowerCase()}.glb`, mimeType: "model/gltf-binary", buffer: glb }); + await page.waitForFunction(() => document.querySelector(".xyzac-machine-view")?.dataset.machineJointState, null, { timeout: 60000 }); await idle(); await page.keyboard.press("Escape"); + console.log(`${coordinates}: standard GLB importer verified`); + // Apply directly from the restored one-file hierarchy as well. + await c("machine-manager-settings").click(); await c("machine-manager-apply").click(); await idle(); assert.match(await page.locator(".machine-manager-footer").textContent(), /已应用/); await page.keyboard.press("Escape"); + const program = `M428 +G21 G90 G94 G17 G40 G49 +G1 X20 Y10 Z10 ${coordinates[3]}20 C30 F120 +G1 X-10 Y-5 Z-5 ${coordinates[3]}-10 C-20 F120 +M2 +`; + await c("program-file-input").setInputFiles({ name: `joint-map-${coordinates.toLowerCase()}.ngc`, mimeType: "text/plain", buffer: Buffer.from(program) }); await idle(); + console.log(`${coordinates}: apply verified; starting simultaneous five-axis WASM motion`); + await c("toolbar-power").click(); await page.waitForFunction(() => document.querySelector("main.axis-app")?.dataset.axisStateMachine === "ON"); await c("manual-home-all").click(); + await page.waitForFunction(() => document.querySelector("main.axis-app")?.dataset.axisHomed === "[true,true,true,true,true]", null, { timeout: 60000 }); await c("toolbar-run").click(); + await page.waitForFunction(() => { + const view = document.querySelector(".xyzac-machine-view"); + const q = JSON.parse(view?.dataset.machineJointInputs ?? "[]"); + return Number(document.querySelector("main.axis-app")?.dataset.axisCurrentLine) > 0 && q.some((v) => Math.abs(v) > 1e-6); + }, null, { timeout: 60000 }); + const samples = []; + for (let i = 0; i < 8; i++) { + if (samples.length) await page.waitForFunction((tick) => Number(document.querySelector(".xyzac-machine-view")?.dataset.machineJointTick) > tick, samples.at(-1).tick, { timeout: 60000 }); + const sample = await page.locator(".xyzac-machine-view").evaluate((el) => ({ tick: Number(el.dataset.machineJointTick), q: JSON.parse(el.dataset.machineJointInputs), nodes: JSON.parse(el.dataset.machineJointState) })); + for (const [index, axisName] of [...coordinates].entries()) { + const state = sample.nodes[axisName]; const original = json.nodes.find((n) => n.joint?.axis === axisName); const d = original.joint; + const q = d.sign * (sample.q[index] - json.calibration.physicalZero[axisName.toLowerCase()]); const direction = new THREE.Vector3(...["x", "y", "z"].map((a) => a === d.direction ? 1 : 0)); + if ("XYZ".includes(axisName)) { near(state.localMm, new THREE.Vector3(...original.zero.positionMm).addScaledVector(direction, q).toArray(), `${axisName} runtime position`); near(state.quaternion, original.zero.quaternion, `${axisName} locked orientation`); } + else { near(state.localMm, original.zero.positionMm, `${axisName} fixed pivot`); near(state.quaternion, new THREE.Quaternion().setFromAxisAngle(direction, q * Math.PI / 180).multiply(new THREE.Quaternion(...original.zero.quaternion)).toArray(), `${axisName} runtime rotation`); } + } + samples.push(sample); + } + for (let axisIndex = 0; axisIndex < 5; axisIndex++) assert.ok(samples.some((s) => Math.abs(s.q[axisIndex] - samples[0].q[axisIndex]) > 1e-6), `${coordinates[axisIndex]} live movement required`); + await c("machine-manager-settings").click(); await c("machine-joint-follow-live").check(); assert.equal(await c("machine-joint-value").isDisabled(), true); + await page.waitForFunction(() => document.querySelector('[data-axis-control="machine-joint-live-state"]')?.textContent.includes("WASM tick")); + const liveSample = await page.evaluate(() => ({ q: JSON.parse(document.querySelector('[data-axis-control="machine-test-state"]').textContent), joints: JSON.parse(document.querySelector("[data-machine-manager-preview]").dataset.machineManagerJointTransforms) })); + const z = json.nodes.find((n) => n.joint?.axis === "Z"); near(liveSample.joints.Z.localMm, [z.zero.positionMm[0], z.zero.positionMm[1], z.zero.positionMm[2] - liveSample.q.z], "dialog follows live Z sign"); + await page.screenshot({ path: new URL(`${coordinates.toLowerCase()}-live.png`, output).pathname }); + report.machines.push({ coordinates, checks: ["fixed or single DOF", "direction/sign and parent isolation", "pivot and child inheritance", "duplicate axis rejected", "JSON on scene and real bed", "GLB roundtrip restores calibration and bindings", "single-file apply", "WASM runtime axis mapping", "live dialog follows actual joints"], nodeCount: json.nodes.length, samples, liveSample }); + await page.close(); + } + assert.deepEqual(report.browser_errors, []); report.status = "verified"; +} catch (e) { report.status = "failed"; report.error = e.stack; for (const page of browser.contexts().flatMap((c) => c.pages())) { report.pageText = await page.locator("body").innerText().catch(() => ""); await page.screenshot({ path: new URL("failure.png", output).pathname }).catch(() => {}); } throw e; } +finally { await writeFile(new URL("browser.report.json", output), JSON.stringify(report, null, 2) + "\n"); await browser.close(); } +console.log(JSON.stringify({ report_id: report.report_id, status: report.status, machines: report.machines.map((m) => ({ coordinates: m.coordinates, checks: m.checks, samples: m.samples.length })), browser_errors: report.browser_errors })); diff --git a/scripts/test_machine_joint_model.mjs b/scripts/test_machine_joint_model.mjs new file mode 100644 index 0000000..49272ce --- /dev/null +++ b/scripts/test_machine_joint_model.mjs @@ -0,0 +1,27 @@ +import assert from "node:assert/strict"; +import * as THREE from "../web/react/node_modules/three/build/three.module.js"; +import { driveJoint, jointZero, validateJointModel, bindJointModel, defaultDof } from "../web/react/src/machine-joints.ts"; +const near = (a, b) => a.forEach((v, i) => assert.ok(Math.abs(v - b[i]) < 1e-9)); +const parent = new THREE.Group(); parent.rotation.z = Math.PI / 2; +const joint = new THREE.Group(); joint.position.set(.1, .2, .3); joint.rotation.y = Math.PI / 6; parent.add(joint); +const child = new THREE.Group(); child.position.set(.4, 0, 0); joint.add(child); +const zero = jointZero(joint), childLocal = child.position.toArray(); +const dof = { axis: "X", direction: "x", sign: -1 }; +for (let i = 0; i < 100; i++) driveJoint(joint, zero, dof, 30, 10); +near(joint.position.toArray(), [.08, .2, .3]); near(joint.quaternion.toArray(), zero.quaternion); +parent.updateMatrixWorld(true); near(joint.getWorldPosition(new THREE.Vector3()).toArray(), [-.2, .08, .3]); near(child.position.toArray(), childLocal); +driveJoint(joint, zero, { axis: "A", direction: "z", sign: 1 }, 100, 10); +near(joint.position.toArray(), zero.positionMm.map((v) => v / 1000)); +near(joint.quaternion.toArray(), new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI / 2).multiply(new THREE.Quaternion(...zero.quaternion)).toArray()); +assert.throws(() => driveJoint(joint, zero, dof, NaN), /无效/); +const scene = new THREE.Group(); const nodes = [..."XYZAC"].map((axis) => { + const n = new THREE.Group(); n.name = axis; n.userData.linuxcncNodeId = axis; scene.add(n); + const config = defaultDof(axis); return { id: axis, name: axis, parentId: null, role: "", source: { file: "test", hash: "hash", path: axis }, zero: jointZero(n), joint: { ...config, dof: 1, type: "XYZ".includes(axis) ? "prismatic" : "revolute", unit: "XYZ".includes(axis) ? "mm" : "deg", frame: "parent" } }; +}); +const model = { schema: "linuxcnc.joint-model/1", coordinates: "XYZAC", units: { length: "mm", angle: "deg", gltfLength: "m" }, controller: { source: "linuxcnc-wasm.visual.joints", axisOrder: [..."XYZAC"] }, calibration: { cOriginMm: [0,0,0], tiltOffsetMm: [0,0,0], physicalZero: { x: 2, y: 0, z: 0, a: 5, b: 0, c: 0 } }, axisMap: Object.fromEntries(nodes.map((n) => [n.id, n.id])), nodes }; +validateJointModel(model); const driver = bindJointModel(scene, model); driver.update({ X: 12, Y: 0, Z: 0, A: 35, C: 0 }); +near(driver.state().X.localMm, [10,0,0]); near(driver.state().A.quaternion, [Math.sin(Math.PI / 12),0,0,Math.cos(Math.PI / 12)]); +const beforeInvalid = driver.state(); assert.throws(() => driver.update({ X: 999, Y: 0, Z: 0, A: 0 }), /缺少/); assert.deepEqual(driver.state(), beforeInvalid); +const reject = (mutate) => { const copy = structuredClone(model); mutate(copy); assert.throws(() => validateJointModel(copy)); }; +reject((m) => { m.nodes[1].joint.axis = "X"; }); reject((m) => { m.nodes[0].joint.dof = 2; }); reject((m) => { m.nodes[0].joint.sign = 0; }); reject((m) => { m.axisMap.X = "Y"; }); reject((m) => { m.nodes[0].parentId = "X"; }); reject((m) => { m.calibration.physicalZero.z = NaN; }); reject((m) => { m.units.length = "m"; }); reject((m) => { m.nodes[0].zero.quaternion = [0,0,0,0]; }); +console.log(JSON.stringify({ report_id: "GLB-MGR-E13-MATH", status: "verified", checks: ["parent-frame motion with nonidentity zero orientation", "unit/sign/physical-zero conversion", "100 updates without drift", "child inheritance", "rotation about own origin", "malformed JSON rejected", "missing live axis rejected atomically"] })); diff --git a/scripts/test_machine_manager_browser.mjs b/scripts/test_machine_manager_browser.mjs index 76584cc..4ef272b 100644 --- a/scripts/test_machine_manager_browser.mjs +++ b/scripts/test_machine_manager_browser.mjs @@ -31,29 +31,17 @@ try { await control("machine-manager-files").setInputFiles(fixtures); await page.waitForFunction(() => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerPartCount === "8"); assert.equal(await page.locator('[data-machine-tree-path="scene"]').count(), 8); assert.ok(await page.locator('[data-axis-control="machine-tree-node"]').count() >= 16); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-origin-count"), "8"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-lighting"), "studio-pmrem"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-exposure"), "1.45"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-webgl-antialias"), "false"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-render-state"), "rendered"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-context-generation"), "1"); - report.checks.push("eight GLB parts rendered with studio PMREM lighting and calibrated exposure"); - const roots = page.locator('[data-machine-tree-path="scene"]'); const sourceRoot = roots.nth(7), targetRoot = roots.nth(6), sourcePart = await sourceRoot.getAttribute("data-machine-tree-part"), targetPart = await targetRoot.getAttribute("data-machine-tree-part"), transfer = await page.evaluateHandle(() => new DataTransfer()); await sourceRoot.locator("button").first().dispatchEvent("dragstart", { dataTransfer: transfer }); await targetRoot.locator("button").first().dispatchEvent("dragover", { dataTransfer: transfer }); await targetRoot.locator("button").first().dispatchEvent("drop", { dataTransfer: transfer }); await page.waitForFunction(({ sourcePart, targetPart }) => document.querySelector(`[data-machine-tree-part="${sourcePart}"][data-machine-tree-path="scene"]`)?.getAttribute("data-machine-tree-parent") === targetPart, { sourcePart, targetPart }); - report.checks.push("dragging GLB root tree nodes updates component parent hierarchy"); - - await control("machine-manager-show-origin").uncheck(); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-origin-count"), "7"); - const beforePartOrigin = JSON.parse(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-selected-joint-world-origin")); const translation = page.locator("fieldset.machine-vector").filter({ hasText: "部件平移" }); await translation.getByLabel("X", { exact: true }).fill("10"); await page.waitForFunction(({ before }) => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-joint-world-origin") ?? "[]"); return current.length === 3 && Math.abs(current[0] - before[0] - .01) < 1e-7; }, { before: beforePartOrigin }); - const beforePartQuaternion = JSON.parse(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-selected-joint-world-quaternion")); const rotation = page.locator("fieldset.machine-vector").filter({ hasText: "部件旋转" }); await rotation.getByLabel("Z", { exact: true }).fill("15"); await page.waitForFunction(({ before }) => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-joint-world-quaternion") ?? "[]"); return current.length === 4 && Math.abs(current[3] - before[3]) > 1e-4; }, { before: beforePartQuaternion }); - await control("machine-manager-rotate").click(); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-transform-mode"), "rotate"); - assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-context-generation"), "1"); + report.checks.push("eight GLB parts rendered with studio PMREM lighting"); + const selectRoot = async (index) => { const node = control("machine-tree-node").filter({ has: page.locator("span").filter({ hasText: new RegExp(`^part_${index}$`) }) }).first(); await node.dispatchEvent("click"); }; + for (const [index, axis] of [[1, "X"], [2, "Y"], [3, "Z"], [5, "A"], [6, "C"]]) { await selectRoot(index); await control("machine-tree-axis-role").selectOption(axis); } + await selectRoot(0); assert.equal(await control("machine-joint-value").isDisabled(), true); + await control("machine-manager-show-origin").uncheck(); + await selectRoot(1); await control("machine-joint-value").fill("10"); + await page.waitForFunction(() => Math.abs(JSON.parse(document.querySelector("[data-machine-manager-preview]").dataset.machineManagerJointTransforms).X.localMm[0] - 10) < 1e-7); + assert.equal(await control("machine-manager-translation").count(), 0); + await control("machine-test-reset").click(); await page.locator(".machine-manager-dialog").screenshot({ path: new URL("manager-ac-config.png", output).pathname }); - report.checks.push("origin visibility, numeric translation/rotation and TransformControls mode update"); - - // Editing an imported mesh must preserve the enclosing GLB placement. - const meshNode = control("machine-tree-node").filter({ hasText: "mesh_0" }).first(); - const meshPath = await meshNode.locator("xpath=ancestor::li[1]").getAttribute("data-machine-tree-path"); - await meshNode.dispatchEvent("click"); - await control("machine-manager-translation").getByLabel("X", { exact: true }).fill("35"); - await control("machine-manager-rotation").getByLabel("Z", { exact: true }).fill("30"); - const baseRoot = page.locator('[data-machine-tree-path="scene"]').first(); - await baseRoot.locator('[data-axis-control="machine-tree-node"]').first().dispatchEvent("click"); - assert.equal(await translation.getByLabel("X", { exact: true }).inputValue(), "10"); - assert.equal(await rotation.getByLabel("Z", { exact: true }).inputValue(), "15"); - + report.checks.push("eight-part roots have unique single DOFs; fixed base cannot move; numeric input drives only bound X"); const calibrations = page.locator(".machine-manager-calibration fieldset.machine-vector"); const cOrigin = calibrations.filter({ hasText: "C 轴原点" }); await cOrigin.getByLabel("X", { exact: true }).fill("24"); await cOrigin.getByLabel("Y", { exact: true }).fill("-12"); await cOrigin.getByLabel("Z", { exact: true }).fill("8"); const offset = calibrations.filter({ hasText: "A 到 C 轴偏置" }); await offset.getByLabel("X", { exact: true }).fill("0"); await offset.getByLabel("Y", { exact: true }).fill("18"); await offset.getByLabel("Z", { exact: true }).fill("-45"); @@ -61,16 +49,14 @@ try { assert.equal(await control("machine-manager-export").isDisabled(), false); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-coordinate-system"), "machine-z-up"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-axis-map"), "X:x,Y:y,Z:z"); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-camera-up"), "0,0,1"); const downloadPromise = page.waitForEvent("download"); await control("machine-manager-export").click(); const download = await downloadPromise; assert.equal(download.suggestedFilename(), "managed-xyzac.glb"); const exportedBytes = await readFile(await download.path()); const exported = JSON.parse(exportedBytes.toString("utf8", 20, 20 + exportedBytes.readUInt32LE(12))); - const exportedMesh = exported.nodes.find((node) => node.name === "mesh_0"); - assert.ok(Math.abs(exportedMesh.translation[0] - .035) < 1e-7); - assert.ok(Math.abs(exportedMesh.rotation[2] - Math.sin(Math.PI / 12)) < 1e-7); - report.checks.push("internal mesh parent-relative position and rotation survive GLB export without moving its parent"); + const model = exported.scenes[0].extras.linuxcncJointModel; + assert.equal(model.schema, "linuxcnc.joint-model/1"); + assert.equal(model.nodes.filter((n) => n.joint).length, 5); report.checks.push("validated assembly exports a self-contained managed GLB"); assert.equal(await control("machine-manager-apply").isDisabled(), false); await control("machine-manager-apply").click(); await page.waitForFunction(() => document.querySelector(".xyzac-machine-view")?.dataset.machineManagerSchema === "linuxcnc.machine-manager/1" && document.querySelector("main.axis-app")?.dataset.axisProgramLoading === "false", null, { timeout: 45000 }); const geometry = JSON.parse(await page.locator(".xyzac-machine-view").getAttribute("data-machine-manager-geometry")); - assert.deepEqual(geometry.cOriginMm, [24, -12, 8]); assert.deepEqual(geometry.tiltOffsetMm, [0, 18, -45]); assert.equal(geometry.physicalZero.x, 5); assert.equal(geometry.physicalZero.a, 10); assert.equal(geometry.physicalZero.c, 20); assert.equal(geometry.parts.length, 8); assert.equal(geometry.parts[0].translationMm[0], 10); assert.equal(geometry.parts[0].rotationDeg[2], 15); - assert.deepEqual(geometry.parts[0].nodeTransforms[meshPath], { translationMm: [35, 0, 0], rotationDeg: [0, 0, 30] }); + assert.deepEqual(geometry.cOriginMm, [24, -12, 8]); assert.deepEqual(geometry.tiltOffsetMm, [0, 18, -45]); assert.equal(geometry.physicalZero.x, 5); assert.equal(geometry.physicalZero.a, 10); assert.equal(geometry.physicalZero.c, 20); assert.equal(geometry.parts.length, 8); assert.equal(geometry.parts[1].nodeJoints.scene.axis, "X"); assert.match(await page.locator(".xyzac-machine-view").getAttribute("data-xyzac-model-source-path"), /^managed-xyzac\.glb$/); report.checks.push("validated hierarchy applied through LOAD_WORKSPACE with geometry and physical zero in userData"); @@ -78,6 +64,7 @@ try { const xyzbcCase = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"; await control("workspace-case-select").selectOption(xyzbcCase); await page.waitForFunction((path) => document.querySelector('[data-axis-control="workspace-case-select"]')?.value === path && document.querySelector("main.axis-app")?.dataset.axisProgramLoading === "false", xyzbcCase, { timeout: 45000 }); await control("machine-manager-settings").click(); assert.match(await page.locator(".machine-manager-dialog .machine-dialog-titlebar").textContent(), /XYZBC/); + await selectRoot(5); await control("machine-tree-axis-role").selectOption("B"); const bcCalibrations = page.locator(".machine-manager-calibration fieldset.machine-vector"); const bcOrigin = bcCalibrations.filter({ hasText: "C 轴原点" }); await bcOrigin.getByLabel("X", { exact: true }).fill("-18"); await bcOrigin.getByLabel("Y", { exact: true }).fill("14"); await bcOrigin.getByLabel("Z", { exact: true }).fill("12"); const bcOffset = bcCalibrations.filter({ hasText: "B 到 C 轴偏置" }); await bcOffset.getByLabel("X", { exact: true }).fill("22"); await bcOffset.getByLabel("Y", { exact: true }).fill("0"); await bcOffset.getByLabel("Z", { exact: true }).fill("-40"); const bcZeros = page.locator("fieldset.machine-zero"); await bcZeros.getByLabel("B", { exact: true }).fill("12"); await bcZeros.getByLabel("C", { exact: true }).fill("5"); await control("machine-manager-apply").click(); diff --git a/scripts/test_machine_manager_mill700_browser.mjs b/scripts/test_machine_manager_mill700_browser.mjs index b3dc84c..75ffce1 100644 --- a/scripts/test_machine_manager_mill700_browser.mjs +++ b/scripts/test_machine_manager_mill700_browser.mjs @@ -1,105 +1,4 @@ -import assert from "node:assert/strict"; -import { mkdir, readFile, writeFile } from "node:fs/promises"; -import { createHash } from "node:crypto"; -import { pathToFileURL } from "node:url"; -import * as THREE from "../web/react/node_modules/three/build/three.module.js"; - -const modulePath = process.env.PLAYWRIGHT_MODULE; -if (!modulePath) { console.log(JSON.stringify({ schema: "machine-manager-mill700-browser.v1", status: "blocked", code: "playwright_unavailable" })); process.exit(0); } -const root = new URL("../", import.meta.url); -const modelPath = process.env.MILL700_GLB ?? new URL("MILL700-U_1.glb", root).pathname; -const model = await readFile(modelPath); const sha256 = createHash("sha256").update(model).digest("hex"); -let offset = 12, json = null; -while (offset < model.length) { const length = model.readUInt32LE(offset), type = model.readUInt32LE(offset + 4); if (type === 0x4e4f534a) json = JSON.parse(model.toString("utf8", offset + 8, offset + 8 + length)); offset += 8 + length; } -assert.ok(json); const parents = new Map(); json.nodes.forEach((node, index) => (node.children ?? []).forEach((child) => parents.set(child, index))); -const depth = (index) => parents.has(index) ? 1 + depth(parents.get(index)) : 0; -const hierarchy = { root: json.nodes[json.scenes[json.scene ?? 0].nodes[0]].name, nodes: json.nodes.length, meshes: json.meshes.length, maxDepth: Math.max(...json.nodes.map((_, index) => depth(index))), materials: json.materials.length, metallic: json.materials.filter((material) => Number(material.pbrMetallicRoughness?.metallicFactor ?? 1) >= .9).length }; -const output = new URL("../coverage/glb-machine-manager/mill700/", import.meta.url); await mkdir(output, { recursive: true }); -const imported = await import(pathToFileURL(modulePath).href); const playwright = imported.default ?? imported; -const browser = await playwright.chromium.launch({ headless: true, executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome", args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] }); -const page = await browser.newPage({ viewport: { width: 1600, height: 1050 } }); const errors = []; -page.on("pageerror", (error) => errors.push(`pageerror:${error.message}`)); page.on("console", (message) => { if (["warning", "error"].includes(message.type())) errors.push(`console:${message.type()}:${message.text()}`); }); -const control = (id) => page.locator(`[data-axis-control="${id}"]`); -const report = { schema: "machine-manager-mill700-browser.v1", report_id: "GLB-MGR-E05", status: "running", model: { path: modelPath, bytes: model.length, sha256 }, checks: [] }; -try { - await page.goto(process.env.MACHINE_MANAGER_URL ?? "http://127.0.0.1:5174/", { waitUntil: "domcontentloaded", timeout: 30000 }); - await page.waitForFunction(() => document.querySelector("main.axis-app")?.dataset.axisProgramLoading === "false" && document.querySelector("main.axis-app")?.dataset.axisStateSource === "linuxcnc-wasm", null, { timeout: 45000 }); - await control("machine-manager-settings").click(); await control("machine-manager-files").setInputFiles({ name: "MILL700-U_1.glb", mimeType: "model/gltf-binary", buffer: model }); - await page.waitForFunction(() => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerPartCount === "1" && document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerRendered === "true" && document.querySelectorAll('[data-axis-control="machine-tree-node"]').length >= 132, null, { timeout: 60000 }); - assert.equal(hierarchy.root, "MILL700-U_1"); assert.equal(hierarchy.nodes, 132); assert.equal(hierarchy.meshes, 126); assert.ok(hierarchy.maxDepth >= 6); assert.ok(hierarchy.metallic > 20); - const transformPreview = page.locator("[data-machine-manager-preview]"); const beforePartTransform = JSON.parse(await transformPreview.getAttribute("data-machine-manager-selected-joint-world-origin")); await control("machine-manager-translation").locator("input").nth(0).fill("100"); await page.waitForFunction(({ before }) => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-joint-world-origin") ?? "[]"); return current.length === 3 && Math.abs(current[0] - before[0] - .1) < 1e-7; }, { before: beforePartTransform }); const beforePartRotation = JSON.parse(await transformPreview.getAttribute("data-machine-manager-selected-joint-world-quaternion")); await control("machine-manager-rotation").locator("input").nth(2).fill("20"); await page.waitForFunction(({ before }) => { const current = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.getAttribute("data-machine-manager-selected-joint-world-quaternion") ?? "[]"); return current.length === 4 && Math.abs(current[3] - before[3]) > 1e-4; }, { before: beforePartRotation }); report.checks.push("root and internal node transforms update their local position and posture relative to their parent"); - const readSelected = async () => JSON.parse(await transformPreview.getAttribute("data-machine-manager-selected-transform")); - const choose = async (name) => { - const button = control("machine-tree-node").filter({ has: page.locator("span").filter({ hasText: new RegExp(`^${name}$`) }) }).first(); - const path = await button.locator("xpath=ancestor::li[1]").getAttribute("data-machine-tree-path"); - await button.dispatchEvent("click"); - await page.waitForFunction((path) => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerSelectedJoint?.endsWith(`:${path}`), path); - return readSelected(); - }; - const closeVector = (actual, expected, label) => actual.forEach((value, index) => assert.ok(Math.abs(value - expected[index]) < 1e-7, `${label}[${index}]: ${value} != ${expected[index]}`)); - const sampleBranch = async () => ({ - parent: await choose("MILL700-A-主轴-JT2"), - sibling: await choose("SHS25C100020_Track_TSXXX-0000058522002"), - child: await choose("MILL700-A-主轴-JT4"), - selected: await choose("MILL700-A-主轴-JT3"), - }); - const beforeEdit = await sampleBranch(); - const translationInputs = control("machine-manager-translation").locator("input"); - for (let i = 0; i < 3; i++) assert.ok(Math.abs(Number(await translationInputs.nth(i).inputValue()) - beforeEdit.selected.local[i] * 1000) < 1e-7); - assert.match(await control("machine-tree-origin").textContent(), /节点自身局部原点.*装配位置.*装配姿态/s); - await translationInputs.nth(0).fill("120"); - // Screenshot complaint: move only the selected joint by -44 mm in its parent's Z direction. - await translationInputs.nth(2).fill(String(beforeEdit.selected.local[2] * 1000 - 44)); - const translated = await sampleBranch(); - const delta = new THREE.Vector3(.12 - beforeEdit.selected.local[0], 0, -.044) - .applyQuaternion(new THREE.Quaternion(...beforeEdit.parent.worldQuaternion)); - closeVector(translated.selected.world, new THREE.Vector3(...beforeEdit.selected.world).add(delta).toArray(), "translation follows rotated parent axes"); - closeVector(translated.child.world, new THREE.Vector3(...beforeEdit.child.world).add(delta).toArray(), "child inherits translation"); - for (const key of ["parent", "sibling"]) assert.deepEqual(translated[key], beforeEdit[key], `${key} must not move`); - closeVector(translated.child.local, beforeEdit.child.local, "child local position unchanged"); - await control("machine-manager-rotation").locator("input").nth(2).fill("25"); - const rotated = await sampleBranch(); - closeVector(rotated.selected.localQuaternion, new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), 25 * Math.PI / 180).toArray(), "selected local rotation"); - closeVector(rotated.selected.world, translated.selected.world, "rotation about own origin"); - for (const key of ["parent", "sibling"]) assert.deepEqual(rotated[key], beforeEdit[key], `${key} must not rotate`); - closeVector(rotated.child.local, beforeEdit.child.local, "child local origin unchanged"); - const childWorld = new THREE.Vector3(...rotated.child.local).applyQuaternion(new THREE.Quaternion(...rotated.selected.worldQuaternion)).add(new THREE.Vector3(...rotated.selected.world)); - closeVector(rotated.child.world, childWorld.toArray(), "child inherits selected rotation"); - assert.ok(rotated.child.world.some((value, i) => Math.abs(value - translated.child.world[i]) > 1e-6)); - assert.equal(await transformPreview.getAttribute("data-machine-manager-selected-joint-axes"), "local-xyz"); - await choose("MILL700-A-主轴-JT2"); - assert.equal(Number(await control("machine-manager-rotation").locator("input").nth(2).inputValue()), 0); - await choose("MILL700-A-主轴-JT3"); - assert.equal(Number(await translationInputs.nth(0).inputValue()), 120); - assert.equal(Number(await control("machine-manager-rotation").locator("input").nth(2).inputValue()), 25); - await page.keyboard.press("Escape"); - await control("machine-manager-settings").click(); - await page.waitForFunction(() => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerRendered === "true"); - closeVector((await readSelected()).local, rotated.selected.local, "reopening preserves node edit"); - report.nodeEditing = { before: beforeEdit, translated, rotated }; - report.checks.push("MILL700 JT3 edits preserve parent and sibling transforms; child inherits movement and rotation around the joint origin; values survive reselection and dialog reopening"); - await page.screenshot({ path: new URL("mill700-studio-lighting.png", output).pathname, timeout: 60000 }); - const source = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A-门1-JT1" }).first(), target = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: "MILL700-A底座" }).first(); await source.dispatchEvent("click"); - await control("machine-manager-rotation").locator("input").nth(2).fill("-15"); - const doorBefore = await readSelected(); - const sourceItem = source.locator("xpath=ancestor::li[1]"), targetItem = target.locator("xpath=ancestor::li[1]"), sourcePath = await sourceItem.getAttribute("data-machine-tree-path"), targetPath = await targetItem.getAttribute("data-machine-tree-path"), partId = await sourceItem.getAttribute("data-machine-tree-part"); await target.evaluate((element, payload) => { const transfer = new DataTransfer(); transfer.setData("application/x-linuxcnc-machine-tree", JSON.stringify({ partId: payload.partId, path: payload.sourcePath })); element.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer })); }, { partId, sourcePath }); await page.waitForFunction(({ sourcePath, targetPath }) => document.querySelector(`[data-machine-tree-path="${sourcePath}"]`)?.getAttribute("data-machine-tree-parent") === targetPath, { sourcePath, targetPath }, { timeout: 60000 }); assert.equal(await page.locator("[data-machine-manager-preview]").getAttribute("data-machine-manager-context-generation"), "1"); - const doorAfter = await readSelected(); - closeVector(doorAfter.world, doorBefore.world, "reparent preserves edited world position"); - closeVector(doorAfter.worldQuaternion, doorBefore.worldQuaternion, "reparent preserves edited world orientation"); - const doorLocalX = Number(await translationInputs.nth(0).inputValue()); - await translationInputs.nth(0).fill(String(doorLocalX + 12)); - const doorEdited = await readSelected(); - closeVector(doorEdited.local, [doorAfter.local[0] + .012, ...doorAfter.local.slice(1)], "editing uses stable path after reparent"); - report.checks.push("reparenting an edited node preserves world pose and subsequent inputs edit that node under its new parent"); - report.hierarchy = hierarchy; report.checks.push("selected MILL700 joint displays its local XYZ coordinate system at local origin"); report.checks.push("existing 132-node, depth-6 hierarchy displayed as a draggable tree with node origins"); - const preview = page.locator("[data-machine-manager-preview]"); assert.equal(await preview.getAttribute("data-machine-manager-lighting"), "studio-pmrem"); assert.equal(await preview.getAttribute("data-machine-manager-coordinate-system"), "machine-z-up"); assert.equal(await preview.getAttribute("data-machine-manager-axis-map"), "X:x,Y:y,Z:z"); assert.equal(await preview.getAttribute("data-machine-manager-camera-up"), "0,0,1"); assert.equal(await preview.getAttribute("data-machine-manager-model-transform"), "none-on-import"); assert.equal(await preview.getAttribute("data-machine-manager-webgl-antialias"), "false"); assert.equal(await preview.getAttribute("data-machine-manager-render-state"), "rendered"); assert.equal(await preview.getAttribute("data-machine-manager-context-generation"), "1"); assert.equal(Number(await preview.getAttribute("data-machine-manager-scene-meshes")), 126); assert.ok(Number(await preview.getAttribute("data-machine-manager-scene-objects")) >= 132); const bounds = JSON.parse(await preview.getAttribute("data-machine-manager-bounds")); assert.ok(bounds.min.every(Number.isFinite) && bounds.max.every(Number.isFinite) && bounds.size > 0); const [bufferWidth, bufferHeight] = (await preview.getAttribute("data-machine-manager-buffer-size")).split("x").map(Number), maxBuffer = Number(await preview.getAttribute("data-machine-manager-max-buffer")); assert.ok(bufferWidth > 0 && bufferHeight > 0 && bufferWidth <= maxBuffer && bufferHeight <= maxBuffer); - const bind = async (name, role) => { const node = page.locator('[data-axis-control="machine-tree-node"]').filter({ hasText: name }).first(); await node.dispatchEvent("click"); await control("machine-tree-axis-role").selectOption(role); }; - await bind("MILL700-A-主轴-JT1", "x"); await bind("MILL700-A-主轴-JT2", "y"); await bind("MILL700-A-主轴-JT3", "z"); await bind("MILL700-A-工作台-JT1", "tilt"); await bind("MILL700-A-工作台-JT2", "c"); await page.waitForFunction(() => { try { const roles = JSON.parse(document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerRoleTransforms ?? "{}"); return ["x", "y", "z", "tilt", "c"].every((role) => roles[role]); } catch { return false; } }); - const baseline = JSON.parse(await preview.getAttribute("data-machine-manager-role-transforms")); await control("machine-test-x").fill("10"); await control("machine-test-y").fill("20"); await control("machine-test-z").fill("30"); await control("machine-test-a").fill("15"); await control("machine-test-c").fill("45"); await page.waitForFunction(() => JSON.parse(document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerTestPose ?? "{}").c === 45); - const driven = JSON.parse(await preview.getAttribute("data-machine-manager-role-transforms")), quaternionAngle = (left, right) => 2 * Math.acos(Math.min(1, Math.abs(left.reduce((sum, value, index) => sum + value * right[index], 0)))); - assert.ok(Math.abs(driven.x.local[0] - baseline.x.local[0] - .01) < 1e-9); assert.ok(Math.abs(driven.y.local[1] - baseline.y.local[1] - .02) < 1e-9); assert.ok(Math.abs(driven.z.local[2] - baseline.z.local[2] - .03) < 1e-9); assert.ok(Math.abs(quaternionAngle(driven.tilt.localQuaternion, baseline.tilt.localQuaternion) - 15 * Math.PI / 180) < 1e-7); assert.ok(Math.abs(quaternionAngle(driven.c.localQuaternion, baseline.c.localQuaternion) - 45 * Math.PI / 180) < 1e-7); assert.deepEqual(driven.tilt.world, baseline.tilt.world); assert.deepEqual(driven.c.local, baseline.c.local); assert.ok(driven.c.world.some((value, index) => Math.abs(value - baseline.c.world[index]) > 1e-6)); assert.equal(await preview.getAttribute("data-machine-manager-context-generation"), "1"); report.motion = { input: { x: 10, y: 20, z: 30, a: 15, c: 45 }, baseline, driven }; report.checks.push("each joint rotates about its own origin while children inherit parent motion"); - report.checks.push("metallic MILL700 surfaces rendered with studio PMREM lighting; screenshot captured for visual review"); assert.deepEqual(errors, []); report.status = "verified"; - await control("machine-manager-delete").click(); await page.waitForFunction(() => document.querySelector("[data-machine-manager-preview]")?.dataset.machineManagerPartCount === "0" && document.querySelectorAll('[data-axis-control="machine-tree-node"]').length === 0); report.checks.push("imported model can be deleted and its tree is removed"); -} catch (error) { report.status = "failed"; report.error = error instanceof Error ? error.stack : String(error); await page.screenshot({ path: new URL("failure.png", output).pathname, timeout: 5000 }).catch(() => undefined); throw error; } -finally { report.browser_errors = errors; await writeFile(new URL("browser.report.json", output), JSON.stringify(report, null, 2) + "\n"); await browser.close(); } -console.log(JSON.stringify(report)); +// MGR-17 supersedes the unrestricted six-component pose editor. Keep this +// established entrypoint exercising MILL700 through the single-DOF contract. +process.env.JOINT_TEST_COORDINATES ??= "XYZAC"; +await import("./test_machine_joint_dof_browser.mjs"); diff --git a/web/react/src/App.tsx b/web/react/src/App.tsx index 0631f84..91d6a0b 100644 --- a/web/react/src/App.tsx +++ b/web/react/src/App.tsx @@ -883,7 +883,7 @@ export function App() { { void loadCase(path); }} /> -
+
{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => )}
diff --git a/web/react/src/MachineManagerPanel.tsx b/web/react/src/MachineManagerPanel.tsx index bbd83e9..27985de 100644 --- a/web/react/src/MachineManagerPanel.tsx +++ b/web/react/src/MachineManagerPanel.tsx @@ -1,28 +1,28 @@ import { useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; -import { TransformControls } from "three/examples/jsm/controls/TransformControls.js"; import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js"; import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js"; import { DraggableDialog } from "./DraggableDialog"; import { disposeMachineScene, type ImportedMachine } from "./imported-machine"; -import { configureMachinePartScene, reparentMachineNode, buildManagedMachine, expectedParentRole, loadMachinePart, machinePartTree, MACHINE_PART_ROLES, roleLabel, type MachinePartDraft, type MachinePartRole, type MachineTreeNode, type PhysicalZero } from "./machine-manager"; +import { configureMachinePartScene, draftNodeDof, makeJointAssembly, reparentMachineNode, buildManagedMachine, expectedParentRole, loadMachinePart, machinePartTree, MACHINE_PART_ROLES, roleLabel, type MachinePartDraft, type MachinePartRole, type MachineTreeNode, type PhysicalZero } from "./machine-manager"; +import { driveJoint, jointZero, defaultDof, isLinear, type JointDof, type CncAxis } from "./machine-joints"; +import { projectXyzacVisual, type XyzacVisualSource } from "./xyzac-visual-adapter"; import type { Vec3 } from "./machine-urdf"; -type Props = { coordinates: "XYZAC" | "XYZBC"; disabled: boolean; onApply: (machine: ImportedMachine) => Promise }; +type Props = { snapshot?: XyzacVisualSource | null; coordinates: "XYZAC" | "XYZBC"; disabled: boolean; onApply: (machine: ImportedMachine) => Promise }; type TestPose = { x: number; y: number; z: number; a: number; b: number; c: number }; type MotionTransform = { local: number[]; localQuaternion: number[]; world: number[]; worldQuaternion: number[] }; const zero3 = (): Vec3 => [0, 0, 0]; const number = (value: string) => Number.isFinite(Number(value)) ? Number(value) : 0; -function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, mode, testPose, onTransform, onMotionState }: { coordinates: "XYZAC" | "XYZBC"; parts: MachinePartDraft[]; selectedId: string | null; selectedNodePath: string; mode: "translate" | "rotate"; testPose: TestPose; onTransform: (id: string, translationMm: Vec3, rotationDeg: Vec3) => void; onMotionState: (state: Record) => void }) { - type PreviewHost = HTMLDivElement & { machineManagerSync?: (parts: MachinePartDraft[], selectedId: string | null, selectedNodePath: string, mode: "translate" | "rotate", testPose: TestPose, coordinates: "XYZAC" | "XYZBC") => void }; +function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, testPose, onMotionState }: { coordinates: "XYZAC" | "XYZBC"; parts: MachinePartDraft[]; selectedId: string | null; selectedNodePath: string; testPose: TestPose; onMotionState: (state: Record) => void }) { + type PreviewHost = HTMLDivElement & { machineManagerSync?: (parts: MachinePartDraft[], selectedId: string | null, selectedNodePath: string, testPose: TestPose, coordinates: "XYZAC" | "XYZBC") => void }; const host = useRef(null); - const stateRef = useRef({ parts, selectedId, selectedNodePath, mode, testPose, coordinates }); stateRef.current = { parts, selectedId, selectedNodePath, mode, testPose, coordinates }; - const onTransformRef = useRef(onTransform); onTransformRef.current = onTransform; + const stateRef = useRef({ parts, selectedId, selectedNodePath, testPose, coordinates }); stateRef.current = { parts, selectedId, selectedNodePath, testPose, coordinates }; const onMotionStateRef = useRef(onMotionState); onMotionStateRef.current = onMotionState; const [fault, setFault] = useState(""); - useEffect(() => { host.current?.machineManagerSync?.(parts, selectedId, selectedNodePath, mode, testPose, coordinates); }, [parts, selectedId, selectedNodePath, mode, testPose, coordinates]); + useEffect(() => { host.current?.machineManagerSync?.(parts, selectedId, selectedNodePath, testPose, coordinates); }, [parts, selectedId, selectedNodePath, testPose, coordinates]); useEffect(() => { const element = host.current; if (!element) return; // Large industrial GLBs can exceed small-GPU multisample renderbuffer @@ -43,27 +43,21 @@ function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, mode const camera = new THREE.PerspectiveCamera(42, 1, .005, 100); camera.up.set(0, 0, 1); camera.position.set(2.4, -2.8, 1.8); const orbit = new OrbitControls(camera, renderer.domElement); orbit.enableDamping = true; const root = new THREE.Group(); root.name = "machine-manager-preview"; scene.add(root); + let dofNodes: Array<{ object: THREE.Object3D; zero: ReturnType; dof: JointDof }> = []; type RoleNode = { object: THREE.Object3D; position: THREE.Vector3; quaternion: THREE.Quaternion }; - let nodes = new Map(), visualNodes = new Map>(), roleNodes = new Map(), helpers: THREE.AxesHelper[] = [], selectedObject: THREE.Group | null = null, selectedJointObject: THREE.Object3D | null = null, selectedPartId: string | null = null, lastParts: MachinePartDraft[] | null = null, fitSignature = "", selectedAxesScale = 1, dirty = true, renderable = false, contextLost = false, frame = 0; + let nodes = new Map(), visualNodes = new Map>(), roleNodes = new Map(), helpers: THREE.AxesHelper[] = [], selectedJointObject: THREE.Object3D | null = null, lastParts: MachinePartDraft[] | null = null, fitSignature = "", selectedAxesScale = 1, dirty = true, renderable = false, contextLost = false, frame = 0; const selectedJointAxes = new THREE.Group(); selectedJointAxes.name = "selected-joint-local-coordinate-system"; selectedJointAxes.visible = false; const jointAxis = (direction: THREE.Vector3, color: number) => selectedJointAxes.add(new THREE.ArrowHelper(direction, new THREE.Vector3(), .28, color, .07, .045)); jointAxis(new THREE.Vector3(1, 0, 0), 0xff3030); jointAxis(new THREE.Vector3(0, 1, 0), 0x35d05b); jointAxis(new THREE.Vector3(0, 0, 1), 0x327cff); scene.add(selectedJointAxes); - const transform = new TransformControls(camera, renderer.domElement); transform.setMode(mode); transform.setSpace("local"); scene.add(transform.getHelper()); - transform.addEventListener("dragging-changed", ((event: { value?: boolean }) => { orbit.enabled = !event.value; }) as never); - transform.addEventListener("objectChange", (() => { - if (!selectedPartId || !selectedObject) return; - onTransformRef.current(selectedPartId, selectedObject.position.toArray().map((value) => value * 1000) as Vec3, [THREE.MathUtils.radToDeg(selectedObject.rotation.x), THREE.MathUtils.radToDeg(selectedObject.rotation.y), THREE.MathUtils.radToDeg(selectedObject.rotation.z)]); - }) as never); const draw = () => { if (contextLost || !renderable) return; renderer.setRenderTarget(null); scene.updateMatrixWorld(true); camera.updateMatrixWorld(true); renderer.clear(); renderer.render(scene, camera); element.dataset.machineManagerRendered = "true"; element.dataset.machineManagerRenderState = "rendered"; dirty = false; }; - element.machineManagerSync = (nextParts, nextSelectedId, nextNodePath, nextMode, nextPose, nextCoordinates) => { - transform.setMode(nextMode); + element.machineManagerSync = (nextParts, nextSelectedId, nextNodePath, nextPose, nextCoordinates) => { if (nextParts !== lastParts) { delete element.dataset.machineManagerRendered; element.dataset.machineManagerRenderState = "syncing-scene"; - transform.detach(); selectedJointAxes.removeFromParent(); root.clear(); helpers.forEach((helper) => helper.dispose()); helpers = []; nodes = new Map(); visualNodes = new Map(); roleNodes = new Map(); + selectedJointAxes.removeFromParent(); root.clear(); helpers.forEach((helper) => helper.dispose()); helpers = []; dofNodes = []; nodes = new Map(); visualNodes = new Map(); roleNodes = new Map(); for (const part of nextParts) { const node = new THREE.Group(); node.name = `draft_${part.id}`; node.visible = part.visible; node.position.fromArray(part.translationMm).multiplyScalar(.001); node.rotation.set(...part.rotationDeg.map(THREE.MathUtils.degToRad) as Vec3); // Clone transforms/hierarchy only. Geometry, materials, textures and @@ -71,6 +65,9 @@ function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, mode const { scene: visual, nodes: pathNodes } = configureMachinePartScene(part.scene.clone(true), part); const internalRoles = Object.entries(part.nodeRoles ?? {}).map(([path, role]) => ({ object: pathNodes.get(path) ?? null, role })); visual.position.add(new THREE.Vector3(...part.originMm).multiplyScalar(-.001)); node.add(visual); visualNodes.set(part.id, pathNodes); + const rootDof = draftNodeDof(part, "scene", nextCoordinates); + if (rootDof) dofNodes.push({ object: node, zero: jointZero(node), dof: rootDof }); + for (const [path, object] of pathNodes) { const dof = path === "scene" ? null : draftNodeDof(part, path, nextCoordinates); if (dof) dofNodes.push({ object, zero: jointZero(object), dof }); } const axes = new THREE.AxesHelper(.12); helpers.push(axes); axes.name = `origin_${part.id}`; axes.visible = part.showOrigin; node.add(axes); root.add(node); nodes.set(part.id, node); if (part.role) roleNodes.set(part.role, { object: node, position: node.position.clone(), quaternion: node.quaternion.clone() }); for (const binding of internalRoles) if (binding.object && binding.role) roleNodes.set(binding.role, { object: binding.object, position: binding.object.position.clone(), quaternion: binding.object.quaternion.clone() }); @@ -85,17 +82,15 @@ function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, mode } lastParts = nextParts; } - selectedPartId = nextSelectedId; selectedObject = nextSelectedId && nextNodePath === "scene" ? nodes.get(nextSelectedId) ?? null : null; transform.detach(); if (selectedObject) transform.attach(selectedObject); - selectedJointAxes.removeFromParent(); selectedJointObject = nextSelectedId ? nextNodePath === "scene" ? nodes.get(nextSelectedId) ?? null : visualNodes.get(nextSelectedId)?.get(nextNodePath) ?? null : null; if (selectedJointObject) { selectedJointObject.add(selectedJointAxes); selectedJointAxes.position.set(0, 0, 0); selectedJointAxes.quaternion.identity(); selectedJointAxes.scale.setScalar(selectedAxesScale); selectedJointAxes.visible = true; element.dataset.machineManagerSelectedJoint = `${nextSelectedId}:${nextNodePath}`; element.dataset.machineManagerSelectedJointAxes = "local-xyz"; element.dataset.machineManagerTransformTarget = nextNodePath === "scene" ? "part-root" : "none-internal-joint"; } else { selectedJointAxes.visible = false; delete element.dataset.machineManagerSelectedJoint; delete element.dataset.machineManagerSelectedJointAxes; delete element.dataset.machineManagerTransformTarget; } - for (const binding of roleNodes.values()) { binding.object.position.copy(binding.position); binding.object.quaternion.copy(binding.quaternion); } - const translate = (role: MachinePartRole, axis: "x" | "y" | "z", value: number) => { const binding = roleNodes.get(role); if (binding) { const direction = new THREE.Vector3(axis === "x" ? 1 : 0, axis === "y" ? 1 : 0, axis === "z" ? 1 : 0).applyQuaternion(binding.quaternion); binding.object.position.addScaledVector(direction, value / 1000); } }; - const rotate = (role: MachinePartRole, axis: THREE.Vector3, degrees: number) => { const binding = roleNodes.get(role); if (binding) binding.object.quaternion.copy(binding.quaternion).multiply(new THREE.Quaternion().setFromAxisAngle(axis, THREE.MathUtils.degToRad(degrees))); }; - translate("x", "x", nextPose.x); translate("y", "y", nextPose.y); translate("z", "z", nextPose.z); rotate("tilt", nextCoordinates === "XYZBC" ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(1, 0, 0), nextCoordinates === "XYZBC" ? nextPose.b : nextPose.a); rotate("c", new THREE.Vector3(0, 0, 1), nextPose.c); - root.updateMatrixWorld(true); let objectCount = 0, meshCount = 0; root.traverse((object) => { let cursor: THREE.Object3D | null = object, isJointHelper = false; while (cursor) { if (cursor === selectedJointAxes) { isJointHelper = true; break; } cursor = cursor.parent; } if (!isJointHelper) { objectCount += 1; if ((object as THREE.Mesh).isMesh) meshCount += 1; } }); const transforms = Object.fromEntries([...roleNodes].map(([role, binding]) => [role, { local: binding.object.position.toArray(), localQuaternion: binding.object.quaternion.toArray(), world: binding.object.getWorldPosition(new THREE.Vector3()).toArray(), worldQuaternion: binding.object.getWorldQuaternion(new THREE.Quaternion()).toArray() }])); element.dataset.machineManagerSceneObjects = String(objectCount); element.dataset.machineManagerSceneMeshes = String(meshCount); element.dataset.machineManagerRoleTransforms = JSON.stringify(transforms); if (selectedJointObject) { element.dataset.machineManagerSelectedJointWorldOrigin = JSON.stringify(selectedJointObject.getWorldPosition(new THREE.Vector3()).toArray()); element.dataset.machineManagerSelectedJointWorldQuaternion = JSON.stringify(selectedJointObject.getWorldQuaternion(new THREE.Quaternion()).toArray()); element.dataset.machineManagerSelectedJointAxesScale = selectedAxesScale.toFixed(6); } const selectedState = selectedJointObject ? { local: selectedJointObject.position.toArray(), localQuaternion: selectedJointObject.quaternion.toArray(), world: selectedJointObject.getWorldPosition(new THREE.Vector3()).toArray(), worldQuaternion: selectedJointObject.getWorldQuaternion(new THREE.Quaternion()).toArray() } : null; + + selectedJointAxes.removeFromParent(); selectedJointObject = nextSelectedId ? nextNodePath === "scene" ? nodes.get(nextSelectedId) ?? null : visualNodes.get(nextSelectedId)?.get(nextNodePath) ?? null : null; if (selectedJointObject) { selectedJointObject.add(selectedJointAxes); selectedJointAxes.position.set(0, 0, 0); selectedJointAxes.quaternion.identity(); selectedJointAxes.scale.setScalar(selectedAxesScale); selectedJointAxes.visible = true; element.dataset.machineManagerSelectedJoint = `${nextSelectedId}:${nextNodePath}`; element.dataset.machineManagerSelectedJointAxes = "local-xyz"; element.dataset.machineManagerTransformTarget = "single-dof-input"; } else { selectedJointAxes.visible = false; delete element.dataset.machineManagerSelectedJoint; delete element.dataset.machineManagerSelectedJointAxes; delete element.dataset.machineManagerTransformTarget; } + for (const binding of dofNodes) driveJoint(binding.object, binding.zero, binding.dof, nextPose[binding.dof.axis.toLowerCase() as keyof TestPose]); + root.updateMatrixWorld(true); let objectCount = 0, meshCount = 0; root.traverse((object) => { let cursor: THREE.Object3D | null = object, isJointHelper = false; while (cursor) { if (cursor === selectedJointAxes) { isJointHelper = true; break; } cursor = cursor.parent; } if (!isJointHelper) { objectCount += 1; if ((object as THREE.Mesh).isMesh) meshCount += 1; } }); const transforms = Object.fromEntries([...roleNodes].map(([role, binding]) => [role, { local: binding.object.position.toArray(), localQuaternion: binding.object.quaternion.toArray(), world: binding.object.getWorldPosition(new THREE.Vector3()).toArray(), worldQuaternion: binding.object.getWorldQuaternion(new THREE.Quaternion()).toArray() }])); element.dataset.machineManagerSceneObjects = String(objectCount); element.dataset.machineManagerSceneMeshes = String(meshCount); element.dataset.machineManagerRoleTransforms = JSON.stringify(transforms); + element.dataset.machineManagerJointTransforms = JSON.stringify(Object.fromEntries(dofNodes.map(({ object, dof }) => [dof.axis, { localMm: object.position.toArray().map((v) => v * 1000), quaternion: object.quaternion.toArray(), world: object.getWorldPosition(new THREE.Vector3()).toArray() }]))); if (selectedJointObject) { element.dataset.machineManagerSelectedJointWorldOrigin = JSON.stringify(selectedJointObject.getWorldPosition(new THREE.Vector3()).toArray()); element.dataset.machineManagerSelectedJointWorldQuaternion = JSON.stringify(selectedJointObject.getWorldQuaternion(new THREE.Quaternion()).toArray()); element.dataset.machineManagerSelectedJointAxesScale = selectedAxesScale.toFixed(6); } const selectedState = selectedJointObject ? { local: selectedJointObject.position.toArray(), localQuaternion: selectedJointObject.quaternion.toArray(), world: selectedJointObject.getWorldPosition(new THREE.Vector3()).toArray(), worldQuaternion: selectedJointObject.getWorldQuaternion(new THREE.Quaternion()).toArray() } : null; element.dataset.machineManagerSelectedTransform = JSON.stringify(selectedState); onMotionStateRef.current(selectedState ? { ...transforms, selected: selectedState } : transforms); element.dataset.machineManagerTestPose = JSON.stringify(nextPose); element.dataset.machineManagerContextGeneration = "1"; dirty = true; draw(); }; - element.machineManagerSync(stateRef.current.parts, stateRef.current.selectedId, stateRef.current.selectedNodePath, stateRef.current.mode, stateRef.current.testPose, stateRef.current.coordinates); + element.machineManagerSync(stateRef.current.parts, stateRef.current.selectedId, stateRef.current.selectedNodePath, stateRef.current.testPose, stateRef.current.coordinates); const resize = () => { const width = element.clientWidth, height = element.clientHeight; if (!Number.isFinite(width) || !Number.isFinite(height) || width < 2 || height < 2) { renderable = false; element.dataset.machineManagerRenderState = "suspended-zero-size"; return; } @@ -109,28 +104,28 @@ function ManagerPreview({ coordinates, parts, selectedId, selectedNodePath, mode const contextLostHandler = (event: Event) => { event.preventDefault(); contextLost = true; renderable = false; element.dataset.machineManagerRenderState = "context-lost"; setFault("WebGL 上下文已丢失,请关闭并重新打开三维机床管理器"); }; renderer.domElement.addEventListener("webglcontextlost", contextLostHandler); const observer = new ResizeObserver(resize); observer.observe(element); resize(); - orbit.addEventListener("change", () => { dirty = true; }); transform.addEventListener("change", () => { dirty = true; }); + orbit.addEventListener("change", () => { dirty = true; }); const render = () => { if (!contextLost && renderable && (dirty || orbit.update())) draw(); frame = contextLost ? 0 : requestAnimationFrame(render); }; render(); - return () => { if (frame) cancelAnimationFrame(frame); observer.disconnect(); delete element.machineManagerSync; renderer.domElement.removeEventListener("webglcontextlost", contextLostHandler); transform.dispose(); orbit.dispose(); helpers.forEach((helper) => helper.dispose()); selectedJointAxes.traverse((object) => { const mesh = object as THREE.Mesh; mesh.geometry?.dispose(); if (mesh.material) for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) material.dispose(); }); environment.dispose(); renderer.dispose(); renderer.domElement.remove(); }; + return () => { if (frame) cancelAnimationFrame(frame); observer.disconnect(); delete element.machineManagerSync; renderer.domElement.removeEventListener("webglcontextlost", contextLostHandler); orbit.dispose(); helpers.forEach((helper) => helper.dispose()); selectedJointAxes.traverse((object) => { const mesh = object as THREE.Mesh; mesh.geometry?.dispose(); if (mesh.material) for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) material.dispose(); }); environment.dispose(); renderer.dispose(); renderer.domElement.remove(); }; }, []); - return
part.showOrigin).length} data-transform-mode={mode} aria-label="三维机床装配预览,机床 Z 轴朝上" />{fault && {fault}}
; + return
part.showOrigin).length} aria-label="三维机床装配预览,机床 Z 轴朝上" />{fault && {fault}}
; } function TreeNode({ part, node, selected, onSelect, onDrop, extra }: { part: MachinePartDraft; node: MachineTreeNode; selected: string; onSelect: (partId: string, path: string) => void; onDrop: (source: { partId: string; path: string }, target: { partId: string; path: string }) => void; extra?: React.ReactNode }) { const key = `${part.id}:${node.path}`; const [expanded, setExpanded] = useState(node.path === "scene"); const hasChildren = node.children.length > 0 || Boolean(extra); - const binding = node.path === "scene" ? part.role : part.nodeRoles?.[node.path]; + const binding = part.nodeJoints?.[node.path]?.axis ?? (node.path === "scene" ? part.role : part.nodeRoles?.[node.path]); return
  • {hasChildren && }
  • ; } -export function MachineManagerPanel({ coordinates, disabled, onApply }: Props) { +export function MachineManagerPanel({ coordinates, disabled, onApply, snapshot }: Props) { const [open, setOpen] = useState(false), [pending, setPending] = useState(false), [message, setMessage] = useState(""); const [parts, setParts] = useState([]); const partsRef = useRef(parts); partsRef.current = parts; - const [selectedId, setSelectedId] = useState(null); const [selectedNodePath, setSelectedNodePath] = useState("scene"); const [mode, setMode] = useState<"translate" | "rotate">("translate"); + const [selectedId, setSelectedId] = useState(null); const [selectedNodePath, setSelectedNodePath] = useState("scene"); const [cOriginMm, setCOriginMm] = useState([0, 0, 0]); const [tiltOffsetMm, setTiltOffsetMm] = useState([0, 0, 0]); const [physicalZero, setPhysicalZero] = useState({ x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 }); const file = useRef(null); const [testPose, setTestPose] = useState({ x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 }); @@ -143,30 +138,27 @@ export function MachineManagerPanel({ coordinates, disabled, onApply }: Props) { const selectedTreeNode = selected ? findTreeNode(trees.get(selected.id), selectedNodePath) : null; const update = (id: string, values: Partial) => setParts((current) => current.map((part) => part.id === id ? { ...part, ...values } : part)); const vectorInput = (label: string, value: Vec3, set: (value: Vec3) => void, unit: string, control?: string) =>
    {label} ({unit}){["X", "Y", "Z"].map((axis, index) => )}
    ; - const updateSelectedTransform = (field: "translationMm" | "rotationDeg", value: Vec3) => { - if (!selected || !selectedTreeNode) return; - if (selectedNodePath === "scene") { update(selected.id, { [field]: value }); return; } - setParts((current) => current.map((part) => { - if (part.id !== selected.id) return part; - const currentTransform = part.nodeTransforms?.[selectedNodePath] ?? { translationMm: selectedTreeNode!.originMm, rotationDeg: selectedTreeNode!.rotationDeg }; - return { ...part, nodeTransforms: { ...(part.nodeTransforms ?? {}), [selectedNodePath]: { ...currentTransform, [field]: value } } }; - })); - }; const importFiles = async (list: FileList | File[] | null) => { if (!list?.length) return; setPending(true); setMessage("正在读取 GLB 部件…"); try { const loaded = await Promise.all([...list].map(async (item) => loadMachinePart(await item.arrayBuffer(), item.name))); + if (loaded.some((source) => source.restoredModel && source.restoredModel.coordinates !== coordinates)) { + loaded.forEach((source) => disposeMachineScene(source.scene)); + throw new Error(`导入机型不同,请先切换到 ${loaded.find((source) => source.restoredModel?.coordinates !== coordinates && source.restoredModel)?.restoredModel?.coordinates}`); + } setParts((current) => { const next = [...current]; for (const source of loaded) { const role = MACHINE_PART_ROLES.find((candidate) => !next.some((part) => part.role === candidate)) ?? ""; const parentRole = role ? expectedParentRole(role) : null; const parentId = parentRole ? next.find((part) => part.role === parentRole)?.id ?? null : null; - next.push({ ...source, role, parentId, visible: true, showOrigin: true, originMm: zero3(), translationMm: zero3(), rotationDeg: zero3(), nodeParents: {}, nodeRoles: {}, nodeTransforms: {} }); + next.push({ ...source, role, parentId, visible: true, showOrigin: true, originMm: zero3(), translationMm: zero3(), rotationDeg: zero3(), nodeParents: {}, nodeTransforms: {}, nodeJoints: source.restoredJoints ?? {}, nodeRoles: source.restoredRoles ?? {} }); } return next; }); - setSelectedId(loaded[0].id); setSelectedNodePath("scene"); setMessage(`已导入 ${loaded.length} 个部件`); + const restored = loaded.find((source) => source.restoredModel)?.restoredModel; + if (restored) { setCOriginMm(restored.calibration.cOriginMm); setTiltOffsetMm(restored.calibration.tiltOffsetMm); setPhysicalZero(restored.calibration.physicalZero as PhysicalZero); setMessage(`已恢复 ${restored.coordinates} 单自由度配置`); } + setSelectedId(loaded[0].id); setSelectedNodePath("scene"); setMessage(restored ? `已恢复 ${restored.coordinates} 关节配置和物理零点` : `已导入 ${loaded.length} 个部件`); } catch (error) { setMessage(error instanceof Error ? error.message : String(error)); } finally { setPending(false); } }; @@ -199,7 +191,9 @@ export function MachineManagerPanel({ coordinates, disabled, onApply }: Props) { setPending(true); setMessage("正在生成包含 userData 的组合 GLB…"); let machine: ImportedMachine | null = null; try { machine = await buildManagedMachine({ coordinates, parts, cOriginMm, tiltOffsetMm, physicalZero }); - const bytes = await new GLTFExporter().parseAsync(machine.scene, { binary: true, trs: true, onlyVisible: false }); + const exportScene = new THREE.Scene(); exportScene.name = machine.scene.name; exportScene.userData = machine.scene.userData; + for (const child of machine.scene.children) exportScene.add(child.clone(true)); + const bytes = await new GLTFExporter().parseAsync(exportScene, { binary: true, trs: true, onlyVisible: false }); const url = URL.createObjectURL(new Blob([bytes as ArrayBuffer], { type: "model/gltf-binary" })); const link = document.createElement("a"); link.href = url; link.download = machine.filename; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); setMessage("组合 GLB 已导出,底座和整机场景包含机床标定 userData"); @@ -207,23 +201,28 @@ export function MachineManagerPanel({ coordinates, disabled, onApply }: Props) { finally { if (machine) disposeMachineScene(machine.scene); setPending(false); } }; const roleChange = (part: MachinePartDraft, role: MachinePartRole | "") => { + if (selectedNodePath !== "scene") { update(part.id, { nodeRoles: { ...part.nodeRoles, [selectedNodePath]: role } }); return; } const parentRole = role ? expectedParentRole(role) : null; update(part.id, { role, parentId: parentRole ? parts.find((item) => item.role === parentRole)?.id ?? null : null }); }; - const selectedBinding = selected ? selectedNodePath === "scene" ? selected.role : selected.nodeRoles?.[selectedNodePath] ?? "" : ""; - const assignTreeRole = (role: MachinePartRole | "") => { + const selectedDof = selected ? draftNodeDof(selected, selectedNodePath, coordinates) : null; + const setDof = (dof: JointDof | null) => { if (!selected) return; - setParts((current) => current.map((part) => { - const nodeRoles = Object.fromEntries(Object.entries(part.nodeRoles ?? {}).filter(([, value]) => !role || value !== role)); - let partRole = role && part.role === role ? "" : part.role; - if (part.id === selected.id) { - if (selectedNodePath === "scene") partRole = role; - else { delete nodeRoles[selectedNodePath]; if (role) nodeRoles[selectedNodePath] = role; } - } - return { ...part, role: partRole, nodeRoles } as MachinePartDraft; - })); + if (dof && parts.some((part) => Object.entries(part.nodeJoints ?? {}).some(([path, existing]) => existing.axis === dof.axis && (part.id !== selected.id || path !== selectedNodePath)))) { setMessage(`${dof.axis} 轴已绑定其他节点,请先取消该绑定`); return; } + setParts((current) => current.map((part) => { if (part.id !== selected.id) return part; const nodeJoints = { ...(part.nodeJoints ?? {}) }; if (dof) nodeJoints[selectedNodePath] = dof; else delete nodeJoints[selectedNodePath]; return { ...part, nodeJoints }; })); + setMessage(""); }; - const poseInput = (axis: keyof TestPose, unit: string) => ; + const [followLive, setFollowLive] = useState(false); + const visual = useMemo(() => projectXyzacVisual(snapshot), [snapshot]); + const liveReady = visual.authoritative && visual.machineModel?.coordinate_system === coordinates; + const effectivePose: TestPose = followLive && liveReady ? { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, ...Object.fromEntries([...coordinates].map((axis, i) => [axis.toLowerCase(), visual.joints[i] - physicalZero[axis.toLowerCase() as keyof PhysicalZero]])) } : testPose; + const validation = useMemo(() => { try { const { model } = makeJointAssembly({ coordinates, parts, cOriginMm, tiltOffsetMm, physicalZero }); const missing = [...coordinates].filter((axis) => !model.axisMap[axis as CncAxis]); return { model, error: missing.length ? `请绑定 ${missing.join("、")} 轴` : "" }; } catch (error) { return { model: null, error: error instanceof Error ? error.message : String(error) }; } }, [coordinates, parts, cOriginMm, tiltOffsetMm, physicalZero]); + const exportJson = () => { + if (!validation.model) return; + const url = URL.createObjectURL(new Blob([JSON.stringify(validation.model, null, 2)], { type: "application/json" })); + const a = document.createElement("a"); a.href = url; a.download = `machine-joints-${coordinates.toLowerCase()}.json`; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); + }; + const poseInput = (axis: keyof TestPose, unit: string) => ; const receiveMotionState = (state: Record) => { const hash = JSON.stringify(state); if (hash !== motionStateHash.current) { motionStateHash.current = hash; setMotionState(state); } }; const selectedDriven = motionState.selected ?? null; const selectedTransform = selectedNodePath === "scene" ? selected : selected?.nodeTransforms?.[selectedNodePath] ?? (selectedTreeNode ? { translationMm: selectedTreeNode.originMm, rotationDeg: selectedTreeNode.rotationDeg } : null); @@ -231,18 +230,21 @@ export function MachineManagerPanel({ coordinates, disabled, onApply }: Props) { return
    setOpen(false)}> -
    {parts.length}/8 部件
    +
    {parts.length} 个导入模型 · 每个运动节点一个自由度
    - {open && update(id, { translationMm, rotationDeg })} onMotionState={receiveMotionState} />} + {open && }

    部件与层级

    {parts.length ?
      {parts.filter((part) => !part.parentId).map(renderPartTree)}
    :

    导入机床或部件后显示 GLB 内部层级。

    }
    - {selected &&

    {selected.filename}

    {selectedTreeNode &&
    {selectedTreeNode.name}节点自身局部原点X 0.000 Y 0.000 Z 0.000装配位置(相对父节点,mm)X {selectedTransform!.translationMm[0].toFixed(3)} Y {selectedTransform!.translationMm[1].toFixed(3)} Z {selectedTransform!.translationMm[2].toFixed(3)}装配姿态(相对父节点,deg)RX {selectedTransform!.rotationDeg[0].toFixed(3)} RY {selectedTransform!.rotationDeg[1].toFixed(3)} RZ {selectedTransform!.rotationDeg[2].toFixed(3)}{selectedDriven && <>当前世界原点(mm)X {(selectedDriven.world[0] * 1000).toFixed(3)} Y {(selectedDriven.world[1] * 1000).toFixed(3)} Z {(selectedDriven.world[2] * 1000).toFixed(3)}}
    }
    {selectedNodePath === "scene" && vectorInput("导入文件装配原点", selected.originMm, (originMm) => update(selected.id, { originMm }), "mm", "machine-manager-origin")} - {selectedTransform && <>

    编辑对象:{selectedTreeNode?.name}。数值为相对父级的位置和 XYZ 欧拉角;旋转中心为本节点原点。

    {vectorInput("部件平移(相对父级位置)", selectedTransform.translationMm, (value) => updateSelectedTransform("translationMm", value), "mm", "machine-manager-translation")}{vectorInput("部件旋转(相对父级姿态)", selectedTransform.rotationDeg, (value) => updateSelectedTransform("rotationDeg", value), "deg", "machine-manager-rotation")}}
    } + {selected &&

    {selected.filename}

    {selectedTreeNode &&
    {selectedTreeNode.name}节点自身局部原点X 0.000 Y 0.000 Z 0.000装配位置(相对父节点,mm)X {selectedTransform!.translationMm[0].toFixed(3)} Y {selectedTransform!.translationMm[1].toFixed(3)} Z {selectedTransform!.translationMm[2].toFixed(3)}装配姿态(相对父节点,deg)RX {selectedTransform!.rotationDeg[0].toFixed(3)} RY {selectedTransform!.rotationDeg[1].toFixed(3)} RZ {selectedTransform!.rotationDeg[2].toFixed(3)}{selectedDriven && <>当前世界原点(mm)X {(selectedDriven.world[0] * 1000).toFixed(3)} Y {(selectedDriven.world[1] * 1000).toFixed(3)} Z {(selectedDriven.world[2] * 1000).toFixed(3)}}
    } + {selectedDof && <>} +

    当前节点:{selectedTreeNode?.name}。{selectedDof ? `仅允许 ${selectedDof.axis} ${isLinear(selectedDof.axis) ? "平移" : "旋转"},沿父级 ${selectedDof.direction.toUpperCase()} 方向;以导入装配姿态为零位。` : "固定节点随父级运动;请先配置唯一自由度。"}

    + +
    }
    -

    层级运动测试

    {(["x", "y", "z"] as const).map((axis) => poseInput(axis, "mm"))}{poseInput(coordinates === "XYZBC" ? "b" : "a", "deg")}{poseInput("c", "deg")}
    {JSON.stringify(testPose)}
    +
    {followLive ? liveReady ? `WASM tick ${visual.tick}` : "等待有效机床数据" : "手动验证(相对物理零点)"}

    层级运动测试

    {(["x", "y", "z"] as const).map((axis) => poseInput(axis, "mm"))}{poseInput(coordinates === "XYZBC" ? "b" : "a", "deg")}{poseInput("c", "deg")}
    {JSON.stringify(effectivePose)}

    机床标定

    {vectorInput("C 轴原点", cOriginMm, setCOriginMm, "mm")}{vectorInput(`${coordinates === "XYZBC" ? "B" : "A"} 到 C 轴偏置`, tiltOffsetMm, setTiltOffsetMm, "mm")}
    机床物理零点 (XYZ mm / ABC deg){["x", "y", "z", coordinates === "XYZBC" ? "b" : "a", "c"].map((axis) => )}
    -
    {message || (disabled ? "关机且程序空闲后可配置" : "配置写入运行模型和底座 userData")}
    +
    {message || validation.error || (disabled ? "关机且程序空闲后可配置" : "配置写入运行模型和底座 userData")}
    { const files = event.target.files ? [...event.target.files] : []; event.target.value = ""; void importFiles(files); }} />
    ; diff --git a/web/react/src/XyzacMachineView.tsx b/web/react/src/XyzacMachineView.tsx index 519a805..98d0937 100644 --- a/web/react/src/XyzacMachineView.tsx +++ b/web/react/src/XyzacMachineView.tsx @@ -26,11 +26,12 @@ export function XyzacMachineView({ importedMachine, visual, toolpath, view = "p" useEffect(() => { const element = host.current; if (!element) return; - const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, preserveDrawingBuffer: true }); + const managedJoints = Boolean(importedMachine?.scene.userData.linuxcncJointModel); + const renderer = new THREE.WebGLRenderer({ antialias: !managedJoints, alpha: false, preserveDrawingBuffer: !managedJoints }); renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, VIEWER.pixel_ratio_limit)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1; - renderer.shadowMap.enabled = true; + renderer.shadowMap.enabled = !managedJoints; renderer.shadowMap.type = THREE.PCFShadowMap; renderer.domElement.dataset.engine = "three.js r185 / xyzac-trt-vismach"; element.appendChild(renderer.domElement); @@ -151,6 +152,11 @@ export function XyzacMachineView({ importedMachine, visual, toolpath, view = "p" const render = () => { const state = visualRef.current, config = settings.current; assembly.update(state); + if (assembly.machine.userData.linuxcncJointState) { + element.dataset.machineJointState = JSON.stringify(assembly.machine.userData.linuxcncJointState); + element.dataset.machineJointTick = String(state.tick); + element.dataset.machineJointInputs = JSON.stringify(state.joints); + } const paths = (config.toolpath ?? state.toolpath) as Segment[]; pathGroup.position.copy(assembly.workOrigin.position); liveGroup.position.copy(assembly.workOrigin.position); diff --git a/web/react/src/imported-machine.ts b/web/react/src/imported-machine.ts index e089a41..0339245 100644 --- a/web/react/src/imported-machine.ts +++ b/web/react/src/imported-machine.ts @@ -1,4 +1,5 @@ import * as THREE from "three"; +import { bindJointModel, validateJointModel, type JointModel, type AxisValues } from "./machine-joints"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { inspectMachineGlb, parseMachineUrdf, type MachineStructure, type Vec3 } from "./machine-urdf"; import { WEB_SETTINGS } from "../../workspace/web-settings.mjs"; @@ -18,7 +19,14 @@ export async function loadImportedMachine(bytes: ArrayBuffer, filename: string): const gltf = await new GLTFLoader(manager).parseAsync(bytes, ""); const hash = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((n) => n.toString(16).padStart(2, "0")).join(""); gltf.scene.traverse((node) => { if ((node as THREE.Mesh).isMesh) { node.castShadow = true; node.receiveShadow = true; } }); - return { scene: gltf.scene, structure, hash, filename }; + const model = gltf.scene.userData.linuxcncJointModel as JointModel | undefined; + if (model) { + validateJointModel(model); if (model.coordinates !== structure.coordinates) throw new Error("关节 JSON 与 URDF 机型不一致"); + bindJointModel(gltf.scene, model); + } + const physicalZero = model?.calibration.physicalZero ?? gltf.scene.userData.linuxcncMachineGeometry?.physicalZero; + if (physicalZero && Object.values(physicalZero).some((value) => typeof value !== "number" || !Number.isFinite(value))) throw new Error("物理零点无效"); + return { scene: gltf.scene, structure, hash, filename, physicalZero }; } function validateImportedConfiguration(bundle: { entrypoint: string; files: Record }, structure: MachineStructure) { const config = readTrtConfiguration(bundle); @@ -68,11 +76,22 @@ export function updateImportedMachineGeometry(asset: ImportedMachine, bundle: { export function buildImportedMachine(asset: ImportedMachine) { const scene = asset.scene.clone(true); scene.userData.linuxcncMachine = { ...scene.userData.linuxcncMachine, urdf: asset.structure.urdf }; + const jointModel = scene.userData.linuxcncJointModel as JointModel | undefined; + const jointDriver = jointModel ? bindJointModel(scene, jointModel) : null; + const physicalZero = asset.physicalZero ?? jointModel?.calibration.physicalZero ?? scene.userData.linuxcncMachineGeometry?.physicalZero ?? {}; const managed = scene.userData.linuxcncMachineGeometry; if (managed?.schema === "linuxcnc.machine-manager/1") { const clean = (value: number) => Object.is(value, -0) ? 0 : value; const effective = { ...managed, cOriginMm: [asset.structure.offsets.x_rot_point, asset.structure.offsets.y_rot_point, asset.structure.offsets.z_rot_point].map(clean), tiltOffsetMm: [asset.structure.offsets.x_offset, asset.structure.offsets.y_offset, asset.structure.offsets.z_offset].map(clean), physicalZero: { ...(asset.physicalZero ?? managed.physicalZero) } }; scene.userData.linuxcncMachineGeometry = effective; + if (jointModel) { + const effectiveJoints: JointModel = { ...jointModel, calibration: { cOriginMm: effective.cOriginMm as Vec3, tiltOffsetMm: effective.tiltOffsetMm as Vec3, physicalZero: { ...physicalZero } } }; + scene.userData.linuxcncJointModel = effectiveJoints; + scene.traverse((node) => { + if (node.userData.linuxcncJointModel) node.userData.linuxcncJointModel = effectiveJoints; + if (node.userData.linuxcncMachineGeometry) node.userData.linuxcncMachineGeometry = effective; + }); + } const base = scene.getObjectByName(asset.structure.base); if (base) base.userData.linuxcncMachineGeometry = effective; } // Include fixed work-frame joints as well as moving pivots. The source GLB @@ -98,11 +117,12 @@ export function buildImportedMachine(asset: ImportedMachine) { const update = (state: Pick) => { for (const { joint, node: link, axis } of moving) { const coordinate = joint.coordinate.toLowerCase() as "x" | "y" | "z" | "a" | "b" | "c"; - const value = state.joints[asset.structure.coordinates.indexOf(joint.coordinate)] - Number(asset.physicalZero?.[coordinate] ?? 0); + const value = state.joints[asset.structure.coordinates.indexOf(joint.coordinate)] - Number(physicalZero[coordinate] ?? 0); link.position.fromArray(joint.origin); link.quaternion.identity(); if (joint.type === "prismatic") link.position.addScaledVector(axis, value / 1000); else link.quaternion.setFromAxisAngle(axis, value * Math.PI / 180); } + if (jointDriver) jointDriver.update(Object.fromEntries([...asset.structure.coordinates].map((axis, i) => [axis, state.joints[i]])) as AxisValues, physicalZero); const length = state.offsets.tool_offset / 1000; toolTip.position.set(0, 0, -length); cutter.visible = Math.abs(length) > 1e-9; cutter.scale.y = Math.abs(length); cutter.position.z = -length / 2; @@ -110,6 +130,7 @@ export function buildImportedMachine(asset: ImportedMachine) { // the master-tool difference along local C-Z, while physical pivots stay fixed. workOrigin.position.copy(originalWork); workOrigin.position.z -= length; machine.updateMatrixWorld(true); + if (jointDriver) machine.userData.linuxcncJointState = jointDriver.state(); }; return { machine, toolTip, workOrigin, joints: { rotate }, update, dispose: () => { cutter.geometry.dispose(); cutter.material.dispose(); } }; } diff --git a/web/react/src/machine-joints.ts b/web/react/src/machine-joints.ts new file mode 100644 index 0000000..35f9318 --- /dev/null +++ b/web/react/src/machine-joints.ts @@ -0,0 +1,88 @@ +import * as THREE from "three"; +import type { Vec3 } from "./machine-urdf"; + +export type CncAxis = "X" | "Y" | "Z" | "A" | "B" | "C"; +export type JointDof = { axis: CncAxis; direction: "x" | "y" | "z"; sign: 1 | -1 }; +export type AxisValues = Partial>; +export type JointZero = { positionMm: Vec3; quaternion: [number, number, number, number]; scale: Vec3 }; +export type JointRecord = { + id: string; parentId: string | null; name: string; role: string; + source: { file: string; hash: string; path: string }; + zero: JointZero; + joint: (JointDof & { dof: 1; type: "prismatic" | "revolute"; unit: "mm" | "deg"; frame: "parent" }) | null; +}; +export type JointModel = { + schema: "linuxcnc.joint-model/1"; + coordinates: "XYZAC" | "XYZBC"; + units: { length: "mm"; angle: "deg"; gltfLength: "m" }; + controller: { source: "linuxcnc-wasm.visual.joints"; axisOrder: CncAxis[] }; + calibration: { cOriginMm: Vec3; tiltOffsetMm: Vec3; physicalZero: Record }; + axisMap: Partial>; + nodes: JointRecord[]; +}; +export const JOINT_MODEL_SCHEMA = "linuxcnc.joint-model/1"; +export const isLinear = (axis: CncAxis) => "XYZ".includes(axis); +export const defaultDof = (axis: CncAxis): JointDof => ({ axis, direction: ({ X: "x", Y: "y", Z: "z", A: "x", B: "y", C: "z" } as const)[axis], sign: 1 }); +export const jointZero = (node: THREE.Object3D): JointZero => ({ positionMm: node.position.toArray().map((v) => v * 1000) as Vec3, quaternion: node.quaternion.toArray(), scale: node.scale.toArray() }); + +// Absolute evaluation from a stored zero pose: no incremental accumulation. +// Direction is expressed in the parent frame; a rotation leaves the pivot at p0. +export function driveJoint(node: THREE.Object3D, zero: JointZero, dof: JointDof | null, value: number, physicalZero = 0) { + if (dof && ![value, physicalZero].every(Number.isFinite)) throw new Error(`轴 ${dof.axis} 位置无效`); + node.position.fromArray(zero.positionMm).multiplyScalar(.001); + node.quaternion.fromArray(zero.quaternion); node.scale.fromArray(zero.scale); + if (dof) { + const direction = new THREE.Vector3(dof.direction === "x" ? 1 : 0, dof.direction === "y" ? 1 : 0, dof.direction === "z" ? 1 : 0); + const q = dof.sign * (value - physicalZero); + if (isLinear(dof.axis)) node.position.addScaledVector(direction, q / 1000); + else node.quaternion.premultiply(new THREE.Quaternion().setFromAxisAngle(direction, THREE.MathUtils.degToRad(q))); + } + node.updateMatrix(); +} + +export function validateJointModel(model: JointModel, complete = true) { + const fail = (message: string): never => { throw new Error(`关节配置无效:${message}`); }; + if (!model || model.schema !== JOINT_MODEL_SCHEMA || !["XYZAC", "XYZBC"].includes(model.coordinates)) fail("schema 或机型错误"); + if (model.units?.length !== "mm" || model.units?.angle !== "deg" || model.units?.gltfLength !== "m" || model.controller?.source !== "linuxcnc-wasm.visual.joints" || model.controller.axisOrder.join("") !== model.coordinates) fail("控制器来源、单位或轴顺序错误"); + if (!Array.isArray(model.nodes) || !model.nodes.length || model.nodes.length > 12000) fail("节点数量错误"); + const finite = (v: unknown, count: number) => Array.isArray(v) && v.length === count && v.every(Number.isFinite); + if (!finite(model.calibration?.cOriginMm, 3) || !finite(model.calibration?.tiltOffsetMm, 3) || !model.calibration?.physicalZero || ![...model.coordinates].every((a) => Number.isFinite(model.calibration.physicalZero[a.toLowerCase()]))) fail("标定参数错误"); + const nodes = new Map(), axes = new Map(); + for (const record of model.nodes) { + if (!record.id || nodes.has(record.id)) fail("节点 ID 缺失或重复"); + nodes.set(record.id, record); + if (!finite(record.zero?.positionMm, 3) || !finite(record.zero?.quaternion, 4) || !finite(record.zero?.scale, 3) || record.zero.scale.some((v) => v <= 0) || Math.abs(record.zero.quaternion.reduce((s, v) => s + v * v, 0) - 1) > 1e-5) fail(`${record.name} 零姿态错误`); + const joint = record.joint; + if (joint) { + if (![...model.coordinates].includes(joint.axis) || joint.dof !== 1 || !["x", "y", "z"].includes(joint.direction) || ![1, -1].includes(joint.sign) || joint.frame !== "parent" || joint.type !== (isLinear(joint.axis) ? "prismatic" : "revolute") || joint.unit !== (isLinear(joint.axis) ? "mm" : "deg")) fail(`${record.name} 单自由度错误`); + if (axes.has(joint.axis)) fail(`${joint.axis} 轴重复绑定`); + axes.set(joint.axis, record.id); + } + } + for (const record of model.nodes) { + const seen = new Set([record.id]); let parent = record.parentId; + while (parent !== null) { if (seen.has(parent) || !nodes.has(parent)) fail("父级不存在或层级循环"); seen.add(parent); parent = nodes.get(parent)!.parentId; } + } + if (!model.axisMap || Object.keys(model.axisMap).length !== axes.size) fail("轴索引错误"); + for (const [axis, id] of axes) if (model.axisMap[axis] !== id) fail(`${axis} 轴索引错误`); + if (complete && [...model.coordinates].some((axis) => !axes.has(axis as CncAxis))) fail(`请完整绑定 ${model.coordinates} 的五个关节`); + return model; +} + +export function bindJointModel(scene: THREE.Object3D, model: JointModel) { + validateJointModel(model); + const nodes = new Map(); + scene.traverse((node) => { const id = node.userData.linuxcncNodeId; if (id) { if (nodes.has(id)) throw new Error("关节模型节点 ID 重复"); nodes.set(id, node); } }); + const bindings = model.nodes.map((record) => { + const node = nodes.get(record.id); + if (!node || (node.parent?.userData.linuxcncNodeId ?? null) !== record.parentId) throw new Error(`关节节点 ${record.name} 与 JSON 层级不一致`); + return { node, record }; + }); + return { + update(values: AxisValues, physicalZero = model.calibration.physicalZero) { + for (const { record } of bindings) if (record.joint && (!Number.isFinite(values[record.joint.axis]) || !Number.isFinite(physicalZero[record.joint.axis.toLowerCase()] ?? 0))) throw new Error(`缺少 ${record.joint.axis} 轴实时位置或物理零点无效`); + for (const { node, record } of bindings) driveJoint(node, record.zero, record.joint, record.joint ? values[record.joint.axis]! : 0, record.joint ? Number(physicalZero[record.joint.axis.toLowerCase()] ?? 0) : 0); + }, + state: () => Object.fromEntries(bindings.filter(({ record }) => record.joint).map(({ node, record }) => [record.joint!.axis, { id: record.id, localMm: node.position.toArray().map((v) => v * 1000), quaternion: node.quaternion.toArray(), world: node.getWorldPosition(new THREE.Vector3()).toArray() }])), + }; +} diff --git a/web/react/src/machine-manager.ts b/web/react/src/machine-manager.ts index 034fa41..870dd92 100644 --- a/web/react/src/machine-manager.ts +++ b/web/react/src/machine-manager.ts @@ -1,10 +1,11 @@ import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { parseMachineUrdf, type MachineStructure, type Vec3 } from "./machine-urdf"; +import { bindJointModel, defaultDof, jointZero, validateJointModel, type CncAxis, type JointDof, type JointModel, type JointRecord } from "./machine-joints"; import type { ImportedMachine } from "./imported-machine"; export type MachinePartRole = "base" | "x" | "y" | "z" | "tool" | "tilt" | "c" | "work"; -export type MachinePartSource = { id: string; filename: string; hash: string; scene: THREE.Group }; +export type MachinePartSource = { id: string; filename: string; hash: string; scene: THREE.Group; restoredJoints?: Record; restoredRoles?: Record; restoredModel?: JointModel }; export type MachineNodeTransform = { translationMm: Vec3; rotationDeg: Vec3 }; export type MachinePartDraft = MachinePartSource & { role: MachinePartRole | ""; @@ -17,6 +18,7 @@ export type MachinePartDraft = MachinePartSource & { nodeParents: Record; nodeRoles: Record; nodeTransforms: Record; + nodeJoints?: Record; }; export type MachineTreeNode = { path: string; name: string; type: "group" | "mesh"; originMm: Vec3; worldOriginMm: Vec3; rotationDeg: Vec3; children: MachineTreeNode[] }; export type PhysicalZero = { x: number; y: number; z: number; a: number; b: number; c: number }; @@ -26,7 +28,7 @@ export type ManagedMachineGeometry = { cOriginMm: Vec3; tiltOffsetMm: Vec3; physicalZero: PhysicalZero; - parts: Array<{ id: string; filename: string; hash: string; role: MachinePartRole; parentId: string | null; originMm: Vec3; translationMm: Vec3; rotationDeg: Vec3; visible: boolean; nodeParents: Record; nodeRoles: Record; nodeTransforms: Record }>; + parts: Array<{ id: string; filename: string; hash: string; role: MachinePartRole | ""; parentId: string | null; originMm: Vec3; translationMm: Vec3; rotationDeg: Vec3; visible: boolean; nodeParents: Record; nodeRoles: Record; nodeTransforms: Record; nodeJoints?: Record }>; }; export const MACHINE_PART_ROLES: MachinePartRole[] = ["base", "x", "y", "z", "tool", "tilt", "c", "work"]; @@ -130,6 +132,21 @@ export async function loadMachinePart(bytes: ArrayBuffer, filename: string): Pro const gltf = await new GLTFLoader(manager).parseAsync(bytes, ""); const hash = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((number) => number.toString(16).padStart(2, "0")).join(""); gltf.scene.traverse((node) => { if ((node as THREE.Mesh).isMesh) { node.castShadow = true; node.receiveShadow = true; } }); + const model = gltf.scene.userData.linuxcncJointModel as JointModel | undefined; + if (model) { + bindJointModel(gltf.scene, model); + const assembly = gltf.scene.getObjectByProperty("name", "joint_model_assembly"); + if (!assembly?.userData.linuxcncVisualAssembly) throw new Error("缺少已保存的关节装配"); + const scene = assembly.clone(true) as THREE.Group; + const records = new Map(model.nodes.map((record) => [record.id, record])); + const restoredJoints: Record = {}, restoredRoles: Record = {}; + for (const [path, node] of scenePathMap(scene).nodes) { + const record = records.get(node.userData.linuxcncNodeId); + if (record?.joint) restoredJoints[path] = { axis: record.joint.axis, direction: record.joint.direction, sign: record.joint.sign }; + if (record?.role) restoredRoles[path] = record.role as MachinePartRole; + } + return { id: crypto.randomUUID(), filename, hash, scene, restoredJoints, restoredRoles, restoredModel: model }; + } return { id: crypto.randomUUID(), filename, hash, scene: gltf.scene }; } @@ -180,6 +197,7 @@ function machineUrdf(coordinates: "XYZAC" | "XYZBC", cOriginMm: Vec3, tiltOffset } export async function buildManagedMachine(input: { coordinates: "XYZAC" | "XYZBC"; parts: MachinePartDraft[]; cOriginMm: Vec3; tiltOffsetMm: Vec3; physicalZero: PhysicalZero }): Promise { + if (input.parts.some((part) => part.nodeJoints !== undefined)) return buildJointManagedMachine(input); const byRole = validateMachineAssembly(input.coordinates, input.parts); for (const [axis, value] of Object.entries(input.physicalZero)) if (!Number.isFinite(value)) throw new Error(`${axis.toUpperCase()} 物理零点无效`); const urdf = machineUrdf(input.coordinates, input.cOriginMm, input.tiltOffsetMm); @@ -208,3 +226,69 @@ export async function buildManagedMachine(input: { coordinates: "XYZAC" | "XYZBC const hash = [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(signature)))].map((number) => number.toString(16).padStart(2, "0")).join(""); return { scene, structure, hash, filename: `managed-${input.coordinates.toLowerCase()}.glb`, physicalZero: { ...input.physicalZero } }; } + +export function draftNodeDof(part: MachinePartDraft, path: string, coordinates: "XYZAC" | "XYZBC"): JointDof | null { + if (part.nodeJoints !== undefined) return part.nodeJoints[path] ?? null; + const role = path === "scene" ? part.role : part.nodeRoles?.[path]; + const axis = role === "tilt" ? coordinates[3] : role && ["x", "y", "z", "c"].includes(role) ? role.toUpperCase() : ""; + return axis ? defaultDof(axis as CncAxis) : null; +} + +type ManagedInput = { coordinates: "XYZAC" | "XYZBC"; parts: MachinePartDraft[]; cOriginMm: Vec3; tiltOffsetMm: Vec3; physicalZero: PhysicalZero }; + +export function makeJointAssembly(input: ManagedInput, deepClone = false) { + if (!input.parts.length) throw new Error("请先导入机床模型"); + const assembly = new THREE.Group(); assembly.name = "joint_model_assembly"; assembly.userData.linuxcncVisualAssembly = true; + const placements = new Map(), records: JointRecord[] = []; + for (const part of input.parts) { + if (placements.has(part.id)) throw new Error("部件 ID 重复"); + finite3(part.translationMm, "部件位置"); finite3(part.rotationDeg, "部件姿态"); finite3(part.originMm, "装配原点"); + const placement = new THREE.Group(); placement.name = `component_${part.id}`; placement.visible = part.visible; + placement.position.fromArray(part.translationMm).multiplyScalar(.001); placement.rotation.set(...part.rotationDeg.map(THREE.MathUtils.degToRad) as Vec3); + placements.set(part.id, placement); assembly.add(placement); + const { scene, nodes } = configureMachinePartScene(deepClone ? cloneMachinePartScene(part.scene) : part.scene.clone(true), part); + scene.position.add(new THREE.Vector3(...part.originMm).multiplyScalar(-.001)); placement.add(scene); + const addRecord = (node: THREE.Object3D, path: string, role: string, dof: JointDof | null) => { + const id = `${part.id}:${path}`; node.userData.linuxcncNodeId = id; + const joint: JointRecord["joint"] = dof ? { ...dof, dof: 1, type: "XYZ".includes(dof.axis) ? "prismatic" : "revolute", unit: "XYZ".includes(dof.axis) ? "mm" : "deg", frame: "parent" } : null; + node.userData.linuxcncJoint = joint; + records.push({ id, parentId: null, name: path === "scene" ? part.filename : node.name, role, source: { file: part.filename, hash: part.hash, path }, zero: jointZero(node), joint }); + }; + addRecord(placement, "scene", part.role, draftNodeDof(part, "scene", input.coordinates)); + for (const [path, node] of nodes) addRecord(node, path === "scene" ? "source-scene" : path, part.nodeRoles?.[path] ?? "", path === "scene" ? null : draftNodeDof(part, path, input.coordinates)); + } + for (const part of input.parts) if (part.parentId) { + const parent = placements.get(part.parentId); if (!parent) throw new Error("父部件不存在"); + let cursor: string | null = part.parentId; const seen = new Set([part.id]); + while (cursor) { if (seen.has(cursor)) throw new Error("部件层级循环"); seen.add(cursor); cursor = input.parts.find((p) => p.id === cursor)?.parentId ?? null; } + parent.add(placements.get(part.id)!); + } + const recordMap = new Map(records.map((record) => [record.id, record])); + assembly.traverse((node) => { const record = recordMap.get(node.userData.linuxcncNodeId); if (record) record.parentId = node.parent?.userData.linuxcncNodeId ?? null; }); + const model: JointModel = { schema: "linuxcnc.joint-model/1", coordinates: input.coordinates, units: { length: "mm", angle: "deg", gltfLength: "m" }, controller: { source: "linuxcnc-wasm.visual.joints", axisOrder: [...input.coordinates] as CncAxis[] }, calibration: { cOriginMm: [...input.cOriginMm], tiltOffsetMm: [...input.tiltOffsetMm], physicalZero: { ...input.physicalZero } }, nodes: records, axisMap: Object.fromEntries(records.filter((r) => r.joint).map((r) => [r.joint!.axis, r.id])) }; + validateJointModel(model, false); + return { assembly, model }; +} + +async function buildJointManagedMachine(input: ManagedInput): Promise { + // Validate using shallow transform clones before allocating deep runtime resources. + validateJointModel(makeJointAssembly(input).model); + const urdf = machineUrdf(input.coordinates, input.cOriginMm, input.tiltOffsetMm), structure = parseMachineUrdf(urdf); + const { assembly, model } = makeJointAssembly(input, true); + const scene = new THREE.Group(); scene.name = `Managed_${input.coordinates}`; + scene.userData.linuxcncMachine = { schema: "linuxcnc.machine/1", urdf }; + scene.userData.linuxcncJointModel = model; + const geometry: ManagedMachineGeometry = { schema: "linuxcnc.machine-manager/1", coordinates: input.coordinates, cOriginMm: [...input.cOriginMm], tiltOffsetMm: [...input.tiltOffsetMm], physicalZero: { ...input.physicalZero }, parts: input.parts.map(({ scene: _scene, restoredJoints: _j, restoredRoles: _r, restoredModel: _m, ...part }) => JSON.parse(JSON.stringify(part))) }; + scene.userData.linuxcncMachineGeometry = geometry; + const frame = new THREE.Group(); frame.name = "urdf_frame"; frame.rotation.x = -Math.PI / 2; scene.add(frame); + const links = Object.fromEntries(MACHINE_PART_ROLES.map((role) => { const node = new THREE.Group(); node.name = role; node.userData.link = role; return [role, node]; })) as Record; + frame.add(links.base); links.base.add(links.x, links.tilt, assembly); links.x.add(links.y); links.y.add(links.z); links.z.add(links.tool); links.tilt.add(links.c); links.c.add(links.work); + for (const joint of structure.joints) links[joint.child as MachinePartRole].position.fromArray(joint.origin); + // The existing URDF frames retain the controller's tool/path reference. Only + // the explicit JSON bindings drive the visual assembly, preventing double motion. + links.base.userData.linuxcncJointModel = model; links.base.userData.linuxcncMachineGeometry = geometry; + const bases = new Set(model.nodes.filter((n) => n.role === "base").map((n) => n.id)); + assembly.traverse((node) => { if (bases.has(node.userData.linuxcncNodeId)) { node.userData.linuxcncJointModel = model; node.userData.linuxcncMachineGeometry = geometry; } }); + const hash = [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify({ model, geometry }))))].map((v) => v.toString(16).padStart(2, "0")).join(""); + return { scene, structure, hash, filename: `managed-${input.coordinates.toLowerCase()}.glb`, physicalZero: { ...input.physicalZero } }; +}