From 95c7277e5588427149969efd92ec569a573c47b7 Mon Sep 17 00:00:00 2001 From: mie <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:08:37 +0300 Subject: [PATCH 01/19] feat: add ViewerElement.SourceId and document metres and Z up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ViewerElement.SourceId holds the element's id in its source format (a Revit UniqueId or an IFC GlobalId); the numeric Id stays the viewer key. The HTML page writes it to the META elements as "sourceId" only when set, the properties panel shows it as its own row (Source ID / ID источника) and the model tree search finds it. JSON round-trips it and leaves it out when null, so snapshots of documents without source ids are unchanged. ViewerDocument, ViewerMesh.Vertices, ViewerGeometry.Vertices and Transform3d now state that the model is in metres with Z up. --- CHANGELOG.md | 10 +++++++++ docs/public-api.md | 4 ++-- src/IOViewer/Exporters/ViewerPayloadWriter.cs | 1 + src/IOViewer/Model/MathTypes.cs | 1 + src/IOViewer/Model/ViewerDocument.cs | 5 +++++ src/IOViewer/Model/ViewerElement.cs | 8 +++++++ src/IOViewer/Model/ViewerGeometry.cs | 2 +- src/IOViewer/Model/ViewerMesh.cs | 1 + src/IOViewer/Viewer/dist/viewer.js | 5 +++-- src/IOViewer/Viewer/src/i18n.js | 2 ++ src/IOViewer/Viewer/src/ui/properties.js | 1 + src/IOViewer/Viewer/src/ui/tree.js | 4 ++-- src/IOViewer/Viewer/test/e2e/ui.spec.js | 14 +++++++++++++ src/IOViewer/Viewer/test/unit/tree.test.js | 8 ++++++- .../IOViewer.Tests/HtmlViewerExporterTests.cs | 21 +++++++++++++++++++ tests/IOViewer.Tests/JsonAndImportTests.cs | 16 ++++++++++++++ tests/fixtures/viewer/building.json | 1 + tests/fixtures/viewer/escaping.json | 1 + tests/fixtures/viewer/generate-fixtures.cs | 10 +++++++-- 19 files changed, 105 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b1c56..18cd239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ API between minor releases. ## [Unreleased] +### Added +- `ViewerElement.SourceId`: the element's id in its source format, such as a Revit UniqueId or an IFC GlobalId. The + HTML viewer shows it as its own row in the properties panel ("Source ID" / "ID источника") and the model tree search + finds it. It is written to the page's element list and to JSON only when set, so the element data of documents + without it is written as before. + +### Changed +- The documentation states the model's units and axes: all coordinates and lengths in a `ViewerDocument` are in + metres, with Z up. + ## [0.2.0] - 2026-11-12 ### Fixed diff --git a/docs/public-api.md b/docs/public-api.md index 720d85a..956b17e 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -6,8 +6,8 @@ ### Document & Model (`IOViewer.Model`) -- `ViewerDocument`: The root container holding elements, meshes, shared geometries, materials, textures, viewpoints, grids, section planes and box, clash tests, linked files, and 4D schedules. -- `ViewerElement`: Represents a BIM element (Revit element, IFC product, Navisworks node) with properties and associated mesh parts. +- `ViewerDocument`: The root container holding elements, meshes, shared geometries, materials, textures, viewpoints, grids, section planes and box, clash tests, linked files, and 4D schedules. All coordinates and lengths are in metres, with Z up; importers and adapters convert to this on the way in. +- `ViewerElement`: Represents a BIM element (Revit element, IFC product, Navisworks node) with properties and associated mesh parts. `SourceId` optionally holds the element's id in its source format (for example a Revit UniqueId or an IFC GlobalId); the viewer shows it in the properties panel and finds it in the model tree search. The numeric `Id` stays the key that meshes, 4D tasks and clashes use. - `ViewerMesh`: Triangulated 3D mesh geometry with vertex coordinates, triangle indices, normals, colors, and bounding box. A mesh can instead draw a shared `ViewerGeometry` (`GeometryId`) and place it with `Transform`. - `ViewerGeometry`: Triangle geometry stored once in `ViewerDocument.Geometries` and drawn by every mesh that references its `Id`. - `Transform3d`: Affine transform stored by columns (`XAxis`, `YAxis`, `ZAxis`, `Translation`), with `Identity`, `Translate`, `Apply` and `Then`. diff --git a/src/IOViewer/Exporters/ViewerPayloadWriter.cs b/src/IOViewer/Exporters/ViewerPayloadWriter.cs index dc0d946..6c2d305 100644 --- a/src/IOViewer/Exporters/ViewerPayloadWriter.cs +++ b/src/IOViewer/Exporters/ViewerPayloadWriter.cs @@ -539,6 +539,7 @@ public void WriteMeta(Stream output) w.WriteString("level", el.Level); if (string.IsNullOrEmpty(el.LinkName)) w.WriteNull("link"); else w.WriteString("link", el.LinkName); + if (!string.IsNullOrEmpty(el.SourceId)) w.WriteString("sourceId", el.SourceId); w.WriteEndObject(); FlushIfLarge(w); } diff --git a/src/IOViewer/Model/MathTypes.cs b/src/IOViewer/Model/MathTypes.cs index b2592d8..ec8a2fd 100644 --- a/src/IOViewer/Model/MathTypes.cs +++ b/src/IOViewer/Model/MathTypes.cs @@ -106,6 +106,7 @@ public BoundingBox3d Encapsulate(BoundingBox3d other) /// Affine 3D transform stored by columns: the result of is /// XAxis * p.X + YAxis * p.Y + ZAxis * p.Z + Translation. /// +/// The translation is in metres, in the document's Z-up coordinates. /// Image of the unit X vector (first matrix column). /// Image of the unit Y vector (second matrix column). /// Image of the unit Z vector (third matrix column). diff --git a/src/IOViewer/Model/ViewerDocument.cs b/src/IOViewer/Model/ViewerDocument.cs index 755b016..33b3bab 100644 --- a/src/IOViewer/Model/ViewerDocument.cs +++ b/src/IOViewer/Model/ViewerDocument.cs @@ -5,6 +5,11 @@ namespace IOViewer.Model; /// /// Root container representing a 3D BIM/CAD model scene graph ready for interactive 3D WebGL viewing. /// +/// +/// All coordinates and lengths in the document are in metres, in a right-handed system with Z up: vertices, +/// transforms, bounding boxes, cameras, grids, sections and clash points. Producers convert to this on the way in +/// (for example, converts glTF's Y-up). +/// public sealed class ViewerDocument { public string Title { get; set; } = "3D BIM Model"; diff --git a/src/IOViewer/Model/ViewerElement.cs b/src/IOViewer/Model/ViewerElement.cs index 18ed756..20985c0 100644 --- a/src/IOViewer/Model/ViewerElement.cs +++ b/src/IOViewer/Model/ViewerElement.cs @@ -14,6 +14,14 @@ public sealed class ViewerElement public string Level { get; set; } = string.Empty; public string? LinkName { get; set; } + /// + /// The element's id in its source format, e.g. a Revit UniqueId or an IFC GlobalId; null when there is none. + /// stays the key the viewer, meshes, 4D tasks and clashes use. + /// + // Left out of JSON when null, so snapshots of documents without source ids stay as they were. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? SourceId { get; set; } + /// /// Parameters grouped by property category (e.g. "Identity Data", "Dimensions", "Phasing"). /// diff --git a/src/IOViewer/Model/ViewerGeometry.cs b/src/IOViewer/Model/ViewerGeometry.cs index bc338e6..9693ef9 100644 --- a/src/IOViewer/Model/ViewerGeometry.cs +++ b/src/IOViewer/Model/ViewerGeometry.cs @@ -8,7 +8,7 @@ public sealed class ViewerGeometry /// Identifier referenced by . public int Id { get; set; } - /// Vertex positions in the geometry's local coordinates. + /// Vertex positions in the geometry's local coordinates, in metres with Z up. public IReadOnlyList Vertices { get; set; } = []; /// Triangle vertex indices (three per triangle). diff --git a/src/IOViewer/Model/ViewerMesh.cs b/src/IOViewer/Model/ViewerMesh.cs index 7b863a5..7e1e610 100644 --- a/src/IOViewer/Model/ViewerMesh.cs +++ b/src/IOViewer/Model/ViewerMesh.cs @@ -9,6 +9,7 @@ public sealed class ViewerMesh public string Name { get; set; } = string.Empty; public long? ElementId { get; set; } + /// Vertex positions in metres with Z up, placed by . public IReadOnlyList Vertices { get; set; } = []; public IReadOnlyList Indices { get; set; } = []; public IReadOnlyList? Normals { get; set; } diff --git a/src/IOViewer/Viewer/dist/viewer.js b/src/IOViewer/Viewer/dist/viewer.js index 2671878..a8d0685 100644 --- a/src/IOViewer/Viewer/dist/viewer.js +++ b/src/IOViewer/Viewer/dist/viewer.js @@ -4252,7 +4252,7 @@ transformedNormal = - transformedNormal; ${Cc}`);t=di(t,"#include ",`${Ic} vTint = state.rgb;`),t=di(t,"#include ","vec3 objectNormal = ioOct( normal.xy );"),t=di(t,"#include ",Tx),t=di(t,"#include ","vec3 transformed = slotPosition;"),t=di(t,"#include ",`#include ${Ex}`),i.vertexShader=t;let e=di(i.fragmentShader,"#include ",`#include -varying vec3 vTint;`);e=di(e,"#include ","diffuseColor.rgb *= vTint;"),i.fragmentShader=e}function Pc(i,t,e=!1){return i.userData.slotUniforms=t,i.onBeforeCompile=Ax,i.index0AttributeName="q",e&&(i.defines={IO_NO_HIDE_BRANCH:""}),i}var Nc=["materials","textures","white","wireframe"],Rx=[245,158,11],Cx=[235,235,237],dr=4294967295,Di=2048,Lc=256,Ix=16,Md=1024,Px=.1,Lx=8192,Dx=1.4;function Nx(i,t){let[e,n,s,r,o,a,l,c,h]=i.subarray(t*12,t*12+9);return e*(o*h-a*c)-r*(n*h-s*c)+l*(n*a-s*o)<0}function Ux(){this.array=null}function yn(i,t,e=!1,n=we){return new n(i,t,e).onUpload(Ux)}var fr=class extends RangeError{constructor(t,e){super(`${t} drawable instances exceed the limit of ${e}.`),this.name="ModelTooLargeError",this.count=t,this.limit=e}},Sd=(i,t)=>{for(let e=t*6;e{let t=i.getHex(Se);return[t>>16&255,t>>8&255,t&255]},pr=class{constructor(t,e={}){let{meta:n}=t,s=n.instances,r=s.count,o=t.view(s.geometry),a=t.view(s.material),l=t.view(s.matrix);this.root=new be,this.elementCount=n.elements.length,this._elements=n.elements,this._meta=n,this._mode="materials",this._opacity=1,this._picking=!1,this._textures=new Map,this._elementOf=t.view(s.element).slice(),this._bucketOf=new Uint32Array(r),this._visible=new Uint8Array(r).fill(1),this._layers=new Map,this._layerHides=new Uint8Array(r),this._highlight=new Uint8Array(r),this._highlighted=[],this._tint=new Map,this._boxes=new Float32Array(r*6),this._minPixels=0,this._viewHeight=1,this.visibleCost=0;let c=!!e.noHideBranch;this.slotUniforms={slotInfo:{value:null},slotState:{value:null}},this._buckets=this._createBuckets(n,a,c),this._indexElements();let h=new Ht,d=new se;for(let p=0;pthis._shown(e)?1:0)}instancesOf(t){return this._checkElement(t),this._elementInstances.subarray(this._elementStart[t],this._elementStart[t+1])}isVisible(t){this._checkElement(t);for(let e=this._elementStart[t];e=0&&t<=1))throw new RangeError(`Opacity must be within 0..1, got ${t}.`);this._opacity=t;for(let e of this._buckets)this._applyOpacity(e);this._changed("appearance")}setDetail(t,e=1){this._minPixels=t,this._viewHeight=e;for(let s of this._cut)this._showUnit(s);this._cut.length=0;let n=this._unitScale;!(t>0)||!n||(this._unitScale=null,this._cutInView=0,this._cutFrame=null,this._units.forEach((s,r)=>{!(n[r]>=0)||s.shown===0||(s.dirty&&this._span(s),!(this._slotRadius[s.slotBase+s.first]*n[r]>=t)&&(s.mesh.visible=!1,s.lines&&(s.lines.visible=!1),this._cut.push(s),this._cutInView+=Dc(s,s.first,s.last+1)))}))}get totalCost(){return this._totalCost}detailThreshold(t,e,n){if(this.visibleCost=this._totalCost,this._totalCost<=n)return 0;t.updateMatrixWorld();let s=nd(t,this._planes),r=wc(t,e,this._detailView),o=this._pixels.fill(-1),a=this._state.image.data,l=this._unitScale=new Float64Array(this._units.length).fill(-1),c=0;for(let h=0;h0?r[6]/p:1/0:r[6];l[h]=g;for(let _=d.slotBase;_o-a)}memory(){let t=this._units[this._chunkCount]?.slotBase??this._slotInstance.length;return{gpuBytes:this._mergedBytes+this._instancedBytes+this._lineBytes+this._stateBytes,mergedBytes:this._mergedBytes,instancedBytes:this._instancedBytes,stateBytes:this._stateBytes,lineBytes:this._lineBytes,chunks:this._chunkCount,cells:this._units.length-this._chunkCount,mergedInstances:t,instancedInstances:this._slotInstance.length-t,triangles:this._triangles}}setClipping(t,e){for(let n of this._buckets)for(let s of[n.material,n.wire])s.clippingPlanes=t.length>0?t:null,s.clipIntersection=e,s.needsUpdate=!0;this._changed("clipping")}_build(t,e,n,s,r){let o=e.length,a=this._meta,l=new Map,c=B=>{let U=l.get(B);if(!U){let H=a.geometries[B],Z=t.view(H.indices),rt=H.uvs?t.view(H.uvs):null;U=yd(ad(t,H),H.q??null,ld(t,H),Z,rt),l.set(B,U)}return U},h=new Uint8Array(o),d=new Float64Array(o*3),u=new Float32Array(o*3),p=new Uint32Array(o),g=new Float32Array(o),_=new Uint8Array(o),m=new Map,f=this._boxes;for(let B=0;BB-U).map(B=>{let U=m.get(B),H=c(e[U[0]]),Z=this._buckets[this._bucketOf[U[0]]].texture!==null,rt=[0,1,2].every(Ft=>(H.max[Ft]-H.min[Ft])/Xn<=65535),at=H.vertices*((rt?12:20)+(Z?8:0))+H.indices.length*(H.vertices>65535?4:2);return{k:U.length,bytes:at,members:U,textured:Z}}),S=s.mergedCap??Ac,b=s.minRepeats??Ix;if(R.reduce((B,U)=>B+U.k*U.bytes,0)>S){let B=this.visibleBox(),U=B.isEmpty()?1/0:B.min.distanceTo(B.max)*(s.cellExtent??Px);for(let H of R)H.k{if(y[U]){for(let H of B.leaves)E.push({shape:B,members:Rc(H,g)});return}for(let H of B.members){v[e[H]]++;let Z=this._bucketOf[H]*2+(h[H]===ws?1:0),rt=T.get(Z);rt||T.set(Z,rt=[]),rt.push(H)}});let P=[];for(let B of[...T.keys()].sort((U,H)=>U-H))for(let U of dd(u,p,T.get(B),s.chunkVertices??cd))P.push(Rc(U,g));let D=[...P,...E.map(B=>B.members)].reduce((B,U)=>B+U.length,0);if(D>r)throw new fr(D,r);let z=Math.max(1,Math.ceil(D/Di)),O=new Float32Array(Di*z*4);this._state=new ei(new Uint8Array(Di*z*4),Di,z),this._state.colorSpace=Se,this._state.onUpdate=()=>{this._fullPending=!1,this._pendingRanges=0},this._fullPending=!0,this._pendingRanges=0,this._dirty=new Uint32Array(Lc),this._dirtyCount=0;let I=new ei(O,Di,z,tn,We);I.needsUpdate=!0,I.onUpdate=()=>I.image.data=null,this.slotUniforms.slotInfo.value=I,this.slotUniforms.slotState.value=this._state,this._stateBytes=Di*z*20,this._slotOf=new Uint32Array(o).fill(dr),this._slotInstance=new Uint32Array(D),this._slotUnit=new Uint32Array(D),this._slotRadius=new Float32Array(D),this._slotCost=new Float32Array(D),this._pixels=new Float32Array(D),this._planes=new Float64Array(24),this._detailView=new Float64Array(8),this._frameView=new Float64Array(8),this._frameViewAt=NaN,this._units=[],this._cut=[],this._unitScale=null,this._cutInView=0,this._cutFrame=-1,this._mergedBytes=0,this._instancedBytes=0,this._lineBytes=0;let k={matrices:n,geometryOf:e,mirrored:_,kind:h,origins:d,source:c,textured:!1};this._lineSources=new Map;let X=0;for(let B of P){let U=this._buckets[this._bucketOf[B[0]]];k.textured=U.texture!==null;let H=vd(B,k);for(let Z=0;ZB+U,0);let Y=new Map;for(let{shape:B,members:U}of E){let H=U[0],Z=c(e[H]),rt=Y.get(B);rt||Y.set(B,rt=this._shape(Z,_[H]===1,B.textured));for(let at=0;atB+U,0)}_shape(t,e,n){let s=t.vertices,r=t.q?new Uint16Array(s*4):new Float32Array(s*4);for(let h=0;hn+g),1,!1,Hn)),a.setIndex(s.index);let l=wd(this._boxes,e),c=new Le(new C(l[0],l[1],l[2]),l[3]);a.boundingSphere=c;let h=new cs(a,t.material,0),d=new Float32Array(o*16);for(let p=0;pthis._range(u,p,_,h),h.visible=!1,this.root.add(h),u}_chunk(t,e,n,s,r,o){let a=new fs;a.instanceCount=1,a.setAttribute("q",yn(s.q,4)),a.setAttribute("normal",yn(s.o,2,!0)),s.uv&&a.setAttribute("uv",yn(s.uv,2)),a.setAttribute("slotBase",yn(new Float32Array([n]),1,!1,Hn)),a.setIndex(yn(s.index,1));let l=wd(this._boxes,e);a.boundingSphere=new Le(new C(l[0],l[1],l[2]),l[3]),this._mergedBytes+=s.q.byteLength+s.o.byteLength+(s.uv?.byteLength??0)+s.index.byteLength;let c=new _e(a,t.material),h={bucket:t,mesh:c,lines:null,slotBase:n,n:e.length,sphere:l,shown:0,first:0,last:-1,end:0,dirty:!1,cell:!1,starts:s.indexStart,vertices:s.q.length/4,f32:s.q instanceof Float32Array,members:e.slice(),lastFrame:-1/0};return c.onBeforeRender=(d,u,p)=>this._range(h,d,p,c),c.visible=!1,this.root.add(c),h}_buildLines(){if(!this._lineSources)return;let{mirrored:t,geometryOf:e}=this._lineMembers;for(let n of this._units){if(n.cell){let h=n.mesh.geometry.index;n.lines=new cs(n.mesh.geometry,n.bucket.wire,0),n.lines.instanceMatrix=n.mesh.instanceMatrix,n.lines.boundingSphere=n.mesh.boundingSphere,n.lines.onBeforeRender=(d,u,p)=>this._range(n,d,p,n.lines),this._lineBytes+=h.count*2*h.array.BYTES_PER_ELEMENT,this.root.add(n.lines);continue}let s=n.starts[n.n],r=Ra(n.vertices,s*2),o=0,a=0;for(let h of n.members){let d=this._lineSources.get(e[h]),u=t[h]===1;for(let p=0;pthis._range(n,h,u,n.lines),this.root.add(n.lines),n.members=null}this._lineSources=null,this._lineMembers=null}_range(t,e,n,s){t.dirty&&this._span(t);let r=t.last+1;if(this._minPixels>0&&r>t.first){let o=e.info.render.frame;o!==this._frameViewAt&&(wc(n,this._viewHeight,this._frameView),this._frameViewAt=o);let a=this._frameView,l=bd(a,t.sphere),c=a[7]?l>0?this._minPixels*l/a[6]:0:this._minPixels/a[6];r=Math.min(r,gd(this._slotRadius,t.slotBase+t.first,t.slotBase+r,c)-t.slotBase)}if(t.cell)s.count=Math.max(r,0);else{let o=s.isLineSegments?2:1,[a,l]=_d(t.starts,t.first,r);s.geometry.setDrawRange(a*o,l*o)}t.end=r,t.lastFrame=e.info.render.frame,this._cutFrame===null&&this._minPixels>0&&(this._cutFrame=t.lastFrame)}_span(t){let e=this._state.image.data,n=0;for(;n=n&&e[(t.slotBase+s)*4+3]===0;)s--;t.first=n0&&!e,t.lines&&(t.lines.visible=t.shown>0&&e)}_createBuckets(t,e,n){let s=new Map,r=[];for(let o=0;oMath.round(h*255)),opacity:c.opacity,texture:c.texture,material:Pc(new ds({side:Be}),this.slotUniforms,n),wire:Pc(new ds({side:Be,wireframe:!0}),this.slotUniforms,n)},this._applyOpacity(l),s.set(a,l),r.push(l)}this._bucketOf[o]=l.index}return r}_indexElements(){let t=new Uint32Array(this.elementCount+1);for(let s of this._elementOf)s>=0&&t[s+1]++;for(let s=0;s{s>=0&&(e[n[s]++]=r)}),this._elementStart=t,this._elementInstances=e}_texture(t){let e=this._textures.get(t);if(!e){let n=this._meta.textures[t],s=new Image;e=new Ue(s),e.colorSpace=Se,e.wrapS=e.wrapT=ns,e.repeat.set(1/n.sx,1/n.sy),s.onload=()=>{e.needsUpdate=!0,this._changed("appearance")},s.src=`data:${n.mime};base64,${n.data}`,this._textures.set(t,e)}return e}_applyOpacity(t){let e=t.opacity*this._opacity,n=e<1;for(let s of[t.material,t.wire])s.transparent!==n&&(s.needsUpdate=!0),s.opacity=e,s.transparent=n,s.depthWrite=!n}_colorOf(t){return this._highlight[t]?Rx:this._tint.get(t)??(this._mode==="white"?Cx:this._buckets[this._bucketOf[t]].bytes)}_applyColor(t){let e=this._slotOf[t];e!==dr&&(this._state.image.data.set(this._colorOf(t),e*4),this._markDirty(e))}_setInstanceVisible(t,e){this._visible[t]=e?1:0,this._draw(t)}_setLayerFlag(t,e,n){t[e]!==(n?1:0)&&(t[e]=n?1:0,this._layerHides[e]+=n?1:-1,this._draw(e))}_shown(t){return this._visible[t]===1&&this._layerHides[t]===0}_draw(t){let e=this._slotOf[t];if(e===dr)return;let n=this._shown(t)?255:0,s=this._state.image.data;if(s[e*4+3]===n)return;s[e*4+3]=n,this._markDirty(e);let r=this._units[this._slotUnit[e]];r.shown+=n?1:-1,r.dirty=!0,this._showUnit(r)}_markDirty(t){this._dirtyCountLc)e.clearUpdateRanges(),this._fullPending=!0;else{for(let n=0;n=this.elementCount)throw new RangeError(`Element index ${t} is out of range 0..${this.elementCount-1}.`)}_expandBox(t,e){if(!Sd(this._boxes,e))return;let n=this._boxes,s=e*6;t.min.set(Math.min(t.min.x,n[s]),Math.min(t.min.y,n[s+1]),Math.min(t.min.z,n[s+2])),t.max.set(Math.max(t.max.x,n[s+3]),Math.max(t.max.y,n[s+4]),Math.max(t.max.z,n[s+5]))}_changed(t){this._flush(),this.root.dispatchEvent({type:"change",detail:{kind:t}})}};function Dc(i,t,e){return e<=t?0:i.cell?(e-t)*i.triangles:(i.starts[e]-i.starts[t])/3}function bd(i,t){return(t[0]-i[0])*i[3]+(t[1]-i[1])*i[4]+(t[2]-i[2])*i[5]-t[3]}function wd(i,t){let e=[1/0,1/0,1/0],n=[-1/0,-1/0,-1/0];for(let s of t)for(let r=0;r<3;r++)e[r]=Math.min(e[r],i[s*6+r]),n[r]=Math.max(n[r],i[s*6+3+r]);return Float64Array.of((e[0]+n[0])/2,(e[1]+n[1])/2,(e[2]+n[2])/2,Math.hypot(n[0]-e[0],n[1]-e[1],n[2]-e[2])/2)}var Bx={ru:{elements:"\u042D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",noGeometry:"\u041D\u0435\u0442 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438",contextLost:"\u0412\u0438\u0434\u0435\u043E\u043A\u0430\u0440\u0442\u0430 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u0430 \u2014 \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443",noWebGL:"\u041D\u0443\u0436\u0435\u043D \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0441 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u043E\u0439 WebGL 2",badData:"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 \u043C\u043E\u0434\u0435\u043B\u0438",tooLarge:"\u041C\u043E\u0434\u0435\u043B\u044C \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0431\u043E\u043B\u044C\u0448\u0430\u044F \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A\u0430: \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u0432 \u0441 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0435\u0439 \u2014 {count}, \u043F\u0440\u0435\u0434\u0435\u043B \u2014 {limit}",selected:"\u0412\u044B\u0431\u0440\u0430\u043D\u043E",close:"\u0417\u0430\u043A\u0440\u044B\u0442\u044C",elementGroup:"\u042D\u043B\u0435\u043C\u0435\u043D\u0442",id:"Id",category:"\u041A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F",type:"\u0422\u0438\u043F",level:"\u0423\u0440\u043E\u0432\u0435\u043D\u044C",link:"\u0421\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 \u0444\u0430\u0439\u043B",panels:"\u041F\u0430\u043D\u0435\u043B\u0438","panel.model":"\u041C\u043E\u0434\u0435\u043B\u044C","panel.views":"\u0412\u0438\u0434\u044B","panel.sections":"\u0421\u0435\u0447\u0435\u043D\u0438\u044F","panel.clashes":"\u041A\u043E\u043B\u043B\u0438\u0437\u0438\u0438","panel.timeliner":"4D-\u0433\u0440\u0430\u0444\u0438\u043A","panel.links":"\u0421\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B","group.navigation":"\u041D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F","group.sections":"\u0421\u0435\u0447\u0435\u043D\u0438\u044F","group.measure":"\u0418\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044F","group.display":"\u041E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435","group.view":"\u0412\u0438\u0434","tool.orbit":"\u0412\u0440\u0430\u0449\u0435\u043D\u0438\u0435","tool.pan":"\u041F\u0430\u043D\u043E\u0440\u0430\u043C\u0430","tool.fit":"\u0412\u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0441\u0451","tool.projection":"\u041F\u0435\u0440\u0441\u043F\u0435\u043A\u0442\u0438\u0432\u0430 / \u043E\u0440\u0442\u043E\u0433\u0440\u0430\u0444\u0438\u044F","tool.plane":"\u041F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u044C \u0441\u0435\u0447\u0435\u043D\u0438\u044F","tool.box":"\u0411\u043E\u043A\u0441 \u0441\u0435\u0447\u0435\u043D\u0438\u044F","tool.resetSections":"\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0441\u0435\u0447\u0435\u043D\u0438\u044F","tool.distance":"\u0420\u0430\u0441\u0441\u0442\u043E\u044F\u043D\u0438\u0435","tool.displayMode":"\u0420\u0435\u0436\u0438\u043C \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F","tool.grids":"\u041E\u0441\u0438","tool.opacity":"\u041F\u0440\u043E\u0437\u0440\u0430\u0447\u043D\u043E\u0441\u0442\u044C","tool.fullscreen":"\u041F\u043E\u043B\u043D\u044B\u0439 \u044D\u043A\u0440\u0430\u043D","menu.properties":"\u0421\u0432\u043E\u0439\u0441\u0442\u0432\u0430","menu.hide":"\u0421\u043A\u0440\u044B\u0442\u044C","menu.isolate":"\u0418\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u0442\u044C","menu.showAll":"\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u0441\u0451","menu.zoomTo":"\u041F\u0440\u0438\u0431\u043B\u0438\u0437\u0438\u0442\u044C \u043A \u043E\u0431\u044A\u0435\u043A\u0442\u0443","menu.sectionBox":"\u0411\u043E\u043A\u0441 \u0441\u0435\u0447\u0435\u043D\u0438\u044F \u043F\u043E \u043E\u0431\u044A\u0435\u043A\u0442\u0443","menu.copyId":"\u041A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C Id","menu.fitAll":"\u0412\u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0441\u0451","menu.resetSections":"\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0441\u0435\u0447\u0435\u043D\u0438\u044F",searchPlaceholder:"\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0438\u043C\u0435\u043D\u0438, \u0442\u0438\u043F\u0443, Id\u2026",noCategory:"\u0411\u0435\u0437 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438",noMatches:"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",idCopied:"Id \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D",copyFailed:"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C Id",fullscreenUnavailable:"\u041F\u043E\u043B\u043D\u043E\u044D\u043A\u0440\u0430\u043D\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D","face.front":"\u0421\u043F\u0435\u0440\u0435\u0434\u0438","face.back":"\u0421\u0437\u0430\u0434\u0438","face.left":"\u0421\u043B\u0435\u0432\u0430","face.right":"\u0421\u043F\u0440\u0430\u0432\u0430","face.top":"\u0421\u0432\u0435\u0440\u0445\u0443","face.bottom":"\u0421\u043D\u0438\u0437\u0443","cube.title":"\u041A\u0443\u0431 \u0432\u0438\u0434\u0430","cube.up":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0432\u0435\u0440\u0445","cube.down":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u043D\u0438\u0437","cube.left":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u043B\u0435\u0432\u043E","cube.right":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u043F\u0440\u0430\u0432\u043E","cube.home":"\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0432\u0438\u0434","sections.mode":"\u0420\u0435\u0436\u0438\u043C \u0441\u0435\u0447\u0435\u043D\u0438\u044F","sections.planes":"\u041F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0438","sections.box":"\u0411\u043E\u043A\u0441","sections.off":"\u0412\u044B\u043A\u043B","sections.axis":"\u041E\u0441\u044C","sections.addPlane":"\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u044C","sections.noPlanes":"\u041F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0435\u0439 \u043D\u0435\u0442","sections.rotated":"\u043F\u043E\u0432\u0451\u0440\u043D\u0443\u0442\u0430","sections.position":"\u041F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0438","sections.flip":"\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u043E\u0440\u043E\u043D\u0443","sections.enabled":"\u0412\u043A\u043B","sections.delete":"\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u044C","sections.keepInside":"\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u043D\u0443\u0442\u0440\u0438","sections.cutInside":"\u0412\u044B\u0440\u0435\u0437\u0430\u0442\u044C \u0432\u043D\u0443\u0442\u0440\u0438","sections.fitSelection":"\u041F\u043E \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043D\u043E\u043C\u0443","sections.showGizmo":"\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043C\u0430\u043D\u0438\u043F\u0443\u043B\u044F\u0442\u043E\u0440","sections.maxPlanes":"\u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 6 \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0435\u0439","unit.m":"\u043C","display.materials":"\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B","display.textures":"\u0422\u0435\u043A\u0441\u0442\u0443\u0440\u044B","display.white":"\u0411\u0435\u043B\u044B\u0439","display.wireframe":"\u041A\u0430\u0440\u043A\u0430\u0441","opacity.label":"\u041D\u0435\u043F\u0440\u043E\u0437\u0440\u0430\u0447\u043D\u043E\u0441\u0442\u044C","opacity.value":"{value} %","views.tour":"\u0422\u0443\u0440 \u043F\u043E \u0432\u0438\u0434\u0430\u043C","views.stop":"\u041E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0442\u0443\u0440","clash.New":"\u041D\u043E\u0432\u0430\u044F","clash.Active":"\u0410\u043A\u0442\u0438\u0432\u043D\u0430\u044F","clash.Reviewed":"\u041F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u0430","clash.Approved":"\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0430","clash.Resolved":"\u0423\u0441\u0442\u0440\u0430\u043D\u0435\u043D\u0430","task.construct":"\u0421\u0442\u0440\u043E\u0438\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E","task.demolish":"\u0421\u043D\u043E\u0441","task.temporary":"\u0412\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0435","player.title":"4D-\u043F\u0440\u043E\u0438\u0433\u0440\u044B\u0432\u0430\u0442\u0435\u043B\u044C","player.play":"\u0412\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u0441\u0442\u0438","player.pause":"\u041F\u0430\u0443\u0437\u0430","player.date":"\u0414\u0430\u0442\u0430 \u0433\u0440\u0430\u0444\u0438\u043A\u0430","player.speed":"\u0421\u043A\u043E\u0440\u043E\u0441\u0442\u044C",truncated:"\u041F\u043E\u043A\u0430\u0437\u0430\u043D\u043E {meshes} \u0438\u0437 {total} \u043C\u0435\u0448\u0435\u0439: \u043C\u043E\u0434\u0435\u043B\u044C \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u0430 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u043E\u0439 MaxMeshVertices"},en:{elements:"Elements",noGeometry:"No geometry",contextLost:"The graphics context was lost \u2014 reload the page",noWebGL:"This page needs a browser with WebGL 2",badData:"The model data could not be read",tooLarge:"The model is too large for this viewer: {count} objects with geometry, the limit is {limit}",selected:"Selected",close:"Close",elementGroup:"Element",id:"Id",category:"Category",type:"Type",level:"Level",link:"Link",panels:"Panels","panel.model":"Model","panel.views":"Views","panel.sections":"Sections","panel.clashes":"Clashes","panel.timeliner":"4D schedule","panel.links":"Linked files","group.navigation":"Navigation","group.sections":"Sections","group.measure":"Measure","group.display":"Display","group.view":"View","tool.orbit":"Orbit","tool.pan":"Pan","tool.fit":"Fit all","tool.projection":"Perspective / orthographic","tool.plane":"Section plane","tool.box":"Section box","tool.resetSections":"Reset sections","tool.distance":"Distance","tool.displayMode":"Display mode","tool.grids":"Grids","tool.opacity":"Transparency","tool.fullscreen":"Full screen","menu.properties":"Properties","menu.hide":"Hide","menu.isolate":"Isolate","menu.showAll":"Show all","menu.zoomTo":"Zoom to object","menu.sectionBox":"Section box around object","menu.copyId":"Copy Id","menu.fitAll":"Fit all","menu.resetSections":"Reset sections",searchPlaceholder:"Search by name, type, Id\u2026",noCategory:"No category",noMatches:"No matches",idCopied:"Id copied",copyFailed:"Could not copy Id",fullscreenUnavailable:"Fullscreen is not available","face.front":"Front","face.back":"Back","face.left":"Left","face.right":"Right","face.top":"Top","face.bottom":"Bottom","cube.title":"View cube","cube.up":"Rotate up","cube.down":"Rotate down","cube.left":"Rotate left","cube.right":"Rotate right","cube.home":"Home view","sections.mode":"Section mode","sections.planes":"Planes","sections.box":"Box","sections.off":"Off","sections.axis":"Axis","sections.addPlane":"Add plane","sections.noPlanes":"No planes","sections.rotated":"rotated","sections.position":"Plane position","sections.flip":"Flip side","sections.enabled":"On","sections.delete":"Delete plane","sections.keepInside":"Show inside","sections.cutInside":"Cut inside","sections.fitSelection":"Fit to selection","sections.showGizmo":"Show gizmo","sections.maxPlanes":"At most 6 planes","unit.m":"m","display.materials":"Materials","display.textures":"Textures","display.white":"White","display.wireframe":"Wireframe","opacity.label":"Opacity","opacity.value":"{value}%","views.tour":"Play tour","views.stop":"Stop tour","clash.New":"New","clash.Active":"Active","clash.Reviewed":"Reviewed","clash.Approved":"Approved","clash.Resolved":"Resolved","task.construct":"Construct","task.demolish":"Demolish","task.temporary":"Temporary","player.title":"4D player","player.play":"Play","player.pause":"Pause","player.date":"Schedule date","player.speed":"Speed",truncated:"Showing {meshes} of {total} meshes: the model was truncated by the MaxMeshVertices setting"}},mr=(globalThis.navigator?.language??"").toLowerCase().startsWith("ru")?"ru":"en";function st(i,t=mr){let e=Bx[t][i];if(e===void 0)throw new Error(`Missing UI string '${i}'.`);return e}var Ed=new C(0,0,1),Ia=Math.PI/2-.01;function Ad(i,t,e=4){return Math.hypot(t.x-i.x,t.y-i.y)<=e}function Rd(i,t,e,n,s,r){let o=i.clone().sub(e),a=t.clone().sub(e),l=d=>(o.applyQuaternion(d),a.applyQuaternion(d));l(new Ne().setFromAxisAngle(Ed,-n*r));let c=a.clone().sub(o).normalize(),h=c.clone().cross(Ed);if(h.lengthSq()>1e-12){let d=Math.asin(Oe.clamp(c.z,-1,1)),u=Oe.clamp(s*r,d-Ia,d+Ia);l(new Ne().setFromAxisAngle(h.normalize(),-u))}return{position:o.add(e),target:a.add(e)}}function Cd(i,t,e,n){let s=r=>r.clone().sub(e).multiplyScalar(n).add(e);return{position:s(i),target:s(t)}}var Ox=.85,zx=100/3;function Id(i,t,e){let n=i*(t===1?zx:t===2?e:1);return Oe.clamp(Ox**(-n/100),.5,2)}var Uc=(i,t)=>t>=i?"window":"crossing";function Pd(i,t,e){let n=Td(i.position,i.target),s=Td(t.position,t.target),r=n.azimuth??s.azimuth??0,o=s.azimuth??r,a=Oe.euclideanModulo(o-r+Math.PI,2*Math.PI)-Math.PI,l=(_,m)=>_+(m-_)*e,c=r+a*e,h=Oe.clamp(l(n.elevation,s.elevation),-Ia,Ia),d=l(n.distance,s.distance),u=i.target.clone().lerp(t.target,e),p=Math.cos(h)*d;return{position:u.clone().add(new C(p*Math.cos(c),p*Math.sin(c),Math.sin(h)*d)),target:u}}function Td(i,t){let e=i.clone().sub(t),n=e.length(),s=Math.hypot(e.x,e.y);return{distance:n,elevation:n>1e-12?Math.asin(Oe.clamp(e.z/n,-1,1)):0,azimuth:s>1e-9*Math.max(n,1e-12)?Math.atan2(e.y,e.x):null}}var kx=.005,Vx=new C(1,-1,.8).normalize(),Fc=(i,t)=>new CustomEvent(i,{detail:t}),Pa=class extends EventTarget{constructor(t,e,{pick:n=()=>null,visibleBox:s=()=>new se}={}){super(),this.dom=e,this.pick=n,this.visibleBox=s,this.target=new C,this.leftButtonMode="orbit",this._perspective=t,this._orthographic=new In(-1,1,1,-1),this.camera=t,this._drag=null,this._wheelPick=null,this._animation=0,this._overlay=null,this._bounds=null,this._extraBounds=new Map,e.style.touchAction="none",e.addEventListener("pointerdown",r=>this._down(r)),e.addEventListener("pointermove",r=>this._move(r)),e.addEventListener("pointerup",r=>this._up(r)),e.addEventListener("pointercancel",()=>this._end()),e.addEventListener("lostpointercapture",r=>this._lostCapture(r)),window.addEventListener("blur",()=>this._end()),e.addEventListener("wheel",r=>this._wheel(r),{passive:!1}),e.addEventListener("contextmenu",r=>r.preventDefault())}setLeftButtonMode(t){if(t!=="orbit"&&t!=="pan")throw new RangeError(`Unknown left button mode: ${t}`);this.leftButtonMode=t}setProjection(t){if(t!=="perspective"&&t!=="orthographic")throw new RangeError(`Unknown projection: ${t}`);let e=t==="perspective"?this._perspective:this._orthographic,n=this.camera;if(e!==n){e.position.copy(n.position),e.quaternion.copy(n.quaternion),e.up.copy(n.up),n.parent?.add(e),n.parent?.remove(n);for(let s of[...n.children])e.add(s);this.camera=e,this.resize()}}resize(){let t=this.camera,e=this.dom.clientWidth/Math.max(this.dom.clientHeight,1),n=Math.max(t.position.distanceTo(this.target),1e-6);if(t.isPerspectiveCamera)t.aspect=e;else{let r=n*Math.tan(Oe.degToRad(this._perspective.fov/2));Object.assign(t,{top:r,bottom:-r,left:-r*e,right:r*e})}let s=this._rangeSphere();if(s){let r=t.position.distanceTo(s.center);t.far=(r+s.radius)*1.05,t.near=Math.max((r-s.radius)*.95,t.far/5e3)}t.updateProjectionMatrix(),this.dispatchEvent(new Event("change"))}setExtraBounds(t,e){e&&!e.isEmpty()?this._extraBounds.set(t,e.clone()):this._extraBounds.delete(t),this.resize()}_rangeSphere(){let t=this._visibleBounds()?.sphere??null;if(this._extraBounds.size===0)return t;let e=t?this._bounds.box.clone():new se;for(let n of this._extraBounds.values())e.union(n);return e.getBoundingSphere(new Le)}invalidateBounds(){this._bounds=null}_visibleBounds(){if(!this._bounds){let t=this.visibleBox();this._bounds={box:t,sphere:t.isEmpty()?null:t.getBoundingSphere(new Le)}}return this._bounds.sphere&&this._bounds}fitBox(t,e=!0){if(t.isEmpty())return;let n=t.getBoundingSphere(new Le),s=Math.max(n.radius,.001),r=Oe.degToRad(this._perspective.fov/2),o=this.dom.clientWidth/Math.max(this.dom.clientHeight,1),a=s/Math.sin(Math.min(r,Math.atan(Math.tan(r)*o))),l=this.camera.position.clone().sub(this.target);l.lengthSq()<1e-12&&l.copy(Vx);let c=l.normalize().multiplyScalar(a).add(n.center);e?this.animateTo({position:c,target:n.center}):this._set(c,n.center)}zoomToBox(t){this.fitBox(t,!0)}animateTo({position:t,target:e},n=400){this._stopAnimation();let s={position:this.camera.position.clone(),target:this.target.clone()},r={position:t??s.position,target:e??s.target},o=performance.now(),a=l=>{let c=Math.min((l-o)/n,1),h=Pd(s,r,c*c*(3-2*c));this._set(h.position,h.target),this._animation=c<1?requestAnimationFrame(a):0};this._animation=requestAnimationFrame(a)}get animating(){return this._animation!==0}_stopAnimation(){cancelAnimationFrame(this._animation),this._animation=0}_set(t,e){this.camera.position.copy(t),this.target.copy(e),this.camera.lookAt(e),this.resize()}_down(t){this._end(),this._stopAnimation(),this.dom.setPointerCapture(t.pointerId),t.preventDefault();let e=t.button===0&&t.shiftKey?"box":t.button===0&&this.leftButtonMode==="orbit"?"orbit":"pan",n={x:t.clientX,y:t.clientY},s=null;e==="orbit"&&(s=this.pick(n.x,n.y),s||(s=this._visibleBounds()?.sphere.center.clone()??this.target.clone())),this._drag={id:t.pointerId,button:t.button,mode:e,pivot:s,down:n,last:n,moved:!1}}_move(t){let e=this._drag;if(!e||t.pointerId!==e.id)return;if(t.buttons===0)return this._end();let n={x:t.clientX,y:t.clientY};if(!e.moved&&Ad(e.down,n))return;e.moved=!0;let s=n.x-e.last.x,r=n.y-e.last.y;if(e.last=n,e.mode==="box")this._drawRect(e.down,n);else if(e.mode==="orbit"){let o=Rd(this.camera.position,this.target,e.pivot,s,r,kx);this._set(o.position,o.target)}else this._pan(s,r)}_pan(t,e){let n=this.camera,s=Math.max(this.dom.clientHeight,1),r=n.isPerspectiveCamera?2*n.position.distanceTo(this.target)*Math.tan(Oe.degToRad(n.fov/2))/s:(n.top-n.bottom)/s,o=new C(1,0,0).applyQuaternion(n.quaternion),a=new C(0,1,0).applyQuaternion(n.quaternion),l=o.multiplyScalar(-t*r).addScaledVector(a,e*r);this._set(n.position.clone().add(l),this.target.clone().add(l))}_up(t){let e=this._drag;if(!e||t.pointerId!==e.id||t.button!==e.button)return;let n={x:t.clientX,y:t.clientY};if(this._end(),e.mode==="box"&&e.moved){let s={x:Math.min(e.down.x,n.x),y:Math.min(e.down.y,n.y),width:Math.abs(n.x-e.down.x),height:Math.abs(n.y-e.down.y)},r=Uc(e.down.x,n.x);this.dispatchEvent(Fc("boxselect",{rect:s,mode:r,ctrl:t.ctrlKey,shift:t.shiftKey}))}else!e.moved&&e.button===0?this.dispatchEvent(Fc("click",{x:n.x,y:n.y,button:0,ctrl:t.ctrlKey,shift:t.shiftKey})):!e.moved&&e.button===2&&this.dispatchEvent(Fc("contextmenu",{x:n.x,y:n.y}))}_lostCapture(t){this._drag&&t.buttons?this.dom.setPointerCapture(this._drag.id):this._end()}_end(){this._drag=null,this._overlay?.remove(),this._overlay=null}_drawRect(t,e){this._overlay||(this._overlay=document.createElement("div"),Object.assign(this._overlay.style,{position:"fixed",pointerEvents:"none",border:"1px solid #2563eb",background:"rgba(37,99,235,0.12)"}),(this.dom.parentElement??document.body).appendChild(this._overlay)),Object.assign(this._overlay.style,{borderStyle:Uc(t.x,e.x)==="window"?"solid":"dashed",left:`${Math.min(t.x,e.x)}px`,top:`${Math.min(t.y,e.y)}px`,width:`${Math.abs(e.x-t.x)}px`,height:`${Math.abs(e.y-t.y)}px`})}_wheel(t){if(t.preventDefault(),t.deltaY===0)return;this._stopAnimation(),this._wheelPick||(this._wheelPick={point:this.pick(t.clientX,t.clientY)},requestAnimationFrame(()=>this._wheelPick=null));let e=this._wheelPick.point??this.target,n=Cd(this.camera.position,this.target,e,Id(t.deltaY,t.deltaMode,this.dom.clientHeight));this._set(n.position,n.target)}};function nt(i,t,e){let n=document.createElement(i);return t&&(n.className=t),e!==void 0&&(n.textContent=e),n}function qe(i,t,e){let n=nt("button",i);return n.type="button",n.innerHTML=t,n.title=e,n.setAttribute("aria-label",e),n}function La(i){i.preventDefault(),i.stopPropagation()}function Da(i){La(i);let t=()=>{window.removeEventListener("pointerup",e,!0),setTimeout(()=>window.removeEventListener("click",La,!0))},e=n=>{La(n),t()};window.addEventListener("pointerup",e,!0),window.addEventListener("click",La,!0)}function Na(i,t,e,n){let s=t.clone().applyMatrix4(e.matrixWorldInverse).z<0,r=t.clone().project(e);i.hidden=!(s&&Math.abs(r.x)<=1&&Math.abs(r.y)<=1),!i.hidden&&(i.style.left=`${n.left+(r.x+1)/2*n.width}px`,i.style.top=`${n.top+(1-r.y)/2*n.height}px`)}var de=i=>``,Ld='',Zt={model:de(''),views:de(''),sections:de(''),clashes:de(''),timeliner:de(''),links:de(''),orbit:de(''),pan:de(''),fit:de(''),projection:de(Ld),plane:de(''),box:de(`${Ld}`),resetSections:de(''),distance:de(''),displayMode:de(''),grids:de(''),opacity:de(''),fullscreen:de(''),close:de(''),chevron:de(''),flip:de(''),remove:de(''),play:de(''),pause:de(''),home:de('')};var gr=new C(0,0,1),on={right:new C(1,0,0),left:new C(-1,0,0),back:new C(0,1,0),front:new C(0,-1,0),top:new C(0,0,1),bottom:new C(0,0,-1)},Hx={right:gr,left:gr,back:gr,front:gr,top:on.back,bottom:on.front},Ni=.7,Gx=Oe.degToRad(1),Dd=.01,Nd=96,Ua=1.75;function Ud(i){let t=i.split("-"),e=new C;for(let n of t){if(!Object.hasOwn(on,n))throw new RangeError(`Unknown view cube region: ${i}`);e.add(on[n])}if(new Set(t).size!==t.length||e.lengthSq()!==t.length)throw new RangeError(`Unknown view cube region: ${i}`);return e.normalize()}function Wx(i){let t=[];return Math.abs(i.z)>=Ni&&t.push(i.z>0?"top":"bottom"),Math.abs(i.y)>=Ni&&t.push(i.y>0?"back":"front"),Math.abs(i.x)>=Ni&&t.push(i.x>0?"right":"left"),t.join("-")}function Fd(i,t=Gx){let e=i.clone().normalize(),n=Math.cos(t);return Object.keys(on).find(s=>e.dot(on[s])>=n)??null}function Bd(i){let t=new C(i.x,i.y,0);if(t.lengthSq()<1e-24)return"front";t.normalize();let e=["front","right","back","left"],n=Math.max(...e.map(s=>t.dot(on[s])));return e.find(s=>t.dot(on[s])>=n-1e-9)}function Bc(i,t){return on[Bd(t)].clone().multiplyScalar(Math.sin(Dd)).addScaledVector(on[i],Math.cos(Dd))}function Xx(i,t){let e=Fd(i);if(!e)return null;let n=e==="top"||e==="bottom";if(t==="left"||t==="right")return(n?Bc(e,i):Ud(e)).applyAxisAngle(gr,t==="right"?Math.PI/2:-Math.PI/2);if(t!=="up"&&t!=="down")throw new RangeError(`Unknown arrow: ${t}`);if(!n)return Bc(t==="up"?"top":"bottom",i);let s=on[Bd(i)];return t==="up"==(e==="top")?s.clone().negate():s.clone()}function qx(i,t){let e=document.createElement("canvas");e.width=e.height=128;let n=e.getContext("2d");n.fillStyle="#2a2f38",n.fillRect(0,0,128,128),n.fillStyle="#cbd5e1",n.textAlign="center",n.textBaseline="middle",n.font=`600 26px ${t}`;let s=n.measureText(i).width;s>112&&(n.font=`600 ${Math.floor(2912/s)}px ${t}`),n.fillText(i,64,64);let r=new Ws(e);return r.colorSpace=Se,r}var Fa=class{constructor(t,e,n){this.controls=n,this.home={position:e.position.clone(),target:n.target.clone()},this.element=nt("div","ioviewer-viewcube"),this.canvas=nt("canvas","ioviewer-viewcube-canvas"),this.canvas.title=st("cube.title"),this.canvas.setAttribute("aria-label",st("cube.title")),this.element.append(this.canvas),this.error=null,this.renderer=this._createRenderer(),this.scene=new Mi,this.camera=new In(-Ua,Ua,Ua,-Ua,.1,10),this.faces=new be;let s=getComputedStyle(t).fontFamily;for(let[a,l]of Object.entries(on)){let c=st(`face.${a}`),h=new _e(new Gn(2,2),new cn({map:qx(c,s)})),d=Hx[a];h.quaternion.setFromRotationMatrix(new Ht().makeBasis(d.clone().cross(l),d,l)),h.position.copy(l),h.name=a,h.userData.label=c,this.faces.add(h)}let r=new gn(new Ti(new hn(2,2,2)),new sn({color:"#5b6575"}));this.highlight=new _e(new hn(1,1,1),new cn({color:"#2563eb",transparent:!0,opacity:.6})),this.highlight.visible=!1,this.scene.add(this.faces,r,this.highlight),this._raycaster=new Ai,this.hovered=null,this.canvas.addEventListener("pointermove",a=>this._hover(this.regionAtClient(a.clientX,a.clientY))),this.canvas.addEventListener("pointerleave",()=>this._hover(null)),this.canvas.addEventListener("click",a=>{let l=this.regionAtClient(a.clientX,a.clientY);if(!l)return;let c=l==="top"||l==="bottom";this._look(c?Bc(l,n.camera.position.clone().sub(n.target)):Ud(l))}),this.arrows=["up","down","left","right"].map(a=>{let l=qe("ioviewer-viewcube-arrow",Zt.chevron,st(`cube.${a}`));return l.dataset.arrow=a,l.addEventListener("click",()=>this._arrow(a)),l});let o=qe("ioviewer-viewcube-home",Zt.home,st("cube.home"));o.addEventListener("click",()=>n.animateTo({position:this.home.position.clone(),target:this.home.target.clone()})),this.element.append(...this.arrows,o),t.append(this.element),this.update()}_createRenderer(){this.canvas.addEventListener("webglcontextlost",e=>{e.stopImmediatePropagation(),this._fail(new Error("The view cube lost its WebGL context."))});let t=this.canvas.getContext("webgl2",{alpha:!0,antialias:!0});if(!t)return this._fail(new Error("WebGL 2 is not available for the view cube."));try{let e=new Ss({canvas:this.canvas,context:t,alpha:!0,antialias:!0});return e.setPixelRatio(window.devicePixelRatio),e.setSize(Nd,Nd),e}catch(e){return this._fail(e)}}_fail(t){return this.error=t,this.renderer=null,this.element.hidden=!0,null}_render(){if(this.renderer)try{this.renderer.render(this.scene,this.camera)}catch(t){this._fail(t)}}update(){if(!this.renderer)return;let t=this.controls.camera;this.camera.quaternion.copy(t.quaternion),this.camera.position.set(0,0,5).applyQuaternion(t.quaternion),this.camera.updateMatrixWorld();let e=Fd(t.position.clone().sub(this.controls.target))!==null;for(let n of this.arrows)n.hidden=!e;this._render()}regionAtClient(t,e){let n=this.canvas.getBoundingClientRect(),s=new zt((t-n.left)/n.width*2-1,1-(e-n.top)/n.height*2);this._raycaster.setFromCamera(s,this.camera);let r=this._raycaster.intersectObjects(this.faces.children)[0];return r?Wx(r.point):null}_hover(t){if(t!==this.hovered){if(this.hovered=t,this.highlight.visible=!!t,t){let e=t.split("-").reduce((a,l)=>a.add(on[l]),new C),n=a=>a===0?[-Ni,Ni]:a>0?[Ni,1.02]:[-1.02,-Ni],[s,r,o]=e.toArray().map(n);this.highlight.scale.set(s[1]-s[0],r[1]-r[0],o[1]-o[0]),this.highlight.position.set((s[0]+s[1])/2,(r[0]+r[1])/2,(o[0]+o[1])/2)}this._render()}}_arrow(t){let n=this.controls.camera.position.clone().sub(this.controls.target),s=Xx(n,t);s&&this._look(s)}_look(t){let{controls:e}=this,n=e.camera.position.distanceTo(e.target);e.animateTo({position:t.clone().multiplyScalar(n).add(e.target),target:e.target.clone()})}};var Yx=` +varying vec3 vTint;`);e=di(e,"#include ","diffuseColor.rgb *= vTint;"),i.fragmentShader=e}function Pc(i,t,e=!1){return i.userData.slotUniforms=t,i.onBeforeCompile=Ax,i.index0AttributeName="q",e&&(i.defines={IO_NO_HIDE_BRANCH:""}),i}var Nc=["materials","textures","white","wireframe"],Rx=[245,158,11],Cx=[235,235,237],dr=4294967295,Di=2048,Lc=256,Ix=16,Md=1024,Px=.1,Lx=8192,Dx=1.4;function Nx(i,t){let[e,n,s,r,o,a,l,c,h]=i.subarray(t*12,t*12+9);return e*(o*h-a*c)-r*(n*h-s*c)+l*(n*a-s*o)<0}function Ux(){this.array=null}function yn(i,t,e=!1,n=we){return new n(i,t,e).onUpload(Ux)}var fr=class extends RangeError{constructor(t,e){super(`${t} drawable instances exceed the limit of ${e}.`),this.name="ModelTooLargeError",this.count=t,this.limit=e}},Sd=(i,t)=>{for(let e=t*6;e{let t=i.getHex(Se);return[t>>16&255,t>>8&255,t&255]},pr=class{constructor(t,e={}){let{meta:n}=t,s=n.instances,r=s.count,o=t.view(s.geometry),a=t.view(s.material),l=t.view(s.matrix);this.root=new be,this.elementCount=n.elements.length,this._elements=n.elements,this._meta=n,this._mode="materials",this._opacity=1,this._picking=!1,this._textures=new Map,this._elementOf=t.view(s.element).slice(),this._bucketOf=new Uint32Array(r),this._visible=new Uint8Array(r).fill(1),this._layers=new Map,this._layerHides=new Uint8Array(r),this._highlight=new Uint8Array(r),this._highlighted=[],this._tint=new Map,this._boxes=new Float32Array(r*6),this._minPixels=0,this._viewHeight=1,this.visibleCost=0;let c=!!e.noHideBranch;this.slotUniforms={slotInfo:{value:null},slotState:{value:null}},this._buckets=this._createBuckets(n,a,c),this._indexElements();let h=new Ht,d=new se;for(let p=0;pthis._shown(e)?1:0)}instancesOf(t){return this._checkElement(t),this._elementInstances.subarray(this._elementStart[t],this._elementStart[t+1])}isVisible(t){this._checkElement(t);for(let e=this._elementStart[t];e=0&&t<=1))throw new RangeError(`Opacity must be within 0..1, got ${t}.`);this._opacity=t;for(let e of this._buckets)this._applyOpacity(e);this._changed("appearance")}setDetail(t,e=1){this._minPixels=t,this._viewHeight=e;for(let s of this._cut)this._showUnit(s);this._cut.length=0;let n=this._unitScale;!(t>0)||!n||(this._unitScale=null,this._cutInView=0,this._cutFrame=null,this._units.forEach((s,r)=>{!(n[r]>=0)||s.shown===0||(s.dirty&&this._span(s),!(this._slotRadius[s.slotBase+s.first]*n[r]>=t)&&(s.mesh.visible=!1,s.lines&&(s.lines.visible=!1),this._cut.push(s),this._cutInView+=Dc(s,s.first,s.last+1)))}))}get totalCost(){return this._totalCost}detailThreshold(t,e,n){if(this.visibleCost=this._totalCost,this._totalCost<=n)return 0;t.updateMatrixWorld();let s=nd(t,this._planes),r=wc(t,e,this._detailView),o=this._pixels.fill(-1),a=this._state.image.data,l=this._unitScale=new Float64Array(this._units.length).fill(-1),c=0;for(let h=0;h0?r[6]/p:1/0:r[6];l[h]=g;for(let _=d.slotBase;_o-a)}memory(){let t=this._units[this._chunkCount]?.slotBase??this._slotInstance.length;return{gpuBytes:this._mergedBytes+this._instancedBytes+this._lineBytes+this._stateBytes,mergedBytes:this._mergedBytes,instancedBytes:this._instancedBytes,stateBytes:this._stateBytes,lineBytes:this._lineBytes,chunks:this._chunkCount,cells:this._units.length-this._chunkCount,mergedInstances:t,instancedInstances:this._slotInstance.length-t,triangles:this._triangles}}setClipping(t,e){for(let n of this._buckets)for(let s of[n.material,n.wire])s.clippingPlanes=t.length>0?t:null,s.clipIntersection=e,s.needsUpdate=!0;this._changed("clipping")}_build(t,e,n,s,r){let o=e.length,a=this._meta,l=new Map,c=B=>{let U=l.get(B);if(!U){let H=a.geometries[B],Z=t.view(H.indices),rt=H.uvs?t.view(H.uvs):null;U=yd(ad(t,H),H.q??null,ld(t,H),Z,rt),l.set(B,U)}return U},h=new Uint8Array(o),d=new Float64Array(o*3),u=new Float32Array(o*3),p=new Uint32Array(o),g=new Float32Array(o),_=new Uint8Array(o),m=new Map,f=this._boxes;for(let B=0;BB-U).map(B=>{let U=m.get(B),H=c(e[U[0]]),Z=this._buckets[this._bucketOf[U[0]]].texture!==null,rt=[0,1,2].every(Ft=>(H.max[Ft]-H.min[Ft])/Xn<=65535),at=H.vertices*((rt?12:20)+(Z?8:0))+H.indices.length*(H.vertices>65535?4:2);return{k:U.length,bytes:at,members:U,textured:Z}}),S=s.mergedCap??Ac,b=s.minRepeats??Ix;if(R.reduce((B,U)=>B+U.k*U.bytes,0)>S){let B=this.visibleBox(),U=B.isEmpty()?1/0:B.min.distanceTo(B.max)*(s.cellExtent??Px);for(let H of R)H.k{if(y[U]){for(let H of B.leaves)E.push({shape:B,members:Rc(H,g)});return}for(let H of B.members){v[e[H]]++;let Z=this._bucketOf[H]*2+(h[H]===ws?1:0),rt=T.get(Z);rt||T.set(Z,rt=[]),rt.push(H)}});let P=[];for(let B of[...T.keys()].sort((U,H)=>U-H))for(let U of dd(u,p,T.get(B),s.chunkVertices??cd))P.push(Rc(U,g));let D=[...P,...E.map(B=>B.members)].reduce((B,U)=>B+U.length,0);if(D>r)throw new fr(D,r);let z=Math.max(1,Math.ceil(D/Di)),O=new Float32Array(Di*z*4);this._state=new ei(new Uint8Array(Di*z*4),Di,z),this._state.colorSpace=Se,this._state.onUpdate=()=>{this._fullPending=!1,this._pendingRanges=0},this._fullPending=!0,this._pendingRanges=0,this._dirty=new Uint32Array(Lc),this._dirtyCount=0;let I=new ei(O,Di,z,tn,We);I.needsUpdate=!0,I.onUpdate=()=>I.image.data=null,this.slotUniforms.slotInfo.value=I,this.slotUniforms.slotState.value=this._state,this._stateBytes=Di*z*20,this._slotOf=new Uint32Array(o).fill(dr),this._slotInstance=new Uint32Array(D),this._slotUnit=new Uint32Array(D),this._slotRadius=new Float32Array(D),this._slotCost=new Float32Array(D),this._pixels=new Float32Array(D),this._planes=new Float64Array(24),this._detailView=new Float64Array(8),this._frameView=new Float64Array(8),this._frameViewAt=NaN,this._units=[],this._cut=[],this._unitScale=null,this._cutInView=0,this._cutFrame=-1,this._mergedBytes=0,this._instancedBytes=0,this._lineBytes=0;let k={matrices:n,geometryOf:e,mirrored:_,kind:h,origins:d,source:c,textured:!1};this._lineSources=new Map;let X=0;for(let B of P){let U=this._buckets[this._bucketOf[B[0]]];k.textured=U.texture!==null;let H=vd(B,k);for(let Z=0;ZB+U,0);let Y=new Map;for(let{shape:B,members:U}of E){let H=U[0],Z=c(e[H]),rt=Y.get(B);rt||Y.set(B,rt=this._shape(Z,_[H]===1,B.textured));for(let at=0;atB+U,0)}_shape(t,e,n){let s=t.vertices,r=t.q?new Uint16Array(s*4):new Float32Array(s*4);for(let h=0;hn+g),1,!1,Hn)),a.setIndex(s.index);let l=wd(this._boxes,e),c=new Le(new C(l[0],l[1],l[2]),l[3]);a.boundingSphere=c;let h=new cs(a,t.material,0),d=new Float32Array(o*16);for(let p=0;pthis._range(u,p,_,h),h.visible=!1,this.root.add(h),u}_chunk(t,e,n,s,r,o){let a=new fs;a.instanceCount=1,a.setAttribute("q",yn(s.q,4)),a.setAttribute("normal",yn(s.o,2,!0)),s.uv&&a.setAttribute("uv",yn(s.uv,2)),a.setAttribute("slotBase",yn(new Float32Array([n]),1,!1,Hn)),a.setIndex(yn(s.index,1));let l=wd(this._boxes,e);a.boundingSphere=new Le(new C(l[0],l[1],l[2]),l[3]),this._mergedBytes+=s.q.byteLength+s.o.byteLength+(s.uv?.byteLength??0)+s.index.byteLength;let c=new _e(a,t.material),h={bucket:t,mesh:c,lines:null,slotBase:n,n:e.length,sphere:l,shown:0,first:0,last:-1,end:0,dirty:!1,cell:!1,starts:s.indexStart,vertices:s.q.length/4,f32:s.q instanceof Float32Array,members:e.slice(),lastFrame:-1/0};return c.onBeforeRender=(d,u,p)=>this._range(h,d,p,c),c.visible=!1,this.root.add(c),h}_buildLines(){if(!this._lineSources)return;let{mirrored:t,geometryOf:e}=this._lineMembers;for(let n of this._units){if(n.cell){let h=n.mesh.geometry.index;n.lines=new cs(n.mesh.geometry,n.bucket.wire,0),n.lines.instanceMatrix=n.mesh.instanceMatrix,n.lines.boundingSphere=n.mesh.boundingSphere,n.lines.onBeforeRender=(d,u,p)=>this._range(n,d,p,n.lines),this._lineBytes+=h.count*2*h.array.BYTES_PER_ELEMENT,this.root.add(n.lines);continue}let s=n.starts[n.n],r=Ra(n.vertices,s*2),o=0,a=0;for(let h of n.members){let d=this._lineSources.get(e[h]),u=t[h]===1;for(let p=0;pthis._range(n,h,u,n.lines),this.root.add(n.lines),n.members=null}this._lineSources=null,this._lineMembers=null}_range(t,e,n,s){t.dirty&&this._span(t);let r=t.last+1;if(this._minPixels>0&&r>t.first){let o=e.info.render.frame;o!==this._frameViewAt&&(wc(n,this._viewHeight,this._frameView),this._frameViewAt=o);let a=this._frameView,l=bd(a,t.sphere),c=a[7]?l>0?this._minPixels*l/a[6]:0:this._minPixels/a[6];r=Math.min(r,gd(this._slotRadius,t.slotBase+t.first,t.slotBase+r,c)-t.slotBase)}if(t.cell)s.count=Math.max(r,0);else{let o=s.isLineSegments?2:1,[a,l]=_d(t.starts,t.first,r);s.geometry.setDrawRange(a*o,l*o)}t.end=r,t.lastFrame=e.info.render.frame,this._cutFrame===null&&this._minPixels>0&&(this._cutFrame=t.lastFrame)}_span(t){let e=this._state.image.data,n=0;for(;n=n&&e[(t.slotBase+s)*4+3]===0;)s--;t.first=n0&&!e,t.lines&&(t.lines.visible=t.shown>0&&e)}_createBuckets(t,e,n){let s=new Map,r=[];for(let o=0;oMath.round(h*255)),opacity:c.opacity,texture:c.texture,material:Pc(new ds({side:Be}),this.slotUniforms,n),wire:Pc(new ds({side:Be,wireframe:!0}),this.slotUniforms,n)},this._applyOpacity(l),s.set(a,l),r.push(l)}this._bucketOf[o]=l.index}return r}_indexElements(){let t=new Uint32Array(this.elementCount+1);for(let s of this._elementOf)s>=0&&t[s+1]++;for(let s=0;s{s>=0&&(e[n[s]++]=r)}),this._elementStart=t,this._elementInstances=e}_texture(t){let e=this._textures.get(t);if(!e){let n=this._meta.textures[t],s=new Image;e=new Ue(s),e.colorSpace=Se,e.wrapS=e.wrapT=ns,e.repeat.set(1/n.sx,1/n.sy),s.onload=()=>{e.needsUpdate=!0,this._changed("appearance")},s.src=`data:${n.mime};base64,${n.data}`,this._textures.set(t,e)}return e}_applyOpacity(t){let e=t.opacity*this._opacity,n=e<1;for(let s of[t.material,t.wire])s.transparent!==n&&(s.needsUpdate=!0),s.opacity=e,s.transparent=n,s.depthWrite=!n}_colorOf(t){return this._highlight[t]?Rx:this._tint.get(t)??(this._mode==="white"?Cx:this._buckets[this._bucketOf[t]].bytes)}_applyColor(t){let e=this._slotOf[t];e!==dr&&(this._state.image.data.set(this._colorOf(t),e*4),this._markDirty(e))}_setInstanceVisible(t,e){this._visible[t]=e?1:0,this._draw(t)}_setLayerFlag(t,e,n){t[e]!==(n?1:0)&&(t[e]=n?1:0,this._layerHides[e]+=n?1:-1,this._draw(e))}_shown(t){return this._visible[t]===1&&this._layerHides[t]===0}_draw(t){let e=this._slotOf[t];if(e===dr)return;let n=this._shown(t)?255:0,s=this._state.image.data;if(s[e*4+3]===n)return;s[e*4+3]=n,this._markDirty(e);let r=this._units[this._slotUnit[e]];r.shown+=n?1:-1,r.dirty=!0,this._showUnit(r)}_markDirty(t){this._dirtyCountLc)e.clearUpdateRanges(),this._fullPending=!0;else{for(let n=0;n=this.elementCount)throw new RangeError(`Element index ${t} is out of range 0..${this.elementCount-1}.`)}_expandBox(t,e){if(!Sd(this._boxes,e))return;let n=this._boxes,s=e*6;t.min.set(Math.min(t.min.x,n[s]),Math.min(t.min.y,n[s+1]),Math.min(t.min.z,n[s+2])),t.max.set(Math.max(t.max.x,n[s+3]),Math.max(t.max.y,n[s+4]),Math.max(t.max.z,n[s+5]))}_changed(t){this._flush(),this.root.dispatchEvent({type:"change",detail:{kind:t}})}};function Dc(i,t,e){return e<=t?0:i.cell?(e-t)*i.triangles:(i.starts[e]-i.starts[t])/3}function bd(i,t){return(t[0]-i[0])*i[3]+(t[1]-i[1])*i[4]+(t[2]-i[2])*i[5]-t[3]}function wd(i,t){let e=[1/0,1/0,1/0],n=[-1/0,-1/0,-1/0];for(let s of t)for(let r=0;r<3;r++)e[r]=Math.min(e[r],i[s*6+r]),n[r]=Math.max(n[r],i[s*6+3+r]);return Float64Array.of((e[0]+n[0])/2,(e[1]+n[1])/2,(e[2]+n[2])/2,Math.hypot(n[0]-e[0],n[1]-e[1],n[2]-e[2])/2)}var Bx={ru:{elements:"\u042D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",noGeometry:"\u041D\u0435\u0442 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438",contextLost:"\u0412\u0438\u0434\u0435\u043E\u043A\u0430\u0440\u0442\u0430 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u0430 \u2014 \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443",noWebGL:"\u041D\u0443\u0436\u0435\u043D \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0441 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u043E\u0439 WebGL 2",badData:"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 \u043C\u043E\u0434\u0435\u043B\u0438",tooLarge:"\u041C\u043E\u0434\u0435\u043B\u044C \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0431\u043E\u043B\u044C\u0448\u0430\u044F \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A\u0430: \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u0432 \u0441 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0435\u0439 \u2014 {count}, \u043F\u0440\u0435\u0434\u0435\u043B \u2014 {limit}",selected:"\u0412\u044B\u0431\u0440\u0430\u043D\u043E",close:"\u0417\u0430\u043A\u0440\u044B\u0442\u044C",elementGroup:"\u042D\u043B\u0435\u043C\u0435\u043D\u0442",id:"Id",category:"\u041A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F",type:"\u0422\u0438\u043F",level:"\u0423\u0440\u043E\u0432\u0435\u043D\u044C",sourceId:"ID \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430",link:"\u0421\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 \u0444\u0430\u0439\u043B",panels:"\u041F\u0430\u043D\u0435\u043B\u0438","panel.model":"\u041C\u043E\u0434\u0435\u043B\u044C","panel.views":"\u0412\u0438\u0434\u044B","panel.sections":"\u0421\u0435\u0447\u0435\u043D\u0438\u044F","panel.clashes":"\u041A\u043E\u043B\u043B\u0438\u0437\u0438\u0438","panel.timeliner":"4D-\u0433\u0440\u0430\u0444\u0438\u043A","panel.links":"\u0421\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B","group.navigation":"\u041D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F","group.sections":"\u0421\u0435\u0447\u0435\u043D\u0438\u044F","group.measure":"\u0418\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044F","group.display":"\u041E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435","group.view":"\u0412\u0438\u0434","tool.orbit":"\u0412\u0440\u0430\u0449\u0435\u043D\u0438\u0435","tool.pan":"\u041F\u0430\u043D\u043E\u0440\u0430\u043C\u0430","tool.fit":"\u0412\u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0441\u0451","tool.projection":"\u041F\u0435\u0440\u0441\u043F\u0435\u043A\u0442\u0438\u0432\u0430 / \u043E\u0440\u0442\u043E\u0433\u0440\u0430\u0444\u0438\u044F","tool.plane":"\u041F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u044C \u0441\u0435\u0447\u0435\u043D\u0438\u044F","tool.box":"\u0411\u043E\u043A\u0441 \u0441\u0435\u0447\u0435\u043D\u0438\u044F","tool.resetSections":"\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0441\u0435\u0447\u0435\u043D\u0438\u044F","tool.distance":"\u0420\u0430\u0441\u0441\u0442\u043E\u044F\u043D\u0438\u0435","tool.displayMode":"\u0420\u0435\u0436\u0438\u043C \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F","tool.grids":"\u041E\u0441\u0438","tool.opacity":"\u041F\u0440\u043E\u0437\u0440\u0430\u0447\u043D\u043E\u0441\u0442\u044C","tool.fullscreen":"\u041F\u043E\u043B\u043D\u044B\u0439 \u044D\u043A\u0440\u0430\u043D","menu.properties":"\u0421\u0432\u043E\u0439\u0441\u0442\u0432\u0430","menu.hide":"\u0421\u043A\u0440\u044B\u0442\u044C","menu.isolate":"\u0418\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u0442\u044C","menu.showAll":"\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u0441\u0451","menu.zoomTo":"\u041F\u0440\u0438\u0431\u043B\u0438\u0437\u0438\u0442\u044C \u043A \u043E\u0431\u044A\u0435\u043A\u0442\u0443","menu.sectionBox":"\u0411\u043E\u043A\u0441 \u0441\u0435\u0447\u0435\u043D\u0438\u044F \u043F\u043E \u043E\u0431\u044A\u0435\u043A\u0442\u0443","menu.copyId":"\u041A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C Id","menu.fitAll":"\u0412\u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0441\u0451","menu.resetSections":"\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0441\u0435\u0447\u0435\u043D\u0438\u044F",searchPlaceholder:"\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0438\u043C\u0435\u043D\u0438, \u0442\u0438\u043F\u0443, Id\u2026",noCategory:"\u0411\u0435\u0437 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438",noMatches:"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",idCopied:"Id \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D",copyFailed:"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C Id",fullscreenUnavailable:"\u041F\u043E\u043B\u043D\u043E\u044D\u043A\u0440\u0430\u043D\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D","face.front":"\u0421\u043F\u0435\u0440\u0435\u0434\u0438","face.back":"\u0421\u0437\u0430\u0434\u0438","face.left":"\u0421\u043B\u0435\u0432\u0430","face.right":"\u0421\u043F\u0440\u0430\u0432\u0430","face.top":"\u0421\u0432\u0435\u0440\u0445\u0443","face.bottom":"\u0421\u043D\u0438\u0437\u0443","cube.title":"\u041A\u0443\u0431 \u0432\u0438\u0434\u0430","cube.up":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0432\u0435\u0440\u0445","cube.down":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u043D\u0438\u0437","cube.left":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u043B\u0435\u0432\u043E","cube.right":"\u041F\u043E\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u043F\u0440\u0430\u0432\u043E","cube.home":"\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0432\u0438\u0434","sections.mode":"\u0420\u0435\u0436\u0438\u043C \u0441\u0435\u0447\u0435\u043D\u0438\u044F","sections.planes":"\u041F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0438","sections.box":"\u0411\u043E\u043A\u0441","sections.off":"\u0412\u044B\u043A\u043B","sections.axis":"\u041E\u0441\u044C","sections.addPlane":"\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u044C","sections.noPlanes":"\u041F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0435\u0439 \u043D\u0435\u0442","sections.rotated":"\u043F\u043E\u0432\u0451\u0440\u043D\u0443\u0442\u0430","sections.position":"\u041F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0438","sections.flip":"\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u043E\u0440\u043E\u043D\u0443","sections.enabled":"\u0412\u043A\u043B","sections.delete":"\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u044C","sections.keepInside":"\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u043D\u0443\u0442\u0440\u0438","sections.cutInside":"\u0412\u044B\u0440\u0435\u0437\u0430\u0442\u044C \u0432\u043D\u0443\u0442\u0440\u0438","sections.fitSelection":"\u041F\u043E \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043D\u043E\u043C\u0443","sections.showGizmo":"\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043C\u0430\u043D\u0438\u043F\u0443\u043B\u044F\u0442\u043E\u0440","sections.maxPlanes":"\u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 6 \u043F\u043B\u043E\u0441\u043A\u043E\u0441\u0442\u0435\u0439","unit.m":"\u043C","display.materials":"\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B","display.textures":"\u0422\u0435\u043A\u0441\u0442\u0443\u0440\u044B","display.white":"\u0411\u0435\u043B\u044B\u0439","display.wireframe":"\u041A\u0430\u0440\u043A\u0430\u0441","opacity.label":"\u041D\u0435\u043F\u0440\u043E\u0437\u0440\u0430\u0447\u043D\u043E\u0441\u0442\u044C","opacity.value":"{value} %","views.tour":"\u0422\u0443\u0440 \u043F\u043E \u0432\u0438\u0434\u0430\u043C","views.stop":"\u041E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0442\u0443\u0440","clash.New":"\u041D\u043E\u0432\u0430\u044F","clash.Active":"\u0410\u043A\u0442\u0438\u0432\u043D\u0430\u044F","clash.Reviewed":"\u041F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u0430","clash.Approved":"\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0430","clash.Resolved":"\u0423\u0441\u0442\u0440\u0430\u043D\u0435\u043D\u0430","task.construct":"\u0421\u0442\u0440\u043E\u0438\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E","task.demolish":"\u0421\u043D\u043E\u0441","task.temporary":"\u0412\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0435","player.title":"4D-\u043F\u0440\u043E\u0438\u0433\u0440\u044B\u0432\u0430\u0442\u0435\u043B\u044C","player.play":"\u0412\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u0441\u0442\u0438","player.pause":"\u041F\u0430\u0443\u0437\u0430","player.date":"\u0414\u0430\u0442\u0430 \u0433\u0440\u0430\u0444\u0438\u043A\u0430","player.speed":"\u0421\u043A\u043E\u0440\u043E\u0441\u0442\u044C",truncated:"\u041F\u043E\u043A\u0430\u0437\u0430\u043D\u043E {meshes} \u0438\u0437 {total} \u043C\u0435\u0448\u0435\u0439: \u043C\u043E\u0434\u0435\u043B\u044C \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u0430 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u043E\u0439 MaxMeshVertices"},en:{elements:"Elements",noGeometry:"No geometry",contextLost:"The graphics context was lost \u2014 reload the page",noWebGL:"This page needs a browser with WebGL 2",badData:"The model data could not be read",tooLarge:"The model is too large for this viewer: {count} objects with geometry, the limit is {limit}",selected:"Selected",close:"Close",elementGroup:"Element",id:"Id",category:"Category",type:"Type",level:"Level",sourceId:"Source ID",link:"Link",panels:"Panels","panel.model":"Model","panel.views":"Views","panel.sections":"Sections","panel.clashes":"Clashes","panel.timeliner":"4D schedule","panel.links":"Linked files","group.navigation":"Navigation","group.sections":"Sections","group.measure":"Measure","group.display":"Display","group.view":"View","tool.orbit":"Orbit","tool.pan":"Pan","tool.fit":"Fit all","tool.projection":"Perspective / orthographic","tool.plane":"Section plane","tool.box":"Section box","tool.resetSections":"Reset sections","tool.distance":"Distance","tool.displayMode":"Display mode","tool.grids":"Grids","tool.opacity":"Transparency","tool.fullscreen":"Full screen","menu.properties":"Properties","menu.hide":"Hide","menu.isolate":"Isolate","menu.showAll":"Show all","menu.zoomTo":"Zoom to object","menu.sectionBox":"Section box around object","menu.copyId":"Copy Id","menu.fitAll":"Fit all","menu.resetSections":"Reset sections",searchPlaceholder:"Search by name, type, Id\u2026",noCategory:"No category",noMatches:"No matches",idCopied:"Id copied",copyFailed:"Could not copy Id",fullscreenUnavailable:"Fullscreen is not available","face.front":"Front","face.back":"Back","face.left":"Left","face.right":"Right","face.top":"Top","face.bottom":"Bottom","cube.title":"View cube","cube.up":"Rotate up","cube.down":"Rotate down","cube.left":"Rotate left","cube.right":"Rotate right","cube.home":"Home view","sections.mode":"Section mode","sections.planes":"Planes","sections.box":"Box","sections.off":"Off","sections.axis":"Axis","sections.addPlane":"Add plane","sections.noPlanes":"No planes","sections.rotated":"rotated","sections.position":"Plane position","sections.flip":"Flip side","sections.enabled":"On","sections.delete":"Delete plane","sections.keepInside":"Show inside","sections.cutInside":"Cut inside","sections.fitSelection":"Fit to selection","sections.showGizmo":"Show gizmo","sections.maxPlanes":"At most 6 planes","unit.m":"m","display.materials":"Materials","display.textures":"Textures","display.white":"White","display.wireframe":"Wireframe","opacity.label":"Opacity","opacity.value":"{value}%","views.tour":"Play tour","views.stop":"Stop tour","clash.New":"New","clash.Active":"Active","clash.Reviewed":"Reviewed","clash.Approved":"Approved","clash.Resolved":"Resolved","task.construct":"Construct","task.demolish":"Demolish","task.temporary":"Temporary","player.title":"4D player","player.play":"Play","player.pause":"Pause","player.date":"Schedule date","player.speed":"Speed",truncated:"Showing {meshes} of {total} meshes: the model was truncated by the MaxMeshVertices setting"}},mr=(globalThis.navigator?.language??"").toLowerCase().startsWith("ru")?"ru":"en";function st(i,t=mr){let e=Bx[t][i];if(e===void 0)throw new Error(`Missing UI string '${i}'.`);return e}var Ed=new C(0,0,1),Ia=Math.PI/2-.01;function Ad(i,t,e=4){return Math.hypot(t.x-i.x,t.y-i.y)<=e}function Rd(i,t,e,n,s,r){let o=i.clone().sub(e),a=t.clone().sub(e),l=d=>(o.applyQuaternion(d),a.applyQuaternion(d));l(new Ne().setFromAxisAngle(Ed,-n*r));let c=a.clone().sub(o).normalize(),h=c.clone().cross(Ed);if(h.lengthSq()>1e-12){let d=Math.asin(Oe.clamp(c.z,-1,1)),u=Oe.clamp(s*r,d-Ia,d+Ia);l(new Ne().setFromAxisAngle(h.normalize(),-u))}return{position:o.add(e),target:a.add(e)}}function Cd(i,t,e,n){let s=r=>r.clone().sub(e).multiplyScalar(n).add(e);return{position:s(i),target:s(t)}}var Ox=.85,zx=100/3;function Id(i,t,e){let n=i*(t===1?zx:t===2?e:1);return Oe.clamp(Ox**(-n/100),.5,2)}var Uc=(i,t)=>t>=i?"window":"crossing";function Pd(i,t,e){let n=Td(i.position,i.target),s=Td(t.position,t.target),r=n.azimuth??s.azimuth??0,o=s.azimuth??r,a=Oe.euclideanModulo(o-r+Math.PI,2*Math.PI)-Math.PI,l=(_,m)=>_+(m-_)*e,c=r+a*e,h=Oe.clamp(l(n.elevation,s.elevation),-Ia,Ia),d=l(n.distance,s.distance),u=i.target.clone().lerp(t.target,e),p=Math.cos(h)*d;return{position:u.clone().add(new C(p*Math.cos(c),p*Math.sin(c),Math.sin(h)*d)),target:u}}function Td(i,t){let e=i.clone().sub(t),n=e.length(),s=Math.hypot(e.x,e.y);return{distance:n,elevation:n>1e-12?Math.asin(Oe.clamp(e.z/n,-1,1)):0,azimuth:s>1e-9*Math.max(n,1e-12)?Math.atan2(e.y,e.x):null}}var kx=.005,Vx=new C(1,-1,.8).normalize(),Fc=(i,t)=>new CustomEvent(i,{detail:t}),Pa=class extends EventTarget{constructor(t,e,{pick:n=()=>null,visibleBox:s=()=>new se}={}){super(),this.dom=e,this.pick=n,this.visibleBox=s,this.target=new C,this.leftButtonMode="orbit",this._perspective=t,this._orthographic=new In(-1,1,1,-1),this.camera=t,this._drag=null,this._wheelPick=null,this._animation=0,this._overlay=null,this._bounds=null,this._extraBounds=new Map,e.style.touchAction="none",e.addEventListener("pointerdown",r=>this._down(r)),e.addEventListener("pointermove",r=>this._move(r)),e.addEventListener("pointerup",r=>this._up(r)),e.addEventListener("pointercancel",()=>this._end()),e.addEventListener("lostpointercapture",r=>this._lostCapture(r)),window.addEventListener("blur",()=>this._end()),e.addEventListener("wheel",r=>this._wheel(r),{passive:!1}),e.addEventListener("contextmenu",r=>r.preventDefault())}setLeftButtonMode(t){if(t!=="orbit"&&t!=="pan")throw new RangeError(`Unknown left button mode: ${t}`);this.leftButtonMode=t}setProjection(t){if(t!=="perspective"&&t!=="orthographic")throw new RangeError(`Unknown projection: ${t}`);let e=t==="perspective"?this._perspective:this._orthographic,n=this.camera;if(e!==n){e.position.copy(n.position),e.quaternion.copy(n.quaternion),e.up.copy(n.up),n.parent?.add(e),n.parent?.remove(n);for(let s of[...n.children])e.add(s);this.camera=e,this.resize()}}resize(){let t=this.camera,e=this.dom.clientWidth/Math.max(this.dom.clientHeight,1),n=Math.max(t.position.distanceTo(this.target),1e-6);if(t.isPerspectiveCamera)t.aspect=e;else{let r=n*Math.tan(Oe.degToRad(this._perspective.fov/2));Object.assign(t,{top:r,bottom:-r,left:-r*e,right:r*e})}let s=this._rangeSphere();if(s){let r=t.position.distanceTo(s.center);t.far=(r+s.radius)*1.05,t.near=Math.max((r-s.radius)*.95,t.far/5e3)}t.updateProjectionMatrix(),this.dispatchEvent(new Event("change"))}setExtraBounds(t,e){e&&!e.isEmpty()?this._extraBounds.set(t,e.clone()):this._extraBounds.delete(t),this.resize()}_rangeSphere(){let t=this._visibleBounds()?.sphere??null;if(this._extraBounds.size===0)return t;let e=t?this._bounds.box.clone():new se;for(let n of this._extraBounds.values())e.union(n);return e.getBoundingSphere(new Le)}invalidateBounds(){this._bounds=null}_visibleBounds(){if(!this._bounds){let t=this.visibleBox();this._bounds={box:t,sphere:t.isEmpty()?null:t.getBoundingSphere(new Le)}}return this._bounds.sphere&&this._bounds}fitBox(t,e=!0){if(t.isEmpty())return;let n=t.getBoundingSphere(new Le),s=Math.max(n.radius,.001),r=Oe.degToRad(this._perspective.fov/2),o=this.dom.clientWidth/Math.max(this.dom.clientHeight,1),a=s/Math.sin(Math.min(r,Math.atan(Math.tan(r)*o))),l=this.camera.position.clone().sub(this.target);l.lengthSq()<1e-12&&l.copy(Vx);let c=l.normalize().multiplyScalar(a).add(n.center);e?this.animateTo({position:c,target:n.center}):this._set(c,n.center)}zoomToBox(t){this.fitBox(t,!0)}animateTo({position:t,target:e},n=400){this._stopAnimation();let s={position:this.camera.position.clone(),target:this.target.clone()},r={position:t??s.position,target:e??s.target},o=performance.now(),a=l=>{let c=Math.min((l-o)/n,1),h=Pd(s,r,c*c*(3-2*c));this._set(h.position,h.target),this._animation=c<1?requestAnimationFrame(a):0};this._animation=requestAnimationFrame(a)}get animating(){return this._animation!==0}_stopAnimation(){cancelAnimationFrame(this._animation),this._animation=0}_set(t,e){this.camera.position.copy(t),this.target.copy(e),this.camera.lookAt(e),this.resize()}_down(t){this._end(),this._stopAnimation(),this.dom.setPointerCapture(t.pointerId),t.preventDefault();let e=t.button===0&&t.shiftKey?"box":t.button===0&&this.leftButtonMode==="orbit"?"orbit":"pan",n={x:t.clientX,y:t.clientY},s=null;e==="orbit"&&(s=this.pick(n.x,n.y),s||(s=this._visibleBounds()?.sphere.center.clone()??this.target.clone())),this._drag={id:t.pointerId,button:t.button,mode:e,pivot:s,down:n,last:n,moved:!1}}_move(t){let e=this._drag;if(!e||t.pointerId!==e.id)return;if(t.buttons===0)return this._end();let n={x:t.clientX,y:t.clientY};if(!e.moved&&Ad(e.down,n))return;e.moved=!0;let s=n.x-e.last.x,r=n.y-e.last.y;if(e.last=n,e.mode==="box")this._drawRect(e.down,n);else if(e.mode==="orbit"){let o=Rd(this.camera.position,this.target,e.pivot,s,r,kx);this._set(o.position,o.target)}else this._pan(s,r)}_pan(t,e){let n=this.camera,s=Math.max(this.dom.clientHeight,1),r=n.isPerspectiveCamera?2*n.position.distanceTo(this.target)*Math.tan(Oe.degToRad(n.fov/2))/s:(n.top-n.bottom)/s,o=new C(1,0,0).applyQuaternion(n.quaternion),a=new C(0,1,0).applyQuaternion(n.quaternion),l=o.multiplyScalar(-t*r).addScaledVector(a,e*r);this._set(n.position.clone().add(l),this.target.clone().add(l))}_up(t){let e=this._drag;if(!e||t.pointerId!==e.id||t.button!==e.button)return;let n={x:t.clientX,y:t.clientY};if(this._end(),e.mode==="box"&&e.moved){let s={x:Math.min(e.down.x,n.x),y:Math.min(e.down.y,n.y),width:Math.abs(n.x-e.down.x),height:Math.abs(n.y-e.down.y)},r=Uc(e.down.x,n.x);this.dispatchEvent(Fc("boxselect",{rect:s,mode:r,ctrl:t.ctrlKey,shift:t.shiftKey}))}else!e.moved&&e.button===0?this.dispatchEvent(Fc("click",{x:n.x,y:n.y,button:0,ctrl:t.ctrlKey,shift:t.shiftKey})):!e.moved&&e.button===2&&this.dispatchEvent(Fc("contextmenu",{x:n.x,y:n.y}))}_lostCapture(t){this._drag&&t.buttons?this.dom.setPointerCapture(this._drag.id):this._end()}_end(){this._drag=null,this._overlay?.remove(),this._overlay=null}_drawRect(t,e){this._overlay||(this._overlay=document.createElement("div"),Object.assign(this._overlay.style,{position:"fixed",pointerEvents:"none",border:"1px solid #2563eb",background:"rgba(37,99,235,0.12)"}),(this.dom.parentElement??document.body).appendChild(this._overlay)),Object.assign(this._overlay.style,{borderStyle:Uc(t.x,e.x)==="window"?"solid":"dashed",left:`${Math.min(t.x,e.x)}px`,top:`${Math.min(t.y,e.y)}px`,width:`${Math.abs(e.x-t.x)}px`,height:`${Math.abs(e.y-t.y)}px`})}_wheel(t){if(t.preventDefault(),t.deltaY===0)return;this._stopAnimation(),this._wheelPick||(this._wheelPick={point:this.pick(t.clientX,t.clientY)},requestAnimationFrame(()=>this._wheelPick=null));let e=this._wheelPick.point??this.target,n=Cd(this.camera.position,this.target,e,Id(t.deltaY,t.deltaMode,this.dom.clientHeight));this._set(n.position,n.target)}};function nt(i,t,e){let n=document.createElement(i);return t&&(n.className=t),e!==void 0&&(n.textContent=e),n}function qe(i,t,e){let n=nt("button",i);return n.type="button",n.innerHTML=t,n.title=e,n.setAttribute("aria-label",e),n}function La(i){i.preventDefault(),i.stopPropagation()}function Da(i){La(i);let t=()=>{window.removeEventListener("pointerup",e,!0),setTimeout(()=>window.removeEventListener("click",La,!0))},e=n=>{La(n),t()};window.addEventListener("pointerup",e,!0),window.addEventListener("click",La,!0)}function Na(i,t,e,n){let s=t.clone().applyMatrix4(e.matrixWorldInverse).z<0,r=t.clone().project(e);i.hidden=!(s&&Math.abs(r.x)<=1&&Math.abs(r.y)<=1),!i.hidden&&(i.style.left=`${n.left+(r.x+1)/2*n.width}px`,i.style.top=`${n.top+(1-r.y)/2*n.height}px`)}var de=i=>``,Ld='',Zt={model:de(''),views:de(''),sections:de(''),clashes:de(''),timeliner:de(''),links:de(''),orbit:de(''),pan:de(''),fit:de(''),projection:de(Ld),plane:de(''),box:de(`${Ld}`),resetSections:de(''),distance:de(''),displayMode:de(''),grids:de(''),opacity:de(''),fullscreen:de(''),close:de(''),chevron:de(''),flip:de(''),remove:de(''),play:de(''),pause:de(''),home:de('')};var gr=new C(0,0,1),on={right:new C(1,0,0),left:new C(-1,0,0),back:new C(0,1,0),front:new C(0,-1,0),top:new C(0,0,1),bottom:new C(0,0,-1)},Hx={right:gr,left:gr,back:gr,front:gr,top:on.back,bottom:on.front},Ni=.7,Gx=Oe.degToRad(1),Dd=.01,Nd=96,Ua=1.75;function Ud(i){let t=i.split("-"),e=new C;for(let n of t){if(!Object.hasOwn(on,n))throw new RangeError(`Unknown view cube region: ${i}`);e.add(on[n])}if(new Set(t).size!==t.length||e.lengthSq()!==t.length)throw new RangeError(`Unknown view cube region: ${i}`);return e.normalize()}function Wx(i){let t=[];return Math.abs(i.z)>=Ni&&t.push(i.z>0?"top":"bottom"),Math.abs(i.y)>=Ni&&t.push(i.y>0?"back":"front"),Math.abs(i.x)>=Ni&&t.push(i.x>0?"right":"left"),t.join("-")}function Fd(i,t=Gx){let e=i.clone().normalize(),n=Math.cos(t);return Object.keys(on).find(s=>e.dot(on[s])>=n)??null}function Bd(i){let t=new C(i.x,i.y,0);if(t.lengthSq()<1e-24)return"front";t.normalize();let e=["front","right","back","left"],n=Math.max(...e.map(s=>t.dot(on[s])));return e.find(s=>t.dot(on[s])>=n-1e-9)}function Bc(i,t){return on[Bd(t)].clone().multiplyScalar(Math.sin(Dd)).addScaledVector(on[i],Math.cos(Dd))}function Xx(i,t){let e=Fd(i);if(!e)return null;let n=e==="top"||e==="bottom";if(t==="left"||t==="right")return(n?Bc(e,i):Ud(e)).applyAxisAngle(gr,t==="right"?Math.PI/2:-Math.PI/2);if(t!=="up"&&t!=="down")throw new RangeError(`Unknown arrow: ${t}`);if(!n)return Bc(t==="up"?"top":"bottom",i);let s=on[Bd(i)];return t==="up"==(e==="top")?s.clone().negate():s.clone()}function qx(i,t){let e=document.createElement("canvas");e.width=e.height=128;let n=e.getContext("2d");n.fillStyle="#2a2f38",n.fillRect(0,0,128,128),n.fillStyle="#cbd5e1",n.textAlign="center",n.textBaseline="middle",n.font=`600 26px ${t}`;let s=n.measureText(i).width;s>112&&(n.font=`600 ${Math.floor(2912/s)}px ${t}`),n.fillText(i,64,64);let r=new Ws(e);return r.colorSpace=Se,r}var Fa=class{constructor(t,e,n){this.controls=n,this.home={position:e.position.clone(),target:n.target.clone()},this.element=nt("div","ioviewer-viewcube"),this.canvas=nt("canvas","ioviewer-viewcube-canvas"),this.canvas.title=st("cube.title"),this.canvas.setAttribute("aria-label",st("cube.title")),this.element.append(this.canvas),this.error=null,this.renderer=this._createRenderer(),this.scene=new Mi,this.camera=new In(-Ua,Ua,Ua,-Ua,.1,10),this.faces=new be;let s=getComputedStyle(t).fontFamily;for(let[a,l]of Object.entries(on)){let c=st(`face.${a}`),h=new _e(new Gn(2,2),new cn({map:qx(c,s)})),d=Hx[a];h.quaternion.setFromRotationMatrix(new Ht().makeBasis(d.clone().cross(l),d,l)),h.position.copy(l),h.name=a,h.userData.label=c,this.faces.add(h)}let r=new gn(new Ti(new hn(2,2,2)),new sn({color:"#5b6575"}));this.highlight=new _e(new hn(1,1,1),new cn({color:"#2563eb",transparent:!0,opacity:.6})),this.highlight.visible=!1,this.scene.add(this.faces,r,this.highlight),this._raycaster=new Ai,this.hovered=null,this.canvas.addEventListener("pointermove",a=>this._hover(this.regionAtClient(a.clientX,a.clientY))),this.canvas.addEventListener("pointerleave",()=>this._hover(null)),this.canvas.addEventListener("click",a=>{let l=this.regionAtClient(a.clientX,a.clientY);if(!l)return;let c=l==="top"||l==="bottom";this._look(c?Bc(l,n.camera.position.clone().sub(n.target)):Ud(l))}),this.arrows=["up","down","left","right"].map(a=>{let l=qe("ioviewer-viewcube-arrow",Zt.chevron,st(`cube.${a}`));return l.dataset.arrow=a,l.addEventListener("click",()=>this._arrow(a)),l});let o=qe("ioviewer-viewcube-home",Zt.home,st("cube.home"));o.addEventListener("click",()=>n.animateTo({position:this.home.position.clone(),target:this.home.target.clone()})),this.element.append(...this.arrows,o),t.append(this.element),this.update()}_createRenderer(){this.canvas.addEventListener("webglcontextlost",e=>{e.stopImmediatePropagation(),this._fail(new Error("The view cube lost its WebGL context."))});let t=this.canvas.getContext("webgl2",{alpha:!0,antialias:!0});if(!t)return this._fail(new Error("WebGL 2 is not available for the view cube."));try{let e=new Ss({canvas:this.canvas,context:t,alpha:!0,antialias:!0});return e.setPixelRatio(window.devicePixelRatio),e.setSize(Nd,Nd),e}catch(e){return this._fail(e)}}_fail(t){return this.error=t,this.renderer=null,this.element.hidden=!0,null}_render(){if(this.renderer)try{this.renderer.render(this.scene,this.camera)}catch(t){this._fail(t)}}update(){if(!this.renderer)return;let t=this.controls.camera;this.camera.quaternion.copy(t.quaternion),this.camera.position.set(0,0,5).applyQuaternion(t.quaternion),this.camera.updateMatrixWorld();let e=Fd(t.position.clone().sub(this.controls.target))!==null;for(let n of this.arrows)n.hidden=!e;this._render()}regionAtClient(t,e){let n=this.canvas.getBoundingClientRect(),s=new zt((t-n.left)/n.width*2-1,1-(e-n.top)/n.height*2);this._raycaster.setFromCamera(s,this.camera);let r=this._raycaster.intersectObjects(this.faces.children)[0];return r?Wx(r.point):null}_hover(t){if(t!==this.hovered){if(this.hovered=t,this.highlight.visible=!!t,t){let e=t.split("-").reduce((a,l)=>a.add(on[l]),new C),n=a=>a===0?[-Ni,Ni]:a>0?[Ni,1.02]:[-1.02,-Ni],[s,r,o]=e.toArray().map(n);this.highlight.scale.set(s[1]-s[0],r[1]-r[0],o[1]-o[0]),this.highlight.position.set((s[0]+s[1])/2,(r[0]+r[1])/2,(o[0]+o[1])/2)}this._render()}}_arrow(t){let n=this.controls.camera.position.clone().sub(this.controls.target),s=Xx(n,t);s&&this._look(s)}_look(t){let{controls:e}=this,n=e.camera.position.distanceTo(e.target);e.animateTo({position:t.clone().multiplyScalar(n).add(e.target),target:e.target.clone()})}};var Yx=` #include ${Cc} #include @@ -4288,7 +4288,8 @@ void main() { #endif }`,Od=i=>i&&{element:i.element,point:i.point.clone()};function Oc(i,t){let e=new Ze({vertexShader:Yx,fragmentShader:$x,uniforms:i,defines:t?{[t]:""}:{},side:Be,clipping:!0});return e.index0AttributeName="q",e}var Ba=class{constructor(t,e,n,s={}){this.renderer=t,this.scene=e,this.camera=n,this._lost=!1,t.domElement.addEventListener("webglcontextlost",()=>this._lost=!0),this._last=null,this._key=new Float64Array(36),e.root.addEventListener("change",o=>o.detail.kind!=="appearance"&&(this._last=null));let r=e.slotUniforms;this._float=s.pickFloat!==!1&&t.extensions.has("EXT_color_buffer_float"),this._float?(this._material=Oc(r,null),this._target=new Fe(1,1,{type:We}),this._pixel=new Float32Array(4)):(this._material=Oc(r,"IO_PICK_RGBA8"),this._depthMaterial=Oc(r,"IO_PICK_DEPTH"),this._target=new Fe(1,1),this._pixel=new Uint8Array(4))}pick(t,e){let n=this.renderer.domElement.getBoundingClientRect(),s=Math.floor(t-n.left),r=Math.floor(e-n.top);if(this._lost||!(s>=0&&r>=0&&sh===a[d]))return Od(l.hit);let c=this._pick(s,r,n);return this._last={camera:o,key:a.slice(),hit:c},Od(c)}_pick(t,e,n){let s=this._render(this._material,t,e,n),r=(this._float?Math.round(s[3]):new Uint32Array(s.buffer)[0])-1;if(r<0)return null;let o=this.scene.slotElement(r);if(this._float)return{element:o,point:new C(s[0],s[1],s[2])};let[a,l,c,h]=this._render(this._depthMaterial,t,e,n),d=a/256+l/65536+c/16777216+h/255/16777216,u=new C((t+.5)/n.width*2-1,1-(e+.5)/n.height*2,d*2-1);return{element:o,point:u.unproject(this.camera)}}_render(t,e,n,s){let{renderer:r,camera:o}=this,a={target:r.getRenderTarget(),color:r.getClearColor(new Dt),alpha:r.getClearAlpha()},l=this.scene._buckets[0]?.material;t.clippingPlanes=l?.clippingPlanes??null,t.clipIntersection=l?.clipIntersection??!1,o.updateMatrixWorld(),o.setViewOffset(s.width,s.height,e,n,1,1);try{r.setRenderTarget(this._target),r.setClearColor(0,0),this.scene.withMaterial(t,()=>r.render(this.scene.root,o)),r.readRenderTargetPixels(this._target,0,0,1,1,this._pixel)}finally{r.setRenderTarget(a.target),r.setClearColor(a.color,a.alpha),o.clearViewOffset()}return this._pixel}};var zd=33.333333333333336,Zx=200,kd=1e6,Oa=class{constructor(t,e,n=1e7){this.scene=t,this.budget=n,this.minPixels=0,this._requestRender=e,this._last=new Float64Array(32),this._seen=!1,this._requestedAt=null,this._frame=null,this._cost=0,this._settle=0}requested(t){this._requestedAt??(this._requestedAt=t)}begin(t,e,n){t.updateMatrixWorld();let s=this._requestedAt===null?0:Math.max(n-this._requestedAt,0);this._requestedAt=null;let r=this._moved(t);this._frame=r?{start:n,waited:s}:null,this.minPixels=r?this.scene.detailThreshold(t,e,this.budget):0,this.scene.setDetail(this.minPixels,e),r&&(clearTimeout(this._settle),this.minPixels>0&&(this._settle=setTimeout(this._requestRender,Zx)))}end(t){this.scene.setDetail(0),this._frame&&this._adapt(this._frame.waited+(t-this._frame.start)),this._frame=null}_adapt(t){this._cost=this._cost?this._cost*.8+t*.2:t,this._cost>zd*1.1?this.budget=Math.max(this.budget*.9,kd):this._cost0&&(this.budget=Math.max(Math.min(this.budget*1.05,this.scene.visibleCost),kd))}_moved(t){let e=this._last,n=[...t.matrixWorld.elements,...t.projectionMatrix.elements],s=(o,a)=>a!==26&&a!==30&&Math.abs(o-e[a])>1e-9*Math.max(1,Math.abs(o)),r=this._seen&&n.some(s);return this._seen=!0,e.set(n),r}};var Mn,Es,za,ka=class extends EventTarget{constructor(){super(...arguments);ja(this,Es);ja(this,Mn,new Set)}get size(){return Zn(this,Mn).size}has(e){return Zn(this,Mn).has(e)}values(){return Zn(this,Mn).values()}set(e){th(this,Mn,new Set(e)),br(this,Es,za).call(this)}toggle(e){Zn(this,Mn).delete(e)||Zn(this,Mn).add(e),br(this,Es,za).call(this)}clear(){Zn(this,Mn).size!==0&&(Zn(this,Mn).clear(),br(this,Es,za).call(this))}};Mn=new WeakMap,Es=new WeakSet,za=function(){this.dispatchEvent(new Event("change"))};function Vd(i,t,e){if(t!=="window"&&t!=="crossing")throw new RangeError(`Unknown box select mode: ${t}`);let n=i.x+i.width,s=i.y+i.height,r=t==="window"?o=>o.minX>=i.x&&o.maxX<=n&&o.minY>=i.y&&o.maxY<=s:o=>o.maxX>=i.x&&o.minX<=n&&o.maxY>=i.y&&o.minY<=s;return e.filter(r).map(o=>o.index)}function Hd(i,t,e){let n=new Ht().multiplyMatrices(t.projectionMatrix,t.matrixWorldInverse).elements,{left:s,top:r,width:o,height:a}=e,l=new se,c=[];for(let h=0;hn===l?r(l):o(l));let m=[...t.children].find(b=>Gd.indexOf(b.dataset.panel)>g);t.insertBefore(_,m??null);let f=nt("section","ioviewer-side-panel ioviewer-floating");f.dataset.panel=l,f.hidden=!0,f.setAttribute("aria-label",h);let w=qe("ioviewer-panel-close",Zt.close,st("close"));w.addEventListener("click",()=>r(l));let R=nt("header","ioviewer-panel-header");R.append(nt("h2","",h),w);let S=nt("div","ioviewer-panel-body");f.append(R,S),i.append(f),e.set(l,{button:_,panel:f,onClose:p}),d(S)}return{rail:t,openPanel:o,closePanel:r,registerPanel:a}}var Xd=[{id:"navigation",items:["orbit","pan","fit","projection"]},{id:"sections",items:["plane","box","resetSections"]},{id:"measure",items:["distance"]},{id:"display",items:["displayMode","grids","opacity"]},{id:"view",items:["fullscreen"]}];function qd(i,t){let e=nt("div","ioviewer-toolbar ioviewer-floating");e.setAttribute("role","toolbar");let n=new Map;for(let o of t){let a=nt("div","ioviewer-toolbar-group");a.setAttribute("role","group"),a.setAttribute("aria-label",st(`group.${o.id}`));let l=nt("div","ioviewer-toolbar-row");for(let c of o.items){let h=qe("ioviewer-tool",Zt[c],st(`tool.${c}`));h.dataset.tool=c,h.setAttribute("aria-disabled","true");let d={button:h,handler:null};h.addEventListener("click",()=>{h.getAttribute("aria-disabled")!=="true"&&d.handler?.()}),n.set(c,d),l.append(h)}a.append(l,nt("div","ioviewer-toolbar-label",st(`group.${o.id}`))),e.append(a)}i.append(e);function s(o){let a=n.get(o);if(!a)throw new RangeError(`Unknown tool: ${o}`);return a}function r(o,a){s(o).button.setAttribute("aria-disabled",String(!a))}return{element:e,setHandler(o,a){s(o).handler=a,r(o,!0)},setEnabled:r,setActive(o,a){let{button:l}=s(o);l.classList.toggle("active",a),l.setAttribute("aria-pressed",String(a))}}}var Ui=24,Jx=8;function Kx(i,t,e,n,s){let r=Math.min(n,Math.max(0,Math.floor(i/t)-s)),o=Math.min(n,Math.ceil((i+e)/t)+s);return{start:r,end:Math.max(r,o)}}function jx(i){let t=null;return e=>{let n=e.trim().toLowerCase();t??(t=i.map(r=>`${r.name??""} ${r.type??""} -${r.id}`.toLowerCase()));let s=[];for(let r=0;r{let s=e.category??"";t.has(s)||t.set(s,[]),t.get(s).push(n)}),[...t].map(([e,n])=>({name:e,indices:n})).sort((e,n)=>e.name.localeCompare(n.name))}function Yd(i,{elements:t,selection:e,scene:n,activate:s}){let r=Qx(t),o=new Int32Array(t.length);r.forEach((b,y)=>b.indices.forEach(T=>o[T]=y));let a=new Uint8Array(r.length),l=jx(t),c="",h=new Int32Array(0),d=-1,u=nt("input","ioviewer-tree-search");u.type="search",u.placeholder=st("searchPlaceholder"),u.setAttribute("aria-label",st("searchPlaceholder"));let p=nt("div","ioviewer-tree");p.setAttribute("role","tree"),p.setAttribute("aria-label",st("panel.model"));let g=nt("div","ioviewer-tree-spacer");p.append(g);let _=nt("div","ioviewer-tree-empty",st("noMatches"));i.append(u,p,_);function m(){if(c)h=Int32Array.from(l(c));else{let b=r.length;r.forEach((T,v)=>a[v]&&(b+=T.indices.length)),h=new Int32Array(b);let y=0;r.forEach((T,v)=>{if(h[y++]=-(v+1),a[v])for(let E of T.indices)h[y++]=E})}g.style.height=`${h.length*Ui}px`,_.hidden=h.length>0||!c,f()}function f(){let{start:b,end:y}=Kx(p.scrollTop,Ui,p.clientHeight,h.length,Jx),T=[];for(let v=b;vp.scrollTop+y&&(p.scrollTop=T+Ui-y)}function S(b){let y=o[b];!c&&!a[y]&&(a[y]=1,m());let T=h.indexOf(b);T>=0&&R(T),f()}u.addEventListener("input",()=>{c=u.value.trim(),p.scrollTop=0,m()}),p.addEventListener("scroll",f),p.addEventListener("click",b=>{let y=b.target.closest(".tree-row");if(!y)return;let T=h[Number(y.dataset.row)];if(T>=0){s(T);return}a[-T-1]^=1,m()}),new ResizeObserver(()=>{d>=0&&p.clientHeight>0&&(R(d),d=-1),f()}).observe(p),e.addEventListener("change",()=>{let b=-1;for(let y of e.values())b=y;b>=0?S(b):f()}),n.root.addEventListener("change",b=>b.detail.kind==="visibility"&&f()),m()}var $d={element:["properties","hide","isolate","showAll","-","zoomTo","sectionBox","-","copyId"],empty:["showAll","fitAll","resetSections"]},tv={hide:"H",isolate:"I",showAll:"Shift+H",zoomTo:"F"},Va=4;function Zd(i){let t=nt("div","ioviewer-context-menu");t.setAttribute("role","menu"),t.hidden=!0,i.append(t);let e=new Map(Object.values($d).flat().filter(l=>l!=="-").map(l=>[l,{enabled:!1,handler:null}]));function n(){t.hidden||(t.hidden=!0,t.replaceChildren(),window.removeEventListener("pointerdown",r,!0),window.removeEventListener("wheel",n,!0),window.removeEventListener("scroll",s,!0),window.removeEventListener("resize",n))}function s(l){i.contains(l.target)||n()}function r(l){t.contains(l.target)||(n(),Da(l))}function o(l,c,h,d){let u=$d[l];if(!u)throw new RangeError(`Unknown menu: ${l}`);n();for(let _ of u){if(_==="-"){t.append(nt("div","ioviewer-menu-separator"));continue}let m=e.get(_),f=nt("button","ioviewer-menu-item");f.type="button",f.dataset.item=_,f.setAttribute("role","menuitem"),f.disabled=!m.enabled,f.append(nt("span","",st(`menu.${_}`)),nt("kbd","",tv[_]??"")),f.addEventListener("click",()=>{n(),m.handler(d)}),t.append(f)}t.hidden=!1;let{width:p,height:g}=t.getBoundingClientRect();t.style.left=`${Math.max(Va,Math.min(c,innerWidth-p-Va))}px`,t.style.top=`${Math.max(Va,Math.min(h,innerHeight-g-Va))}px`,window.addEventListener("pointerdown",r,!0),window.addEventListener("wheel",n,!0),window.addEventListener("scroll",s,!0),window.addEventListener("resize",n)}function a(l,{enabled:c=!0,handler:h}){let d=e.get(l);if(!d)throw new RangeError(`Unknown menu item: ${l}`);h&&(d.handler=h),d.enabled=c&&!!d.handler}return{element:t,open:o,close:n,setItem:a,get isOpen(){return!t.hidden}}}var ev="input, textarea, select, [contenteditable]";function nv(i){if(i.ctrlKey||i.altKey||i.metaKey||i.repeat||i.target?.closest?.(ev))return null;switch(i.code){case"KeyH":return i.shiftKey?"showAll":"hide";case"KeyI":return i.shiftKey?null:"isolate";case"KeyF":return i.shiftKey?null:"zoomTo";case"Escape":return"escape";default:return null}}function Jd(i,t){i.addEventListener("keydown",e=>{let n=nv(e);n&&(e.preventDefault(),t[n]())})}var iv=1600;function Kd(i,t,e){let n=()=>i.textContent=`${st("selected")}: ${t.size} \xB7 ${st("elements")}: ${e}`;t.addEventListener("change",n),n()}function jd(i,t){if(!t)return;let e=nt("div","ioviewer-banner ioviewer-floating");e.setAttribute("role","status");let n=st("truncated").replace("{meshes}",t.meshes).replace("{total}",t.totalMeshes),s=qe("ioviewer-panel-close",Zt.close,st("close"));s.addEventListener("click",()=>e.remove()),e.append(nt("span","",n),s),i.append(e)}function Ts(i,t){let e=nt("div","ioviewer-toast",t);e.setAttribute("role","status"),i.append(e),setTimeout(()=>e.remove(),iv)}function Qd(i,t){let e=nt("table");for(let[s,r]of t)e.insertRow().append(nt("th","",s),nt("td","",String(r??"")));let n=nt("section");return n.append(nt("h3","",i),e),n}function tf(i,t,e){let n=nt("h2","ioviewer-properties-title"),s=nt("button","ioviewer-properties-close","\xD7");s.type="button",s.title=st("close"),s.setAttribute("aria-label",st("close")),s.addEventListener("click",()=>t.clear());let r=nt("header");r.append(n,s);let o=nt("div","ioviewer-properties-body");i.replaceChildren(r,o),i.hidden=!0,t.addEventListener("change",()=>{if(i.hidden=t.size===0,o.replaceChildren(),t.size!==1){n.textContent=`${st("selected")}: ${t.size}`;return}let[a]=t.values(),l=e.meta.elements[a];n.textContent=l.name||`#${l.id}`;let c=[[st("id"),l.id],[st("category"),l.category],[st("type"),l.type],[st("level"),l.level]];l.link&&c.push([st("link"),l.link]),o.append(Qd(st("elementGroup"),c));for(let[h,d]of Object.entries(e.props(l.id)??{}))o.append(Qd(h,Object.entries(d)))})}async function sv(i){if(await navigator.clipboard?.writeText(i).then(()=>!0,()=>!1))return!0;let t=document.createElement("textarea");t.value=i,t.setAttribute("readonly",""),t.className="ioviewer-offscreen",document.body.append(t),t.select();try{return document.execCommand("copy")}finally{t.remove()}}function ef({root:i,data:t,scene:e,controls:n,picker:s,selection:r,measure:o}){let{meta:a}=t,l=()=>n.fitBox(e.visibleBox()),c={hide:()=>r.size&&e.setVisible([...r.values()],!1),isolate:()=>r.size&&e.isolate([...r.values()]),showAll:()=>e.showAll(),zoomTo:()=>{if(!r.size)return l();let[m,...f]=r.values(),w=e.elementBox(m);for(let R of f)w.union(e.elementBox(R));n.zoomToBox(w)},fitAll:l},h=Wd(i);h.registerPanel("model",{icon:Zt.model,title:st("panel.model"),render:m=>Yd(m,{elements:a.elements,selection:r,scene:e,activate:f=>{r.set([f]),n.zoomToBox(e.elementBox(f))}})});let d=document.createElement("aside");d.id="ioviewer-properties",d.className="ioviewer-floating",i.append(d),tf(d,r,t);let u=qd(i,Xd),p=m=>{n.setLeftButtonMode(m),u.setActive("orbit",m==="orbit"),u.setActive("pan",m==="pan")};u.setHandler("orbit",()=>p("orbit")),u.setHandler("pan",()=>p("pan")),p(n.leftButtonMode),u.setHandler("fit",l),u.setHandler("projection",()=>{let m=!n.camera.isOrthographicCamera;n.setProjection(m?"orthographic":"perspective"),u.setActive("projection",m)}),u.setActive("projection",!1),document.fullscreenEnabled&&(u.setHandler("fullscreen",()=>{(document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()).catch(()=>{u.setActive("fullscreen",!!document.fullscreenElement),Ts(i,st("fullscreenUnavailable"))})}),document.addEventListener("fullscreenchange",()=>u.setActive("fullscreen",!!document.fullscreenElement)));let g=Zd(i);g.setItem("properties",{handler:m=>r.set([m])});for(let m of["hide","isolate","showAll","zoomTo","fitAll"])g.setItem(m,{handler:c[m]});g.setItem("copyId",{handler:m=>sv(String(a.elements[m].id)).then(f=>Ts(i,st(f?"idCopied":"copyFailed")),()=>Ts(i,st("copyFailed")))}),n.addEventListener("contextmenu",m=>{let{x:f,y:w}=m.detail;if(o.active){let[S]=r.values();return S===void 0?g.open("empty",f,w,null):g.open("element",f,w,S)}let R=s.pick(f,w);if(!R||R.element<0)return g.open("empty",f,w,null);r.has(R.element)||r.set([R.element]),g.open("element",f,w,R.element)});let _=[];return Jd(window,{...c,escape:()=>{if(g.isOpen)return g.close();_.some(m=>m())||r.clear()}}),Kd(document.getElementById("ioviewer-status"),r,e.elementCount),jd(i,a.truncated),{shell:h,toolbar:u,contextMenu:g,onEscape:m=>_.push(m)}}var rv={x:new C(1,0,0),y:new C(0,1,0),z:new C(0,0,1)};function zc(i){let t=rv[i];if(!t)throw new RangeError(`Unknown axis: ${i}`);return t.clone()}function nf(i,t,e,n){let s=i.clone().normalize(),r=s.dot(t)+e;return n?new ge(s,-r):new ge(s.negate(),r)}function sf(i,t){if(t!=="keepInside"&&t!=="cutInside")throw new RangeError(`Unknown box mode: ${t}`);let{min:e,max:n}=i,s=[new ge(new C(1,0,0),-e.x),new ge(new C(-1,0,0),n.x),new ge(new C(0,1,0),-e.y),new ge(new C(0,-1,0),n.y),new ge(new C(0,0,1),-e.z),new ge(new C(0,0,-1),n.z)];return t==="keepInside"?{planes:s,intersection:!1}:{planes:s.map(r=>r.negate()),intersection:!0}}function rf(i){let{min:t,max:e}=i;return Array.from({length:8},(n,s)=>new C(s&1?e.x:t.x,s&2?e.y:t.y,s&4?e.z:t.z))}function Ha(i,t,e){if(t.length===0||i.isEmpty())return!1;let n=rf(i),s=r=>n.every(o=>r.distanceToPoint(o)<0);return e?t.every(s):t.some(s)}function of(i,t){let e=i.getSize(new C).multiplyScalar(.5);return Math.abs(t.x)*e.x+Math.abs(t.y)*e.y+Math.abs(t.z)*e.z}function _r(i,t,e){let n=t.clone().sub(i.origin),s=e.dot(i.direction),r=1-s*s;return r<.03?null:(s*i.direction.dot(n)-e.dot(n))/r}function af(i,t,e){let n=new C(1,0,0).applyQuaternion(t),s=new C(0,1,0).applyQuaternion(t),[r,o,a,l]=[1/0,-1/0,1/0,-1/0];for(let h of rf(i)){h.sub(e);let d=h.dot(n),u=h.dot(s);[r,o,a,l]=[Math.min(r,d),Math.max(o,d),Math.min(a,u),Math.max(l,u)]}return{center:e.clone().addScaledVector(n,(r+o)/2).addScaledVector(s,(a+l)/2),width:(o-r)*1.1,height:(l-a)*1.1}}var xr=6,ov=["off","planes","box"],av=.05,lf=new C(0,0,1),Ga=class extends EventTarget{constructor(t,e){super(),this.scene=t,this.modelBox=e.clone(),this.center=e.getCenter(new C),this.planes=[],this.active=-1,this.box=null,this.boxMode="keepInside",this.gizmoVisible=!0,this._mode="off"}get mode(){return this._mode}setMode(t){if(!ov.includes(t))throw new RangeError(`Unknown section mode: ${t}`);t==="box"&&!this.box&&this.fitBoxToShown()||(this._mode=t,this._changed(!0))}addPlane(t){let e=zc(t);if(this.planes.length>=xr||this.modelBox.isEmpty())return null;let n=new Ne().setFromUnitVectors(lf,e);return this.planes.push({axis:t,quaternion:n,offset:0,flipped:!1,enabled:!0,rotated:!1}),this.active=this.planes.length-1,this._mode="planes",this._changed(!0),this.active}removePlane(t){this._plane(t),this.planes.splice(t,1),(this.active>=this.planes.length||this.active>t)&&this.active--,this._changed(!0)}updatePlane(t,{offset:e,flipped:n,enabled:s,quaternion:r}){let o=this._plane(t);if(e!==void 0){if(!Number.isFinite(e))throw new RangeError(`Offset must be a finite number, got ${e}.`);o.offset=e}n!==void 0&&(o.flipped=!!n),s!==void 0&&(o.enabled=!!s),r&&(o.quaternion.copy(r).normalize(),o.rotated=this.normal(t).angleTo(zc(o.axis))>1e-6),this._changed(!0)}setActivePlane(t){this._plane(t),this.active=t,this._changed(!1)}normal(t){return lf.clone().applyQuaternion(this._plane(t).quaternion)}extent(t){return of(this.modelBox,this.normal(t))}origin(t){return this.center.clone().addScaledVector(this.normal(t),this._plane(t).offset)}clipPlane(t){let e=this._plane(t);return nf(this.normal(t),this.center,e.offset,e.flipped)}setBox(t){if(t.isEmpty())throw new RangeError("The section box is empty.");this.box=t.clone(),this._mode="box",this._changed(!0)}setBoxMode(t){if(t!=="keepInside"&&t!=="cutInside")throw new RangeError(`Unknown box mode: ${t}`);this.boxMode=t,this._changed(!0)}fitBoxToElements(t){let e=new se;for(let s of t)e.union(this.scene.elementBox(s));if(e.isEmpty())return!1;let n=e.getSize(new C);return this.setBox(e.expandByScalar(av*Math.max(n.x,n.y,n.z))),!0}fitBoxToShown(){let t=this.scene.visibleBox();if(t.isEmpty())return!1;let e=t.getSize(new C);return this.setBox(t.expandByScalar(.001*Math.max(e.x,e.y,e.z))),!0}setGizmoVisible(t){this.gizmoVisible=!!t,this._changed(!1)}reset(){this.planes=[],this.active=-1,this.box=null,this._mode="off",this._changed(!0)}clipping(){return this._mode==="planes"?{planes:this.planes.flatMap((e,n)=>e.enabled?[this.clipPlane(n)]:[]),intersection:!1}:this._mode==="box"&&this.box?sf(this.box,this.boxMode):{planes:[],intersection:!1}}isCut(t){let{planes:e,intersection:n}=this.clipping();return Ha(t,e,n)}_plane(t){let e=this.planes[t];if(!Number.isInteger(t)||!e)throw new RangeError(`Plane index ${t} is out of range 0..${this.planes.length-1}.`);return e}_changed(t){if(t){let{planes:e,intersection:n}=this.clipping();this.scene.setClipping(e,n)}this.dispatchEvent(new Event("change"))}};var lv=80,cv=8,fi={x:"#ef4444",y:"#22c55e",z:"#3b82f6",centre:"#e2e8f0",hover:"#facc15",frame:"#93c5fd",fill:"#60a5fa"},Wa=["x","y","z"],hv=Math.cos(Oe.degToRad(12)),cf=new C(0,0,1),uv=i=>new cn({color:i,depthTest:!1,depthWrite:!1,transparent:!0,opacity:.9}),dv=new cn({visible:!1});function Xa(i,t,e,n){let s=new be,r=uv(t);for(let a of e)s.add(new _e(a,r));let o=new _e(n,dv);return o.userData.handle=s,s.add(o),s.userData={kind:i,color:t,material:r,proxy:o},s.traverse(a=>a.renderOrder=1e3),s}var qa=class extends EventTarget{constructor(t,e){super(),this.sections=t,this.dom=e,this.camera=null,this.group=new be,this.group.name="sections",this._raycaster=new Ai,this._drag=null,this._hovered=null,this._buildPlanes(),this._buildPlaneGizmo(),this._buildBox(),t.addEventListener("change",()=>this._sync()),(e.parentElement??e).addEventListener("pointerdown",n=>this._down(n),!0),e.addEventListener("pointermove",n=>this._move(n)),e.addEventListener("pointerup",n=>this._up(n)),e.addEventListener("pointercancel",()=>this._end()),e.addEventListener("lostpointercapture",()=>this._end()),this._sync()}get dragging(){return this._drag!==null}update(t){this.camera=t;let e=this.planeGizmo;e.visible&&(e.scale.setScalar(this._worldPerPixel(e.position)*lv),this.arrow.visible=this._across(cf.clone().applyQuaternion(e.quaternion),e.position));for(let n of this.boxHandles){let{shown:s,axis:r}=n.userData;n.visible=s&&(!r||this._across(new C().setComponent(Wa.indexOf(r),1),n.position)),n.visible&&n.scale.setScalar(this._worldPerPixel(n.position)*cv)}}_across(t,e){let n=this.camera,s=n.isOrthographicCamera?n.getWorldDirection(new C):e.clone().sub(n.position).normalize();return Math.abs(t.dot(s))new C(r/2,o/2,0))),n=new cn({color:fi.fill,transparent:!0,opacity:.12,side:Be,depthWrite:!1}),s=new sn({color:fi.frame});this.planeVisuals=Array.from({length:xr},()=>{let r=new be;return r.add(new _e(t,n),new Hs(e,s)),this.group.add(r),r})}_buildPlaneGizmo(){let t=(s,r)=>s.rotateX(Math.PI/2).translate(0,0,r),e=Xa("move",fi.z,[t(new Ei(.025,.025,1),.5),t(new qs(.08,.25),1.125)],t(new Ei(.12,.12,1.25),.625));e.name="arrow",this.arrow=e;let n=(s,r,o)=>{let a=Xa("rotate",fi[r],[o(new us(.8,.018,8,64))],o(new us(.8,.09,8,48)));return a.name=s,a.userData.axis=r,a};this.planeGizmo=new be,this.planeGizmo.add(e,n("ringX","x",s=>s.rotateY(Math.PI/2)),n("ringY","y",s=>s.rotateX(Math.PI/2))),this.group.add(this.planeGizmo)}_buildBox(){this.boxWire=new gn(new Ti(new hn(1,1,1)),new sn({color:fi.frame})),this.group.add(this.boxWire);let t=new hs(1,16,12),e=new hs(1.6,8,6);this.boxHandles=[];for(let s of Wa)for(let r of[-1,1]){let o=Xa("face",fi[s],[t],e);o.name=`face${r>0?"+":"-"}${s}`,Object.assign(o.userData,{axis:s,sign:r}),this.boxHandles.push(o)}let n=Xa("centre",fi.centre,[new hn(1.4,1.4,1.4)],e);n.name="centre",this.boxHandles.push(n),this.group.add(...this.boxHandles)}_sync(){let t=this.sections,e=t.mode==="planes";this.planeVisuals.forEach((r,o)=>{let a=t.planes[o];if(r.visible=e&&!!a?.enabled,!r.visible)return;let l=af(t.modelBox,a.quaternion,t.origin(o));r.position.copy(l.center),r.quaternion.copy(a.quaternion),r.scale.set(l.width,l.height,1)});let n=t.planes[t.active];this.planeGizmo.visible=e&&t.gizmoVisible&&!!n?.enabled,this.planeGizmo.visible&&(this.planeGizmo.position.copy(t.origin(t.active)),this.planeGizmo.quaternion.copy(n.quaternion));let s=t.mode==="box"?t.box:null;this.boxWire.visible=!!s;for(let r of this.boxHandles)r.visible=r.userData.shown=!!s&&t.gizmoVisible;if(s){let r=s.getCenter(new C),o=s.getSize(new C);this.boxWire.position.copy(r),this.boxWire.scale.copy(o).max(new C(1e-6,1e-6,1e-6));for(let a of this.boxHandles){a.position.copy(r);let{axis:l,sign:c}=a.userData;l&&(a.position[l]=c>0?s.max[l]:s.min[l])}}}_worldPerPixel(t){let e=this.camera,n=Math.max(this.dom.clientHeight,1);if(e.isOrthographicCamera)return(e.top-e.bottom)/e.zoom/n;let s=t.clone().sub(e.position).dot(e.getWorldDirection(new C));return 2*Math.max(s,e.near)*Math.tan(Oe.degToRad(e.fov/2))/n}_ray(t){let e=this.dom.getBoundingClientRect(),n=new zt((t.clientX-e.left)/e.width*2-1,1-(t.clientY-e.top)/e.height*2);return this.camera.updateMatrixWorld(),this._raycaster.setFromCamera(n,this.camera),this._raycaster.ray.clone()}_hit(t){if(!this.camera)return null;this.update(this.camera),this.group.updateMatrixWorld(!0);let e=r=>{for(;r&&r!==this.group;r=r.parent)if(!r.visible)return!1;return!0},n=[];this.group.traverse(r=>r.userData.handle&&e(r)&&n.push(r)),this._ray(t);let s=this._raycaster.intersectObjects(n,!1)[0];return s?s.object.userData.handle:null}_down(t){if(t.target!==this.dom||t.button!==0)return;let e=this._hit(t),n=e&&this._start(e,this._ray(t));n&&(t.stopPropagation(),t.preventDefault(),this.dom.setPointerCapture(t.pointerId),this._drag={id:t.pointerId,handle:e,...n},this._highlight(e))}_start(t,e){let n=this.sections,{kind:s}=t.userData;if(s==="move"||s==="rotate"){let c=n.active,h=n.origin(c),d=n.planes[c].quaternion.clone();if(s==="move"){let _=n.normal(c),m=_r(e,h,_);return m===null?null:{index:c,origin:h,normal:_,offset:n.planes[c].offset,s0:m}}let u=new C().setComponent(Wa.indexOf(t.userData.axis),1).applyQuaternion(d),p=new ge().setFromNormalAndCoplanarPoint(u,h),g=e.intersectPlane(p,new C)?.sub(h);return g?{index:c,origin:h,quaternion:d,axis:u,plane:p,from:g}:null}let r=n.box.clone();if(s==="face"){let{axis:c,sign:h}=t.userData,d=new C().setComponent(Wa.indexOf(c),h),u=t.position.clone(),p=_r(e,u,d);return p===null?null:{box:r,normal:d,at:u,s0:p}}let o=this.camera.getWorldDirection(new C),a=new ge().setFromNormalAndCoplanarPoint(o,r.getCenter(new C)),l=e.intersectPlane(a,new C);return l?{box:r,plane:a,from:l}:null}_move(t){let e=this._drag;if(!e)return this._hover(t);if(t.pointerId!==e.id)return;if(t.buttons===0)return this._end();let n=this._ray(t),s=this.sections,{kind:r}=e.handle.userData;if(r==="move"){let o=_r(n,e.origin,e.normal);o!==null&&s.updatePlane(e.index,{offset:e.offset+o-e.s0})}else if(r==="rotate"){let o=n.intersectPlane(e.plane,new C)?.sub(e.origin);if(!o)return;let a=Math.atan2(e.axis.dot(new C().crossVectors(e.from,o)),e.from.dot(o)),l=new Ne().setFromAxisAngle(e.axis,a).multiply(e.quaternion),c=cf.clone().applyQuaternion(l);s.updatePlane(e.index,{quaternion:l,offset:c.dot(e.origin.clone().sub(s.center))})}else if(r==="face"){let o=_r(n,e.at,e.normal);if(o===null)return;let{axis:a,sign:l}=e.handle.userData,c=e.box.clone(),h=.001*Math.max(...s.modelBox.getSize(new C).toArray(),.001),d=(l>0?e.box.max[a]:e.box.min[a])+l*(o-e.s0);l>0?c.max[a]=Math.max(d,c.min[a]+h):c.min[a]=Math.min(d,c.max[a]-h),s.setBox(c)}else{let o=n.intersectPlane(e.plane,new C);o&&s.setBox(e.box.clone().translate(o.sub(e.from)))}}_up(t){this._drag&&t.pointerId===this._drag.id&&this._end()}_end(){this._drag&&(this._drag=null,this._highlight(null))}_hover(t){t.buttons===0&&this._highlight(this._hit(t))}_highlight(t){if(this._hovered!==t){for(let e of[this._hovered,t])e&&e.userData.material.color.set(e===t?fi.hover:e.userData.color);this._hovered=t,this.dom.style.cursor=t?"grab":"",this.dispatchEvent(new Event("change"))}}};function vr(i,t,e){let n=nt("div","ioviewer-segmented");n.setAttribute("role","group"),n.setAttribute("aria-label",i);let s=t.map(([o,a])=>{let l=nt("button","",a);return l.type="button",l.dataset.value=o,l.addEventListener("click",()=>e(o)),n.append(l),l});return{element:n,update:o=>s.forEach(a=>a.setAttribute("aria-pressed",String(a.dataset.value===o)))}}function hf(i,t){let e=nt("label","ioviewer-check"),n=nt("input");return n.type="checkbox",n.addEventListener("change",()=>t(n.checked)),e.append(n,nt("span","",i)),{element:e,input:n}}function fv(i,t){let e=nt("li","ioviewer-section-plane");e.addEventListener("pointerdown",()=>i.active!==t&&i.setActivePlane(t));let n=nt("span","ioviewer-section-name"),s=qe("ioviewer-icon-button",Zt.flip,st("sections.flip"));s.addEventListener("click",()=>i.updatePlane(t,{flipped:!i.planes[t].flipped}));let r=hf(st("sections.enabled"),h=>i.updatePlane(t,{enabled:h})),o=qe("ioviewer-icon-button",Zt.remove,st("sections.delete"));o.addEventListener("click",()=>i.removePlane(t));let a=nt("input","ioviewer-section-slider");a.type="range",a.step="any",a.setAttribute("aria-label",st("sections.position")),a.addEventListener("input",()=>i.updatePlane(t,{offset:Number(a.value)}));let l=nt("div","ioviewer-section-head");return l.append(n,s,r.element,o),e.append(l,a),{element:e,update:()=>{let h=i.planes[t],d=h.rotated?` (${st("sections.rotated")})`:"";n.textContent=`${t+1}. ${h.axis.toUpperCase()}${d}`,e.classList.toggle("active",i.active===t),s.setAttribute("aria-pressed",String(h.flipped)),r.input.checked=h.enabled;let u=i.extent(t);a.min=String(-u),a.max=String(u),a.value=String(h.offset)}}}function uf({root:i,shell:t,toolbar:e,contextMenu:n,sections:s,selection:r}){let o="z",a=()=>{s.addPlane(o)===null&&(s.setMode("planes"),Ts(i,st("sections.maxPlanes")))},l=()=>[...r.values()],c=()=>s.fitBoxToElements(l()),h=vr(st("sections.mode"),[["planes",st("sections.planes")],["box",st("sections.box")],["off",st("sections.off")]],E=>s.setMode(E)),d=vr(st("sections.axis"),["x","y","z"].map(E=>[E,E.toUpperCase()]),E=>d.update(o=E));d.update(o);let u=nt("button","ioviewer-text-button",st("sections.addPlane"));u.type="button",u.addEventListener("click",a);let p=nt("ul","ioviewer-section-list"),g=nt("div","ioviewer-section-empty",st("sections.noPlanes")),_=nt("div","ioviewer-section-block"),m=nt("div","ioviewer-section-row");m.append(nt("span","",st("sections.axis")),d.element,u),_.append(m,p,g);let f=vr(st("sections.box"),[["keepInside",st("sections.keepInside")],["cutInside",st("sections.cutInside")]],E=>s.setBoxMode(E)),w=nt("button","ioviewer-text-button",st("sections.fitSelection"));w.type="button",w.addEventListener("click",c);let R=nt("div","ioviewer-section-block");R.append(f.element,w);let S=hf(st("sections.showGizmo"),E=>s.setGizmoVisible(E)),b=[],y=()=>{h.update(s.mode),_.hidden=s.mode!=="planes",R.hidden=s.mode!=="box",u.disabled=s.planes.length>=xr,b.length!==s.planes.length&&(b=s.planes.map((E,P)=>fv(s,P)),p.replaceChildren(...b.map(E=>E.element))),b.forEach(E=>E.update()),g.hidden=b.length>0,f.update(s.boxMode),S.input.checked=s.gizmoVisible,e.setActive("plane",s.mode==="planes"),e.setActive("box",s.mode==="box")},T=()=>w.disabled=r.size===0;if(t.registerPanel("sections",{icon:Zt.sections,title:st("panel.sections"),visible:!s.modelBox.isEmpty(),render:E=>E.append(h.element,_,R,S.element)}),s.addEventListener("change",y),r.addEventListener("change",T),y(),T(),e.setActive("plane",!1),e.setActive("box",!1),s.modelBox.isEmpty())return;let v=()=>t.openPanel("sections");e.setHandler("plane",()=>{a(),v()}),e.setHandler("box",()=>{!(r.size&&c())&&!s.fitBoxToShown()||v()}),e.setHandler("resetSections",()=>s.reset()),n.setItem("sectionBox",{handler:E=>{s.fitBoxToElements(r.has(E)?l():[E]),v()}}),n.setItem("resetSections",{handler:()=>s.reset()})}var pv="#facc15",mv=9;function kc(i,t=mr){return`${new Intl.NumberFormat(t,{minimumFractionDigits:2,maximumFractionDigits:2,useGrouping:!1}).format(i)} ${st("unit.m",t)}`}var Ya=class extends EventTarget{constructor(t){super(),this.active=!1,this.points=[],this.group=new be,this.group.name="measure";let e=new re;e.setAttribute("position",new we(new Float32Array(6),3));let n={color:pv,depthTest:!1,depthWrite:!1,transparent:!0};this._line=new bi(e,new sn(n)),this._markers=new wi(e,new ni({...n,size:mv,sizeAttenuation:!1}));for(let s of[this._line,this._markers])s.frustumCulled=!1,s.renderOrder=1001,this.group.add(s);this.label=nt("div","ioviewer-measure-label"),this.label.hidden=!0,t.prepend(this.label),this._sync()}get distance(){return this.points.length===2?this.points[0].distanceTo(this.points[1]):null}get box(){return new se().setFromPoints(this.points)}setActive(t){this.active=!!t,this.active||(this.points=[]),this._sync()}add(t){this.active&&(this.points=this.points.length===1?[this.points[0],t.clone()]:[t.clone()],this._sync())}clear(){return this.points.length===0?!1:(this.points=[],this._sync(),!0)}update(t,e){this.distance!==null&&Na(this.label,this.points[0].clone().lerp(this.points[1],.5),t,e)}_sync(){let t=this._line.geometry.attributes.position;this.points.forEach((e,n)=>t.setXYZ(n,e.x,e.y,e.z)),t.needsUpdate=!0,this._line.geometry.setDrawRange(0,this.points.length),this._line.visible=this.points.length===2,this._markers.visible=this.points.length>0,this.label.hidden=this.distance===null,this.distance!==null&&(this.label.textContent=kc(this.distance)),this.dispatchEvent(new Event("change"))}};var $a=class extends EventTarget{constructor(t,e){super(),this.count=e.length,this.group=new gn(new re().setAttribute("position",new ce(e.flatMap(s=>[...s.start,...s.end]),3)),new sn({color:"#93c5fd",transparent:!0,opacity:.55,depthTest:!1,depthWrite:!1})),this.group.name="grids",this.group.geometry.computeBoundingBox(),this.box=this.group.geometry.boundingBox,this.group.renderOrder=999,this._labels=e.flatMap(s=>[s.start,s.end].map(r=>({element:nt("div","ioviewer-grid-label",s.name),point:new C(...r)})));let n=nt("div","ioviewer-grid-labels");n.append(...this._labels.map(s=>s.element)),t.prepend(n),this._layer=n,this.setVisible(this.count>0)}get visible(){return this.group.visible}setVisible(t){this.group.visible=!!t,this._layer.hidden=!t,this.dispatchEvent(new Event("change"))}update(t,e){if(this.visible)for(let{element:n,point:s}of this._labels)Na(n,s,t,e)}};var gv=8,df=4;function Vc(i,t,e,n="dialog"){let s=nt("div","ioviewer-popover ioviewer-floating");s.setAttribute("role",n),s.setAttribute("aria-label",e),s.hidden=!0,t.setAttribute("aria-haspopup",n),t.setAttribute("aria-expanded","false"),i.append(s);function r(l){s.contains(l.target)||(a(),Da(l))}s.addEventListener("keydown",l=>{l.key==="Escape"&&(l.preventDefault(),l.stopPropagation(),a(),t.focus())});function o(){s.hidden=!1,t.setAttribute("aria-expanded","true");let l=t.getBoundingClientRect(),{width:c}=s.getBoundingClientRect();s.style.left=`${Math.max(df,Math.min(l.left+l.width/2-c/2,innerWidth-c-df))}px`,s.style.bottom=`${innerHeight-l.top+gv}px`,window.addEventListener("pointerdown",r,!0),window.addEventListener("resize",a)}function a(){return s.hidden?!1:(s.hidden=!0,t.setAttribute("aria-expanded","false"),window.removeEventListener("pointerdown",r,!0),window.removeEventListener("resize",a),!0)}return{element:s,close:a,toggle:()=>s.hidden?o():a(),get isOpen(){return!s.hidden}}}function _v(i){return i?.initialWireframe?"wireframe":i?.initialWhiteMode?"white":"materials"}function ff({root:i,toolbar:t,scene:e,meta:n,grids:s}){let r=_=>t.element.querySelector(`[data-tool="${_}"]`),o=Vc(i,r("displayMode"),st("tool.displayMode"),"menu");o.element.classList.add("ioviewer-display-modes");let a=(n.textures??[]).length>0,l=Nc.map(_=>{let m=nt("button","ioviewer-menu-item",st(`display.${_}`));return m.type="button",m.dataset.mode=_,m.setAttribute("role","menuitemradio"),m.disabled=_==="textures"&&!a,m.addEventListener("click",()=>{c(_),o.close()}),o.element.append(m),m}),c=_=>{e.setDisplayMode(_);for(let m of l)m.setAttribute("aria-checked",String(m.dataset.mode===_))};c(_v(n.options)),t.setHandler("displayMode",o.toggle),t.setActive("grids",s.visible),s.count>0&&t.setHandler("grids",()=>{s.setVisible(!s.visible),t.setActive("grids",s.visible)});let h=Vc(i,r("opacity"),st("tool.opacity"));h.element.classList.add("ioviewer-opacity");let d=nt("input","ioviewer-opacity-slider");Object.assign(d,{type:"range",min:"10",max:"100",step:"1",value:"100"}),d.setAttribute("aria-label",st("opacity.label"));let u=nt("output","ioviewer-opacity-value"),p=()=>u.textContent=st("opacity.value").replace("{value}",d.value);d.addEventListener("input",()=>{e.setOpacity(Number(d.value)/100),p()}),p();let g=nt("div","ioviewer-opacity-head");return g.append(nt("span","",st("opacity.label")),u),h.element.append(g,d),t.setHandler("opacity",h.toggle),[o,h]}var xv=3e3;function Hc(i,t){let e=nt("button",i);return e.type="button",e.setAttribute("title",t),e.append(nt("span","ioviewer-row-name",t)),e}function pf({shell:i,toolbar:t,controls:e,views:n}){let s=null,r=nt("button","ioviewer-text-button ioviewer-tour");r.type="button";function o(g){let _=g.camera.projection==="orthographic";e.setProjection(_?"orthographic":"perspective"),t.setActive("projection",_),e.animateTo({position:new C(...g.camera.position),target:new C(...g.camera.target)})}function a(){o(n[s.next]),s.next=(s.next+1)%n.length;let g=()=>{e.animating?s.frame=requestAnimationFrame(g):s.timer=setTimeout(a,xv)};s.frame=requestAnimationFrame(g)}let l=g=>r.contains(g.target),c=g=>!l(g)&&d(),h=g=>!(l(g)&&(g.key==="Enter"||g.key===" "))&&d();function d(){s&&(cancelAnimationFrame(s.frame),clearTimeout(s.timer),s=null,window.removeEventListener("pointerdown",c,!0),window.removeEventListener("wheel",d,!0),window.removeEventListener("keydown",h,!0),p())}function u(){s={next:0,frame:0,timer:0},window.addEventListener("pointerdown",c,!0),window.addEventListener("wheel",d,!0),window.addEventListener("keydown",h,!0),p(),a()}function p(){let g=st(s?"views.stop":"views.tour");r.replaceChildren(),r.insertAdjacentHTML("afterbegin",s?Zt.pause:Zt.play),r.append(nt("span","",g)),r.setAttribute("aria-pressed",String(!!s))}r.addEventListener("click",()=>s?d():u()),p(),i.registerPanel("views",{icon:Zt.views,title:st("panel.views"),visible:n.length>0,render:g=>{let _=nt("div","ioviewer-row-list");for(let m of n){let f=Hc("ioviewer-row ioviewer-view",String(m.name??""));f.addEventListener("click",()=>o(m)),_.append(f)}g.append(r,_)}})}function mf(i,t){let e=[];for(let[n,s]of[[i.element1Id,i.element1Name],[i.element2Id,i.element2Name]]){let r=n!=null?t.findIndex(o=>o.id===n&&!o.mesh):vv(t,s);r>=0&&!e.includes(r)&&e.push(r)}return e}function vv(i,t){if(t==null)return-1;let e=i.findIndex(n=>n.name===t);return e>=0&&i.findIndex((n,s)=>s>e&&n.name===t)<0?e:-1}var yv=["New","Active","Reviewed","Approved","Resolved"],Mv=14,Sv=2,bv=new C(1,-1,.8).normalize();function gf({shell:i,tests:t,elements:e,scene:n,controls:s,selection:r}){let o=new be;o.name="clash-marker";let a=new re;a.setAttribute("position",new we(new Float32Array(3),3));let l={color:"#ef4444",depthTest:!1,depthWrite:!1,transparent:!0},c=new wi(a,new ni({...l,size:Mv,sizeAttenuation:!1}));c.frustumCulled=!1,c.renderOrder=1002,o.add(c),o.visible=!1;let h=null;function d(){o.visible=!1,h?.removeAttribute("aria-current"),h=null,s.setExtraBounds("clash",null)}function u(_,m){let f=mf(_,e);f.length?r.set(f):r.clear();let w=new C(..._.point),R=0;for(let y of f){let T=n.elementBox(y);T.isEmpty()||(R=Math.max(R,...T.getSize(new C).toArray()))}let S=s.camera.position.clone().sub(s.target);S.lengthSq()<1e-12&&S.copy(bv);let b=Math.max(3*R,Sv);s.animateTo({position:S.normalize().multiplyScalar(b).add(w),target:w}),a.attributes.position.setXYZ(0,w.x,w.y,w.z),a.attributes.position.needsUpdate=!0,o.visible=!0,h?.removeAttribute("aria-current"),h=m,m.setAttribute("aria-current","true"),s.setExtraBounds("clash",new se(w.clone(),w.clone()))}function p(_){let m=Hc("ioviewer-row ioviewer-clash",String(_.name??""));m.setAttribute("title",`${_.name??""}: ${_.element1Name??""} \u2014 ${_.element2Name??""}`);let f=yv.includes(_.status),w=nt("span","ioviewer-clash-status",f?st(`clash.${_.status}`):String(_.status??""));return f&&(w.dataset.status=_.status),m.append(w,nt("span","ioviewer-clash-distance",kc(_.distance))),m.addEventListener("click",()=>u(_,m)),m}function g(_){let m=nt("div","ioviewer-clash-test"),f=nt("button","ioviewer-row ioviewer-clash-header");f.type="button",f.setAttribute("aria-expanded","true");let w=`${_.name??""} (${_.clashes.length})`;f.setAttribute("title",w),f.insertAdjacentHTML("afterbegin",Zt.chevron),f.append(nt("span","ioviewer-row-name",w));let R=nt("div","ioviewer-row-list");return R.append(..._.clashes.map(p)),f.addEventListener("click",()=>{R.hidden=!R.hidden,f.setAttribute("aria-expanded",String(!R.hidden))}),m.append(f,R),m}return i.registerPanel("clashes",{icon:Zt.clashes,title:st("panel.clashes"),visible:t.length>0,render:_=>_.append(...t.map(g)),onClose:()=>o.visible&&d()}),{group:o}}var wv=["construct","demolish","temporary"],Ev=Date.parse("0002-01-01T00:00:00Z");function _f(i){let t=typeof i=="string"?Date.parse(i):NaN;return t>=Ev?t:NaN}function xf(i,t){let e=new Map(t.flatMap((o,a)=>o.mesh?[]:[[o.id,a]])),n=[];for(let o of Array.isArray(i?.tasks)?i.tasks:[]){let a=_f(o?.start),l=_f(o?.end);if(!wv.includes(o?.type)||!(a<=l))continue;let c=Array.isArray(o.attachedElements)?o.attachedElements:[];n.push({name:o.name,type:o.type,start:a,end:l,elements:c.filter(h=>e.has(h)).map(h=>e.get(h))})}let s=n.reduce((o,a)=>Math.min(o,a.start),1/0),r=n.reduce((o,a)=>Math.max(o,a.end),-1/0);return{start:s,end:r,tasks:n}}function vf(i,t){let e=new Set,n=new Map;for(let s of i){let r=ts.end,a=s.type==="construct"?r:s.type==="demolish"?o:r||o;for(let l of s.elements)a?e.add(l):!r&&!o&&n.set(l,s.type)}for(let s of e)n.delete(s);return{hidden:e,active:n}}var Tv=864e5,Av=["1","5","15"],Rv={construct:"#22c55e",demolish:"#ef4444",temporary:"#a855f7"},Cv="156px";function yf({root:i,shell:t,timeliner:e,elements:n,scene:s}){let r=xf(e,n);if(r.tasks.length===0)return t.registerPanel("timeliner",{icon:Zt.timeliner,title:st("panel.timeliner"),visible:!1,render:()=>{}}),null;let o=new Intl.DateTimeFormat(void 0,{dateStyle:"short",timeZone:"UTC"}),a=new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:"UTC"}),l=r.start,c=1,h=0,d=0,u=!1,p=new Set,g=new Map,_=nt("div","ioviewer-player ioviewer-floating");_.setAttribute("role","group"),_.setAttribute("aria-label",st("player.title")),_.hidden=!0;let m=qe("ioviewer-icon-button ioviewer-player-play",Zt.play,st("player.play"));m.addEventListener("click",()=>h?D():P());let f=nt("output","ioviewer-player-date"),w=nt("input","ioviewer-player-slider");Object.assign(w,{type:"range",min:String(r.start),max:String(r.end),step:"any"}),w.setAttribute("aria-label",st("player.date")),w.addEventListener("input",()=>y(Number(w.value)));let R=vr(st("player.speed"),Av.map(O=>[O,`${O}\xD7`]),O=>{c=Number(O),R.update(O)});R.update(String(c));let S=qe("ioviewer-panel-close",Zt.close,st("close"));S.addEventListener("click",v),_.append(m,f,w,R.element,S),i.append(_);function b(){let O=vf(r.tasks,l),I=[...p].filter(B=>!O.hidden.has(B)),k=[...O.hidden].filter(B=>!p.has(B));p=O.hidden;let X=[...g.keys()].filter(B=>O.active.get(B)!==g.get(B)),Y=new Map;for(let[B,U]of O.active)g.get(B)!==U&&(Y.has(U)||Y.set(U,[]),Y.get(U).push(B));g=O.active,I.length&&s.setLayerHidden("schedule",I,!1),k.length&&s.setLayerHidden("schedule",k,!0),X.length&&s.setTint(X,null);for(let[B,U]of Y)s.setTint(U,Rv[B])}function y(O){l=Math.min(Math.max(O,r.start),r.end),w.value=String(l),f.textContent=a.format(l),u&&b()}function T(){u||(u=!0,_.hidden=!1,i.style.setProperty("--toolbar-space",Cv),y(r.start))}function v(){u&&(D(),u=!1,s.setTint([...g.keys()],null),g=new Map,p=new Set,s.clearLayer("schedule"),_.hidden=!0,i.style.removeProperty("--toolbar-space"))}function E(O){y(l+Math.max(O-d,0)/1e3*c*Tv),d=O,l>=r.end?D():h=requestAnimationFrame(E)}function P(){T(),!h&&(l>=r.end&&y(r.start),d=performance.now(),h=requestAnimationFrame(E),z())}function D(){cancelAnimationFrame(h),h=0,z()}function z(){let O=st(h?"player.pause":"player.play");m.innerHTML=h?Zt.pause:Zt.play,m.title=O,m.setAttribute("aria-label",O)}return t.registerPanel("timeliner",{icon:Zt.timeliner,title:st("panel.timeliner"),render:O=>{let I=nt("button","ioviewer-text-button ioviewer-player-open");I.type="button",I.insertAdjacentHTML("afterbegin",Zt.play),I.append(nt("span","",st("player.play"))),I.addEventListener("click",P);let k=nt("div","ioviewer-row-list");for(let X of r.tasks){let Y=String(X.name??""),B=nt("div","ioviewer-row ioviewer-task");B.dataset.type=X.type,B.setAttribute("title",`${Y} \xB7 ${st(`task.${X.type}`)}`);let U=`${o.format(X.start)} \u2013 ${o.format(X.end)}`;B.append(nt("span","ioviewer-row-name",Y),nt("span","ioviewer-task-dates",U)),k.append(B)}O.append(I,k)}}),{open:T,close:v,play:P,pause:D,setDate:y,get date(){return l},get isOpen(){return u}}}function Mf({shell:i,files:t,elements:e,scene:n}){let s=t.map((o,a)=>({file:o,layer:`link:${a}`,members:e.flatMap((l,c)=>l.link!=null&&(l.link===o.name||l.link===o.path)?[c]:[])})),r=(o,a)=>a?n.clearLayer(o.layer):n.setLayerHidden(o.layer,o.members,!0);i.registerPanel("links",{icon:Zt.links,title:st("panel.links"),visible:s.length>0,render:o=>{let a=nt("div","ioviewer-row-list");for(let l of s){let c=String(l.file.name??""),h=nt("label","ioviewer-row ioviewer-check ioviewer-link");h.setAttribute("title",`${c} (${l.file.path??""})`);let d=nt("input");d.type="checkbox",d.checked=l.members.length===0||l.file.visible!==!1,d.disabled=l.members.length===0,d.addEventListener("change",()=>r(l,d.checked)),d.checked||r(l,!1),h.append(d,nt("span","ioviewer-row-name",c),nt("span","ioviewer-row-count",String(l.members.length))),a.append(h)}o.append(a)}})}document.documentElement.lang=mr;var pi=document.getElementById("ioviewer-canvas");function yr(i){let t=document.getElementById("ioviewer-message");t.textContent=i,t.hidden=!1}var Sf=pi.getContext("webgl2",{antialias:!0});if(!Sf)yr(st("noWebGL"));else try{Iv(ju(),Sf)}catch(i){if(!(i instanceof Li))throw i;yr(st("badData"))}function Iv(i,t){i.meta.title&&(document.title=i.meta.title);let e=new Ss({canvas:pi,context:t,antialias:!0});e.setPixelRatio(window.devicePixelRatio),e.localClippingEnabled=!0;let n=new Mi;n.background=new Dt("#1f2228");let s=new Zs("#ffffff","#8a8f99",1.6);s.position.set(0,0,1),n.add(s);let r=new Ge(45,1,.1,1e3);r.up.set(0,0,1),r.position.set(1,-1,.8);let o=new Ks("#ffffff",1.4);o.position.set(.3,.6,0),r.add(o,o.target),o.target.position.set(0,0,-1),n.add(r);let a=window.IOVIEWER_TEST??{},l=_();i.releaseBinary();let c=new Oa(l,S,Math.min(l.totalCost,2e7));n.add(l.root);let h=new Ga(l,l.visibleBox()),d=new qa(h,pi);n.add(d.group);let u=document.getElementById("ioviewer-ui"),p=new Ya(u),g=new $a(u,i.meta.grids??[]);n.add(p.group,g.group);function _(){try{return new pr(i,{...a,maxTextureSize:e.capabilities.maxTextureSize})}catch(I){if(!(I instanceof fr))throw I;yr(st("tooLarge").replace("{count}",I.count).replace("{limit}",I.limit));let k=Y=>({o:0,n:0,t:Y}),X={count:0,geometry:k("u32"),material:k("u32"),element:k("i32"),matrix:k("f32")};return new pr({...i,meta:{...i.meta,instances:X}})}}i.meta.instances.count===0&&yr(st("noGeometry"));let m=!1;pi.addEventListener("webglcontextlost",()=>{m=!0,yr(st("contextLost"))});let f,w=new Promise(I=>f=I),R=!1;function S(){R||m||(R=!0,c.requested(performance.now()),requestAnimationFrame(()=>{if(R=!1,m)return;let I=pi.getBoundingClientRect();y.camera.updateMatrixWorld(),d.update(y.camera),p.update(y.camera,I),g.update(y.camera,I),c.begin(y.camera,I.height,performance.now()),e.render(n,y.camera),c.end(performance.now()),O.update(),f()}))}l.root.addEventListener("change",S),h.addEventListener("change",S),d.addEventListener("change",S),p.addEventListener("change",S),g.addEventListener("change",S);let b=new Ba(e,l,r,{pickFloat:a.pickFloat}),y=new Pa(r,pi,{pick:(I,k)=>b.pick(I,k)?.point??null,visibleBox:()=>l.visibleBox()});y.addEventListener("change",()=>{b.camera=y.camera,S()}),l.root.addEventListener("change",I=>{I.detail.kind==="visibility"&&(y.invalidateBounds(),y.resize())});let T=new ka;T.addEventListener("change",()=>l.setHighlighted([...T.values()]));let v=ef({root:u,data:i,scene:l,controls:y,picker:b,selection:T,measure:p});uf({root:u,...v,sections:h,selection:T});let{meta:E}=i;pf({...v,controls:y,views:E.viewpoints??[]});let P=gf({...v,tests:E.clashTests??[],elements:E.elements,scene:l,controls:y,selection:T});n.add(P.group);let D=yf({root:u,...v,timeliner:E.timeliner,elements:E.elements,scene:l});Mf({...v,files:E.linkedFiles??[],elements:E.elements,scene:l});for(let I of ff({root:u,toolbar:v.toolbar,scene:l,meta:i.meta,grids:g}))v.onEscape(I.close);v.toolbar.setHandler("distance",()=>{p.setActive(!p.active),v.toolbar.setActive("distance",p.active)}),v.toolbar.setActive("distance",!1),v.onEscape(()=>p.clear());let z=()=>{y.setExtraBounds("grids",g.visible?g.box:null),y.setExtraBounds("measure",p.box)};g.addEventListener("change",z),p.addEventListener("change",z),z(),y.addEventListener("click",I=>{let{x:k,y:X,ctrl:Y,shift:B}=I.detail,U=b.pick(k,X),H=U?.element??-1;p.active?U&&p.add(U.point):Y?H>=0&&T.toggle(H):H>=0?T.set([H]):B||T.clear()}),y.addEventListener("boxselect",I=>{if(p.active)return;let{rect:k,mode:X,ctrl:Y,shift:B}=I.detail;if(k.width<4||k.height<4){!Y&&!B&&T.clear();return}let U=new se,H=Vd(k,X,Hd(l,y.camera,pi.getBoundingClientRect())),{planes:Z,intersection:rt}=h.clipping(),at=H.filter(Ft=>!Ha(l.elementBox(Ft,U),Z,rt));T.set(Y?[...T.values(),...at]:at)}),window.addEventListener("resize",()=>{e.setSize(window.innerWidth,window.innerHeight,!1),y.resize()}),e.setSize(window.innerWidth,window.innerHeight,!1),y.resize(),y.fitBox(l.visibleBox(),!1);let O=new Fa(document.getElementById("ioviewer-ui"),r,y);window.addEventListener("pointerdown",I=>{(I.target===pi||O.element.contains(I.target))&&document.activeElement?.blur()},!0),window.__ioviewer={scene:l,motionDetail:c,controls:y,picker:b,selection:T,ui:v,viewCube:O,sections:h,sectionGizmo:d,measure:p,grids:g,clashes:P,timeliner:D,ready:w,get camera(){return y.camera}}}})(); +${r.id} +${r.sourceId??""}`.toLowerCase()));let s=[];for(let r=0;r{let s=e.category??"";t.has(s)||t.set(s,[]),t.get(s).push(n)}),[...t].map(([e,n])=>({name:e,indices:n})).sort((e,n)=>e.name.localeCompare(n.name))}function Yd(i,{elements:t,selection:e,scene:n,activate:s}){let r=Qx(t),o=new Int32Array(t.length);r.forEach((b,y)=>b.indices.forEach(T=>o[T]=y));let a=new Uint8Array(r.length),l=jx(t),c="",h=new Int32Array(0),d=-1,u=nt("input","ioviewer-tree-search");u.type="search",u.placeholder=st("searchPlaceholder"),u.setAttribute("aria-label",st("searchPlaceholder"));let p=nt("div","ioviewer-tree");p.setAttribute("role","tree"),p.setAttribute("aria-label",st("panel.model"));let g=nt("div","ioviewer-tree-spacer");p.append(g);let _=nt("div","ioviewer-tree-empty",st("noMatches"));i.append(u,p,_);function m(){if(c)h=Int32Array.from(l(c));else{let b=r.length;r.forEach((T,v)=>a[v]&&(b+=T.indices.length)),h=new Int32Array(b);let y=0;r.forEach((T,v)=>{if(h[y++]=-(v+1),a[v])for(let E of T.indices)h[y++]=E})}g.style.height=`${h.length*Ui}px`,_.hidden=h.length>0||!c,f()}function f(){let{start:b,end:y}=Kx(p.scrollTop,Ui,p.clientHeight,h.length,Jx),T=[];for(let v=b;vp.scrollTop+y&&(p.scrollTop=T+Ui-y)}function S(b){let y=o[b];!c&&!a[y]&&(a[y]=1,m());let T=h.indexOf(b);T>=0&&R(T),f()}u.addEventListener("input",()=>{c=u.value.trim(),p.scrollTop=0,m()}),p.addEventListener("scroll",f),p.addEventListener("click",b=>{let y=b.target.closest(".tree-row");if(!y)return;let T=h[Number(y.dataset.row)];if(T>=0){s(T);return}a[-T-1]^=1,m()}),new ResizeObserver(()=>{d>=0&&p.clientHeight>0&&(R(d),d=-1),f()}).observe(p),e.addEventListener("change",()=>{let b=-1;for(let y of e.values())b=y;b>=0?S(b):f()}),n.root.addEventListener("change",b=>b.detail.kind==="visibility"&&f()),m()}var $d={element:["properties","hide","isolate","showAll","-","zoomTo","sectionBox","-","copyId"],empty:["showAll","fitAll","resetSections"]},tv={hide:"H",isolate:"I",showAll:"Shift+H",zoomTo:"F"},Va=4;function Zd(i){let t=nt("div","ioviewer-context-menu");t.setAttribute("role","menu"),t.hidden=!0,i.append(t);let e=new Map(Object.values($d).flat().filter(l=>l!=="-").map(l=>[l,{enabled:!1,handler:null}]));function n(){t.hidden||(t.hidden=!0,t.replaceChildren(),window.removeEventListener("pointerdown",r,!0),window.removeEventListener("wheel",n,!0),window.removeEventListener("scroll",s,!0),window.removeEventListener("resize",n))}function s(l){i.contains(l.target)||n()}function r(l){t.contains(l.target)||(n(),Da(l))}function o(l,c,h,d){let u=$d[l];if(!u)throw new RangeError(`Unknown menu: ${l}`);n();for(let _ of u){if(_==="-"){t.append(nt("div","ioviewer-menu-separator"));continue}let m=e.get(_),f=nt("button","ioviewer-menu-item");f.type="button",f.dataset.item=_,f.setAttribute("role","menuitem"),f.disabled=!m.enabled,f.append(nt("span","",st(`menu.${_}`)),nt("kbd","",tv[_]??"")),f.addEventListener("click",()=>{n(),m.handler(d)}),t.append(f)}t.hidden=!1;let{width:p,height:g}=t.getBoundingClientRect();t.style.left=`${Math.max(Va,Math.min(c,innerWidth-p-Va))}px`,t.style.top=`${Math.max(Va,Math.min(h,innerHeight-g-Va))}px`,window.addEventListener("pointerdown",r,!0),window.addEventListener("wheel",n,!0),window.addEventListener("scroll",s,!0),window.addEventListener("resize",n)}function a(l,{enabled:c=!0,handler:h}){let d=e.get(l);if(!d)throw new RangeError(`Unknown menu item: ${l}`);h&&(d.handler=h),d.enabled=c&&!!d.handler}return{element:t,open:o,close:n,setItem:a,get isOpen(){return!t.hidden}}}var ev="input, textarea, select, [contenteditable]";function nv(i){if(i.ctrlKey||i.altKey||i.metaKey||i.repeat||i.target?.closest?.(ev))return null;switch(i.code){case"KeyH":return i.shiftKey?"showAll":"hide";case"KeyI":return i.shiftKey?null:"isolate";case"KeyF":return i.shiftKey?null:"zoomTo";case"Escape":return"escape";default:return null}}function Jd(i,t){i.addEventListener("keydown",e=>{let n=nv(e);n&&(e.preventDefault(),t[n]())})}var iv=1600;function Kd(i,t,e){let n=()=>i.textContent=`${st("selected")}: ${t.size} \xB7 ${st("elements")}: ${e}`;t.addEventListener("change",n),n()}function jd(i,t){if(!t)return;let e=nt("div","ioviewer-banner ioviewer-floating");e.setAttribute("role","status");let n=st("truncated").replace("{meshes}",t.meshes).replace("{total}",t.totalMeshes),s=qe("ioviewer-panel-close",Zt.close,st("close"));s.addEventListener("click",()=>e.remove()),e.append(nt("span","",n),s),i.append(e)}function Ts(i,t){let e=nt("div","ioviewer-toast",t);e.setAttribute("role","status"),i.append(e),setTimeout(()=>e.remove(),iv)}function Qd(i,t){let e=nt("table");for(let[s,r]of t)e.insertRow().append(nt("th","",s),nt("td","",String(r??"")));let n=nt("section");return n.append(nt("h3","",i),e),n}function tf(i,t,e){let n=nt("h2","ioviewer-properties-title"),s=nt("button","ioviewer-properties-close","\xD7");s.type="button",s.title=st("close"),s.setAttribute("aria-label",st("close")),s.addEventListener("click",()=>t.clear());let r=nt("header");r.append(n,s);let o=nt("div","ioviewer-properties-body");i.replaceChildren(r,o),i.hidden=!0,t.addEventListener("change",()=>{if(i.hidden=t.size===0,o.replaceChildren(),t.size!==1){n.textContent=`${st("selected")}: ${t.size}`;return}let[a]=t.values(),l=e.meta.elements[a];n.textContent=l.name||`#${l.id}`;let c=[[st("id"),l.id],[st("category"),l.category],[st("type"),l.type],[st("level"),l.level]];l.sourceId&&c.push([st("sourceId"),l.sourceId]),l.link&&c.push([st("link"),l.link]),o.append(Qd(st("elementGroup"),c));for(let[h,d]of Object.entries(e.props(l.id)??{}))o.append(Qd(h,Object.entries(d)))})}async function sv(i){if(await navigator.clipboard?.writeText(i).then(()=>!0,()=>!1))return!0;let t=document.createElement("textarea");t.value=i,t.setAttribute("readonly",""),t.className="ioviewer-offscreen",document.body.append(t),t.select();try{return document.execCommand("copy")}finally{t.remove()}}function ef({root:i,data:t,scene:e,controls:n,picker:s,selection:r,measure:o}){let{meta:a}=t,l=()=>n.fitBox(e.visibleBox()),c={hide:()=>r.size&&e.setVisible([...r.values()],!1),isolate:()=>r.size&&e.isolate([...r.values()]),showAll:()=>e.showAll(),zoomTo:()=>{if(!r.size)return l();let[m,...f]=r.values(),w=e.elementBox(m);for(let R of f)w.union(e.elementBox(R));n.zoomToBox(w)},fitAll:l},h=Wd(i);h.registerPanel("model",{icon:Zt.model,title:st("panel.model"),render:m=>Yd(m,{elements:a.elements,selection:r,scene:e,activate:f=>{r.set([f]),n.zoomToBox(e.elementBox(f))}})});let d=document.createElement("aside");d.id="ioviewer-properties",d.className="ioviewer-floating",i.append(d),tf(d,r,t);let u=qd(i,Xd),p=m=>{n.setLeftButtonMode(m),u.setActive("orbit",m==="orbit"),u.setActive("pan",m==="pan")};u.setHandler("orbit",()=>p("orbit")),u.setHandler("pan",()=>p("pan")),p(n.leftButtonMode),u.setHandler("fit",l),u.setHandler("projection",()=>{let m=!n.camera.isOrthographicCamera;n.setProjection(m?"orthographic":"perspective"),u.setActive("projection",m)}),u.setActive("projection",!1),document.fullscreenEnabled&&(u.setHandler("fullscreen",()=>{(document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()).catch(()=>{u.setActive("fullscreen",!!document.fullscreenElement),Ts(i,st("fullscreenUnavailable"))})}),document.addEventListener("fullscreenchange",()=>u.setActive("fullscreen",!!document.fullscreenElement)));let g=Zd(i);g.setItem("properties",{handler:m=>r.set([m])});for(let m of["hide","isolate","showAll","zoomTo","fitAll"])g.setItem(m,{handler:c[m]});g.setItem("copyId",{handler:m=>sv(String(a.elements[m].id)).then(f=>Ts(i,st(f?"idCopied":"copyFailed")),()=>Ts(i,st("copyFailed")))}),n.addEventListener("contextmenu",m=>{let{x:f,y:w}=m.detail;if(o.active){let[S]=r.values();return S===void 0?g.open("empty",f,w,null):g.open("element",f,w,S)}let R=s.pick(f,w);if(!R||R.element<0)return g.open("empty",f,w,null);r.has(R.element)||r.set([R.element]),g.open("element",f,w,R.element)});let _=[];return Jd(window,{...c,escape:()=>{if(g.isOpen)return g.close();_.some(m=>m())||r.clear()}}),Kd(document.getElementById("ioviewer-status"),r,e.elementCount),jd(i,a.truncated),{shell:h,toolbar:u,contextMenu:g,onEscape:m=>_.push(m)}}var rv={x:new C(1,0,0),y:new C(0,1,0),z:new C(0,0,1)};function zc(i){let t=rv[i];if(!t)throw new RangeError(`Unknown axis: ${i}`);return t.clone()}function nf(i,t,e,n){let s=i.clone().normalize(),r=s.dot(t)+e;return n?new ge(s,-r):new ge(s.negate(),r)}function sf(i,t){if(t!=="keepInside"&&t!=="cutInside")throw new RangeError(`Unknown box mode: ${t}`);let{min:e,max:n}=i,s=[new ge(new C(1,0,0),-e.x),new ge(new C(-1,0,0),n.x),new ge(new C(0,1,0),-e.y),new ge(new C(0,-1,0),n.y),new ge(new C(0,0,1),-e.z),new ge(new C(0,0,-1),n.z)];return t==="keepInside"?{planes:s,intersection:!1}:{planes:s.map(r=>r.negate()),intersection:!0}}function rf(i){let{min:t,max:e}=i;return Array.from({length:8},(n,s)=>new C(s&1?e.x:t.x,s&2?e.y:t.y,s&4?e.z:t.z))}function Ha(i,t,e){if(t.length===0||i.isEmpty())return!1;let n=rf(i),s=r=>n.every(o=>r.distanceToPoint(o)<0);return e?t.every(s):t.some(s)}function of(i,t){let e=i.getSize(new C).multiplyScalar(.5);return Math.abs(t.x)*e.x+Math.abs(t.y)*e.y+Math.abs(t.z)*e.z}function _r(i,t,e){let n=t.clone().sub(i.origin),s=e.dot(i.direction),r=1-s*s;return r<.03?null:(s*i.direction.dot(n)-e.dot(n))/r}function af(i,t,e){let n=new C(1,0,0).applyQuaternion(t),s=new C(0,1,0).applyQuaternion(t),[r,o,a,l]=[1/0,-1/0,1/0,-1/0];for(let h of rf(i)){h.sub(e);let d=h.dot(n),u=h.dot(s);[r,o,a,l]=[Math.min(r,d),Math.max(o,d),Math.min(a,u),Math.max(l,u)]}return{center:e.clone().addScaledVector(n,(r+o)/2).addScaledVector(s,(a+l)/2),width:(o-r)*1.1,height:(l-a)*1.1}}var xr=6,ov=["off","planes","box"],av=.05,lf=new C(0,0,1),Ga=class extends EventTarget{constructor(t,e){super(),this.scene=t,this.modelBox=e.clone(),this.center=e.getCenter(new C),this.planes=[],this.active=-1,this.box=null,this.boxMode="keepInside",this.gizmoVisible=!0,this._mode="off"}get mode(){return this._mode}setMode(t){if(!ov.includes(t))throw new RangeError(`Unknown section mode: ${t}`);t==="box"&&!this.box&&this.fitBoxToShown()||(this._mode=t,this._changed(!0))}addPlane(t){let e=zc(t);if(this.planes.length>=xr||this.modelBox.isEmpty())return null;let n=new Ne().setFromUnitVectors(lf,e);return this.planes.push({axis:t,quaternion:n,offset:0,flipped:!1,enabled:!0,rotated:!1}),this.active=this.planes.length-1,this._mode="planes",this._changed(!0),this.active}removePlane(t){this._plane(t),this.planes.splice(t,1),(this.active>=this.planes.length||this.active>t)&&this.active--,this._changed(!0)}updatePlane(t,{offset:e,flipped:n,enabled:s,quaternion:r}){let o=this._plane(t);if(e!==void 0){if(!Number.isFinite(e))throw new RangeError(`Offset must be a finite number, got ${e}.`);o.offset=e}n!==void 0&&(o.flipped=!!n),s!==void 0&&(o.enabled=!!s),r&&(o.quaternion.copy(r).normalize(),o.rotated=this.normal(t).angleTo(zc(o.axis))>1e-6),this._changed(!0)}setActivePlane(t){this._plane(t),this.active=t,this._changed(!1)}normal(t){return lf.clone().applyQuaternion(this._plane(t).quaternion)}extent(t){return of(this.modelBox,this.normal(t))}origin(t){return this.center.clone().addScaledVector(this.normal(t),this._plane(t).offset)}clipPlane(t){let e=this._plane(t);return nf(this.normal(t),this.center,e.offset,e.flipped)}setBox(t){if(t.isEmpty())throw new RangeError("The section box is empty.");this.box=t.clone(),this._mode="box",this._changed(!0)}setBoxMode(t){if(t!=="keepInside"&&t!=="cutInside")throw new RangeError(`Unknown box mode: ${t}`);this.boxMode=t,this._changed(!0)}fitBoxToElements(t){let e=new se;for(let s of t)e.union(this.scene.elementBox(s));if(e.isEmpty())return!1;let n=e.getSize(new C);return this.setBox(e.expandByScalar(av*Math.max(n.x,n.y,n.z))),!0}fitBoxToShown(){let t=this.scene.visibleBox();if(t.isEmpty())return!1;let e=t.getSize(new C);return this.setBox(t.expandByScalar(.001*Math.max(e.x,e.y,e.z))),!0}setGizmoVisible(t){this.gizmoVisible=!!t,this._changed(!1)}reset(){this.planes=[],this.active=-1,this.box=null,this._mode="off",this._changed(!0)}clipping(){return this._mode==="planes"?{planes:this.planes.flatMap((e,n)=>e.enabled?[this.clipPlane(n)]:[]),intersection:!1}:this._mode==="box"&&this.box?sf(this.box,this.boxMode):{planes:[],intersection:!1}}isCut(t){let{planes:e,intersection:n}=this.clipping();return Ha(t,e,n)}_plane(t){let e=this.planes[t];if(!Number.isInteger(t)||!e)throw new RangeError(`Plane index ${t} is out of range 0..${this.planes.length-1}.`);return e}_changed(t){if(t){let{planes:e,intersection:n}=this.clipping();this.scene.setClipping(e,n)}this.dispatchEvent(new Event("change"))}};var lv=80,cv=8,fi={x:"#ef4444",y:"#22c55e",z:"#3b82f6",centre:"#e2e8f0",hover:"#facc15",frame:"#93c5fd",fill:"#60a5fa"},Wa=["x","y","z"],hv=Math.cos(Oe.degToRad(12)),cf=new C(0,0,1),uv=i=>new cn({color:i,depthTest:!1,depthWrite:!1,transparent:!0,opacity:.9}),dv=new cn({visible:!1});function Xa(i,t,e,n){let s=new be,r=uv(t);for(let a of e)s.add(new _e(a,r));let o=new _e(n,dv);return o.userData.handle=s,s.add(o),s.userData={kind:i,color:t,material:r,proxy:o},s.traverse(a=>a.renderOrder=1e3),s}var qa=class extends EventTarget{constructor(t,e){super(),this.sections=t,this.dom=e,this.camera=null,this.group=new be,this.group.name="sections",this._raycaster=new Ai,this._drag=null,this._hovered=null,this._buildPlanes(),this._buildPlaneGizmo(),this._buildBox(),t.addEventListener("change",()=>this._sync()),(e.parentElement??e).addEventListener("pointerdown",n=>this._down(n),!0),e.addEventListener("pointermove",n=>this._move(n)),e.addEventListener("pointerup",n=>this._up(n)),e.addEventListener("pointercancel",()=>this._end()),e.addEventListener("lostpointercapture",()=>this._end()),this._sync()}get dragging(){return this._drag!==null}update(t){this.camera=t;let e=this.planeGizmo;e.visible&&(e.scale.setScalar(this._worldPerPixel(e.position)*lv),this.arrow.visible=this._across(cf.clone().applyQuaternion(e.quaternion),e.position));for(let n of this.boxHandles){let{shown:s,axis:r}=n.userData;n.visible=s&&(!r||this._across(new C().setComponent(Wa.indexOf(r),1),n.position)),n.visible&&n.scale.setScalar(this._worldPerPixel(n.position)*cv)}}_across(t,e){let n=this.camera,s=n.isOrthographicCamera?n.getWorldDirection(new C):e.clone().sub(n.position).normalize();return Math.abs(t.dot(s))new C(r/2,o/2,0))),n=new cn({color:fi.fill,transparent:!0,opacity:.12,side:Be,depthWrite:!1}),s=new sn({color:fi.frame});this.planeVisuals=Array.from({length:xr},()=>{let r=new be;return r.add(new _e(t,n),new Hs(e,s)),this.group.add(r),r})}_buildPlaneGizmo(){let t=(s,r)=>s.rotateX(Math.PI/2).translate(0,0,r),e=Xa("move",fi.z,[t(new Ei(.025,.025,1),.5),t(new qs(.08,.25),1.125)],t(new Ei(.12,.12,1.25),.625));e.name="arrow",this.arrow=e;let n=(s,r,o)=>{let a=Xa("rotate",fi[r],[o(new us(.8,.018,8,64))],o(new us(.8,.09,8,48)));return a.name=s,a.userData.axis=r,a};this.planeGizmo=new be,this.planeGizmo.add(e,n("ringX","x",s=>s.rotateY(Math.PI/2)),n("ringY","y",s=>s.rotateX(Math.PI/2))),this.group.add(this.planeGizmo)}_buildBox(){this.boxWire=new gn(new Ti(new hn(1,1,1)),new sn({color:fi.frame})),this.group.add(this.boxWire);let t=new hs(1,16,12),e=new hs(1.6,8,6);this.boxHandles=[];for(let s of Wa)for(let r of[-1,1]){let o=Xa("face",fi[s],[t],e);o.name=`face${r>0?"+":"-"}${s}`,Object.assign(o.userData,{axis:s,sign:r}),this.boxHandles.push(o)}let n=Xa("centre",fi.centre,[new hn(1.4,1.4,1.4)],e);n.name="centre",this.boxHandles.push(n),this.group.add(...this.boxHandles)}_sync(){let t=this.sections,e=t.mode==="planes";this.planeVisuals.forEach((r,o)=>{let a=t.planes[o];if(r.visible=e&&!!a?.enabled,!r.visible)return;let l=af(t.modelBox,a.quaternion,t.origin(o));r.position.copy(l.center),r.quaternion.copy(a.quaternion),r.scale.set(l.width,l.height,1)});let n=t.planes[t.active];this.planeGizmo.visible=e&&t.gizmoVisible&&!!n?.enabled,this.planeGizmo.visible&&(this.planeGizmo.position.copy(t.origin(t.active)),this.planeGizmo.quaternion.copy(n.quaternion));let s=t.mode==="box"?t.box:null;this.boxWire.visible=!!s;for(let r of this.boxHandles)r.visible=r.userData.shown=!!s&&t.gizmoVisible;if(s){let r=s.getCenter(new C),o=s.getSize(new C);this.boxWire.position.copy(r),this.boxWire.scale.copy(o).max(new C(1e-6,1e-6,1e-6));for(let a of this.boxHandles){a.position.copy(r);let{axis:l,sign:c}=a.userData;l&&(a.position[l]=c>0?s.max[l]:s.min[l])}}}_worldPerPixel(t){let e=this.camera,n=Math.max(this.dom.clientHeight,1);if(e.isOrthographicCamera)return(e.top-e.bottom)/e.zoom/n;let s=t.clone().sub(e.position).dot(e.getWorldDirection(new C));return 2*Math.max(s,e.near)*Math.tan(Oe.degToRad(e.fov/2))/n}_ray(t){let e=this.dom.getBoundingClientRect(),n=new zt((t.clientX-e.left)/e.width*2-1,1-(t.clientY-e.top)/e.height*2);return this.camera.updateMatrixWorld(),this._raycaster.setFromCamera(n,this.camera),this._raycaster.ray.clone()}_hit(t){if(!this.camera)return null;this.update(this.camera),this.group.updateMatrixWorld(!0);let e=r=>{for(;r&&r!==this.group;r=r.parent)if(!r.visible)return!1;return!0},n=[];this.group.traverse(r=>r.userData.handle&&e(r)&&n.push(r)),this._ray(t);let s=this._raycaster.intersectObjects(n,!1)[0];return s?s.object.userData.handle:null}_down(t){if(t.target!==this.dom||t.button!==0)return;let e=this._hit(t),n=e&&this._start(e,this._ray(t));n&&(t.stopPropagation(),t.preventDefault(),this.dom.setPointerCapture(t.pointerId),this._drag={id:t.pointerId,handle:e,...n},this._highlight(e))}_start(t,e){let n=this.sections,{kind:s}=t.userData;if(s==="move"||s==="rotate"){let c=n.active,h=n.origin(c),d=n.planes[c].quaternion.clone();if(s==="move"){let _=n.normal(c),m=_r(e,h,_);return m===null?null:{index:c,origin:h,normal:_,offset:n.planes[c].offset,s0:m}}let u=new C().setComponent(Wa.indexOf(t.userData.axis),1).applyQuaternion(d),p=new ge().setFromNormalAndCoplanarPoint(u,h),g=e.intersectPlane(p,new C)?.sub(h);return g?{index:c,origin:h,quaternion:d,axis:u,plane:p,from:g}:null}let r=n.box.clone();if(s==="face"){let{axis:c,sign:h}=t.userData,d=new C().setComponent(Wa.indexOf(c),h),u=t.position.clone(),p=_r(e,u,d);return p===null?null:{box:r,normal:d,at:u,s0:p}}let o=this.camera.getWorldDirection(new C),a=new ge().setFromNormalAndCoplanarPoint(o,r.getCenter(new C)),l=e.intersectPlane(a,new C);return l?{box:r,plane:a,from:l}:null}_move(t){let e=this._drag;if(!e)return this._hover(t);if(t.pointerId!==e.id)return;if(t.buttons===0)return this._end();let n=this._ray(t),s=this.sections,{kind:r}=e.handle.userData;if(r==="move"){let o=_r(n,e.origin,e.normal);o!==null&&s.updatePlane(e.index,{offset:e.offset+o-e.s0})}else if(r==="rotate"){let o=n.intersectPlane(e.plane,new C)?.sub(e.origin);if(!o)return;let a=Math.atan2(e.axis.dot(new C().crossVectors(e.from,o)),e.from.dot(o)),l=new Ne().setFromAxisAngle(e.axis,a).multiply(e.quaternion),c=cf.clone().applyQuaternion(l);s.updatePlane(e.index,{quaternion:l,offset:c.dot(e.origin.clone().sub(s.center))})}else if(r==="face"){let o=_r(n,e.at,e.normal);if(o===null)return;let{axis:a,sign:l}=e.handle.userData,c=e.box.clone(),h=.001*Math.max(...s.modelBox.getSize(new C).toArray(),.001),d=(l>0?e.box.max[a]:e.box.min[a])+l*(o-e.s0);l>0?c.max[a]=Math.max(d,c.min[a]+h):c.min[a]=Math.min(d,c.max[a]-h),s.setBox(c)}else{let o=n.intersectPlane(e.plane,new C);o&&s.setBox(e.box.clone().translate(o.sub(e.from)))}}_up(t){this._drag&&t.pointerId===this._drag.id&&this._end()}_end(){this._drag&&(this._drag=null,this._highlight(null))}_hover(t){t.buttons===0&&this._highlight(this._hit(t))}_highlight(t){if(this._hovered!==t){for(let e of[this._hovered,t])e&&e.userData.material.color.set(e===t?fi.hover:e.userData.color);this._hovered=t,this.dom.style.cursor=t?"grab":"",this.dispatchEvent(new Event("change"))}}};function vr(i,t,e){let n=nt("div","ioviewer-segmented");n.setAttribute("role","group"),n.setAttribute("aria-label",i);let s=t.map(([o,a])=>{let l=nt("button","",a);return l.type="button",l.dataset.value=o,l.addEventListener("click",()=>e(o)),n.append(l),l});return{element:n,update:o=>s.forEach(a=>a.setAttribute("aria-pressed",String(a.dataset.value===o)))}}function hf(i,t){let e=nt("label","ioviewer-check"),n=nt("input");return n.type="checkbox",n.addEventListener("change",()=>t(n.checked)),e.append(n,nt("span","",i)),{element:e,input:n}}function fv(i,t){let e=nt("li","ioviewer-section-plane");e.addEventListener("pointerdown",()=>i.active!==t&&i.setActivePlane(t));let n=nt("span","ioviewer-section-name"),s=qe("ioviewer-icon-button",Zt.flip,st("sections.flip"));s.addEventListener("click",()=>i.updatePlane(t,{flipped:!i.planes[t].flipped}));let r=hf(st("sections.enabled"),h=>i.updatePlane(t,{enabled:h})),o=qe("ioviewer-icon-button",Zt.remove,st("sections.delete"));o.addEventListener("click",()=>i.removePlane(t));let a=nt("input","ioviewer-section-slider");a.type="range",a.step="any",a.setAttribute("aria-label",st("sections.position")),a.addEventListener("input",()=>i.updatePlane(t,{offset:Number(a.value)}));let l=nt("div","ioviewer-section-head");return l.append(n,s,r.element,o),e.append(l,a),{element:e,update:()=>{let h=i.planes[t],d=h.rotated?` (${st("sections.rotated")})`:"";n.textContent=`${t+1}. ${h.axis.toUpperCase()}${d}`,e.classList.toggle("active",i.active===t),s.setAttribute("aria-pressed",String(h.flipped)),r.input.checked=h.enabled;let u=i.extent(t);a.min=String(-u),a.max=String(u),a.value=String(h.offset)}}}function uf({root:i,shell:t,toolbar:e,contextMenu:n,sections:s,selection:r}){let o="z",a=()=>{s.addPlane(o)===null&&(s.setMode("planes"),Ts(i,st("sections.maxPlanes")))},l=()=>[...r.values()],c=()=>s.fitBoxToElements(l()),h=vr(st("sections.mode"),[["planes",st("sections.planes")],["box",st("sections.box")],["off",st("sections.off")]],E=>s.setMode(E)),d=vr(st("sections.axis"),["x","y","z"].map(E=>[E,E.toUpperCase()]),E=>d.update(o=E));d.update(o);let u=nt("button","ioviewer-text-button",st("sections.addPlane"));u.type="button",u.addEventListener("click",a);let p=nt("ul","ioviewer-section-list"),g=nt("div","ioviewer-section-empty",st("sections.noPlanes")),_=nt("div","ioviewer-section-block"),m=nt("div","ioviewer-section-row");m.append(nt("span","",st("sections.axis")),d.element,u),_.append(m,p,g);let f=vr(st("sections.box"),[["keepInside",st("sections.keepInside")],["cutInside",st("sections.cutInside")]],E=>s.setBoxMode(E)),w=nt("button","ioviewer-text-button",st("sections.fitSelection"));w.type="button",w.addEventListener("click",c);let R=nt("div","ioviewer-section-block");R.append(f.element,w);let S=hf(st("sections.showGizmo"),E=>s.setGizmoVisible(E)),b=[],y=()=>{h.update(s.mode),_.hidden=s.mode!=="planes",R.hidden=s.mode!=="box",u.disabled=s.planes.length>=xr,b.length!==s.planes.length&&(b=s.planes.map((E,P)=>fv(s,P)),p.replaceChildren(...b.map(E=>E.element))),b.forEach(E=>E.update()),g.hidden=b.length>0,f.update(s.boxMode),S.input.checked=s.gizmoVisible,e.setActive("plane",s.mode==="planes"),e.setActive("box",s.mode==="box")},T=()=>w.disabled=r.size===0;if(t.registerPanel("sections",{icon:Zt.sections,title:st("panel.sections"),visible:!s.modelBox.isEmpty(),render:E=>E.append(h.element,_,R,S.element)}),s.addEventListener("change",y),r.addEventListener("change",T),y(),T(),e.setActive("plane",!1),e.setActive("box",!1),s.modelBox.isEmpty())return;let v=()=>t.openPanel("sections");e.setHandler("plane",()=>{a(),v()}),e.setHandler("box",()=>{!(r.size&&c())&&!s.fitBoxToShown()||v()}),e.setHandler("resetSections",()=>s.reset()),n.setItem("sectionBox",{handler:E=>{s.fitBoxToElements(r.has(E)?l():[E]),v()}}),n.setItem("resetSections",{handler:()=>s.reset()})}var pv="#facc15",mv=9;function kc(i,t=mr){return`${new Intl.NumberFormat(t,{minimumFractionDigits:2,maximumFractionDigits:2,useGrouping:!1}).format(i)} ${st("unit.m",t)}`}var Ya=class extends EventTarget{constructor(t){super(),this.active=!1,this.points=[],this.group=new be,this.group.name="measure";let e=new re;e.setAttribute("position",new we(new Float32Array(6),3));let n={color:pv,depthTest:!1,depthWrite:!1,transparent:!0};this._line=new bi(e,new sn(n)),this._markers=new wi(e,new ni({...n,size:mv,sizeAttenuation:!1}));for(let s of[this._line,this._markers])s.frustumCulled=!1,s.renderOrder=1001,this.group.add(s);this.label=nt("div","ioviewer-measure-label"),this.label.hidden=!0,t.prepend(this.label),this._sync()}get distance(){return this.points.length===2?this.points[0].distanceTo(this.points[1]):null}get box(){return new se().setFromPoints(this.points)}setActive(t){this.active=!!t,this.active||(this.points=[]),this._sync()}add(t){this.active&&(this.points=this.points.length===1?[this.points[0],t.clone()]:[t.clone()],this._sync())}clear(){return this.points.length===0?!1:(this.points=[],this._sync(),!0)}update(t,e){this.distance!==null&&Na(this.label,this.points[0].clone().lerp(this.points[1],.5),t,e)}_sync(){let t=this._line.geometry.attributes.position;this.points.forEach((e,n)=>t.setXYZ(n,e.x,e.y,e.z)),t.needsUpdate=!0,this._line.geometry.setDrawRange(0,this.points.length),this._line.visible=this.points.length===2,this._markers.visible=this.points.length>0,this.label.hidden=this.distance===null,this.distance!==null&&(this.label.textContent=kc(this.distance)),this.dispatchEvent(new Event("change"))}};var $a=class extends EventTarget{constructor(t,e){super(),this.count=e.length,this.group=new gn(new re().setAttribute("position",new ce(e.flatMap(s=>[...s.start,...s.end]),3)),new sn({color:"#93c5fd",transparent:!0,opacity:.55,depthTest:!1,depthWrite:!1})),this.group.name="grids",this.group.geometry.computeBoundingBox(),this.box=this.group.geometry.boundingBox,this.group.renderOrder=999,this._labels=e.flatMap(s=>[s.start,s.end].map(r=>({element:nt("div","ioviewer-grid-label",s.name),point:new C(...r)})));let n=nt("div","ioviewer-grid-labels");n.append(...this._labels.map(s=>s.element)),t.prepend(n),this._layer=n,this.setVisible(this.count>0)}get visible(){return this.group.visible}setVisible(t){this.group.visible=!!t,this._layer.hidden=!t,this.dispatchEvent(new Event("change"))}update(t,e){if(this.visible)for(let{element:n,point:s}of this._labels)Na(n,s,t,e)}};var gv=8,df=4;function Vc(i,t,e,n="dialog"){let s=nt("div","ioviewer-popover ioviewer-floating");s.setAttribute("role",n),s.setAttribute("aria-label",e),s.hidden=!0,t.setAttribute("aria-haspopup",n),t.setAttribute("aria-expanded","false"),i.append(s);function r(l){s.contains(l.target)||(a(),Da(l))}s.addEventListener("keydown",l=>{l.key==="Escape"&&(l.preventDefault(),l.stopPropagation(),a(),t.focus())});function o(){s.hidden=!1,t.setAttribute("aria-expanded","true");let l=t.getBoundingClientRect(),{width:c}=s.getBoundingClientRect();s.style.left=`${Math.max(df,Math.min(l.left+l.width/2-c/2,innerWidth-c-df))}px`,s.style.bottom=`${innerHeight-l.top+gv}px`,window.addEventListener("pointerdown",r,!0),window.addEventListener("resize",a)}function a(){return s.hidden?!1:(s.hidden=!0,t.setAttribute("aria-expanded","false"),window.removeEventListener("pointerdown",r,!0),window.removeEventListener("resize",a),!0)}return{element:s,close:a,toggle:()=>s.hidden?o():a(),get isOpen(){return!s.hidden}}}function _v(i){return i?.initialWireframe?"wireframe":i?.initialWhiteMode?"white":"materials"}function ff({root:i,toolbar:t,scene:e,meta:n,grids:s}){let r=_=>t.element.querySelector(`[data-tool="${_}"]`),o=Vc(i,r("displayMode"),st("tool.displayMode"),"menu");o.element.classList.add("ioviewer-display-modes");let a=(n.textures??[]).length>0,l=Nc.map(_=>{let m=nt("button","ioviewer-menu-item",st(`display.${_}`));return m.type="button",m.dataset.mode=_,m.setAttribute("role","menuitemradio"),m.disabled=_==="textures"&&!a,m.addEventListener("click",()=>{c(_),o.close()}),o.element.append(m),m}),c=_=>{e.setDisplayMode(_);for(let m of l)m.setAttribute("aria-checked",String(m.dataset.mode===_))};c(_v(n.options)),t.setHandler("displayMode",o.toggle),t.setActive("grids",s.visible),s.count>0&&t.setHandler("grids",()=>{s.setVisible(!s.visible),t.setActive("grids",s.visible)});let h=Vc(i,r("opacity"),st("tool.opacity"));h.element.classList.add("ioviewer-opacity");let d=nt("input","ioviewer-opacity-slider");Object.assign(d,{type:"range",min:"10",max:"100",step:"1",value:"100"}),d.setAttribute("aria-label",st("opacity.label"));let u=nt("output","ioviewer-opacity-value"),p=()=>u.textContent=st("opacity.value").replace("{value}",d.value);d.addEventListener("input",()=>{e.setOpacity(Number(d.value)/100),p()}),p();let g=nt("div","ioviewer-opacity-head");return g.append(nt("span","",st("opacity.label")),u),h.element.append(g,d),t.setHandler("opacity",h.toggle),[o,h]}var xv=3e3;function Hc(i,t){let e=nt("button",i);return e.type="button",e.setAttribute("title",t),e.append(nt("span","ioviewer-row-name",t)),e}function pf({shell:i,toolbar:t,controls:e,views:n}){let s=null,r=nt("button","ioviewer-text-button ioviewer-tour");r.type="button";function o(g){let _=g.camera.projection==="orthographic";e.setProjection(_?"orthographic":"perspective"),t.setActive("projection",_),e.animateTo({position:new C(...g.camera.position),target:new C(...g.camera.target)})}function a(){o(n[s.next]),s.next=(s.next+1)%n.length;let g=()=>{e.animating?s.frame=requestAnimationFrame(g):s.timer=setTimeout(a,xv)};s.frame=requestAnimationFrame(g)}let l=g=>r.contains(g.target),c=g=>!l(g)&&d(),h=g=>!(l(g)&&(g.key==="Enter"||g.key===" "))&&d();function d(){s&&(cancelAnimationFrame(s.frame),clearTimeout(s.timer),s=null,window.removeEventListener("pointerdown",c,!0),window.removeEventListener("wheel",d,!0),window.removeEventListener("keydown",h,!0),p())}function u(){s={next:0,frame:0,timer:0},window.addEventListener("pointerdown",c,!0),window.addEventListener("wheel",d,!0),window.addEventListener("keydown",h,!0),p(),a()}function p(){let g=st(s?"views.stop":"views.tour");r.replaceChildren(),r.insertAdjacentHTML("afterbegin",s?Zt.pause:Zt.play),r.append(nt("span","",g)),r.setAttribute("aria-pressed",String(!!s))}r.addEventListener("click",()=>s?d():u()),p(),i.registerPanel("views",{icon:Zt.views,title:st("panel.views"),visible:n.length>0,render:g=>{let _=nt("div","ioviewer-row-list");for(let m of n){let f=Hc("ioviewer-row ioviewer-view",String(m.name??""));f.addEventListener("click",()=>o(m)),_.append(f)}g.append(r,_)}})}function mf(i,t){let e=[];for(let[n,s]of[[i.element1Id,i.element1Name],[i.element2Id,i.element2Name]]){let r=n!=null?t.findIndex(o=>o.id===n&&!o.mesh):vv(t,s);r>=0&&!e.includes(r)&&e.push(r)}return e}function vv(i,t){if(t==null)return-1;let e=i.findIndex(n=>n.name===t);return e>=0&&i.findIndex((n,s)=>s>e&&n.name===t)<0?e:-1}var yv=["New","Active","Reviewed","Approved","Resolved"],Mv=14,Sv=2,bv=new C(1,-1,.8).normalize();function gf({shell:i,tests:t,elements:e,scene:n,controls:s,selection:r}){let o=new be;o.name="clash-marker";let a=new re;a.setAttribute("position",new we(new Float32Array(3),3));let l={color:"#ef4444",depthTest:!1,depthWrite:!1,transparent:!0},c=new wi(a,new ni({...l,size:Mv,sizeAttenuation:!1}));c.frustumCulled=!1,c.renderOrder=1002,o.add(c),o.visible=!1;let h=null;function d(){o.visible=!1,h?.removeAttribute("aria-current"),h=null,s.setExtraBounds("clash",null)}function u(_,m){let f=mf(_,e);f.length?r.set(f):r.clear();let w=new C(..._.point),R=0;for(let y of f){let T=n.elementBox(y);T.isEmpty()||(R=Math.max(R,...T.getSize(new C).toArray()))}let S=s.camera.position.clone().sub(s.target);S.lengthSq()<1e-12&&S.copy(bv);let b=Math.max(3*R,Sv);s.animateTo({position:S.normalize().multiplyScalar(b).add(w),target:w}),a.attributes.position.setXYZ(0,w.x,w.y,w.z),a.attributes.position.needsUpdate=!0,o.visible=!0,h?.removeAttribute("aria-current"),h=m,m.setAttribute("aria-current","true"),s.setExtraBounds("clash",new se(w.clone(),w.clone()))}function p(_){let m=Hc("ioviewer-row ioviewer-clash",String(_.name??""));m.setAttribute("title",`${_.name??""}: ${_.element1Name??""} \u2014 ${_.element2Name??""}`);let f=yv.includes(_.status),w=nt("span","ioviewer-clash-status",f?st(`clash.${_.status}`):String(_.status??""));return f&&(w.dataset.status=_.status),m.append(w,nt("span","ioviewer-clash-distance",kc(_.distance))),m.addEventListener("click",()=>u(_,m)),m}function g(_){let m=nt("div","ioviewer-clash-test"),f=nt("button","ioviewer-row ioviewer-clash-header");f.type="button",f.setAttribute("aria-expanded","true");let w=`${_.name??""} (${_.clashes.length})`;f.setAttribute("title",w),f.insertAdjacentHTML("afterbegin",Zt.chevron),f.append(nt("span","ioviewer-row-name",w));let R=nt("div","ioviewer-row-list");return R.append(..._.clashes.map(p)),f.addEventListener("click",()=>{R.hidden=!R.hidden,f.setAttribute("aria-expanded",String(!R.hidden))}),m.append(f,R),m}return i.registerPanel("clashes",{icon:Zt.clashes,title:st("panel.clashes"),visible:t.length>0,render:_=>_.append(...t.map(g)),onClose:()=>o.visible&&d()}),{group:o}}var wv=["construct","demolish","temporary"],Ev=Date.parse("0002-01-01T00:00:00Z");function _f(i){let t=typeof i=="string"?Date.parse(i):NaN;return t>=Ev?t:NaN}function xf(i,t){let e=new Map(t.flatMap((o,a)=>o.mesh?[]:[[o.id,a]])),n=[];for(let o of Array.isArray(i?.tasks)?i.tasks:[]){let a=_f(o?.start),l=_f(o?.end);if(!wv.includes(o?.type)||!(a<=l))continue;let c=Array.isArray(o.attachedElements)?o.attachedElements:[];n.push({name:o.name,type:o.type,start:a,end:l,elements:c.filter(h=>e.has(h)).map(h=>e.get(h))})}let s=n.reduce((o,a)=>Math.min(o,a.start),1/0),r=n.reduce((o,a)=>Math.max(o,a.end),-1/0);return{start:s,end:r,tasks:n}}function vf(i,t){let e=new Set,n=new Map;for(let s of i){let r=ts.end,a=s.type==="construct"?r:s.type==="demolish"?o:r||o;for(let l of s.elements)a?e.add(l):!r&&!o&&n.set(l,s.type)}for(let s of e)n.delete(s);return{hidden:e,active:n}}var Tv=864e5,Av=["1","5","15"],Rv={construct:"#22c55e",demolish:"#ef4444",temporary:"#a855f7"},Cv="156px";function yf({root:i,shell:t,timeliner:e,elements:n,scene:s}){let r=xf(e,n);if(r.tasks.length===0)return t.registerPanel("timeliner",{icon:Zt.timeliner,title:st("panel.timeliner"),visible:!1,render:()=>{}}),null;let o=new Intl.DateTimeFormat(void 0,{dateStyle:"short",timeZone:"UTC"}),a=new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:"UTC"}),l=r.start,c=1,h=0,d=0,u=!1,p=new Set,g=new Map,_=nt("div","ioviewer-player ioviewer-floating");_.setAttribute("role","group"),_.setAttribute("aria-label",st("player.title")),_.hidden=!0;let m=qe("ioviewer-icon-button ioviewer-player-play",Zt.play,st("player.play"));m.addEventListener("click",()=>h?D():P());let f=nt("output","ioviewer-player-date"),w=nt("input","ioviewer-player-slider");Object.assign(w,{type:"range",min:String(r.start),max:String(r.end),step:"any"}),w.setAttribute("aria-label",st("player.date")),w.addEventListener("input",()=>y(Number(w.value)));let R=vr(st("player.speed"),Av.map(O=>[O,`${O}\xD7`]),O=>{c=Number(O),R.update(O)});R.update(String(c));let S=qe("ioviewer-panel-close",Zt.close,st("close"));S.addEventListener("click",v),_.append(m,f,w,R.element,S),i.append(_);function b(){let O=vf(r.tasks,l),I=[...p].filter(B=>!O.hidden.has(B)),k=[...O.hidden].filter(B=>!p.has(B));p=O.hidden;let X=[...g.keys()].filter(B=>O.active.get(B)!==g.get(B)),Y=new Map;for(let[B,U]of O.active)g.get(B)!==U&&(Y.has(U)||Y.set(U,[]),Y.get(U).push(B));g=O.active,I.length&&s.setLayerHidden("schedule",I,!1),k.length&&s.setLayerHidden("schedule",k,!0),X.length&&s.setTint(X,null);for(let[B,U]of Y)s.setTint(U,Rv[B])}function y(O){l=Math.min(Math.max(O,r.start),r.end),w.value=String(l),f.textContent=a.format(l),u&&b()}function T(){u||(u=!0,_.hidden=!1,i.style.setProperty("--toolbar-space",Cv),y(r.start))}function v(){u&&(D(),u=!1,s.setTint([...g.keys()],null),g=new Map,p=new Set,s.clearLayer("schedule"),_.hidden=!0,i.style.removeProperty("--toolbar-space"))}function E(O){y(l+Math.max(O-d,0)/1e3*c*Tv),d=O,l>=r.end?D():h=requestAnimationFrame(E)}function P(){T(),!h&&(l>=r.end&&y(r.start),d=performance.now(),h=requestAnimationFrame(E),z())}function D(){cancelAnimationFrame(h),h=0,z()}function z(){let O=st(h?"player.pause":"player.play");m.innerHTML=h?Zt.pause:Zt.play,m.title=O,m.setAttribute("aria-label",O)}return t.registerPanel("timeliner",{icon:Zt.timeliner,title:st("panel.timeliner"),render:O=>{let I=nt("button","ioviewer-text-button ioviewer-player-open");I.type="button",I.insertAdjacentHTML("afterbegin",Zt.play),I.append(nt("span","",st("player.play"))),I.addEventListener("click",P);let k=nt("div","ioviewer-row-list");for(let X of r.tasks){let Y=String(X.name??""),B=nt("div","ioviewer-row ioviewer-task");B.dataset.type=X.type,B.setAttribute("title",`${Y} \xB7 ${st(`task.${X.type}`)}`);let U=`${o.format(X.start)} \u2013 ${o.format(X.end)}`;B.append(nt("span","ioviewer-row-name",Y),nt("span","ioviewer-task-dates",U)),k.append(B)}O.append(I,k)}}),{open:T,close:v,play:P,pause:D,setDate:y,get date(){return l},get isOpen(){return u}}}function Mf({shell:i,files:t,elements:e,scene:n}){let s=t.map((o,a)=>({file:o,layer:`link:${a}`,members:e.flatMap((l,c)=>l.link!=null&&(l.link===o.name||l.link===o.path)?[c]:[])})),r=(o,a)=>a?n.clearLayer(o.layer):n.setLayerHidden(o.layer,o.members,!0);i.registerPanel("links",{icon:Zt.links,title:st("panel.links"),visible:s.length>0,render:o=>{let a=nt("div","ioviewer-row-list");for(let l of s){let c=String(l.file.name??""),h=nt("label","ioviewer-row ioviewer-check ioviewer-link");h.setAttribute("title",`${c} (${l.file.path??""})`);let d=nt("input");d.type="checkbox",d.checked=l.members.length===0||l.file.visible!==!1,d.disabled=l.members.length===0,d.addEventListener("change",()=>r(l,d.checked)),d.checked||r(l,!1),h.append(d,nt("span","ioviewer-row-name",c),nt("span","ioviewer-row-count",String(l.members.length))),a.append(h)}o.append(a)}})}document.documentElement.lang=mr;var pi=document.getElementById("ioviewer-canvas");function yr(i){let t=document.getElementById("ioviewer-message");t.textContent=i,t.hidden=!1}var Sf=pi.getContext("webgl2",{antialias:!0});if(!Sf)yr(st("noWebGL"));else try{Iv(ju(),Sf)}catch(i){if(!(i instanceof Li))throw i;yr(st("badData"))}function Iv(i,t){i.meta.title&&(document.title=i.meta.title);let e=new Ss({canvas:pi,context:t,antialias:!0});e.setPixelRatio(window.devicePixelRatio),e.localClippingEnabled=!0;let n=new Mi;n.background=new Dt("#1f2228");let s=new Zs("#ffffff","#8a8f99",1.6);s.position.set(0,0,1),n.add(s);let r=new Ge(45,1,.1,1e3);r.up.set(0,0,1),r.position.set(1,-1,.8);let o=new Ks("#ffffff",1.4);o.position.set(.3,.6,0),r.add(o,o.target),o.target.position.set(0,0,-1),n.add(r);let a=window.IOVIEWER_TEST??{},l=_();i.releaseBinary();let c=new Oa(l,S,Math.min(l.totalCost,2e7));n.add(l.root);let h=new Ga(l,l.visibleBox()),d=new qa(h,pi);n.add(d.group);let u=document.getElementById("ioviewer-ui"),p=new Ya(u),g=new $a(u,i.meta.grids??[]);n.add(p.group,g.group);function _(){try{return new pr(i,{...a,maxTextureSize:e.capabilities.maxTextureSize})}catch(I){if(!(I instanceof fr))throw I;yr(st("tooLarge").replace("{count}",I.count).replace("{limit}",I.limit));let k=Y=>({o:0,n:0,t:Y}),X={count:0,geometry:k("u32"),material:k("u32"),element:k("i32"),matrix:k("f32")};return new pr({...i,meta:{...i.meta,instances:X}})}}i.meta.instances.count===0&&yr(st("noGeometry"));let m=!1;pi.addEventListener("webglcontextlost",()=>{m=!0,yr(st("contextLost"))});let f,w=new Promise(I=>f=I),R=!1;function S(){R||m||(R=!0,c.requested(performance.now()),requestAnimationFrame(()=>{if(R=!1,m)return;let I=pi.getBoundingClientRect();y.camera.updateMatrixWorld(),d.update(y.camera),p.update(y.camera,I),g.update(y.camera,I),c.begin(y.camera,I.height,performance.now()),e.render(n,y.camera),c.end(performance.now()),O.update(),f()}))}l.root.addEventListener("change",S),h.addEventListener("change",S),d.addEventListener("change",S),p.addEventListener("change",S),g.addEventListener("change",S);let b=new Ba(e,l,r,{pickFloat:a.pickFloat}),y=new Pa(r,pi,{pick:(I,k)=>b.pick(I,k)?.point??null,visibleBox:()=>l.visibleBox()});y.addEventListener("change",()=>{b.camera=y.camera,S()}),l.root.addEventListener("change",I=>{I.detail.kind==="visibility"&&(y.invalidateBounds(),y.resize())});let T=new ka;T.addEventListener("change",()=>l.setHighlighted([...T.values()]));let v=ef({root:u,data:i,scene:l,controls:y,picker:b,selection:T,measure:p});uf({root:u,...v,sections:h,selection:T});let{meta:E}=i;pf({...v,controls:y,views:E.viewpoints??[]});let P=gf({...v,tests:E.clashTests??[],elements:E.elements,scene:l,controls:y,selection:T});n.add(P.group);let D=yf({root:u,...v,timeliner:E.timeliner,elements:E.elements,scene:l});Mf({...v,files:E.linkedFiles??[],elements:E.elements,scene:l});for(let I of ff({root:u,toolbar:v.toolbar,scene:l,meta:i.meta,grids:g}))v.onEscape(I.close);v.toolbar.setHandler("distance",()=>{p.setActive(!p.active),v.toolbar.setActive("distance",p.active)}),v.toolbar.setActive("distance",!1),v.onEscape(()=>p.clear());let z=()=>{y.setExtraBounds("grids",g.visible?g.box:null),y.setExtraBounds("measure",p.box)};g.addEventListener("change",z),p.addEventListener("change",z),z(),y.addEventListener("click",I=>{let{x:k,y:X,ctrl:Y,shift:B}=I.detail,U=b.pick(k,X),H=U?.element??-1;p.active?U&&p.add(U.point):Y?H>=0&&T.toggle(H):H>=0?T.set([H]):B||T.clear()}),y.addEventListener("boxselect",I=>{if(p.active)return;let{rect:k,mode:X,ctrl:Y,shift:B}=I.detail;if(k.width<4||k.height<4){!Y&&!B&&T.clear();return}let U=new se,H=Vd(k,X,Hd(l,y.camera,pi.getBoundingClientRect())),{planes:Z,intersection:rt}=h.clipping(),at=H.filter(Ft=>!Ha(l.elementBox(Ft,U),Z,rt));T.set(Y?[...T.values(),...at]:at)}),window.addEventListener("resize",()=>{e.setSize(window.innerWidth,window.innerHeight,!1),y.resize()}),e.setSize(window.innerWidth,window.innerHeight,!1),y.resize(),y.fitBox(l.visibleBox(),!1);let O=new Fa(document.getElementById("ioviewer-ui"),r,y);window.addEventListener("pointerdown",I=>{(I.target===pi||O.element.contains(I.target))&&document.activeElement?.blur()},!0),window.__ioviewer={scene:l,motionDetail:c,controls:y,picker:b,selection:T,ui:v,viewCube:O,sections:h,sectionGizmo:d,measure:p,grids:g,clashes:P,timeliner:D,ready:w,get camera(){return y.camera}}}})(); /*! Bundled license information: three/build/three.core.js: diff --git a/src/IOViewer/Viewer/src/i18n.js b/src/IOViewer/Viewer/src/i18n.js index 53ede53..7d437f0 100644 --- a/src/IOViewer/Viewer/src/i18n.js +++ b/src/IOViewer/Viewer/src/i18n.js @@ -13,6 +13,7 @@ const dictionaries = { category: 'Категория', type: 'Тип', level: 'Уровень', + sourceId: 'ID источника', link: 'Связанный файл', panels: 'Панели', 'panel.model': 'Модель', @@ -120,6 +121,7 @@ const dictionaries = { category: 'Category', type: 'Type', level: 'Level', + sourceId: 'Source ID', link: 'Link', panels: 'Panels', 'panel.model': 'Model', diff --git a/src/IOViewer/Viewer/src/ui/properties.js b/src/IOViewer/Viewer/src/ui/properties.js index 1ad2465..d655b41 100644 --- a/src/IOViewer/Viewer/src/ui/properties.js +++ b/src/IOViewer/Viewer/src/ui/properties.js @@ -41,6 +41,7 @@ export function mountProperties(container, selection, data) { const element = data.meta.elements[index]; title.textContent = element.name || `#${element.id}`; const fields = [[t('id'), element.id], [t('category'), element.category], [t('type'), element.type], [t('level'), element.level]]; + if (element.sourceId) fields.push([t('sourceId'), element.sourceId]); if (element.link) fields.push([t('link'), element.link]); body.append(group(t('elementGroup'), fields)); for (const [name, values] of Object.entries(data.props(element.id) ?? {})) body.append(group(name, Object.entries(values))); diff --git a/src/IOViewer/Viewer/src/ui/tree.js b/src/IOViewer/Viewer/src/ui/tree.js index c5f98a9..e898c65 100644 --- a/src/IOViewer/Viewer/src/ui/tree.js +++ b/src/IOViewer/Viewer/src/ui/tree.js @@ -12,12 +12,12 @@ export function visibleRange(scrollTop, rowHeight, viewportHeight, total, oversc return { start, end: Math.max(start, end) }; } -// search(query) -> indices of elements whose name, type or Id contains the query, case-insensitively. +// search(query) -> indices of elements whose name, type, Id or source id contains the query, case-insensitively. export function createSearch(elements) { let texts = null; // lower-cased once, on the first search return (query) => { const q = query.trim().toLowerCase(); - texts ??= elements.map((e) => `${e.name ?? ''}\n${e.type ?? ''}\n${e.id}`.toLowerCase()); + texts ??= elements.map((e) => `${e.name ?? ''}\n${e.type ?? ''}\n${e.id}\n${e.sourceId ?? ''}`.toLowerCase()); const found = []; for (let i = 0; i < texts.length; i++) if (texts[i].includes(q)) found.push(i); return found; diff --git a/src/IOViewer/Viewer/test/e2e/ui.spec.js b/src/IOViewer/Viewer/test/e2e/ui.spec.js index b53f340..294d68e 100644 --- a/src/IOViewer/Viewer/test/e2e/ui.spec.js +++ b/src/IOViewer/Viewer/test/e2e/ui.spec.js @@ -269,6 +269,8 @@ test('tree: search by Id, type and name flattens the matches', async ({ page }) await expect(search).toHaveAttribute('placeholder', 'Поиск по имени, типу, Id…'); await search.fill('300063'); await expect(rows(page)).toHaveText(['Supply Duct SA-1Rectangular 600x400']); + await search.fill('2XQ$N5SLP'); + await expect(rows(page)).toHaveText(['Supply Duct SA-1Rectangular 600x400']); await search.fill('steel i'); await expect(rows(page)).toHaveCount(34); await search.fill('column d2'); @@ -402,6 +404,7 @@ test('hostile names and properties are shown as text and never run (Review Focus const errors = await open(page, 'escaping'); const name = ''; const script = ''; + const sourceId = '">'; await rail(page, 'model').click(); await rows(page).filter({ hasText: 'Walls' }).click(); const row = rows(page).filter({ hasText: name }); @@ -413,6 +416,7 @@ test('hostile names and properties are shown as text and never run (Review Focus await expect(panel.locator('h3').nth(1)).toHaveText(script); await expect(panel.locator('th', { hasText: 'window.__xss=2' })).toHaveText(script); await expect(panel.locator('td', { hasText: 'window.__xss=2' })).toHaveText(script); + await expect(panel.locator('tr', { hasText: 'ID источника' }).locator('td')).toHaveText(sourceId); expect(await page.evaluate(() => window.__xss)).toBeUndefined(); expect(await page.locator('#ioviewer-ui img').count()).toBe(0); expect(errors).toEqual([]); @@ -429,4 +433,14 @@ test.describe('in English', () => { await page.mouse.click(...blank, { button: 'right' }); await expect(menu(page).locator('.ioviewer-menu-item span')).toHaveText(['Show all', 'Fit all', 'Reset sections']); }); + + test('properties show the source id as its own row, only for elements that have one', async ({ page }) => { + await open(page); + const panel = page.locator('#ioviewer-properties'); + await page.evaluate((i) => window.__ioviewer.selection.set([i]), await indexOf(page, 'Supply Duct SA-1')); + await expect(panel.locator('table').first().locator('th')).toHaveText(['Id', 'Category', 'Type', 'Level', 'Source ID', 'Link']); + await expect(panel.locator('tr', { hasText: 'Source ID' }).locator('td')).toHaveText('2xQ$n5SLP5MBLyL442paFx'); + await page.evaluate((i) => window.__ioviewer.selection.set([i]), await indexOf(page, 'Column D2 Level 1')); + await expect(panel.locator('table').first().locator('th')).toHaveText(['Id', 'Category', 'Type', 'Level', 'Link']); + }); }); diff --git a/src/IOViewer/Viewer/test/unit/tree.test.js b/src/IOViewer/Viewer/test/unit/tree.test.js index 8cc0da5..14e96ba 100644 --- a/src/IOViewer/Viewer/test/unit/tree.test.js +++ b/src/IOViewer/Viewer/test/unit/tree.test.js @@ -21,7 +21,7 @@ test('visibleRange stays empty and in range when scrolled past the end (list shr const elements = [ { id: 300001, name: 'Ground Slab', category: 'Floors', type: 'Concrete 300' }, { id: 300002, name: 'Column A1', category: 'Structural Columns', type: 'Concrete 400x400' }, - { id: 300063, name: 'Supply Duct SA-1', category: 'Ducts', type: 'Rectangular 600x400' }, + { id: 300063, name: 'Supply Duct SA-1', category: 'Ducts', type: 'Rectangular 600x400', sourceId: '2xQ$n5SLP5MBLyL442paFx' }, { id: 7, name: '', category: '', type: null }, ]; @@ -40,6 +40,12 @@ test('search finds by type and name, ignoring case and surrounding spaces', () = assert.deepEqual(search('missing'), []); }); +test('search finds by source id, ignoring case; elements without one never match "undefined"', () => { + const search = createSearch(elements); + assert.deepEqual(search('2XQ$N5'), [2]); + assert.deepEqual(search('undefined'), []); +}); + test('groupByCategory sorts categories by name and keeps element order inside', () => { const groups = groupByCategory([...elements, { id: 9, name: 'Slab 2', category: 'Floors' }]); assert.deepEqual(groups.map((g) => [g.name, g.indices]), [ diff --git a/tests/IOViewer.Tests/HtmlViewerExporterTests.cs b/tests/IOViewer.Tests/HtmlViewerExporterTests.cs index acf51ef..20e87fc 100644 --- a/tests/IOViewer.Tests/HtmlViewerExporterTests.cs +++ b/tests/IOViewer.Tests/HtmlViewerExporterTests.cs @@ -143,6 +143,27 @@ public void Meta_HasFormat3AndRtcOffset() Assert.Equal("structure.rvt", meta.GetProperty("linkedFiles")[0].GetProperty("path").GetString()); } + [Fact] + public void Meta_WritesSourceIdOnlyWhenSet() + { + const string hostile = "\">"; + var doc = new ViewerDocument("Source ids"); + doc.Elements.Add(new ViewerElement { Id = 1, Name = "Wall", SourceId = "2xQ$n5SLP5MBLyL442paFx" }); + doc.Elements.Add(new ViewerElement { Id = 2, Name = "Door" }); + doc.Elements.Add(new ViewerElement { Id = 3, Name = "Empty", SourceId = "" }); + doc.Elements.Add(new ViewerElement { Id = 4, Name = "Hostile", SourceId = hostile }); + + var view = ViewerHtml.Export(doc); + var elements = view.Meta.GetProperty("elements"); + + Assert.Equal("2xQ$n5SLP5MBLyL442paFx", elements[0].GetProperty("sourceId").GetString()); + Assert.False(elements[1].TryGetProperty("sourceId", out _)); + Assert.False(elements[2].TryGetProperty("sourceId", out _)); + Assert.Equal(hostile, elements[3].GetProperty("sourceId").GetString()); + // The value is escaped inside the META block: the markup never reaches the page as is. + Assert.DoesNotContain(hostile, view.Html); + } + [Fact] public void Save_WritesSameHtmlAsExportToString() { diff --git a/tests/IOViewer.Tests/JsonAndImportTests.cs b/tests/IOViewer.Tests/JsonAndImportTests.cs index eff6ea2..79cea16 100644 --- a/tests/IOViewer.Tests/JsonAndImportTests.cs +++ b/tests/IOViewer.Tests/JsonAndImportTests.cs @@ -72,6 +72,22 @@ public void JsonRoundtrip_KeepsGetterOnlyCollections() Assert.Equal("T1.1", Assert.Single(importedTask.Children).Id); } + [Fact] + public void JsonRoundtrip_KeepsSourceIdAndOmitsItWhenNull() + { + var doc = new ViewerDocument("Source ids"); + doc.Elements.Add(new ViewerElement { Id = 1, Name = "Wall", SourceId = "2xQ$n5SLP5MBLyL442paFx" }); + doc.Elements.Add(new ViewerElement { Id = 2, Name = "Door" }); + + string json = JsonViewerExporter.ExportToString(doc); + Assert.Equal(1, json.Split("\"SourceId\"").Length - 1); + + using var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)); + var imported = JsonViewerImporter.Load(ms); + Assert.Equal("2xQ$n5SLP5MBLyL442paFx", imported.Elements[0].SourceId); + Assert.Null(imported.Elements[1].SourceId); + } + [Fact] public void ObjViewerImporter_ParsesWavefrontObj() { diff --git a/tests/fixtures/viewer/building.json b/tests/fixtures/viewer/building.json index 3d828d1..e757639 100644 --- a/tests/fixtures/viewer/building.json +++ b/tests/fixtures/viewer/building.json @@ -3219,6 +3219,7 @@ "TypeName": "Rectangular 600x400", "Level": "Level 2", "LinkName": "MEP.rvt", + "SourceId": "2xQ$n5SLP5MBLyL442paFx", "Properties": { "Identity Data": { "Mark": "Supply Duct SA-1" diff --git a/tests/fixtures/viewer/escaping.json b/tests/fixtures/viewer/escaping.json index 6c0a410..9ae3fde 100644 --- a/tests/fixtures/viewer/escaping.json +++ b/tests/fixtures/viewer/escaping.json @@ -43,6 +43,7 @@ "TypeName": "Generic 200", "Level": "Level 1", "LinkName": null, + "SourceId": "\">", "Properties": { "": { "": "" diff --git a/tests/fixtures/viewer/generate-fixtures.cs b/tests/fixtures/viewer/generate-fixtures.cs index d1628ab..71866cb 100644 --- a/tests/fixtures/viewer/generate-fixtures.cs +++ b/tests/fixtures/viewer/generate-fixtures.cs @@ -112,6 +112,7 @@ void AddMesh(ViewerElement? e, ViewerMesh m) AddBox(curtain, new(0, -0.72, 0.1), new(18, -0.68, 2 * storey), glass, 0.35); var duct = Element("Supply Duct SA-1", "Ducts", "Rectangular 600x400", "Level 2", "MEP.rvt"); +duct.SourceId = "2xQ$n5SLP5MBLyL442paFx"; // IFC GlobalId style duct.AddProperty("Mechanical", "System", "Supply Air"); AddBox(duct, new(8.7, -1, storey - slab - 0.4), new(9.3, 13, storey - slab), ductColor); @@ -198,17 +199,22 @@ void AddBox(ViewerElement e, Vector3d min, Vector3d max, RgbaColor color, double AddMesh(e, new ViewerMesh { Name = e.Name, Vertices = v, Indices = i, Normals = n, Color = color, Opacity = opacity }); } -// Hostile markup in an element name and in a property group, name and value: the viewer must show it as text. +// Hostile markup in an element name, source id and in a property group, name and value: the viewer must show it as text. // No clashes, 4D, links or viewpoints, so the panels for them stay hidden. ViewerDocument Escaping() { const string script = ""; + const string sourceId = "\">"; var hostile = new ViewerDocument("Escaping"); string[] names = ["", "Plain Wall", "Plain Slab"]; for (int k = 0; k < names.Length; k++) { var e = new ViewerElement { Id = 500001 + k, Name = names[k], Category = "Walls", TypeName = "Generic 200", Level = "Level 1" }; - if (k == 0) e.AddProperty(script, script, script); + if (k == 0) + { + e.AddProperty(script, script, script); + e.SourceId = sourceId; + } else e.AddProperty("Identity Data", "Mark", names[k]); var (v, i, n) = Box(new(k * 3, 0, 0), new(k * 3 + 2, 0.3, 3)); e.MeshIndices.Add(k); From 1f8df99ce2acd223c6ea97635db3428144539c64 Mon Sep 17 00:00:00 2001 From: mie <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:21:22 +0300 Subject: [PATCH 02/19] Add IOViewer.IfcIO adapter for IfcIO models IfcViewerAdapter converts an IfcModel into a ViewerDocument in metres with Z up: one element per IFC product with the STEP id as Id, the GlobalId as SourceId, the storey as Level, and property and quantity sets as property groups. Colours come from IfcIO materials, with a fallback per IFC class, and work schedules become the TimeLiner. IfcIO bakes mapped items into world meshes, so every mesh is its own; the conversion report says so and lists elements without geometry by reason. The IfcIO version is set in EngineVersions.props (IfcIOVersion, 0.11.0, the first release with world placements); until it is published, restore needs a local feed. --- Directory.Packages.props | 2 + EngineVersions.props | 9 + IOViewer.slnx | 2 + src/IOViewer.IfcIO/IOViewer.IfcIO.csproj | 16 + src/IOViewer.IfcIO/IfcViewerAdapter.cs | 264 +++++++++++++ src/IOViewer.IfcIO/IfcViewerConversion.cs | 79 ++++ src/IOViewer.IfcIO/IfcViewerOptions.cs | 15 + src/IOViewer.IfcIO/icon.png | Bin 0 -> 1918 bytes .../IOViewer.IfcIO.Tests.csproj | 20 + .../IfcViewerAdapterTests.cs | 360 ++++++++++++++++++ 10 files changed, 767 insertions(+) create mode 100644 EngineVersions.props create mode 100644 src/IOViewer.IfcIO/IOViewer.IfcIO.csproj create mode 100644 src/IOViewer.IfcIO/IfcViewerAdapter.cs create mode 100644 src/IOViewer.IfcIO/IfcViewerConversion.cs create mode 100644 src/IOViewer.IfcIO/IfcViewerOptions.cs create mode 100644 src/IOViewer.IfcIO/icon.png create mode 100644 tests/IOViewer.IfcIO.Tests/IOViewer.IfcIO.Tests.csproj create mode 100644 tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index eb93399..e2fb820 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,10 @@ + true + diff --git a/EngineVersions.props b/EngineVersions.props new file mode 100644 index 0000000..0842e08 --- /dev/null +++ b/EngineVersions.props @@ -0,0 +1,9 @@ + + + + 0.11.0 + + diff --git a/IOViewer.slnx b/IOViewer.slnx index 7ed60cb..b58cdf0 100644 --- a/IOViewer.slnx +++ b/IOViewer.slnx @@ -1,5 +1,7 @@ + + diff --git a/src/IOViewer.IfcIO/IOViewer.IfcIO.csproj b/src/IOViewer.IfcIO/IOViewer.IfcIO.csproj new file mode 100644 index 0000000..a2b1339 --- /dev/null +++ b/src/IOViewer.IfcIO/IOViewer.IfcIO.csproj @@ -0,0 +1,16 @@ + + + + true + IOViewer.IfcIO + Converts IFC models read by IfcIO into IOViewer documents for standalone interactive 3D HTML viewers, keeping GlobalIds, storeys, property sets and quantities. + bim;ifc;ifcio;ioviewer;webgl;html-viewer;3d-viewer + true + + + + + + + + diff --git a/src/IOViewer.IfcIO/IfcViewerAdapter.cs b/src/IOViewer.IfcIO/IfcViewerAdapter.cs new file mode 100644 index 0000000..e66ffbd --- /dev/null +++ b/src/IOViewer.IfcIO/IfcViewerAdapter.cs @@ -0,0 +1,264 @@ +using System.Globalization; +using IfcIO; +using IfcIO.Model; +using IfcIO.Model.Sequence; +using IfcIO.Schema; +using IOViewer.Model; + +namespace IOViewer.IfcIO; + +/// +/// Converts an read by IfcIO into a . +/// +/// +/// +/// The adapter works on the model in memory and never reads the file again. Geometry is IfcIO's world mesh of each +/// element (), scaled from to metres by an +/// exact factor; IFC is Z up like the viewer, so axes are not swapped. Coordinates stay doubles; the HTML exporter +/// moves them relative to the model centre. +/// +/// +/// Each element of (and of with +/// ) becomes one : +/// is , which for a model opened with +/// is the element's STEP entity id (#123 gives 123), stable for a given file; +/// is the GlobalId; is the IFC class as IfcIO +/// reports it (upper case, for example IFCWALL); is the name of the storey +/// that contains it. Property sets and quantity sets become property groups named after the set; quantity values are +/// written as IfcIO reads them, in the file's units, with invariant formatting. +/// +/// +/// Colour and transparency come from (IfcIO reads the colour of +/// IfcSurfaceStyleShading); elements without one get a default colour for their IFC class. IfcIO's 4D work +/// schedules become . Openings are not converted. +/// +/// +public static class IfcViewerAdapter +{ + /// + /// Converts into a viewer document. + /// + /// The model, for example from . + /// Conversion options; null uses the defaults. + /// The document, in metres with Z up. + /// Two converted elements have the same . + public static ViewerDocument ToViewerDocument(IfcModel model, IfcViewerOptions? options = null) => + Convert(model, options).Document; + + /// + /// Converts into a viewer document and reports what was converted. + /// + /// The model, for example from . + /// Conversion options; null uses the defaults. + /// The document with a report of elements without geometry, meshes, units and skipped 4D tasks. + /// Two converted elements have the same . + public static IfcViewerConversion Convert(IfcModel model, IfcViewerOptions? options = null) + { + ArgumentNullException.ThrowIfNull(model); + options ??= new IfcViewerOptions(); + + double scale = MetresPerUnit(model.LengthUnit); + var doc = new ViewerDocument(model.ProjectName); + var withoutGeometry = new List(); + var materials = new Dictionary(ReferenceEqualityComparer.Instance); + var converted = new Dictionary(ReferenceEqualityComparer.Instance); + var ids = new HashSet(); + + foreach (var storey in model.Storeys) + { + var elements = options.IncludeSpaces ? storey.Elements.Concat(storey.Spaces) : storey.Elements; + foreach (var element in elements) + { + // Mesh ElementIds, 4D tasks and clashes all key on Id, so a repeat would merge two elements. + if (!ids.Add(element.Id)) + { + throw new ArgumentException( + $"Element id {element.Id} ('{element.Name}') is used twice. Ids are unique in models read with IfcModel.Open; " + + "a model built in memory numbers elements per storey, so save and reopen it first.", + nameof(model)); + } + + converted.Add(element, element.Id); + var issue = AddElement(doc, element, storey.Name, scale, materials); + if (issue is { } reason) + { + withoutGeometry.Add(new IfcViewerElementWithoutGeometry(element.Id, element.GlobalId, reason)); + } + } + } + + int skippedTasks = AddSchedule(doc, model.WorkSchedules, converted); + doc.ComputeBoundingBox(); + return new IfcViewerConversion(doc, model.LengthUnit, scale, withoutGeometry, skippedTasks); + } + + // Exact lengths of the units IfcIO reports; it already converts any other file unit to metres while reading. + private static double MetresPerUnit(IfcLengthUnit unit) => unit switch + { + IfcLengthUnit.Millimetre => 0.001, + IfcLengthUnit.Metre => 1.0, + IfcLengthUnit.Foot => 0.3048, + IfcLengthUnit.Inch => 0.0254, + _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, "Unknown IFC length unit."), + }; + + private static IfcViewerGeometryIssue? AddElement( + ViewerDocument doc, IfcElement element, string level, double scale, Dictionary materials) + { + var viewerElement = new ViewerElement + { + Id = element.Id, + Name = element.Name, + Category = element.IfcType, + TypeName = element.TypeProduct?.Name ?? string.Empty, + Level = level, + SourceId = element.GlobalId, + }; + foreach (var pset in element.PropertySets) + { + foreach (var property in pset.Properties) + { + viewerElement.AddProperty(pset.Name, property.Name, property.Value); + } + } + foreach (var qset in element.QuantitySets) + { + foreach (var quantity in qset.Quantities) + { + viewerElement.AddProperty(qset.Name, quantity.Name, quantity.Value.ToString(CultureInfo.InvariantCulture)); + } + } + doc.Elements.Add(viewerElement); + + var source = element.GetEffectiveMesh(); + if (source is null) return IfcViewerGeometryIssue.NoGeometry; + if (source.Indices.Count == 0 || source.Vertices.Count == 0) return IfcViewerGeometryIssue.EmptyMesh; + + var vertices = new Vector3d[source.Vertices.Count]; + for (int i = 0; i < vertices.Length; i++) + { + var v = source.Vertices[i]; + if (!double.IsFinite(v.X) || !double.IsFinite(v.Y) || !double.IsFinite(v.Z)) return IfcViewerGeometryIssue.InvalidMesh; + vertices[i] = new Vector3d(v.X * scale, v.Y * scale, v.Z * scale); + } + if (source.Indices.Count % 3 != 0 || source.Indices.Any(index => (uint)index >= (uint)vertices.Length)) + { + return IfcViewerGeometryIssue.InvalidMesh; + } + + var mesh = new ViewerMesh + { + Id = doc.Meshes.Count + 1, + Name = element.Name, + ElementId = element.Id, + Vertices = vertices, + Indices = [.. source.Indices], + }; + if (element.Material is { } material) + { + mesh.MaterialId = MaterialId(doc, material, materials); + } + else + { + (mesh.Color, mesh.Opacity) = DefaultColor(element); + } + + viewerElement.MeshIndices.Add(doc.Meshes.Count); + doc.Meshes.Add(mesh); + return null; + } + + private static int MaterialId(ViewerDocument doc, IfcMaterial material, Dictionary materials) + { + if (materials.TryGetValue(material, out int id)) return id; + + id = doc.Materials.Count + 1; + doc.Materials.Add(new ViewerMaterial + { + Id = id, + Name = material.Name, + Color = new RgbaColor(Channel(material.R), Channel(material.G), Channel(material.B)), + Opacity = Math.Clamp(material.Alpha, 0, 1), + }); + materials.Add(material, id); + return id; + } + + private static byte Channel(double value) => (byte)Math.Round(Math.Clamp(value, 0, 1) * 255); + + // Fallback colours by IFC class family, matched on the class name prefix as IfcIO lists the classes it reads. + private static (RgbaColor Color, double Opacity) DefaultColor(IfcElement element) + { + string type = element.IfcType.ToUpperInvariant(); + if (element is IfcSpace) return (new RgbaColor(150, 200, 235), 0.3); + if (type.StartsWith("IFCWINDOW", StringComparison.Ordinal) || type.StartsWith("IFCCURTAINWALL", StringComparison.Ordinal)) + { + return (new RgbaColor(140, 190, 225), 0.5); + } + if (type.StartsWith("IFCWALL", StringComparison.Ordinal)) return (new RgbaColor(215, 213, 205), 1); + if (type.StartsWith("IFCSLAB", StringComparison.Ordinal) || type.StartsWith("IFCROOF", StringComparison.Ordinal) + || type.StartsWith("IFCFOOTING", StringComparison.Ordinal)) + { + return (new RgbaColor(190, 190, 190), 1); + } + if (type.StartsWith("IFCCOLUMN", StringComparison.Ordinal) || type.StartsWith("IFCBEAM", StringComparison.Ordinal) + || type.StartsWith("IFCMEMBER", StringComparison.Ordinal)) + { + return (new RgbaColor(120, 140, 185), 1); + } + if (type.StartsWith("IFCDOOR", StringComparison.Ordinal)) return (new RgbaColor(150, 105, 60), 1); + if (type.StartsWith("IFCPIPE", StringComparison.Ordinal) || type.StartsWith("IFCFLOW", StringComparison.Ordinal)) + { + return (new RgbaColor(225, 110, 60), 1); + } + if (type.StartsWith("IFCDUCT", StringComparison.Ordinal)) return (new RgbaColor(70, 190, 110), 1); + return (new RgbaColor(180, 180, 180), 1); + } + + private static int AddSchedule(ViewerDocument doc, List schedules, Dictionary converted) + { + var withTasks = schedules.Where(s => s.RootTasks.Count > 0).ToList(); + if (withTasks.Count == 0) return 0; + + // The viewer shows one schedule, so the tasks of several work schedules go into one, side by side. + var timeLiner = new ViewerTimeLinerSchedule(string.Join(", ", withTasks.Select(s => s.Name))); + int skipped = 0; + foreach (var task in withTasks.SelectMany(s => s.RootTasks)) + { + AddTask(task, timeLiner.Tasks); + } + doc.TimeLiner = timeLiner.Tasks.Count > 0 ? timeLiner : null; + return skipped; + + void AddTask(IfcTask task, List target) + { + if (task.Time?.PlannedStartDate is not { } start || task.Time.PlannedFinishDate is not { } finish) + { + skipped++; + foreach (var sub in task.SubTasks) AddTask(sub, target); + return; + } + + var viewerTask = new ViewerTimeLinerTask( + task.GlobalId, + task.Name, + Instant(start), + Instant(finish), + task.TaskType == IfcTaskType.Demolition ? ViewerTimeLinerTaskType.Demolish : ViewerTimeLinerTaskType.Construct) + { + ActualStart = task.Time.ActualStartDate is { } actualStart ? Instant(actualStart) : null, + ActualEnd = task.Time.ActualFinishDate is { } actualEnd ? Instant(actualEnd) : null, + }; + foreach (var element in task.AssignedElements) + { + if (converted.TryGetValue(element, out long id)) viewerTask.AttachedElementIds.Add(id); + } + target.Add(viewerTask); + foreach (var sub in task.SubTasks) AddTask(sub, viewerTask.Children); + } + } + + // IfcIO reads IFC date-times as UTC (converted to local time); a date set in memory without a kind is taken as UTC too. + private static DateTimeOffset Instant(DateTime value) => + value.Kind == DateTimeKind.Unspecified ? new DateTimeOffset(value, TimeSpan.Zero) : new DateTimeOffset(value).ToUniversalTime(); +} diff --git a/src/IOViewer.IfcIO/IfcViewerConversion.cs b/src/IOViewer.IfcIO/IfcViewerConversion.cs new file mode 100644 index 0000000..af7308a --- /dev/null +++ b/src/IOViewer.IfcIO/IfcViewerConversion.cs @@ -0,0 +1,79 @@ +using IfcIO; +using IfcIO.Schema; +using IOViewer.Model; + +namespace IOViewer.IfcIO; + +/// +/// The result of : the viewer document and what happened to the model's data. +/// +public sealed class IfcViewerConversion +{ + internal IfcViewerConversion( + ViewerDocument document, + IfcLengthUnit lengthUnit, + double metresPerUnit, + IReadOnlyList elementsWithoutGeometry, + int skippedTasks) + { + Document = document; + LengthUnit = lengthUnit; + MetresPerUnit = metresPerUnit; + ElementsWithoutGeometry = elementsWithoutGeometry; + SkippedTasks = skippedTasks; + } + + /// The converted document, in metres with Z up. + public ViewerDocument Document { get; } + + /// The model's length unit () the geometry was converted from. + public IfcLengthUnit LengthUnit { get; } + + /// The exact factor applied to every coordinate: the length of one in metres. + public double MetresPerUnit { get; } + + /// Number of converted elements, with or without geometry. + public int ElementCount => Document.Elements.Count; + + /// Number of converted elements that have a mesh. + public int ElementsWithGeometry => Document.Elements.Count - ElementsWithoutGeometry.Count; + + /// + /// Number of meshes with their own vertices. IfcIO bakes mapped items (IfcMappedItem) into each element's + /// world mesh and does not expose the shared representation, so every mesh is its own, one per element with + /// geometry, and stays empty. + /// + public int OwnMeshes => Document.Meshes.Count; + + /// Converted elements that have no mesh, with the reason; they still appear in the model tree. + public IReadOnlyList ElementsWithoutGeometry { get; } + + /// + /// Number of 4D tasks left out of because they have no planned start and finish + /// (IfcIO reads task times from IFC4 files only). Their subtasks are kept. + /// + public int SkippedTasks { get; } +} + +/// +/// A converted element that has no mesh. +/// +/// The element's . +/// The element's IFC GlobalId, if any. +/// Why the element has no mesh. +public sealed record IfcViewerElementWithoutGeometry(long ElementId, string? GlobalId, IfcViewerGeometryIssue Reason); + +/// +/// Why a converted element has no mesh. +/// +public enum IfcViewerGeometryIssue +{ + /// IfcIO returned no geometry: the element has no representation, or none IfcIO can read. + NoGeometry, + + /// The geometry has no triangles. + EmptyMesh, + + /// The geometry has a non-finite coordinate, an index out of range or an index count not divisible by 3. + InvalidMesh, +} diff --git a/src/IOViewer.IfcIO/IfcViewerOptions.cs b/src/IOViewer.IfcIO/IfcViewerOptions.cs new file mode 100644 index 0000000..47c7ab2 --- /dev/null +++ b/src/IOViewer.IfcIO/IfcViewerOptions.cs @@ -0,0 +1,15 @@ +using IfcIO; + +namespace IOViewer.IfcIO; + +/// +/// Options for . +/// +public sealed class IfcViewerOptions +{ + /// + /// Also converts spaces (IfcSpace, ), drawn translucent unless their + /// material says otherwise. False by default, because closed room volumes hide the elements inside them. + /// + public bool IncludeSpaces { get; set; } +} diff --git a/src/IOViewer.IfcIO/icon.png b/src/IOViewer.IfcIO/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd500b06ab17b4d4e66f15c242355fbd09b58ee GIT binary patch literal 1918 zcmZ{lc{JOJ7RP^nvJgZN)E>2$+R9X|v5r_1^kLN2(xNlSFxEz5Yfwc|vGb^1kfM*; zccxXXGIrV-t*!Pg)vD*sd*{q~=e+mFz4zRI?)~HYz4w!dxA~q6A&dY3fD31CVs~CC z{|YwPdG6Eqd;tJ(Q=EyRLsE$lS;nd6{Mbk(7+UTFWR? zAe$^ina3275if#7&=Jw)AVp`w@hQT>z*R(34421)tFSTC@_AtAW@9K z3ue_;Gp6ZmuIyHiys_aJg<8QmF8Fj*uVdGMulsiXR15L? z#>&2(t=sKfwM7F+;JRaw{PZXg98RDbNSMM zoC2~==XUEK_1zP@d}$hPkrk))Z5j;Lc!R9>+jqN8wCmNuFzfNU$M?2`qklSNq5AjC-99nMn;0u8-TDwB;YVbzB$4eq49&ePqs* zZ`BlI!9|uiU=J&PAO{!!iRmAQQ!b}C6*U3gJMc)hV zK=|OggTQM$*Euh;A4Ci^KQ-H4gZgjE^7O6ilK1R~*;z|z(Je$WUuP%Dz?`phmaF%q z96ALm)#Sf+_3sJnYMIOkxpf)tt`g9*9lrxzmD$J>aJstjYtsuykpD0JJXz5Hzp1F0 z#Lh3WC~4CSP5m#V55DR{WNv4QrQR8OYS%nB=ME1DVAVIL0gtIV9s{U^me~)If12XI zh9(}$5@sLfr3{%6`O=y48FP;QI^uJkE;g=h`Tt}2ly|XaIOofe(oP$Hv1_oX9tMs= zo0F(%Jyq%mWNqqoM`@B`hk(IFzsQ$?pMPlxVM5KH0O+r?H33G;$r3E02IEKg(GR8N zyHW>q{)iAVD3@$JHdn79`2po2e0p>xR;fc}t4>;60gCaW+~A52_v9o;1L@eM^6uspv#l~)No11a()LLB z;}KwCahzTsCFnqb24eZHtG7yO_=-MFA4I0+_G~nZ>kiHeI z;wWhAo2DAT;fJzyVS1^3U+z9~wzwvCb{$%|G4h$92wmDVn<(g>Uya-O!L<~$oE1`{ z!}O_D+_8zV7}akO3ulD{pzs^q!Li~FBs)UFajN(cdtBWN?-P>n{-&$66drc zYp&?{SIlT|nTjQ*2;#a@p9LWV{KMioM81SPZG3?#+-(j<`AmqjL%d@S-uv=*4_iL3 zXt44)zwZAC$%~N)mCK$U&HzRfiE0GGcpQo*bvFfvMn_s3FH2$x@<-XDHpsP8mi0s{ zKQ+aZhQBEb`XIZVv3h&xFN5m%cXsm~o)^o7f?R;}PYa^jh4C0HY{1$;jfYMNbayKUrKZkP$d+ENNP!cKk~93g&t$}cNIG;F zx|!zuW^4zKkt&%;*v$CEM;VV*=;zB)U8CMS30vQGLMjK$kHmJ*m`4mAh(|YuShdY? z + + + net8.0;net10.0 + false + $(NoWarn);CA1707 + + + + + + + + + + + + + + diff --git a/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs b/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs new file mode 100644 index 0000000..aff74e4 --- /dev/null +++ b/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs @@ -0,0 +1,360 @@ +using System.Globalization; +using IfcIO; +using IfcIO.Geometry; +using IfcIO.Model; +using IfcIO.Schema; +using IOViewer.Exporters; +using IOViewer.Model; +using IOViewer.Tests; +using Xunit; + +namespace IOViewer.IfcIO.Tests; + +public class IfcViewerAdapterTests +{ + private const string Millimetre = "#11=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);"; + private const string Foot = """ + #11=IFCCONVERSIONBASEDUNIT(#12,.LENGTHUNIT.,'FOOT',#13); + #12=IFCDIMENSIONALEXPONENTS(1,0,0,0,0,0,0); + #13=IFCMEASUREWITHUNIT(IFCLENGTHMEASURE(0.3048),#14); + #14=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); + """; + private const string Hostile = "\">"; + + // Site far from the origin; storey 3000 up; Panel placed at (10,20,0) in the storey and styled red. + // Fitting A and B draw one representation map (origin (0,0,100)); B's mapping moves it by (0,250,0). + private static string PlacedIfc(string lengthUnit) => $""" + ISO-10303-21; + HEADER; + FILE_DESCRIPTION((''),'2;1'); + FILE_NAME('adapter.ifc','2026-01-01T00:00:00',(''),(''),'','',''); + FILE_SCHEMA(('IFC2X3')); + ENDSEC; + DATA; + #1=IFCPROJECT('0AdapterProject0000001',$,'Adapter',$,$,$,$,(#20),#10); + #10=IFCUNITASSIGNMENT((#11)); + {lengthUnit} + #20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#21,$); + #21=IFCAXIS2PLACEMENT3D(#22,$,$); + #22=IFCCARTESIANPOINT((0.,0.,0.)); + #30=IFCSITE('0AdapterSite0000000001',$,'Site',$,$,#31,$,$,.ELEMENT.,$,$,$,$,$); + #31=IFCLOCALPLACEMENT($,#32); + #32=IFCAXIS2PLACEMENT3D(#33,$,$); + #33=IFCCARTESIANPOINT((412345678.,2000.,0.)); + #40=IFCBUILDINGSTOREY('0AdapterStorey00000001',$,'Level 1',$,$,#41,$,$,.ELEMENT.,3000.); + #41=IFCLOCALPLACEMENT(#31,#42); + #42=IFCAXIS2PLACEMENT3D(#43,$,$); + #43=IFCCARTESIANPOINT((0.,0.,3000.)); + #100=IFCBUILDINGELEMENTPROXY('1AdapterPanel000000001',$,'Panel',$,$,#101,#103,$,$); + #101=IFCLOCALPLACEMENT(#41,#102); + #102=IFCAXIS2PLACEMENT3D(#104,$,$); + #104=IFCCARTESIANPOINT((10.,20.,0.)); + #103=IFCPRODUCTDEFINITIONSHAPE($,$,(#105)); + #105=IFCSHAPEREPRESENTATION(#20,'Body','Brep',(#106)); + #106=IFCFACETEDBREP(#107); + #107=IFCCLOSEDSHELL((#108)); + #108=IFCFACE((#109)); + #109=IFCFACEOUTERBOUND(#110,.T.); + #110=IFCPOLYLOOP((#22,#111,#112)); + #111=IFCCARTESIANPOINT((100.,0.,0.)); + #112=IFCCARTESIANPOINT((0.,100.,0.)); + #120=IFCCOLOURRGB($,1.,0.,0.); + #121=IFCSURFACESTYLESHADING(#120); + #122=IFCSURFACESTYLE('Red',.BOTH.,(#121)); + #123=IFCSTYLEDITEM(#106,(#122),$); + #200=IFCREPRESENTATIONMAP(#201,#203); + #201=IFCAXIS2PLACEMENT3D(#202,$,$); + #202=IFCCARTESIANPOINT((0.,0.,100.)); + #203=IFCSHAPEREPRESENTATION(#20,'Body','Brep',(#204)); + #204=IFCFACETEDBREP(#107); + #300=IFCFLOWFITTING('1AdapterFittingA000001',$,'Fitting A',$,$,#301,#303,$); + #301=IFCLOCALPLACEMENT($,#302); + #302=IFCAXIS2PLACEMENT3D(#304,$,$); + #304=IFCCARTESIANPOINT((500.,0.,0.)); + #303=IFCPRODUCTDEFINITIONSHAPE($,$,(#305)); + #305=IFCSHAPEREPRESENTATION(#20,'Body','MappedRepresentation',(#306)); + #306=IFCMAPPEDITEM(#200,#307); + #307=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#22,$,$); + #310=IFCFLOWFITTING('1AdapterFittingB000001',$,'Fitting B',$,$,#301,#313,$); + #313=IFCPRODUCTDEFINITIONSHAPE($,$,(#315)); + #315=IFCSHAPEREPRESENTATION(#20,'Body','MappedRepresentation',(#316)); + #316=IFCMAPPEDITEM(#200,#317); + #317=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#318,$,$); + #318=IFCCARTESIANPOINT((0.,250.,0.)); + #400=IFCRELCONTAINEDINSPATIALSTRUCTURE('2AdapterContainment001',$,$,$,(#100,#300,#310),#40); + ENDSEC; + END-ISO-10303-21; + """; + + private static readonly Vector3d[] Triangle = [new(0, 0, 0), new(100, 0, 0), new(0, 100, 0)]; + + [Theory] + [InlineData(Millimetre, IfcLengthUnit.Millimetre, 0.001)] + [InlineData(Foot, IfcLengthUnit.Foot, 0.3048)] + public void Convert_PlacedAndMappedElements_LandInWorldMetres(string lengthUnit, IfcLengthUnit expectedUnit, double metres) + { + var model = OpenText(PlacedIfc(lengthUnit)); + + var conversion = IfcViewerAdapter.Convert(model); + var doc = conversion.Document; + + Assert.Equal(expectedUnit, conversion.LengthUnit); + Assert.Equal(metres, conversion.MetresPerUnit); + AssertPoints(Offset(new(412345678 + 10, 2000 + 20, 3000), metres), MeshOf(doc, 100).Vertices); + AssertPoints(Offset(new(500, 0, 100), metres), MeshOf(doc, 300).Vertices); + AssertPoints(Offset(new(500, 250, 100), metres), MeshOf(doc, 310).Vertices); + + // IfcIO bakes mapped items into world meshes: the two fittings are two own meshes, nothing is shared. + Assert.Empty(doc.Geometries); + Assert.Equal(3, conversion.OwnMeshes); + Assert.All(doc.Meshes, m => Assert.Null(m.Transform)); + + var panel = Assert.Single(doc.Elements, e => e.Id == 100); + Assert.Equal("1AdapterPanel000000001", panel.SourceId); + Assert.Equal("IFCBUILDINGELEMENTPROXY", panel.Category); + Assert.Equal("Level 1", panel.Level); + var red = Assert.Single(doc.Materials); + Assert.Equal(new RgbaColor(255, 0, 0), red.Color); + Assert.Equal(red.Id, MeshOf(doc, 100).MaterialId); + } + + [Theory] + [InlineData(IfcLengthUnit.Millimetre, 0.001)] + [InlineData(IfcLengthUnit.Metre, 1.0)] + public void Convert_SavedModel_ScalesFromItsLengthUnit(IfcLengthUnit unit, double metres) + { + var model = IfcModel.Create("Units"); + model.LengthUnit = unit; + model.AddMesh("Slab", [new(1000, 0, 0), new(2000, 0, 0), new(1000, 1500, 250)], [0, 1, 2], ifcType: "IFCSLAB"); + var opened = SaveAndOpen(model); + + var conversion = IfcViewerAdapter.Convert(opened); + + Assert.Equal(unit, conversion.LengthUnit); + var source = Assert.Single(opened.AllElements).Mesh!.Vertices; + var vertices = Assert.Single(conversion.Document.Meshes).Vertices; + Assert.Equal(source.Select(v => new Vector3d(v.X * metres, v.Y * metres, v.Z * metres)), vertices); + Assert.Equal(new Vector3d(2000 * metres, 1500 * metres, 250 * metres), conversion.Document.BoundingBox.Max); + } + + [Fact] + public void Convert_SavedModel_KeepsPropertiesQuantitiesGlobalIdsAndReportsElementsWithoutGeometry() + { + var model = IfcModel.Create("Properties"); + var storey = model.AddStorey("Level 2", 3000); + var wall = model.AddMesh("Wall", Mm(Triangle), [0, 1, 2], new IfcMaterial { Name = "Brick", R = 1, G = 0.5, B = 0 }, storey, "IFCWALL"); + wall.GetOrCreatePropertySet("Pset_WallCommon").Add("FireRating", "EI 60"); + wall.GetOrCreateQuantitySet("Qto_WallBaseQuantities").AddLength("Length", 3000.5); + model.AddElement(new IfcElement { Id = 2, Name = "Annotation" }, storey); + var opened = SaveAndOpen(model); + var openedWall = Assert.Single(opened.AllElements, e => e.Name == "Wall"); + var openedEmpty = Assert.Single(opened.AllElements, e => e.Name == "Annotation"); + + var conversion = IfcViewerAdapter.Convert(opened); + var doc = conversion.Document; + + var element = Assert.Single(doc.Elements, e => e.Id == openedWall.Id); + Assert.False(string.IsNullOrEmpty(openedWall.GlobalId)); + Assert.Equal(openedWall.GlobalId, element.SourceId); + Assert.Equal("IFCWALL", element.Category); + Assert.Equal("Level 2", element.Level); + Assert.Equal("EI 60", element.Properties["Pset_WallCommon"]["FireRating"]); + Assert.Equal("3000.5", element.Properties["Qto_WallBaseQuantities"]["Length"]); + var brick = Assert.Single(doc.Materials); + Assert.Equal(new RgbaColor(255, 128, 0), brick.Color); + Assert.Equal(1.0, brick.Opacity); + + Assert.Equal(2, conversion.ElementCount); + Assert.Equal(1, conversion.ElementsWithGeometry); + Assert.Equal( + new IfcViewerElementWithoutGeometry(openedEmpty.Id, openedEmpty.GlobalId, IfcViewerGeometryIssue.NoGeometry), + Assert.Single(conversion.ElementsWithoutGeometry)); + Assert.Empty(Assert.Single(doc.Elements, e => e.Id == openedEmpty.Id).MeshIndices); + + var view = ViewerHtml.Export(doc); + string id = openedWall.Id.ToString(CultureInfo.InvariantCulture); + Assert.Equal("EI 60", view.Props.GetProperty(id).GetProperty("Pset_WallCommon").GetProperty("FireRating").GetString()); + var meta = Assert.Single(view.Meta.GetProperty("elements").EnumerateArray(), e => e.GetProperty("id").GetInt64() == openedWall.Id); + Assert.Equal(openedWall.GlobalId, meta.GetProperty("sourceId").GetString()); + } + + [Fact] + public void Convert_HostileStrings_AreEscapedInHtml() + { + var model = IfcModel.Create("Hostile"); + var wall = model.AddMesh(Hostile, Mm(Triangle), [0, 1, 2], ifcType: "IFCWALL"); + wall.GetOrCreatePropertySet(Hostile).Add(Hostile, Hostile); + var opened = SaveAndOpen(model); + var openedWall = Assert.Single(opened.AllElements); + + var view = ViewerHtml.Export(IfcViewerAdapter.ToViewerDocument(opened)); + + string id = openedWall.Id.ToString(CultureInfo.InvariantCulture); + Assert.Equal(Hostile, view.Props.GetProperty(id).GetProperty(Hostile).GetProperty(Hostile).GetString()); + Assert.Equal(Hostile, Assert.Single(view.Meta.GetProperty("elements").EnumerateArray()).GetProperty("name").GetString()); + Assert.DoesNotContain(Hostile, view.Html, StringComparison.Ordinal); + } + + [Fact] + public void Convert_MaterialAlpha_BecomesOpacityAndElementsWithoutMaterialGetClassColour() + { + // IfcIO does not read transparency from files yet, so the material is set in memory. + var model = IfcModel.Create("Glass"); + model.AddMesh("Pane", Mm(Triangle), [0, 1, 2], new IfcMaterial { Name = "Glass", R = 0.2, G = 0.4, B = 0.6, Alpha = 0.35 }, ifcType: "IFCPLATE"); + model.AddMesh("Window", Mm(Triangle), [0, 1, 2], ifcType: "IFCWINDOW"); + + var doc = IfcViewerAdapter.ToViewerDocument(model); + + var glass = Assert.Single(doc.Materials); + Assert.Equal(0.35, glass.Opacity); + Assert.Equal(new RgbaColor(51, 102, 153), glass.Color); + var window = Assert.Single(doc.Meshes, m => m.Name == "Window"); + Assert.Null(window.MaterialId); + Assert.Equal(0.5, window.Opacity); + Assert.Contains( + ViewerHtml.Export(doc).Meta.GetProperty("materials").EnumerateArray(), + m => Math.Abs(m.GetProperty("opacity").GetDouble() - 0.35) < 1e-9); + } + + [Fact] + public void Convert_SpacesOnlyWhenIncluded() + { + var model = IfcModel.Create("Spaces"); + model.AddMesh("Wall", Mm(Triangle), [0, 1, 2], ifcType: "IFCWALL"); + var space = model.AddSpace("Room 1", mesh: IfcMesh.Create(1, "Room 1", Mm(Triangle), [0, 1, 2])); + + Assert.DoesNotContain(IfcViewerAdapter.ToViewerDocument(model).Elements, e => e.Id == space.Id); + + var doc = IfcViewerAdapter.ToViewerDocument(model, new IfcViewerOptions { IncludeSpaces = true }); + Assert.Equal("IFCSPACE", Assert.Single(doc.Elements, e => e.Id == space.Id).Category); + Assert.Equal(0.3, Assert.Single(doc.Meshes, m => m.ElementId == space.Id).Opacity); + } + + [Fact] + public void Convert_DuplicateElementIds_Throws() + { + // A model built in memory numbers elements per storey, so two storeys both start at 1. + var model = IfcModel.Create("Duplicates"); + model.AddMesh("A", Mm(Triangle), [0, 1, 2]); + model.AddMesh("B", Mm(Triangle), [0, 1, 2], storey: model.AddStorey("Level 2", 3000)); + + var ex = Assert.Throws(() => IfcViewerAdapter.Convert(model)); + Assert.Contains("used twice", ex.Message, StringComparison.Ordinal); + } + + [Fact] + public void Convert_InvalidMesh_IsReportedNotDrawn() + { + var model = IfcModel.Create("Invalid"); + var bad = model.AddMesh("Bad", Mm(Triangle), [0, 1, 7]); + var empty = model.AddMesh("Empty", Mm(Triangle), []); + + var conversion = IfcViewerAdapter.Convert(model); + + Assert.Empty(conversion.Document.Meshes); + Assert.Equal( + [ + new IfcViewerElementWithoutGeometry(bad.Id, null, IfcViewerGeometryIssue.InvalidMesh), + new IfcViewerElementWithoutGeometry(empty.Id, null, IfcViewerGeometryIssue.EmptyMesh), + ], + conversion.ElementsWithoutGeometry); + } + + [Fact] + public void Convert_WorkSchedule_BecomesTimeLinerWithAttachedElements() + { + var model = IfcModel.Create("Schedule", IfcSchema.Ifc4); + var wall = model.AddMesh("Wall", Mm(Triangle), [0, 1, 2], ifcType: "IFCWALL"); + var schedule = model.AddWorkSchedule("Build"); + var start = new DateTime(2026, 3, 1, 8, 0, 0, DateTimeKind.Utc); + schedule.AddTask("Erect walls", "T1", start, start.AddDays(9)).AssignElement(wall); + schedule.AddTask("Undated", "T2"); + var opened = SaveAndOpen(model); + + var conversion = IfcViewerAdapter.Convert(opened); + + var timeLiner = conversion.Document.TimeLiner; + Assert.NotNull(timeLiner); + Assert.Equal("Build", timeLiner.Name); + var task = Assert.Single(timeLiner.Tasks); + Assert.Equal("Erect walls", task.Name); + Assert.Equal(new DateTimeOffset(start), task.PlannedStart); + Assert.Equal(new DateTimeOffset(start.AddDays(9)), task.PlannedEnd); + Assert.Equal([Assert.Single(opened.AllElements).Id], task.AttachedElementIds); + Assert.Equal(1, conversion.SkippedTasks); + } + + [IfcFileFact] + public void Convert_ConfiguredIfcFile_ConvertsEveryElementAndExports() + { + var model = IfcModel.Open(Environment.GetEnvironmentVariable(IfcFileFactAttribute.Variable)!); + + var conversion = IfcViewerAdapter.Convert(model); + + Assert.Equal(model.AllElements.Count(), conversion.ElementCount); + Assert.Equal(conversion.ElementsWithGeometry, conversion.OwnMeshes); + Assert.All(conversion.Document.Elements, e => Assert.False(string.IsNullOrEmpty(e.SourceId))); + Assert.NotEmpty(HtmlViewerExporter.ExportToString(conversion.Document)); + } + + private static IfcModel OpenText(string ifc) + { + string path = Path.Combine(Path.GetTempPath(), $"ioviewer_ifc_{Guid.NewGuid():N}.ifc"); + try + { + File.WriteAllText(path, ifc); + return IfcModel.Open(path); + } + finally + { + File.Delete(path); + } + } + + // Models are saved as IFC2x3 (faceted breps) unless a test needs IFC4: IfcIO does not read back the + // IfcTriangulatedFaceSet it writes for IFC4. + private static IfcModel SaveAndOpen(IfcModel model) + { + string path = Path.Combine(Path.GetTempPath(), $"ioviewer_ifc_{Guid.NewGuid():N}.ifc"); + try + { + model.Save(path); + return IfcModel.Open(path); + } + finally + { + File.Delete(path); + } + } + + private static IfcVector3[] Mm(Vector3d[] points) => [.. points.Select(p => new IfcVector3(p.X, p.Y, p.Z))]; + + private static ViewerMesh MeshOf(ViewerDocument doc, long elementId) => Assert.Single(doc.Meshes, m => m.ElementId == elementId); + + private static Vector3d[] Offset(Vector3d origin, double metres) => + [.. Triangle.Select(p => (p + origin) * metres)]; + + private static void AssertPoints(Vector3d[] expected, IReadOnlyList actual) + { + Assert.Equal(expected.Length, actual.Count); + for (int i = 0; i < expected.Length; i++) + { + // 1e-6 m: far tighter than float precision at 400 km from the origin, so doubles must be kept. + Assert.True(Vector3d.Distance(expected[i], actual[i]) <= 1e-6, $"Vertex {i}: expected {expected[i]}, actual {actual[i]}"); + } + } +} + +/// Runs only when names an IFC file; the repository ships none. +public sealed class IfcFileFactAttribute : FactAttribute +{ + public const string Variable = "IOVIEWER_IFC_FILE"; + + public IfcFileFactAttribute() + { + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Variable))) + { + Skip = $"Set {Variable} to an IFC file to run this test."; + } + } +} From ce3083e2b572d04e52897e36ed3613c18bb30320 Mon Sep 17 00:00:00 2001 From: mie <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:23:11 +0300 Subject: [PATCH 03/19] Document the IfcIO adapter README lists the engine adapters with an IfcIO example, getting started gets a From an IFC file section with the mapping and its limits, and the public API page, docfx metadata, docs workflow paths and changelog cover IOViewer.IfcIO. CONTRIBUTING states that engine adapters live here and how to build against an engine release from a local feed. --- .github/workflows/docs.yml | 4 +++ CHANGELOG.md | 6 +++++ CONTRIBUTING.md | 29 +++++++++++++++++++--- README.md | 20 +++++++++++++++ docfx.json | 3 ++- docs/getting-started.md | 50 ++++++++++++++++++++++++++++++++++++++ docs/public-api.md | 18 ++++++++++++++ 7 files changed, 126 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bc687dc..d600d08 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,6 +9,8 @@ on: - 'templates/**' - '.config/dotnet-tools.json' - 'src/IOViewer/**' + - 'src/IOViewer.IfcIO/**' + - 'EngineVersions.props' - '.github/workflows/docs.yml' pull_request: paths: @@ -17,6 +19,8 @@ on: - 'templates/**' - '.config/dotnet-tools.json' - 'src/IOViewer/**' + - 'src/IOViewer.IfcIO/**' + - 'EngineVersions.props' - '.github/workflows/docs.yml' workflow_dispatch: diff --git a/CHANGELOG.md b/CHANGELOG.md index 18cd239..4d11c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ API between minor releases. HTML viewer shows it as its own row in the properties panel ("Source ID" / "ID источника") and the model tree search finds it. It is written to the page's element list and to JSON only when set, so the element data of documents without it is written as before. +- `IOViewer.IfcIO` package: `IfcViewerAdapter` converts an IfcIO `IfcModel` into a `ViewerDocument` in metres with + Z up, without reading the file again. Elements keep their STEP id, GlobalId (`SourceId`), IFC class, storey, property + sets and quantities; colours come from IfcIO materials, and work schedules become the TimeLiner. `Convert` also + reports the length unit, elements without geometry by reason and skipped 4D tasks. IfcIO bakes mapped items into + world meshes, so each element gets its own mesh. It needs IfcIO 0.11.0, which reads world placements; the `IOViewer` + package itself still has no dependencies. ### Changed - The documentation states the model's units and axes: all coordinates and lengths in a `ViewerDocument` are in diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2055bc3..ec173e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,9 +33,32 @@ dotnet build IOViewer.slnx -c Release dotnet test IOViewer.slnx -c Release ``` -The library is in `src/IOViewer`, the CLI in `src/IOViewer.Cli`, and tests in -`tests/IOViewer.Tests`. Tests use small synthetic models built in code. -The repository ships no customer models. +The library is in `src/IOViewer`, the CLI in `src/IOViewer.Cli`, engine adapters +in `src/IOViewer.` (for example `src/IOViewer.IfcIO`), and tests in +`tests/IOViewer.Tests` and `tests/IOViewer..Tests`. Tests use small +synthetic models built in code. The repository ships no customer models; set +`IOVIEWER_IFC_FILE` to a private IFC file to run one more adapter test on it. + +### Engine adapters + +Engines (IfcIO, RvtIO and others) depend neither on IOViewer nor on each other; +an adapter from an engine's model to `ViewerDocument` lives in this repository, +depends on IOViewer and that one engine, and `src/IOViewer` takes no engine +dependency. Engine package versions are set in `EngineVersions.props`. + +To build against an engine release that is not on nuget.org yet (IfcIO 0.11.0 +at the time of writing, so restoring the solution fails without it), pack the +engine into a local folder and pass its version and the folder: + +```sh +dotnet build IOViewer.slnx -c Release -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= +dotnet test IOViewer.slnx -c Release --no-build +dotnet pack src/IOViewer.IfcIO/IOViewer.IfcIO.csproj -c Release -p:Version=0.2.0-local.1 -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= -o +``` + +Packing needs a prerelease `Version` as well, because NuGet refuses a stable +package that depends on a prerelease one. Do not commit a `nuget.config` that +points to a local feed. ### Viewer diff --git a/README.md b/README.md index 0c053ca..9274573 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,26 @@ var doc = GlbViewerImporter.Load("complex_building.glb"); HtmlViewerExporter.Save(doc, "complex_building.html"); ``` +### Engine adapters + +An adapter turns the model of an IOExtend engine into a `ViewerDocument`, keeping what a GLB loses: source ids, +storeys and properties. Each adapter is its own package that depends on IOViewer and one engine; IOViewer itself +depends on no engine. + +| Package | Engine | Input | +| --- | --- | --- | +| `IOViewer.IfcIO` | [IfcIO](https://github.com/IOExtend/IfcIO) 0.11.0 or later | `IfcModel` (IFC2x3, IFC4) | + +```csharp +using IfcIO; +using IOViewer.Exporters; +using IOViewer.IfcIO; + +var model = IfcModel.Open("building.ifc"); +var doc = IfcViewerAdapter.ToViewerDocument(model); +HtmlViewerExporter.Save(doc, "building.html"); +``` + See the [public API](docs/public-api.md) for the supported surface and [getting started guide](docs/getting-started.md) for detailed workflows. ## Documentation diff --git a/docfx.json b/docfx.json index 007fca4..a59ec0e 100644 --- a/docfx.json +++ b/docfx.json @@ -2,7 +2,8 @@ "metadata": [ { "src": [ - { "src": "src/IOViewer", "files": ["IOViewer.csproj"] } + { "src": "src/IOViewer", "files": ["IOViewer.csproj"] }, + { "src": "src/IOViewer.IfcIO", "files": ["IOViewer.IfcIO.csproj"] } ], "dest": "docs/api", "properties": { "TargetFramework": "net10.0" }, diff --git a/docs/getting-started.md b/docs/getting-started.md index f1e9647..4e75fd1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -84,6 +84,56 @@ for (int i = 0; i < 100; i++) `GlbViewerImporter` fills these properties from glTF nodes and meshes. Meshes with their own `Vertices` keep working as before; the exporter also writes exact translated copies of such meshes once. +## From an IFC file + +The `IOViewer.IfcIO` package converts a model read by [IfcIO](https://github.com/IOExtend/IfcIO) (0.11.0 or later) +into a `ViewerDocument`. Unlike an IFC to GLB to HTML route, it keeps GlobalIds, storeys, property sets and quantities. + +```sh +dotnet add package IOViewer.IfcIO +``` + +```csharp +using IfcIO; +using IOViewer.Exporters; +using IOViewer.IfcIO; + +var model = IfcModel.Open("building.ifc"); +var conversion = IfcViewerAdapter.Convert(model); + +foreach (var skipped in conversion.ElementsWithoutGeometry) +{ + Console.Error.WriteLine($"#{skipped.ElementId} ({skipped.GlobalId}): {skipped.Reason}"); +} + +HtmlViewerExporter.Save(conversion.Document, "building.html"); +``` + +`IfcViewerAdapter.ToViewerDocument(model)` returns the document alone. The adapter works on the model in memory and +does not read the file again. It maps the model as follows: + +- Geometry: IfcIO's world mesh of each element, converted from `IfcModel.LengthUnit` to metres by an exact factor + (`conversion.MetresPerUnit`). IFC is Z up like the viewer, so axes are not swapped. Coordinates stay doubles until the + HTML exporter makes them relative to the model centre, so georeferenced models keep their precision. +- Elements: one per element of `IfcModel.AllElements`. `Id` is the STEP entity id (`#123` gives 123), `SourceId` the + GlobalId, `Category` the IFC class as IfcIO reports it (for example `IFCWALL`), `Level` the storey name. + `IfcViewerOptions.IncludeSpaces` adds spaces, drawn translucent. Openings are not converted. +- Properties: each property set and quantity set becomes a property group with its name. Quantity values are written as + IfcIO reads them, in the file's units. +- Colours: the element's IfcIO material (surface style colour and its alpha as opacity), otherwise a default colour for + the IFC class. +- 4D: IfcIO work schedules become the TimeLiner, with each task's elements attached; tasks without planned dates are + counted in `conversion.SkippedTasks`. + +Current limits, all from what IfcIO exposes: + +- IfcIO bakes mapped items (`IfcMappedItem`) into each element's mesh and does not expose the shared representation, + so every element gets its own mesh (`conversion.OwnMeshes`) and repeated shapes are not stored once. +- IfcIO does not read type objects (`TypeName` stays empty), grids, transparency from files or task times from IFC2x3. +- Clash results from `IfcModel.DetectClashes` are not converted. +- Element ids must be unique. They are for a model read with `IfcModel.Open`; a model built in memory numbers its + elements per storey, so save and reopen it before converting. + ## Using the viewer The model's up axis is +Z. The interface is in Russian when the browser language is Russian and in English otherwise. diff --git a/docs/public-api.md b/docs/public-api.md index 956b17e..75b4f88 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -30,3 +30,21 @@ - `GlbViewerImporter`: Reads binary glTF 2.0 (`.glb`) files into `ViewerDocument`. Each glTF mesh primitive becomes one `ViewerGeometry` and each node that uses it a `ViewerMesh` with the node's world transform, converted from glTF's Y-up to the viewer's Z-up. Each such node also becomes a `ViewerElement` (category `GLB`, named after the node, else the mesh) that all its primitives belong to. A node hierarchy that is not a tree (a node referenced twice or a cycle) is rejected with `InvalidDataException`. - `ObjViewerImporter`: Reads Wavefront OBJ geometry into `ViewerDocument` as is, without changing the up axis. Each object or group (`o`/`g`) becomes one `ViewerMesh` holding only the vertices its faces use and one `ViewerElement` (category `OBJ`) with its name. Material (`.mtl`) files are not read. - `JsonViewerImporter`: Reads JSON document snapshots into `ViewerDocument`. + +### IfcIO adapter (`IOViewer.IfcIO`, package `IOViewer.IfcIO`) + +A separate package that depends on IOViewer and [IfcIO](https://github.com/IOExtend/IfcIO) 0.11.0 or later; the +`IOViewer` package does not depend on IfcIO. + +- `IfcViewerAdapter.ToViewerDocument(IfcModel, IfcViewerOptions?)`: Converts a model in memory, without reading the file + again, into a `ViewerDocument` in metres with Z up. Each element becomes a `ViewerElement` with the STEP entity id as + `Id`, the GlobalId as `SourceId`, the IFC class as `Category`, the storey as `Level`, and its property sets and + quantity sets as property groups; an element with geometry gets one mesh of its own. Work schedules become the TimeLiner. Duplicate element + ids throw `ArgumentException`. +- `IfcViewerAdapter.Convert(IfcModel, IfcViewerOptions?)`: The same, returning an `IfcViewerConversion` with the + document and a report: the length unit and its exact factor to metres, the number of elements and of own meshes, + elements without geometry with an `IfcViewerGeometryIssue` (`NoGeometry`, `EmptyMesh`, `InvalidMesh`), and the number + of 4D tasks left out for lack of planned dates. +- `IfcViewerOptions.IncludeSpaces`: Also converts spaces; off by default. + +See [From an IFC file](getting-started.md#from-an-ifc-file) for the mapping and its current limits. From 938f802d27e5dbfe0b4818cfe4c75b68bb9803cb Mon Sep 17 00:00:00 2001 From: mie <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:49:58 +0300 Subject: [PATCH 04/19] Skip IFC type objects in the IfcIO adapter and document its limits IfcIO matches products by class-name prefix, so type objects such as IfcWallType or IfcDoorStyle and door and window lining properties reached the viewer as elements without geometry. The adapter now skips them. Document that Level comes from IfcIO's storey assignment, which puts elements outside a storey and spaces in the first storey. Pack the whole solution with a prerelease of the next version for local engine builds, and note that CI, docs and release restore fail until the IfcIO version is published. --- CONTRIBUTING.md | 15 +++++++---- docs/getting-started.md | 9 +++++-- docs/public-api.md | 5 ++-- src/IOViewer.IfcIO/IfcViewerAdapter.cs | 19 ++++++++++--- .../IfcViewerAdapterTests.cs | 27 +++++++++++++++++-- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec173e5..1527151 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,12 +53,17 @@ engine into a local folder and pass its version and the folder: ```sh dotnet build IOViewer.slnx -c Release -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= dotnet test IOViewer.slnx -c Release --no-build -dotnet pack src/IOViewer.IfcIO/IOViewer.IfcIO.csproj -c Release -p:Version=0.2.0-local.1 -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= -o +dotnet pack IOViewer.slnx -c Release -p:Version=0.3.0-local.1 -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= -o ``` -Packing needs a prerelease `Version` as well, because NuGet refuses a stable -package that depends on a prerelease one. Do not commit a `nuget.config` that -points to a local feed. +Pack the whole solution with a prerelease of the next version: NuGet refuses a +stable package that depends on a prerelease one, and the adapter package +depends on the `IOViewer` package of the same version, so both must be in the +output. Do not commit a `nuget.config` that points to a local feed. + +Until the IfcIO version in `EngineVersions.props` is on nuget.org, restoring +the solution fails in the CI build job, the Docs workflow and `release.yml` +(the CLI alone still restores), so do not tag a release before then. ### Viewer @@ -128,7 +133,7 @@ Write commit messages in the imperative mood, for example, Push a tag matching the package version, such as `v0.2.0`, to run `release.yml`. The workflow builds and tests the solution, attaches packages, symbols, source and checksums to a GitHub release, -and publishes the NuGet packages through Trusted Publishing. The release notes are the section of `CHANGELOG.md` for the version, so add that section before the tag. +and publishes the NuGet packages, including the engine adapters such as `IOViewer.IfcIO`, through Trusted Publishing. The release notes are the section of `CHANGELOG.md` for the version, so add that section before the tag. The NuGet policy must name this repository, `release.yml`, the configured NuGet profile and the `IOViewer*` package pattern. ## Licensing contributions diff --git a/docs/getting-started.md b/docs/getting-started.md index 4e75fd1..2b47753 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -115,8 +115,10 @@ does not read the file again. It maps the model as follows: - Geometry: IfcIO's world mesh of each element, converted from `IfcModel.LengthUnit` to metres by an exact factor (`conversion.MetresPerUnit`). IFC is Z up like the viewer, so axes are not swapped. Coordinates stay doubles until the HTML exporter makes them relative to the model centre, so georeferenced models keep their precision. -- Elements: one per element of `IfcModel.AllElements`. `Id` is the STEP entity id (`#123` gives 123), `SourceId` the - GlobalId, `Category` the IFC class as IfcIO reports it (for example `IFCWALL`), `Level` the storey name. +- Elements: one per element of `IfcModel.AllElements`, except the type objects (for example `IFCWALLTYPE`, + `IFCDOORSTYLE`) and door and window lining and panel properties that IfcIO also lists there. `Id` is the STEP entity + id (`#123` gives 123), `SourceId` the GlobalId, `Category` the IFC class as IfcIO reports it (for example `IFCWALL`), + `Level` the name of the IfcIO storey that holds it. `IfcViewerOptions.IncludeSpaces` adds spaces, drawn translucent. Openings are not converted. - Properties: each property set and quantity set becomes a property group with its name. Quantity values are written as IfcIO reads them, in the file's units. @@ -130,6 +132,9 @@ Current limits, all from what IfcIO exposes: - IfcIO bakes mapped items (`IfcMappedItem`) into each element's mesh and does not expose the shared representation, so every element gets its own mesh (`conversion.OwnMeshes`) and repeated shapes are not stored once. - IfcIO does not read type objects (`TypeName` stays empty), grids, transparency from files or task times from IFC2x3. +- IfcIO assigns storeys only from `IfcRelContainedInSpatialStructure` and puts everything else in the first storey, so + elements contained in the site or building, and spaces (which IFC aggregates to storeys with `IfcRelAggregates`), get + the first storey's name as `Level`. - Clash results from `IfcModel.DetectClashes` are not converted. - Element ids must be unique. They are for a model read with `IfcModel.Open`; a model built in memory numbers its elements per storey, so save and reopen it before converting. diff --git a/docs/public-api.md b/docs/public-api.md index 75b4f88..3dc9025 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -38,8 +38,9 @@ A separate package that depends on IOViewer and [IfcIO](https://github.com/IOExt - `IfcViewerAdapter.ToViewerDocument(IfcModel, IfcViewerOptions?)`: Converts a model in memory, without reading the file again, into a `ViewerDocument` in metres with Z up. Each element becomes a `ViewerElement` with the STEP entity id as - `Id`, the GlobalId as `SourceId`, the IFC class as `Category`, the storey as `Level`, and its property sets and - quantity sets as property groups; an element with geometry gets one mesh of its own. Work schedules become the TimeLiner. Duplicate element + `Id`, the GlobalId as `SourceId`, the IFC class as `Category`, the IfcIO storey as `Level` (elements outside a storey + and spaces get the first storey), and its property sets and quantity sets as property groups; type objects and lining + and panel properties that IfcIO lists as elements are skipped; an element with geometry gets one mesh of its own. Work schedules become the TimeLiner. Duplicate element ids throw `ArgumentException`. - `IfcViewerAdapter.Convert(IfcModel, IfcViewerOptions?)`: The same, returning an `IfcViewerConversion` with the document and a report: the length unit and its exact factor to metres, the number of elements and of own meshes, diff --git a/src/IOViewer.IfcIO/IfcViewerAdapter.cs b/src/IOViewer.IfcIO/IfcViewerAdapter.cs index e66ffbd..797158c 100644 --- a/src/IOViewer.IfcIO/IfcViewerAdapter.cs +++ b/src/IOViewer.IfcIO/IfcViewerAdapter.cs @@ -19,12 +19,15 @@ namespace IOViewer.IfcIO; /// /// /// Each element of (and of with -/// ) becomes one : +/// ) becomes one , except the type objects +/// (for example IFCWALLTYPE, IFCDOORSTYLE) and lining and panel properties that IfcIO also lists there: /// is , which for a model opened with /// is the element's STEP entity id (#123 gives 123), stable for a given file; /// is the GlobalId; is the IFC class as IfcIO -/// reports it (upper case, for example IFCWALL); is the name of the storey -/// that contains it. Property sets and quantity sets become property groups named after the set; quantity values are +/// reports it (upper case, for example IFCWALL); is the name of the IfcIO +/// storey that holds it. IfcIO places an element in a storey only through IfcRelContainedInSpatialStructure, so +/// elements contained in the site or building, and spaces aggregated to a storey (IfcRelAggregates, which IfcIO +/// does not read), get the first storey's name. Property sets and quantity sets become property groups named after the set; quantity values are /// written as IfcIO reads them, in the file's units, with invariant formatting. /// /// @@ -69,6 +72,8 @@ public static IfcViewerConversion Convert(IfcModel model, IfcViewerOptions? opti var elements = options.IncludeSpaces ? storey.Elements.Concat(storey.Spaces) : storey.Elements; foreach (var element in elements) { + if (IsNotProduct(element.IfcType)) continue; + // Mesh ElementIds, 4D tasks and clashes all key on Id, so a repeat would merge two elements. if (!ids.Add(element.Id)) { @@ -92,6 +97,14 @@ public static IfcViewerConversion Convert(IfcModel model, IfcViewerOptions? opti return new IfcViewerConversion(doc, model.LengthUnit, scale, withoutGeometry, skippedTasks); } + // shortcut: IfcIO picks products by class-name prefix (IFCWALL, IFCDOOR, ...), so type objects such as IfcWallType or + // IfcDoorStyle and the door and window lining and panel properties arrive as elements without geometry; no product + // class it matches ends like these. Remove once IfcIO reads only IfcProduct subtypes. + private static bool IsNotProduct(string ifcType) => + ifcType.EndsWith("TYPE", StringComparison.OrdinalIgnoreCase) + || ifcType.EndsWith("STYLE", StringComparison.OrdinalIgnoreCase) + || ifcType.EndsWith("PROPERTIES", StringComparison.OrdinalIgnoreCase); + // Exact lengths of the units IfcIO reports; it already converts any other file unit to metres while reading. private static double MetresPerUnit(IfcLengthUnit unit) => unit switch { diff --git a/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs b/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs index aff74e4..21093ef 100644 --- a/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs +++ b/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Text.RegularExpressions; using IfcIO; using IfcIO.Geometry; using IfcIO.Model; @@ -23,7 +24,7 @@ public class IfcViewerAdapterTests // Site far from the origin; storey 3000 up; Panel placed at (10,20,0) in the storey and styled red. // Fitting A and B draw one representation map (origin (0,0,100)); B's mapping moves it by (0,250,0). - private static string PlacedIfc(string lengthUnit) => $""" + private static string PlacedIfc(string lengthUnit, string extra = "") => $""" ISO-10303-21; HEADER; FILE_DESCRIPTION((''),'2;1'); @@ -82,6 +83,7 @@ private static string PlacedIfc(string lengthUnit) => $""" #317=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#318,$,$); #318=IFCCARTESIANPOINT((0.,250.,0.)); #400=IFCRELCONTAINEDINSPATIALSTRUCTURE('2AdapterContainment001',$,$,$,(#100,#300,#310),#40); + {extra} ENDSEC; END-ISO-10303-21; """; @@ -118,6 +120,25 @@ public void Convert_PlacedAndMappedElements_LandInWorldMetres(string lengthUnit, Assert.Equal(red.Id, MeshOf(doc, 100).MaterialId); } + [Fact] + public void Convert_TypeObjectsAndLiningProperties_AreNotElements() + { + const string NonProducts = """ + #500=IFCWALLTYPE('1AdapterWallType000001',$,'WT',$,$,$,$,$,$,.STANDARD.); + #501=IFCDOORSTYLE('1AdapterDoorStyle00001',$,'DS',$,$,$,$,$,.SINGLE_SWING_LEFT.,.WOOD.,.F.,.F.); + #502=IFCDOORLININGPROPERTIES('1AdapterDoorLining0001',$,'DL',$,50.,$,$,$,$,$,$,$,$,$,$); + """; + var model = OpenText(PlacedIfc(Millimetre, NonProducts)); + long[] nonProducts = [500, 501, 502]; + // IfcIO lists them as elements, which is what the adapter has to undo. + Assert.All(nonProducts, id => Assert.Contains(model.AllElements, e => e.Id == id)); + + var conversion = IfcViewerAdapter.Convert(model); + + Assert.Equal(new long[] { 100, 300, 310 }, conversion.Document.Elements.Select(e => e.Id).Order()); + Assert.Empty(conversion.ElementsWithoutGeometry); + } + [Theory] [InlineData(IfcLengthUnit.Millimetre, 0.001)] [InlineData(IfcLengthUnit.Metre, 1.0)] @@ -291,7 +312,9 @@ public void Convert_ConfiguredIfcFile_ConvertsEveryElementAndExports() var conversion = IfcViewerAdapter.Convert(model); - Assert.Equal(model.AllElements.Count(), conversion.ElementCount); + Assert.Equal( + model.AllElements.Count(e => !Regex.IsMatch(e.IfcType, "(TYPE|STYLE|PROPERTIES)$", RegexOptions.IgnoreCase)), + conversion.ElementCount); Assert.Equal(conversion.ElementsWithGeometry, conversion.OwnMeshes); Assert.All(conversion.Document.Elements, e => Assert.False(string.IsNullOrEmpty(e.SourceId))); Assert.NotEmpty(HtmlViewerExporter.ExportToString(conversion.Document)); From edee6b0000de214bd70c6d3593bae754fb572b0b Mon Sep 17 00:00:00 2001 From: mahach666 <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 13:26:17 +0300 Subject: [PATCH 05/19] Freeze the 0.2.0 JSON snapshots as legacy fixtures --- tests/IOViewer.Tests/IOViewer.Tests.csproj | 4 + tests/IOViewer.Tests/LegacyFixtureTests.cs | 29 + tests/fixtures/legacy-0.2/README.md | 22 + tests/fixtures/legacy-0.2/building.json | 12409 ++++++++++++++++ tests/fixtures/legacy-0.2/empty.json | 50 + tests/fixtures/legacy-0.2/escaping.json | 1483 ++ .../legacy-0.2/meshes-without-elements.json | 502 + tests/fixtures/legacy-0.2/textured.json | 2687 ++++ 8 files changed, 17186 insertions(+) create mode 100644 tests/IOViewer.Tests/LegacyFixtureTests.cs create mode 100644 tests/fixtures/legacy-0.2/README.md create mode 100644 tests/fixtures/legacy-0.2/building.json create mode 100644 tests/fixtures/legacy-0.2/empty.json create mode 100644 tests/fixtures/legacy-0.2/escaping.json create mode 100644 tests/fixtures/legacy-0.2/meshes-without-elements.json create mode 100644 tests/fixtures/legacy-0.2/textured.json diff --git a/tests/IOViewer.Tests/IOViewer.Tests.csproj b/tests/IOViewer.Tests/IOViewer.Tests.csproj index f98dd62..69b4b01 100644 --- a/tests/IOViewer.Tests/IOViewer.Tests.csproj +++ b/tests/IOViewer.Tests/IOViewer.Tests.csproj @@ -12,6 +12,10 @@ + + + + diff --git a/tests/IOViewer.Tests/LegacyFixtureTests.cs b/tests/IOViewer.Tests/LegacyFixtureTests.cs new file mode 100644 index 0000000..03c0d44 --- /dev/null +++ b/tests/IOViewer.Tests/LegacyFixtureTests.cs @@ -0,0 +1,29 @@ +using IOViewer.Importers; +using IOViewer.Model; +using Xunit; + +namespace IOViewer.Tests; + +/// +/// Pins the frozen IOViewer 0.2.0 snapshots in tests/fixtures/legacy-0.2. +/// +public class LegacyFixtureTests +{ + [Theory] + [InlineData("building.json", 65, 67, 7, 3)] + [InlineData("empty.json", 0, 0, 0, 0)] + [InlineData("escaping.json", 3, 3, 0, 0)] + [InlineData("textured.json", 2, 2, 0, 0)] + [InlineData("meshes-without-elements.json", 0, 4, 1, 1)] + public void LegacySnapshot_LoadsWithExpectedCounts(string file, int elements, int meshes, int tasks, int clashes) + { + var doc = JsonViewerImporter.Load(Path.Combine(AppContext.BaseDirectory, "legacy-0.2", file)); + + Assert.Equal(elements, doc.Elements.Count); + Assert.Equal(meshes, doc.Meshes.Count); + Assert.Equal(tasks, doc.TimeLiner is null ? 0 : CountTasks(doc.TimeLiner.Tasks)); + Assert.Equal(clashes, doc.ClashTests.Sum(t => t.Clashes.Count)); + } + + private static int CountTasks(IEnumerable tasks) => tasks.Sum(t => 1 + CountTasks(t.Children)); +} diff --git a/tests/fixtures/legacy-0.2/README.md b/tests/fixtures/legacy-0.2/README.md new file mode 100644 index 0000000..65621de --- /dev/null +++ b/tests/fixtures/legacy-0.2/README.md @@ -0,0 +1,22 @@ +# Legacy 0.2 snapshots + +Frozen JSON output of IOViewer 0.2.0 (`JsonViewerExporter`, indented, LF line endings). +The files are never regenerated and never edited: they are the old snapshot shape that IOViewer must keep loading. +`tests/IOViewer.Tests/LegacyFixtureTests.cs` pins their content. + +| File | Elements | Meshes | 4D tasks | Clashes | Origin | +|---|---|---|---|---|---| +| `building.json` | 65 | 67 | 7 | 3 | `tests/fixtures/viewer/building.json` at the 0.2.0 release commit | +| `empty.json` | 0 | 0 | 0 | 0 | `tests/fixtures/viewer/empty.json` at the 0.2.0 release commit | +| `escaping.json` | 3 | 3 | 0 | 0 | `tests/fixtures/viewer/escaping.json` at the 0.2.0 release commit | +| `textured.json` | 2 | 2 | 0 | 0 | `tests/fixtures/viewer/textured.json` at the 0.2.0 release commit | +| `meshes-without-elements.json` | 0 | 4 | 1 | 1 | written with the IOViewer 0.2.0 package from nuget.org | + +`meshes-without-elements.json` has the shape of a snapshot made from GLB or OBJ: meshes and no elements. + +- Meshes 1 and 2 share `ElementId` 100, mesh 3 has `ElementId` 200, mesh 4 has none. +- The 4D task is attached to 100, 200 and 4 (the id of the mesh without an `ElementId`). +- The clash names elements 100 and 200. +- Material 1 has `Opacity` 0.5 and mesh 1 has `Opacity` 0.8. +- Mesh 3 has `TextureIndex` 0. +- One linked file is listed, with `Visible` false. diff --git a/tests/fixtures/legacy-0.2/building.json b/tests/fixtures/legacy-0.2/building.json new file mode 100644 index 0000000..3d828d1 --- /dev/null +++ b/tests/fixtures/legacy-0.2/building.json @@ -0,0 +1,12409 @@ +{ + "Title": "Two-Storey Frame Building", + "SourcePath": null, + "Author": "IOViewer", + "CreatedAt": "2026-11-11T12:00:00Z", + "Application": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + }, + "Elements": [ + { + "Id": 300001, + "Name": "Ground Slab", + "Category": "Floors", + "TypeName": "Concrete 300", + "Level": "Level 1", + "LinkName": null, + "Properties": { + "Identity Data": { + "Mark": "Ground Slab" + }, + "Constraints": { + "Level": "Level 1" + } + }, + "MeshIndices": [ + 0 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300002, + "Name": "Column A1 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column A1 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 1 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300003, + "Name": "Column A2 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column A2 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 2 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300004, + "Name": "Column A3 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column A3 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 3 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300005, + "Name": "Column B1 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column B1 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 4 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300006, + "Name": "Column B2 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column B2 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 5 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300007, + "Name": "Column B3 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column B3 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 6 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300008, + "Name": "Column C1 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column C1 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 7 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300009, + "Name": "Column C2 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column C2 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 8 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300010, + "Name": "Column C3 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column C3 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 9 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300011, + "Name": "Column D1 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column D1 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 10 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300012, + "Name": "Column D2 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column D2 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 11 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300013, + "Name": "Column D3 Level 1", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column D3 Level 1" + }, + "Constraints": { + "Level": "Level 1" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 12 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300014, + "Name": "Beam A-B/1 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A-B/1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 13 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300015, + "Name": "Beam B-C/1 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B-C/1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 14 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300016, + "Name": "Beam C-D/1 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C-D/1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 15 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300017, + "Name": "Beam A-B/2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A-B/2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 16 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300018, + "Name": "Beam B-C/2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B-C/2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 17 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300019, + "Name": "Beam C-D/2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C-D/2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 18 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300020, + "Name": "Beam A-B/3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A-B/3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 19 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300021, + "Name": "Beam B-C/3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B-C/3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 20 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300022, + "Name": "Beam C-D/3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C-D/3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 21 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300023, + "Name": "Beam A/1-2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A/1-2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 22 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300024, + "Name": "Beam A/2-3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A/2-3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 23 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300025, + "Name": "Beam B/1-2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B/1-2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 24 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300026, + "Name": "Beam B/2-3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B/2-3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 25 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300027, + "Name": "Beam C/1-2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C/1-2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 26 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300028, + "Name": "Beam C/2-3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C/2-3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 27 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300029, + "Name": "Beam D/1-2 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam D/1-2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 28 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300030, + "Name": "Beam D/2-3 Level 2", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam D/2-3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 29 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300031, + "Name": "Column A1 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column A1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 30 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300032, + "Name": "Column A2 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column A2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 31 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300033, + "Name": "Column A3 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column A3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 32 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300034, + "Name": "Column B1 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column B1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 33 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300035, + "Name": "Column B2 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column B2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 34 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300036, + "Name": "Column B3 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column B3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 35 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300037, + "Name": "Column C1 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column C1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 36 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300038, + "Name": "Column C2 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column C2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 37 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300039, + "Name": "Column C3 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column C3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 38 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300040, + "Name": "Column D1 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column D1 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 39 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300041, + "Name": "Column D2 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column D2 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 40 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300042, + "Name": "Column D3 Level 2", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 2", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column D3 Level 2" + }, + "Constraints": { + "Level": "Level 2" + }, + "Dimensions": { + "Section": "400 x 400 mm" + } + }, + "MeshIndices": [ + 41 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300043, + "Name": "Beam A-B/1 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A-B/1 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 42 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300044, + "Name": "Beam B-C/1 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B-C/1 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 43 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300045, + "Name": "Beam C-D/1 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C-D/1 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 44 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300046, + "Name": "Beam A-B/2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A-B/2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 45 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300047, + "Name": "Beam B-C/2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B-C/2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 46 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300048, + "Name": "Beam C-D/2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C-D/2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 47 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300049, + "Name": "Beam A-B/3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A-B/3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 48 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300050, + "Name": "Beam B-C/3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B-C/3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 49 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300051, + "Name": "Beam C-D/3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C-D/3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 50 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300052, + "Name": "Beam A/1-2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A/1-2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 51 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300053, + "Name": "Beam A/2-3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam A/2-3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 52 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300054, + "Name": "Beam B/1-2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B/1-2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 53 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300055, + "Name": "Beam B/2-3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam B/2-3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 54 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300056, + "Name": "Beam C/1-2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C/1-2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 55 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300057, + "Name": "Beam C/2-3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam C/2-3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 56 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300058, + "Name": "Beam D/1-2 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam D/1-2 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 57 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300059, + "Name": "Beam D/2-3 Roof", + "Category": "Structural Framing", + "TypeName": "Steel I 300", + "Level": "Roof", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Beam D/2-3 Roof" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 58 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300060, + "Name": "Floor Slab Level 2", + "Category": "Floors", + "TypeName": "Concrete 200", + "Level": "Level 2", + "LinkName": null, + "Properties": { + "Identity Data": { + "Mark": "Floor Slab Level 2" + }, + "Constraints": { + "Level": "Level 2" + } + }, + "MeshIndices": [ + 59 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300061, + "Name": "Roof Slab", + "Category": "Roofs", + "TypeName": "Concrete 200", + "Level": "Roof", + "LinkName": null, + "Properties": { + "Identity Data": { + "Mark": "Roof Slab" + }, + "Constraints": { + "Level": "Roof" + } + }, + "MeshIndices": [ + 60 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300062, + "Name": "Curtain Wall South", + "Category": "Curtain Walls", + "TypeName": "Storefront", + "Level": "Level 1", + "LinkName": null, + "Properties": { + "Identity Data": { + "Mark": "Curtain Wall South" + }, + "Constraints": { + "Level": "Level 1" + }, + "Materials": { + "Glazing": "Clear glass 35 %" + } + }, + "MeshIndices": [ + 61, + 62 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300063, + "Name": "Supply Duct SA-1", + "Category": "Ducts", + "TypeName": "Rectangular 600x400", + "Level": "Level 2", + "LinkName": "MEP.rvt", + "Properties": { + "Identity Data": { + "Mark": "Supply Duct SA-1" + }, + "Constraints": { + "Level": "Level 2" + }, + "Mechanical": { + "System": "Supply Air" + } + }, + "MeshIndices": [ + 63 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300064, + "Name": "Column Twin", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column Twin" + }, + "Constraints": { + "Level": "Level 1" + } + }, + "MeshIndices": [ + 65 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 300065, + "Name": "Column Twin Mirrored", + "Category": "Structural Columns", + "TypeName": "Concrete 400x400", + "Level": "Level 1", + "LinkName": "Structure.rvt", + "Properties": { + "Identity Data": { + "Mark": "Column Twin Mirrored" + }, + "Constraints": { + "Level": "Level 1" + } + }, + "MeshIndices": [ + 66 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + } + ], + "Meshes": [ + { + "Id": 0, + "Name": "Ground Slab", + "ElementId": 300001, + "Vertices": [ + { + "X": 19, + "Y": -1, + "Z": -0.3, + "LengthSquared": 362.09, + "Length": 19.02866259094422 + }, + { + "X": 19, + "Y": 13, + "Z": -0.3, + "LengthSquared": 530.09, + "Length": 23.023683458560665 + }, + { + "X": 19, + "Y": 13, + "Z": 0, + "LengthSquared": 530, + "Length": 23.021728866442675 + }, + { + "X": 19, + "Y": -1, + "Z": 0, + "LengthSquared": 362, + "Length": 19.026297590440446 + }, + { + "X": -1, + "Y": 13, + "Z": -0.3, + "LengthSquared": 170.09, + "Length": 13.041855696180663 + }, + { + "X": -1, + "Y": -1, + "Z": -0.3, + "LengthSquared": 2.09, + "Length": 1.445683229480096 + }, + { + "X": -1, + "Y": -1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + }, + { + "X": -1, + "Y": 13, + "Z": 0, + "LengthSquared": 170, + "Length": 13.038404810405298 + }, + { + "X": 19, + "Y": 13, + "Z": -0.3, + "LengthSquared": 530.09, + "Length": 23.023683458560665 + }, + { + "X": -1, + "Y": 13, + "Z": -0.3, + "LengthSquared": 170.09, + "Length": 13.041855696180663 + }, + { + "X": -1, + "Y": 13, + "Z": 0, + "LengthSquared": 170, + "Length": 13.038404810405298 + }, + { + "X": 19, + "Y": 13, + "Z": 0, + "LengthSquared": 530, + "Length": 23.021728866442675 + }, + { + "X": -1, + "Y": -1, + "Z": -0.3, + "LengthSquared": 2.09, + "Length": 1.445683229480096 + }, + { + "X": 19, + "Y": -1, + "Z": -0.3, + "LengthSquared": 362.09, + "Length": 19.02866259094422 + }, + { + "X": 19, + "Y": -1, + "Z": 0, + "LengthSquared": 362, + "Length": 19.026297590440446 + }, + { + "X": -1, + "Y": -1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + }, + { + "X": -1, + "Y": -1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + }, + { + "X": 19, + "Y": -1, + "Z": 0, + "LengthSquared": 362, + "Length": 19.026297590440446 + }, + { + "X": 19, + "Y": 13, + "Z": 0, + "LengthSquared": 530, + "Length": 23.021728866442675 + }, + { + "X": -1, + "Y": 13, + "Z": 0, + "LengthSquared": 170, + "Length": 13.038404810405298 + }, + { + "X": -1, + "Y": 13, + "Z": -0.3, + "LengthSquared": 170.09, + "Length": 13.041855696180663 + }, + { + "X": 19, + "Y": 13, + "Z": -0.3, + "LengthSquared": 530.09, + "Length": 23.023683458560665 + }, + { + "X": 19, + "Y": -1, + "Z": -0.3, + "LengthSquared": 362.09, + "Length": 19.02866259094422 + }, + { + "X": -1, + "Y": -1, + "Z": -0.3, + "LengthSquared": 2.09, + "Length": 1.445683229480096 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 1, + "Name": "Column A1 Level 1", + "ElementId": 300002, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 2, + "Name": "Column A2 Level 1", + "ElementId": 300003, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 6, + "Z": 0, + "LengthSquared": 36, + "Length": 6 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 3, + "Name": "Column A3 Level 1", + "ElementId": 300004, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 12, + "Z": 0, + "LengthSquared": 144, + "Length": 12 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 4, + "Name": "Column B1 Level 1", + "ElementId": 300005, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 0, + "Z": 0, + "LengthSquared": 36, + "Length": 6 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 5, + "Name": "Column B2 Level 1", + "ElementId": 300006, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 6, + "Z": 0, + "LengthSquared": 72, + "Length": 8.48528137423857 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 6, + "Name": "Column B3 Level 1", + "ElementId": 300007, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 12, + "Z": 0, + "LengthSquared": 180, + "Length": 13.416407864998739 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 7, + "Name": "Column C1 Level 1", + "ElementId": 300008, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 0, + "Z": 0, + "LengthSquared": 144, + "Length": 12 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 8, + "Name": "Column C2 Level 1", + "ElementId": 300009, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 6, + "Z": 0, + "LengthSquared": 180, + "Length": 13.416407864998739 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 9, + "Name": "Column C3 Level 1", + "ElementId": 300010, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 12, + "Z": 0, + "LengthSquared": 288, + "Length": 16.97056274847714 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 10, + "Name": "Column D1 Level 1", + "ElementId": 300011, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 0, + "Z": 0, + "LengthSquared": 324, + "Length": 18 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 11, + "Name": "Column D2 Level 1", + "ElementId": 300012, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 6, + "Z": 0, + "LengthSquared": 360, + "Length": 18.973665961010276 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 12, + "Name": "Column D3 Level 1", + "ElementId": 300013, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 12, + "Z": 0, + "LengthSquared": 468, + "Length": 21.633307652783937 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 13, + "Name": "Beam A-B/1 Level 2", + "ElementId": 300014, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 0, + "Z": 3.4, + "LengthSquared": 11.559999999999999, + "Length": 3.4 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 14, + "Name": "Beam B-C/1 Level 2", + "ElementId": 300015, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 0, + "Z": 3.4, + "LengthSquared": 47.56, + "Length": 6.896375859826668 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 15, + "Name": "Beam C-D/1 Level 2", + "ElementId": 300016, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 0, + "Z": 3.4, + "LengthSquared": 155.56, + "Length": 12.472369462135092 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 16, + "Name": "Beam A-B/2 Level 2", + "ElementId": 300017, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 6, + "Z": 3.4, + "LengthSquared": 47.56, + "Length": 6.896375859826668 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 17, + "Name": "Beam B-C/2 Level 2", + "ElementId": 300018, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 6, + "Z": 3.4, + "LengthSquared": 83.56, + "Length": 9.14111590562115 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 18, + "Name": "Beam C-D/2 Level 2", + "ElementId": 300019, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 6, + "Z": 3.4, + "LengthSquared": 191.56, + "Length": 13.840520221436766 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 19, + "Name": "Beam A-B/3 Level 2", + "ElementId": 300020, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 12, + "Z": 3.4, + "LengthSquared": 155.56, + "Length": 12.472369462135092 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 20, + "Name": "Beam B-C/3 Level 2", + "ElementId": 300021, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 12, + "Z": 3.4, + "LengthSquared": 191.56, + "Length": 13.840520221436766 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 21, + "Name": "Beam C-D/3 Level 2", + "ElementId": 300022, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 12, + "Z": 3.4, + "LengthSquared": 299.56, + "Length": 17.307801709055948 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 22, + "Name": "Beam A/1-2 Level 2", + "ElementId": 300023, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 0, + "Y": 0, + "Z": 3.4, + "LengthSquared": 11.559999999999999, + "Length": 3.4 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 23, + "Name": "Beam A/2-3 Level 2", + "ElementId": 300024, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 0, + "Y": 6, + "Z": 3.4, + "LengthSquared": 47.56, + "Length": 6.896375859826668 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 24, + "Name": "Beam B/1-2 Level 2", + "ElementId": 300025, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 6, + "Y": 0, + "Z": 3.4, + "LengthSquared": 47.56, + "Length": 6.896375859826668 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 25, + "Name": "Beam B/2-3 Level 2", + "ElementId": 300026, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 6, + "Y": 6, + "Z": 3.4, + "LengthSquared": 83.56, + "Length": 9.14111590562115 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 26, + "Name": "Beam C/1-2 Level 2", + "ElementId": 300027, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 12, + "Y": 0, + "Z": 3.4, + "LengthSquared": 155.56, + "Length": 12.472369462135092 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 27, + "Name": "Beam C/2-3 Level 2", + "ElementId": 300028, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 12, + "Y": 6, + "Z": 3.4, + "LengthSquared": 191.56, + "Length": 13.840520221436766 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 28, + "Name": "Beam D/1-2 Level 2", + "ElementId": 300029, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 18, + "Y": 0, + "Z": 3.4, + "LengthSquared": 335.56, + "Length": 18.318296864064628 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 29, + "Name": "Beam D/2-3 Level 2", + "ElementId": 300030, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1.0000000000000004, + "LengthSquared": 1.0000000000000009, + "Length": 1.0000000000000004 + }, + "Translation": { + "X": 18, + "Y": 6, + "Z": 3.4, + "LengthSquared": 371.56, + "Length": 19.275891678467172 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 30, + "Name": "Column A1 Level 2", + "ElementId": 300031, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 0, + "Z": 3.6, + "LengthSquared": 12.96, + "Length": 3.6 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 31, + "Name": "Column A2 Level 2", + "ElementId": 300032, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 6, + "Z": 3.6, + "LengthSquared": 48.96, + "Length": 6.997142273814361 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 32, + "Name": "Column A3 Level 2", + "ElementId": 300033, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 12, + "Z": 3.6, + "LengthSquared": 156.96, + "Length": 12.52836781069266 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 33, + "Name": "Column B1 Level 2", + "ElementId": 300034, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 0, + "Z": 3.6, + "LengthSquared": 48.96, + "Length": 6.997142273814361 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 34, + "Name": "Column B2 Level 2", + "ElementId": 300035, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 6, + "Z": 3.6, + "LengthSquared": 84.96000000000001, + "Length": 9.21737489744233 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 35, + "Name": "Column B3 Level 2", + "ElementId": 300036, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 12, + "Z": 3.6, + "LengthSquared": 192.96, + "Length": 13.891004283348272 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 36, + "Name": "Column C1 Level 2", + "ElementId": 300037, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 0, + "Z": 3.6, + "LengthSquared": 156.96, + "Length": 12.52836781069266 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 37, + "Name": "Column C2 Level 2", + "ElementId": 300038, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 6, + "Z": 3.6, + "LengthSquared": 192.96, + "Length": 13.891004283348272 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 38, + "Name": "Column C3 Level 2", + "ElementId": 300039, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 12, + "Z": 3.6, + "LengthSquared": 300.96, + "Length": 17.348198753761153 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 39, + "Name": "Column D1 Level 2", + "ElementId": 300040, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 0, + "Z": 3.6, + "LengthSquared": 336.96, + "Length": 18.356470248934023 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 40, + "Name": "Column D2 Level 2", + "ElementId": 300041, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 6, + "Z": 3.6, + "LengthSquared": 372.96, + "Length": 19.312172327317295 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 41, + "Name": "Column D3 Level 2", + "ElementId": 300042, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 12, + "Z": 3.6, + "LengthSquared": 480.96, + "Length": 21.930800258996477 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 42, + "Name": "Beam A-B/1 Roof", + "ElementId": 300043, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 0, + "Z": 7, + "LengthSquared": 49, + "Length": 7 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 43, + "Name": "Beam B-C/1 Roof", + "ElementId": 300044, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 0, + "Z": 7, + "LengthSquared": 85, + "Length": 9.219544457292887 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 44, + "Name": "Beam C-D/1 Roof", + "ElementId": 300045, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 0, + "Z": 7, + "LengthSquared": 193, + "Length": 13.892443989449804 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 45, + "Name": "Beam A-B/2 Roof", + "ElementId": 300046, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 6, + "Z": 7, + "LengthSquared": 85, + "Length": 9.219544457292887 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 46, + "Name": "Beam B-C/2 Roof", + "ElementId": 300047, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 6, + "Z": 7, + "LengthSquared": 121, + "Length": 11 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 47, + "Name": "Beam C-D/2 Roof", + "ElementId": 300048, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 6, + "Z": 7, + "LengthSquared": 229, + "Length": 15.132745950421556 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 48, + "Name": "Beam A-B/3 Roof", + "ElementId": 300049, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 12, + "Z": 7, + "LengthSquared": 193, + "Length": 13.892443989449804 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 49, + "Name": "Beam B-C/3 Roof", + "ElementId": 300050, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 12, + "Z": 7, + "LengthSquared": 229, + "Length": 15.132745950421556 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 50, + "Name": "Beam C-D/3 Roof", + "ElementId": 300051, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 12, + "Z": 7, + "LengthSquared": 337, + "Length": 18.35755975068582 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 51, + "Name": "Beam A/1-2 Roof", + "ElementId": 300052, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 0, + "Z": 7, + "LengthSquared": 49, + "Length": 7 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 52, + "Name": "Beam A/2-3 Roof", + "ElementId": 300053, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 0, + "Y": 6, + "Z": 7, + "LengthSquared": 85, + "Length": 9.219544457292887 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 53, + "Name": "Beam B/1-2 Roof", + "ElementId": 300054, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 0, + "Z": 7, + "LengthSquared": 85, + "Length": 9.219544457292887 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 54, + "Name": "Beam B/2-3 Roof", + "ElementId": 300055, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 6, + "Y": 6, + "Z": 7, + "LengthSquared": 121, + "Length": 11 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 55, + "Name": "Beam C/1-2 Roof", + "ElementId": 300056, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 0, + "Z": 7, + "LengthSquared": 193, + "Length": 13.892443989449804 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 56, + "Name": "Beam C/2-3 Roof", + "ElementId": 300057, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 12, + "Y": 6, + "Z": 7, + "LengthSquared": 229, + "Length": 15.132745950421556 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 57, + "Name": "Beam D/1-2 Roof", + "ElementId": 300058, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 0, + "Z": 7, + "LengthSquared": 373, + "Length": 19.313207915827967 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 58, + "Name": "Beam D/2-3 Roof", + "ElementId": 300059, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 2, + "Transform": { + "XAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": -1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 18, + "Y": 6, + "Z": 7, + "LengthSquared": 409, + "Length": 20.223748416156685 + } + }, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 59, + "Name": "Floor Slab Level 2", + "ElementId": 300060, + "Vertices": [ + { + "X": 18.5, + "Y": -0.5, + "Z": 3.4, + "LengthSquared": 354.06, + "Length": 18.816482136680065 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 3.4, + "LengthSquared": 510.06, + "Length": 22.58450796453179 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 3.6, + "LengthSquared": 511.46, + "Length": 22.615481423131367 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 3.6, + "LengthSquared": 355.46, + "Length": 18.853646862079493 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 3.4, + "LengthSquared": 168.06, + "Length": 12.963795740445775 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 3.4, + "LengthSquared": 12.059999999999999, + "Length": 3.4727510708370675 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 3.6, + "LengthSquared": 13.46, + "Length": 3.6687872655688283 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 3.6, + "LengthSquared": 169.46, + "Length": 13.017680284904834 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 3.4, + "LengthSquared": 510.06, + "Length": 22.58450796453179 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 3.4, + "LengthSquared": 168.06, + "Length": 12.963795740445775 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 3.6, + "LengthSquared": 169.46, + "Length": 13.017680284904834 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 3.6, + "LengthSquared": 511.46, + "Length": 22.615481423131367 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 3.4, + "LengthSquared": 12.059999999999999, + "Length": 3.4727510708370675 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 3.4, + "LengthSquared": 354.06, + "Length": 18.816482136680065 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 3.6, + "LengthSquared": 355.46, + "Length": 18.853646862079493 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 3.6, + "LengthSquared": 13.46, + "Length": 3.6687872655688283 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 3.6, + "LengthSquared": 13.46, + "Length": 3.6687872655688283 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 3.6, + "LengthSquared": 355.46, + "Length": 18.853646862079493 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 3.6, + "LengthSquared": 511.46, + "Length": 22.615481423131367 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 3.6, + "LengthSquared": 169.46, + "Length": 13.017680284904834 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 3.4, + "LengthSquared": 168.06, + "Length": 12.963795740445775 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 3.4, + "LengthSquared": 510.06, + "Length": 22.58450796453179 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 3.4, + "LengthSquared": 354.06, + "Length": 18.816482136680065 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 3.4, + "LengthSquared": 12.059999999999999, + "Length": 3.4727510708370675 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 60, + "Name": "Roof Slab", + "ElementId": 300061, + "Vertices": [ + { + "X": 18.5, + "Y": -0.5, + "Z": 7, + "LengthSquared": 391.5, + "Length": 19.78635893740938 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 7, + "LengthSquared": 547.5, + "Length": 23.39871791359518 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 7.2, + "LengthSquared": 550.34, + "Length": 23.459326503546517 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 7.2, + "LengthSquared": 394.34000000000003, + "Length": 19.857995870681414 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 7, + "LengthSquared": 205.5, + "Length": 14.33527118683145 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 7, + "LengthSquared": 49.5, + "Length": 7.035623639735144 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 7.2, + "LengthSquared": 52.34, + "Length": 7.234638899074369 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 7.2, + "LengthSquared": 208.34, + "Length": 14.433987668000828 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 7, + "LengthSquared": 547.5, + "Length": 23.39871791359518 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 7, + "LengthSquared": 205.5, + "Length": 14.33527118683145 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 7.2, + "LengthSquared": 208.34, + "Length": 14.433987668000828 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 7.2, + "LengthSquared": 550.34, + "Length": 23.459326503546517 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 7, + "LengthSquared": 49.5, + "Length": 7.035623639735144 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 7, + "LengthSquared": 391.5, + "Length": 19.78635893740938 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 7.2, + "LengthSquared": 394.34000000000003, + "Length": 19.857995870681414 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 7.2, + "LengthSquared": 52.34, + "Length": 7.234638899074369 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 7.2, + "LengthSquared": 52.34, + "Length": 7.234638899074369 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 7.2, + "LengthSquared": 394.34000000000003, + "Length": 19.857995870681414 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 7.2, + "LengthSquared": 550.34, + "Length": 23.459326503546517 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 7.2, + "LengthSquared": 208.34, + "Length": 14.433987668000828 + }, + { + "X": -0.5, + "Y": 12.5, + "Z": 7, + "LengthSquared": 205.5, + "Length": 14.33527118683145 + }, + { + "X": 18.5, + "Y": 12.5, + "Z": 7, + "LengthSquared": 547.5, + "Length": 23.39871791359518 + }, + { + "X": 18.5, + "Y": -0.5, + "Z": 7, + "LengthSquared": 391.5, + "Length": 19.78635893740938 + }, + { + "X": -0.5, + "Y": -0.5, + "Z": 7, + "LengthSquared": 49.5, + "Length": 7.035623639735144 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 61, + "Name": "Curtain Wall South", + "ElementId": 300062, + "Vertices": [ + { + "X": 18, + "Y": -0.75, + "Z": 0, + "LengthSquared": 324.5625, + "Length": 18.01561822419647 + }, + { + "X": 18, + "Y": -0.65, + "Z": 0, + "LengthSquared": 324.4225, + "Length": 18.011732287595215 + }, + { + "X": 18, + "Y": -0.65, + "Z": 0.1, + "LengthSquared": 324.4325, + "Length": 18.012009882297978 + }, + { + "X": 18, + "Y": -0.75, + "Z": 0.1, + "LengthSquared": 324.5725, + "Length": 18.015895759023476 + }, + { + "X": 0, + "Y": -0.65, + "Z": 0, + "LengthSquared": 0.42250000000000004, + "Length": 0.65 + }, + { + "X": 0, + "Y": -0.75, + "Z": 0, + "LengthSquared": 0.5625, + "Length": 0.75 + }, + { + "X": 0, + "Y": -0.75, + "Z": 0.1, + "LengthSquared": 0.5725, + "Length": 0.7566372975210778 + }, + { + "X": 0, + "Y": -0.65, + "Z": 0.1, + "LengthSquared": 0.43250000000000005, + "Length": 0.6576473218982953 + }, + { + "X": 18, + "Y": -0.65, + "Z": 0, + "LengthSquared": 324.4225, + "Length": 18.011732287595215 + }, + { + "X": 0, + "Y": -0.65, + "Z": 0, + "LengthSquared": 0.42250000000000004, + "Length": 0.65 + }, + { + "X": 0, + "Y": -0.65, + "Z": 0.1, + "LengthSquared": 0.43250000000000005, + "Length": 0.6576473218982953 + }, + { + "X": 18, + "Y": -0.65, + "Z": 0.1, + "LengthSquared": 324.4325, + "Length": 18.012009882297978 + }, + { + "X": 0, + "Y": -0.75, + "Z": 0, + "LengthSquared": 0.5625, + "Length": 0.75 + }, + { + "X": 18, + "Y": -0.75, + "Z": 0, + "LengthSquared": 324.5625, + "Length": 18.01561822419647 + }, + { + "X": 18, + "Y": -0.75, + "Z": 0.1, + "LengthSquared": 324.5725, + "Length": 18.015895759023476 + }, + { + "X": 0, + "Y": -0.75, + "Z": 0.1, + "LengthSquared": 0.5725, + "Length": 0.7566372975210778 + }, + { + "X": 0, + "Y": -0.75, + "Z": 0.1, + "LengthSquared": 0.5725, + "Length": 0.7566372975210778 + }, + { + "X": 18, + "Y": -0.75, + "Z": 0.1, + "LengthSquared": 324.5725, + "Length": 18.015895759023476 + }, + { + "X": 18, + "Y": -0.65, + "Z": 0.1, + "LengthSquared": 324.4325, + "Length": 18.012009882297978 + }, + { + "X": 0, + "Y": -0.65, + "Z": 0.1, + "LengthSquared": 0.43250000000000005, + "Length": 0.6576473218982953 + }, + { + "X": 0, + "Y": -0.65, + "Z": 0, + "LengthSquared": 0.42250000000000004, + "Length": 0.65 + }, + { + "X": 18, + "Y": -0.65, + "Z": 0, + "LengthSquared": 324.4225, + "Length": 18.011732287595215 + }, + { + "X": 18, + "Y": -0.75, + "Z": 0, + "LengthSquared": 324.5625, + "Length": 18.01561822419647 + }, + { + "X": 0, + "Y": -0.75, + "Z": 0, + "LengthSquared": 0.5625, + "Length": 0.75 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 80, + "G": 82, + "B": 88, + "A": 255, + "Rf": 0.3137254901960784, + "Gf": 0.3215686274509804, + "Bf": 0.34509803921568627, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 62, + "Name": "Curtain Wall South", + "ElementId": 300062, + "Vertices": [ + { + "X": 18, + "Y": -0.72, + "Z": 0.1, + "LengthSquared": 324.5284, + "Length": 18.014671798287083 + }, + { + "X": 18, + "Y": -0.68, + "Z": 0.1, + "LengthSquared": 324.4724, + "Length": 18.01311744257501 + }, + { + "X": 18, + "Y": -0.68, + "Z": 7.2, + "LengthSquared": 376.30240000000003, + "Length": 19.39851540711299 + }, + { + "X": 18, + "Y": -0.72, + "Z": 7.2, + "LengthSquared": 376.35839999999996, + "Length": 19.39995876284277 + }, + { + "X": 0, + "Y": -0.68, + "Z": 0.1, + "LengthSquared": 0.4724000000000001, + "Length": 0.6873136110975834 + }, + { + "X": 0, + "Y": -0.72, + "Z": 0.1, + "LengthSquared": 0.5284, + "Length": 0.72691127381545 + }, + { + "X": 0, + "Y": -0.72, + "Z": 7.2, + "LengthSquared": 52.3584, + "Length": 7.235910447207041 + }, + { + "X": 0, + "Y": -0.68, + "Z": 7.2, + "LengthSquared": 52.302400000000006, + "Length": 7.232039822899208 + }, + { + "X": 18, + "Y": -0.68, + "Z": 0.1, + "LengthSquared": 324.4724, + "Length": 18.01311744257501 + }, + { + "X": 0, + "Y": -0.68, + "Z": 0.1, + "LengthSquared": 0.4724000000000001, + "Length": 0.6873136110975834 + }, + { + "X": 0, + "Y": -0.68, + "Z": 7.2, + "LengthSquared": 52.302400000000006, + "Length": 7.232039822899208 + }, + { + "X": 18, + "Y": -0.68, + "Z": 7.2, + "LengthSquared": 376.30240000000003, + "Length": 19.39851540711299 + }, + { + "X": 0, + "Y": -0.72, + "Z": 0.1, + "LengthSquared": 0.5284, + "Length": 0.72691127381545 + }, + { + "X": 18, + "Y": -0.72, + "Z": 0.1, + "LengthSquared": 324.5284, + "Length": 18.014671798287083 + }, + { + "X": 18, + "Y": -0.72, + "Z": 7.2, + "LengthSquared": 376.35839999999996, + "Length": 19.39995876284277 + }, + { + "X": 0, + "Y": -0.72, + "Z": 7.2, + "LengthSquared": 52.3584, + "Length": 7.235910447207041 + }, + { + "X": 0, + "Y": -0.72, + "Z": 7.2, + "LengthSquared": 52.3584, + "Length": 7.235910447207041 + }, + { + "X": 18, + "Y": -0.72, + "Z": 7.2, + "LengthSquared": 376.35839999999996, + "Length": 19.39995876284277 + }, + { + "X": 18, + "Y": -0.68, + "Z": 7.2, + "LengthSquared": 376.30240000000003, + "Length": 19.39851540711299 + }, + { + "X": 0, + "Y": -0.68, + "Z": 7.2, + "LengthSquared": 52.302400000000006, + "Length": 7.232039822899208 + }, + { + "X": 0, + "Y": -0.68, + "Z": 0.1, + "LengthSquared": 0.4724000000000001, + "Length": 0.6873136110975834 + }, + { + "X": 18, + "Y": -0.68, + "Z": 0.1, + "LengthSquared": 324.4724, + "Length": 18.01311744257501 + }, + { + "X": 18, + "Y": -0.72, + "Z": 0.1, + "LengthSquared": 324.5284, + "Length": 18.014671798287083 + }, + { + "X": 0, + "Y": -0.72, + "Z": 0.1, + "LengthSquared": 0.5284, + "Length": 0.72691127381545 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 150, + "G": 200, + "B": 230, + "A": 255, + "Rf": 0.5882352941176471, + "Gf": 0.7843137254901961, + "Bf": 0.9019607843137255, + "Af": 1 + }, + "Opacity": 0.35, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 63, + "Name": "Supply Duct SA-1", + "ElementId": 300063, + "Vertices": [ + { + "X": 9.3, + "Y": -1, + "Z": 3, + "LengthSquared": 96.49000000000001, + "Length": 9.822932352408827 + }, + { + "X": 9.3, + "Y": 13, + "Z": 3, + "LengthSquared": 264.49, + "Length": 16.263148526653747 + }, + { + "X": 9.3, + "Y": 13, + "Z": 3.4, + "LengthSquared": 267.05, + "Length": 16.341664541900254 + }, + { + "X": 9.3, + "Y": -1, + "Z": 3.4, + "LengthSquared": 99.05000000000001, + "Length": 9.95238664843765 + }, + { + "X": 8.7, + "Y": 13, + "Z": 3, + "LengthSquared": 253.69, + "Length": 15.927648916271353 + }, + { + "X": 8.7, + "Y": -1, + "Z": 3, + "LengthSquared": 85.68999999999998, + "Length": 9.256889326334196 + }, + { + "X": 8.7, + "Y": -1, + "Z": 3.4, + "LengthSquared": 88.24999999999999, + "Length": 9.394147114027968 + }, + { + "X": 8.7, + "Y": 13, + "Z": 3.4, + "LengthSquared": 256.25, + "Length": 16.00781059358212 + }, + { + "X": 9.3, + "Y": 13, + "Z": 3, + "LengthSquared": 264.49, + "Length": 16.263148526653747 + }, + { + "X": 8.7, + "Y": 13, + "Z": 3, + "LengthSquared": 253.69, + "Length": 15.927648916271353 + }, + { + "X": 8.7, + "Y": 13, + "Z": 3.4, + "LengthSquared": 256.25, + "Length": 16.00781059358212 + }, + { + "X": 9.3, + "Y": 13, + "Z": 3.4, + "LengthSquared": 267.05, + "Length": 16.341664541900254 + }, + { + "X": 8.7, + "Y": -1, + "Z": 3, + "LengthSquared": 85.68999999999998, + "Length": 9.256889326334196 + }, + { + "X": 9.3, + "Y": -1, + "Z": 3, + "LengthSquared": 96.49000000000001, + "Length": 9.822932352408827 + }, + { + "X": 9.3, + "Y": -1, + "Z": 3.4, + "LengthSquared": 99.05000000000001, + "Length": 9.95238664843765 + }, + { + "X": 8.7, + "Y": -1, + "Z": 3.4, + "LengthSquared": 88.24999999999999, + "Length": 9.394147114027968 + }, + { + "X": 8.7, + "Y": -1, + "Z": 3.4, + "LengthSquared": 88.24999999999999, + "Length": 9.394147114027968 + }, + { + "X": 9.3, + "Y": -1, + "Z": 3.4, + "LengthSquared": 99.05000000000001, + "Length": 9.95238664843765 + }, + { + "X": 9.3, + "Y": 13, + "Z": 3.4, + "LengthSquared": 267.05, + "Length": 16.341664541900254 + }, + { + "X": 8.7, + "Y": 13, + "Z": 3.4, + "LengthSquared": 256.25, + "Length": 16.00781059358212 + }, + { + "X": 8.7, + "Y": 13, + "Z": 3, + "LengthSquared": 253.69, + "Length": 15.927648916271353 + }, + { + "X": 9.3, + "Y": 13, + "Z": 3, + "LengthSquared": 264.49, + "Length": 16.263148526653747 + }, + { + "X": 9.3, + "Y": -1, + "Z": 3, + "LengthSquared": 96.49000000000001, + "Length": 9.822932352408827 + }, + { + "X": 8.7, + "Y": -1, + "Z": 3, + "LengthSquared": 85.68999999999998, + "Length": 9.256889326334196 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 205, + "G": 215, + "B": 225, + "A": 255, + "Rf": 0.803921568627451, + "Gf": 0.8431372549019608, + "Bf": 0.8823529411764706, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 64, + "Name": "Survey Marker", + "ElementId": null, + "Vertices": [ + { + "X": -3.3, + "Y": -3.3, + "Z": 0, + "LengthSquared": 21.779999999999998, + "Length": 4.666904755831213 + }, + { + "X": -2.7, + "Y": -3.3, + "Z": 0, + "LengthSquared": 18.18, + "Length": 4.263801121065568 + }, + { + "X": -2.7, + "Y": -2.7, + "Z": 0, + "LengthSquared": 14.580000000000002, + "Length": 3.818376618407357 + }, + { + "X": -3.3, + "Y": -2.7, + "Z": 0, + "LengthSquared": 18.18, + "Length": 4.263801121065568 + }, + { + "X": -3, + "Y": -3, + "Z": 0.8, + "LengthSquared": 18.64, + "Length": 4.317406628984581 + } + ], + "Indices": [ + 0, + 2, + 1, + 0, + 3, + 2, + 0, + 1, + 4, + 1, + 2, + 4, + 2, + 3, + 4, + 3, + 0, + 4 + ], + "Normals": null, + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 220, + "G": 60, + "B": 40, + "A": 255, + "Rf": 0.8627450980392157, + "Gf": 0.23529411764705882, + "Bf": 0.1568627450980392, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 65, + "Name": "Column Twin", + "ElementId": 300064, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 22, + "Y": -3, + "Z": 0, + "LengthSquared": 493, + "Length": 22.20360331117452 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 66, + "Name": "Column Twin Mirrored", + "ElementId": 300065, + "Vertices": [], + "Indices": [], + "Normals": null, + "UVs": null, + "GeometryId": 1, + "Transform": { + "XAxis": { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + "YAxis": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "ZAxis": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "Translation": { + "X": 24, + "Y": -3, + "Z": 0, + "LengthSquared": 585, + "Length": 24.186773244895647 + } + }, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + } + ], + "Geometries": [ + { + "Id": 1, + "Vertices": [ + { + "X": 0.2, + "Y": -0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": 0.2, + "Y": 0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": 0.2, + "Y": 0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": 0.2, + "Y": -0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": 0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": -0.2, + "Y": -0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": -0.2, + "Y": -0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": 0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": 0.2, + "Y": 0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": -0.2, + "Y": 0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": -0.2, + "Y": 0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": 0.2, + "Y": 0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": -0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": 0.2, + "Y": -0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": 0.2, + "Y": -0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": -0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": -0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": 0.2, + "Y": -0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": 0.2, + "Y": 0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": 0.2, + "Z": 3.4, + "LengthSquared": 11.639999999999999, + "Length": 3.411744421846396 + }, + { + "X": -0.2, + "Y": 0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": 0.2, + "Y": 0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": 0.2, + "Y": -0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + }, + { + "X": -0.2, + "Y": -0.2, + "Z": 0, + "LengthSquared": 0.08000000000000002, + "Length": 0.28284271247461906 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null + }, + { + "Id": 2, + "Vertices": [ + { + "X": 6, + "Y": -0.1, + "Z": -0.3, + "LengthSquared": 36.1, + "Length": 6.008327554319921 + }, + { + "X": 6, + "Y": 0.1, + "Z": -0.3, + "LengthSquared": 36.1, + "Length": 6.008327554319921 + }, + { + "X": 6, + "Y": 0.1, + "Z": 0, + "LengthSquared": 36.01, + "Length": 6.000833275470999 + }, + { + "X": 6, + "Y": -0.1, + "Z": 0, + "LengthSquared": 36.01, + "Length": 6.000833275470999 + }, + { + "X": 0, + "Y": 0.1, + "Z": -0.3, + "LengthSquared": 0.1, + "Length": 0.31622776601683794 + }, + { + "X": 0, + "Y": -0.1, + "Z": -0.3, + "LengthSquared": 0.1, + "Length": 0.31622776601683794 + }, + { + "X": 0, + "Y": -0.1, + "Z": 0, + "LengthSquared": 0.010000000000000002, + "Length": 0.1 + }, + { + "X": 0, + "Y": 0.1, + "Z": 0, + "LengthSquared": 0.010000000000000002, + "Length": 0.1 + }, + { + "X": 6, + "Y": 0.1, + "Z": -0.3, + "LengthSquared": 36.1, + "Length": 6.008327554319921 + }, + { + "X": 0, + "Y": 0.1, + "Z": -0.3, + "LengthSquared": 0.1, + "Length": 0.31622776601683794 + }, + { + "X": 0, + "Y": 0.1, + "Z": 0, + "LengthSquared": 0.010000000000000002, + "Length": 0.1 + }, + { + "X": 6, + "Y": 0.1, + "Z": 0, + "LengthSquared": 36.01, + "Length": 6.000833275470999 + }, + { + "X": 0, + "Y": -0.1, + "Z": -0.3, + "LengthSquared": 0.1, + "Length": 0.31622776601683794 + }, + { + "X": 6, + "Y": -0.1, + "Z": -0.3, + "LengthSquared": 36.1, + "Length": 6.008327554319921 + }, + { + "X": 6, + "Y": -0.1, + "Z": 0, + "LengthSquared": 36.01, + "Length": 6.000833275470999 + }, + { + "X": 0, + "Y": -0.1, + "Z": 0, + "LengthSquared": 0.010000000000000002, + "Length": 0.1 + }, + { + "X": 0, + "Y": -0.1, + "Z": 0, + "LengthSquared": 0.010000000000000002, + "Length": 0.1 + }, + { + "X": 6, + "Y": -0.1, + "Z": 0, + "LengthSquared": 36.01, + "Length": 6.000833275470999 + }, + { + "X": 6, + "Y": 0.1, + "Z": 0, + "LengthSquared": 36.01, + "Length": 6.000833275470999 + }, + { + "X": 0, + "Y": 0.1, + "Z": 0, + "LengthSquared": 0.010000000000000002, + "Length": 0.1 + }, + { + "X": 0, + "Y": 0.1, + "Z": -0.3, + "LengthSquared": 0.1, + "Length": 0.31622776601683794 + }, + { + "X": 6, + "Y": 0.1, + "Z": -0.3, + "LengthSquared": 36.1, + "Length": 6.008327554319921 + }, + { + "X": 6, + "Y": -0.1, + "Z": -0.3, + "LengthSquared": 36.1, + "Length": 6.008327554319921 + }, + { + "X": 0, + "Y": -0.1, + "Z": -0.3, + "LengthSquared": 0.1, + "Length": 0.31622776601683794 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null + } + ], + "Materials": [], + "Textures": [], + "Grids": { + "Name": "Structural Grids", + "Lines": [ + { + "Name": "A", + "Start": { + "X": 0, + "Y": -2, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + "End": { + "X": 0, + "Y": 14, + "Z": 0, + "LengthSquared": 196, + "Length": 14 + }, + "Elevation": 0 + }, + { + "Name": "B", + "Start": { + "X": 6, + "Y": -2, + "Z": 0, + "LengthSquared": 40, + "Length": 6.324555320336759 + }, + "End": { + "X": 6, + "Y": 14, + "Z": 0, + "LengthSquared": 232, + "Length": 15.231546211727817 + }, + "Elevation": 0 + }, + { + "Name": "C", + "Start": { + "X": 12, + "Y": -2, + "Z": 0, + "LengthSquared": 148, + "Length": 12.165525060596439 + }, + "End": { + "X": 12, + "Y": 14, + "Z": 0, + "LengthSquared": 340, + "Length": 18.439088914585774 + }, + "Elevation": 0 + }, + { + "Name": "D", + "Start": { + "X": 18, + "Y": -2, + "Z": 0, + "LengthSquared": 328, + "Length": 18.110770276274835 + }, + "End": { + "X": 18, + "Y": 14, + "Z": 0, + "LengthSquared": 520, + "Length": 22.80350850198276 + }, + "Elevation": 0 + }, + { + "Name": "1", + "Start": { + "X": -2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + "End": { + "X": 20, + "Y": 0, + "Z": 0, + "LengthSquared": 400, + "Length": 20 + }, + "Elevation": 0 + }, + { + "Name": "2", + "Start": { + "X": -2, + "Y": 6, + "Z": 0, + "LengthSquared": 40, + "Length": 6.324555320336759 + }, + "End": { + "X": 20, + "Y": 6, + "Z": 0, + "LengthSquared": 436, + "Length": 20.8806130178211 + }, + "Elevation": 0 + }, + { + "Name": "3", + "Start": { + "X": -2, + "Y": 12, + "Z": 0, + "LengthSquared": 148, + "Length": 12.165525060596439 + }, + "End": { + "X": 20, + "Y": 12, + "Z": 0, + "LengthSquared": 544, + "Length": 23.323807579381203 + }, + "Elevation": 0 + } + ] + }, + "Viewpoints": [ + { + "Name": "Isometric", + "Camera": { + "Position": { + "X": 36, + "Y": -24, + "Z": 24, + "LengthSquared": 2448, + "Length": 49.47726750741192 + }, + "Target": { + "X": 9, + "Y": 6, + "Z": 3.6, + "LengthSquared": 129.96, + "Length": 11.4 + }, + "Up": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "FieldOfView": 0.785, + "AspectRatio": 1.6, + "NearDistance": 0.1, + "FarDistance": 1000, + "Projection": 0 + } + }, + { + "Name": "Front", + "Camera": { + "Position": { + "X": 9, + "Y": -40, + "Z": 3.6, + "LengthSquared": 1693.96, + "Length": 41.15774532211404 + }, + "Target": { + "X": 9, + "Y": 6, + "Z": 3.6, + "LengthSquared": 129.96, + "Length": 11.4 + }, + "Up": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "FieldOfView": 0.785, + "AspectRatio": 1.6, + "NearDistance": 0.1, + "FarDistance": 1000, + "Projection": 0 + } + }, + { + "Name": "Top", + "Camera": { + "Position": { + "X": 9, + "Y": 6, + "Z": 50, + "LengthSquared": 2617, + "Length": 51.15662224971465 + }, + "Target": { + "X": 9, + "Y": 6, + "Z": 0, + "LengthSquared": 117, + "Length": 10.816653826391969 + }, + "Up": { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + "FieldOfView": 0.785, + "AspectRatio": 1.6, + "NearDistance": 0.1, + "FarDistance": 1000, + "Projection": 0 + } + }, + { + "Name": "Duct Clash", + "Camera": { + "Position": { + "X": 14, + "Y": -4, + "Z": 5, + "LengthSquared": 237, + "Length": 15.394804318340652 + }, + "Target": { + "X": 9, + "Y": 3, + "Z": 3.2, + "LengthSquared": 100.24000000000001, + "Length": 10.011992808627062 + }, + "Up": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "FieldOfView": 0.785, + "AspectRatio": 1.6, + "NearDistance": 0.1, + "FarDistance": 1000, + "Projection": 0 + } + }, + { + "Name": "Plan", + "Camera": { + "Position": { + "X": 9, + "Y": 6, + "Z": 40, + "LengthSquared": 1717, + "Length": 41.43669871020132 + }, + "Target": { + "X": 9, + "Y": 6, + "Z": 0, + "LengthSquared": 117, + "Length": 10.816653826391969 + }, + "Up": { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + "FieldOfView": 0.785, + "AspectRatio": 1.6, + "NearDistance": 0.1, + "FarDistance": 1000, + "Projection": 1 + } + } + ], + "SectionPlanes": [], + "SectionBox": null, + "TimeLiner": { + "Name": "Construction Schedule", + "Tasks": [ + { + "Id": "T1", + "Name": "Ground slab", + "TaskType": 0, + "PlannedStart": "2026-03-02T08:00:00+00:00", + "PlannedEnd": "2026-03-12T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 100000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300001 + ], + "Children": [] + }, + { + "Id": "T2", + "Name": "Columns Level 1", + "TaskType": 0, + "PlannedStart": "2026-03-12T08:00:00+00:00", + "PlannedEnd": "2026-03-20T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 80000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300002, + 300003, + 300004, + 300005, + 300006, + 300007, + 300008, + 300009, + 300010, + 300011, + 300012, + 300013 + ], + "Children": [] + }, + { + "Id": "T3", + "Name": "Beams and slab Level 2", + "TaskType": 0, + "PlannedStart": "2026-03-20T08:00:00+00:00", + "PlannedEnd": "2026-04-01T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 120000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300014, + 300015, + 300016, + 300017, + 300018, + 300019, + 300020, + 300021, + 300022, + 300023, + 300024, + 300025, + 300026, + 300027, + 300028, + 300029, + 300030, + 300060 + ], + "Children": [] + }, + { + "Id": "T4", + "Name": "Columns Level 2", + "TaskType": 0, + "PlannedStart": "2026-04-01T08:00:00+00:00", + "PlannedEnd": "2026-04-09T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 80000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300031, + 300032, + 300033, + 300034, + 300035, + 300036, + 300037, + 300038, + 300039, + 300040, + 300041, + 300042 + ], + "Children": [] + }, + { + "Id": "T5", + "Name": "Beams and roof slab", + "TaskType": 0, + "PlannedStart": "2026-04-09T08:00:00+00:00", + "PlannedEnd": "2026-04-21T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 120000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300043, + 300044, + 300045, + 300046, + 300047, + 300048, + 300049, + 300050, + 300051, + 300052, + 300053, + 300054, + 300055, + 300056, + 300057, + 300058, + 300059, + 300061 + ], + "Children": [] + }, + { + "Id": "T6", + "Name": "Curtain wall", + "TaskType": 0, + "PlannedStart": "2026-04-21T08:00:00+00:00", + "PlannedEnd": "2026-05-01T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 100000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300062 + ], + "Children": [] + }, + { + "Id": "T7", + "Name": "Supply duct", + "TaskType": 0, + "PlannedStart": "2026-04-26T08:00:00+00:00", + "PlannedEnd": "2026-05-01T08:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": 50000, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 300063 + ], + "Children": [] + } + ] + }, + "ClashTests": [ + { + "Name": "Duct vs Structure", + "Clashes": [ + { + "Id": "CL1", + "Name": "Clash 1", + "Element1Name": "Supply Duct SA-1", + "Element2Name": "Beam B-C/1 Level 2", + "Element1Id": 300063, + "Element2Id": 300015, + "Point": { + "X": 9, + "Y": 0, + "Z": 3.25, + "LengthSquared": 91.5625, + "Length": 9.56882960450232 + }, + "Distance": -0.15, + "Status": 0 + }, + { + "Id": "CL2", + "Name": "Clash 2", + "Element1Name": "Supply Duct SA-1", + "Element2Name": "Beam B-C/2 Level 2", + "Element1Id": 300063, + "Element2Id": 300018, + "Point": { + "X": 9, + "Y": 6, + "Z": 3.25, + "LengthSquared": 127.5625, + "Length": 11.294356998076518 + }, + "Distance": -0.15, + "Status": 0 + }, + { + "Id": "CL3", + "Name": "Clash 3", + "Element1Name": "Supply Duct SA-1", + "Element2Name": "Beam B-C/3 Level 2", + "Element1Id": 300063, + "Element2Id": 300021, + "Point": { + "X": 9, + "Y": 12, + "Z": 3.25, + "LengthSquared": 235.5625, + "Length": 15.348045478170828 + }, + "Distance": -0.15, + "Status": 0 + } + ] + } + ], + "LinkedFiles": [ + { + "Name": "Structure", + "Path": "Structure.rvt", + "Visible": true + }, + { + "Name": "MEP", + "Path": "MEP.rvt", + "Visible": true + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/legacy-0.2/empty.json b/tests/fixtures/legacy-0.2/empty.json new file mode 100644 index 0000000..d98d558 --- /dev/null +++ b/tests/fixtures/legacy-0.2/empty.json @@ -0,0 +1,50 @@ +{ + "Title": "Empty Model", + "SourcePath": null, + "Author": null, + "CreatedAt": null, + "Application": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + }, + "Elements": [], + "Meshes": [], + "Geometries": [], + "Materials": [], + "Textures": [], + "Grids": null, + "Viewpoints": [], + "SectionPlanes": [], + "SectionBox": null, + "TimeLiner": null, + "ClashTests": [], + "LinkedFiles": [] +} \ No newline at end of file diff --git a/tests/fixtures/legacy-0.2/escaping.json b/tests/fixtures/legacy-0.2/escaping.json new file mode 100644 index 0000000..6c0a410 --- /dev/null +++ b/tests/fixtures/legacy-0.2/escaping.json @@ -0,0 +1,1483 @@ +{ + "Title": "Escaping", + "SourcePath": null, + "Author": null, + "CreatedAt": null, + "Application": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + }, + "Elements": [ + { + "Id": 500001, + "Name": "", + "Category": "Walls", + "TypeName": "Generic 200", + "Level": "Level 1", + "LinkName": null, + "Properties": { + "": { + "": "" + } + }, + "MeshIndices": [ + 0 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 500002, + "Name": "Plain Wall", + "Category": "Walls", + "TypeName": "Generic 200", + "Level": "Level 1", + "LinkName": null, + "Properties": { + "Identity Data": { + "Mark": "Plain Wall" + } + }, + "MeshIndices": [ + 1 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 500003, + "Name": "Plain Slab", + "Category": "Walls", + "TypeName": "Generic 200", + "Level": "Level 1", + "LinkName": null, + "Properties": { + "Identity Data": { + "Mark": "Plain Slab" + } + }, + "MeshIndices": [ + 2 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + } + ], + "Meshes": [ + { + "Id": 0, + "Name": "", + "ElementId": 500001, + "Vertices": [ + { + "X": 2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 2, + "Y": 0.3, + "Z": 0, + "LengthSquared": 4.09, + "Length": 2.0223748416156684 + }, + { + "X": 2, + "Y": 0.3, + "Z": 3, + "LengthSquared": 13.09, + "Length": 3.6180105030251086 + }, + { + "X": 2, + "Y": 0, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0, + "Y": 0.3, + "Z": 0, + "LengthSquared": 0.09, + "Length": 0.3 + }, + { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + }, + { + "X": 0, + "Y": 0, + "Z": 3, + "LengthSquared": 9, + "Length": 3 + }, + { + "X": 0, + "Y": 0.3, + "Z": 3, + "LengthSquared": 9.09, + "Length": 3.014962686336267 + }, + { + "X": 2, + "Y": 0.3, + "Z": 0, + "LengthSquared": 4.09, + "Length": 2.0223748416156684 + }, + { + "X": 0, + "Y": 0.3, + "Z": 0, + "LengthSquared": 0.09, + "Length": 0.3 + }, + { + "X": 0, + "Y": 0.3, + "Z": 3, + "LengthSquared": 9.09, + "Length": 3.014962686336267 + }, + { + "X": 2, + "Y": 0.3, + "Z": 3, + "LengthSquared": 13.09, + "Length": 3.6180105030251086 + }, + { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + }, + { + "X": 2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 2, + "Y": 0, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0, + "Y": 0, + "Z": 3, + "LengthSquared": 9, + "Length": 3 + }, + { + "X": 0, + "Y": 0, + "Z": 3, + "LengthSquared": 9, + "Length": 3 + }, + { + "X": 2, + "Y": 0, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 2, + "Y": 0.3, + "Z": 3, + "LengthSquared": 13.09, + "Length": 3.6180105030251086 + }, + { + "X": 0, + "Y": 0.3, + "Z": 3, + "LengthSquared": 9.09, + "Length": 3.014962686336267 + }, + { + "X": 0, + "Y": 0.3, + "Z": 0, + "LengthSquared": 0.09, + "Length": 0.3 + }, + { + "X": 2, + "Y": 0.3, + "Z": 0, + "LengthSquared": 4.09, + "Length": 2.0223748416156684 + }, + { + "X": 2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 1, + "Name": "Plain Wall", + "ElementId": 500002, + "Vertices": [ + { + "X": 5, + "Y": 0, + "Z": 0, + "LengthSquared": 25, + "Length": 5 + }, + { + "X": 5, + "Y": 0.3, + "Z": 0, + "LengthSquared": 25.09, + "Length": 5.008991914547277 + }, + { + "X": 5, + "Y": 0.3, + "Z": 3, + "LengthSquared": 34.09, + "Length": 5.838664230798 + }, + { + "X": 5, + "Y": 0, + "Z": 3, + "LengthSquared": 34, + "Length": 5.830951894845301 + }, + { + "X": 3, + "Y": 0.3, + "Z": 0, + "LengthSquared": 9.09, + "Length": 3.014962686336267 + }, + { + "X": 3, + "Y": 0, + "Z": 0, + "LengthSquared": 9, + "Length": 3 + }, + { + "X": 3, + "Y": 0, + "Z": 3, + "LengthSquared": 18, + "Length": 4.242640687119285 + }, + { + "X": 3, + "Y": 0.3, + "Z": 3, + "LengthSquared": 18.09, + "Length": 4.253234063627348 + }, + { + "X": 5, + "Y": 0.3, + "Z": 0, + "LengthSquared": 25.09, + "Length": 5.008991914547277 + }, + { + "X": 3, + "Y": 0.3, + "Z": 0, + "LengthSquared": 9.09, + "Length": 3.014962686336267 + }, + { + "X": 3, + "Y": 0.3, + "Z": 3, + "LengthSquared": 18.09, + "Length": 4.253234063627348 + }, + { + "X": 5, + "Y": 0.3, + "Z": 3, + "LengthSquared": 34.09, + "Length": 5.838664230798 + }, + { + "X": 3, + "Y": 0, + "Z": 0, + "LengthSquared": 9, + "Length": 3 + }, + { + "X": 5, + "Y": 0, + "Z": 0, + "LengthSquared": 25, + "Length": 5 + }, + { + "X": 5, + "Y": 0, + "Z": 3, + "LengthSquared": 34, + "Length": 5.830951894845301 + }, + { + "X": 3, + "Y": 0, + "Z": 3, + "LengthSquared": 18, + "Length": 4.242640687119285 + }, + { + "X": 3, + "Y": 0, + "Z": 3, + "LengthSquared": 18, + "Length": 4.242640687119285 + }, + { + "X": 5, + "Y": 0, + "Z": 3, + "LengthSquared": 34, + "Length": 5.830951894845301 + }, + { + "X": 5, + "Y": 0.3, + "Z": 3, + "LengthSquared": 34.09, + "Length": 5.838664230798 + }, + { + "X": 3, + "Y": 0.3, + "Z": 3, + "LengthSquared": 18.09, + "Length": 4.253234063627348 + }, + { + "X": 3, + "Y": 0.3, + "Z": 0, + "LengthSquared": 9.09, + "Length": 3.014962686336267 + }, + { + "X": 5, + "Y": 0.3, + "Z": 0, + "LengthSquared": 25.09, + "Length": 5.008991914547277 + }, + { + "X": 5, + "Y": 0, + "Z": 0, + "LengthSquared": 25, + "Length": 5 + }, + { + "X": 3, + "Y": 0, + "Z": 0, + "LengthSquared": 9, + "Length": 3 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 2, + "Name": "Plain Slab", + "ElementId": 500003, + "Vertices": [ + { + "X": 8, + "Y": 0, + "Z": 0, + "LengthSquared": 64, + "Length": 8 + }, + { + "X": 8, + "Y": 0.3, + "Z": 0, + "LengthSquared": 64.09, + "Length": 8.005623023850173 + }, + { + "X": 8, + "Y": 0.3, + "Z": 3, + "LengthSquared": 73.09, + "Length": 8.5492689745966 + }, + { + "X": 8, + "Y": 0, + "Z": 3, + "LengthSquared": 73, + "Length": 8.54400374531753 + }, + { + "X": 6, + "Y": 0.3, + "Z": 0, + "LengthSquared": 36.09, + "Length": 6.007495318350236 + }, + { + "X": 6, + "Y": 0, + "Z": 0, + "LengthSquared": 36, + "Length": 6 + }, + { + "X": 6, + "Y": 0, + "Z": 3, + "LengthSquared": 45, + "Length": 6.708203932499369 + }, + { + "X": 6, + "Y": 0.3, + "Z": 3, + "LengthSquared": 45.09, + "Length": 6.7149087856798175 + }, + { + "X": 8, + "Y": 0.3, + "Z": 0, + "LengthSquared": 64.09, + "Length": 8.005623023850173 + }, + { + "X": 6, + "Y": 0.3, + "Z": 0, + "LengthSquared": 36.09, + "Length": 6.007495318350236 + }, + { + "X": 6, + "Y": 0.3, + "Z": 3, + "LengthSquared": 45.09, + "Length": 6.7149087856798175 + }, + { + "X": 8, + "Y": 0.3, + "Z": 3, + "LengthSquared": 73.09, + "Length": 8.5492689745966 + }, + { + "X": 6, + "Y": 0, + "Z": 0, + "LengthSquared": 36, + "Length": 6 + }, + { + "X": 8, + "Y": 0, + "Z": 0, + "LengthSquared": 64, + "Length": 8 + }, + { + "X": 8, + "Y": 0, + "Z": 3, + "LengthSquared": 73, + "Length": 8.54400374531753 + }, + { + "X": 6, + "Y": 0, + "Z": 3, + "LengthSquared": 45, + "Length": 6.708203932499369 + }, + { + "X": 6, + "Y": 0, + "Z": 3, + "LengthSquared": 45, + "Length": 6.708203932499369 + }, + { + "X": 8, + "Y": 0, + "Z": 3, + "LengthSquared": 73, + "Length": 8.54400374531753 + }, + { + "X": 8, + "Y": 0.3, + "Z": 3, + "LengthSquared": 73.09, + "Length": 8.5492689745966 + }, + { + "X": 6, + "Y": 0.3, + "Z": 3, + "LengthSquared": 45.09, + "Length": 6.7149087856798175 + }, + { + "X": 6, + "Y": 0.3, + "Z": 0, + "LengthSquared": 36.09, + "Length": 6.007495318350236 + }, + { + "X": 8, + "Y": 0.3, + "Z": 0, + "LengthSquared": 64.09, + "Length": 8.005623023850173 + }, + { + "X": 8, + "Y": 0, + "Z": 0, + "LengthSquared": 64, + "Length": 8 + }, + { + "X": 6, + "Y": 0, + "Z": 0, + "LengthSquared": 36, + "Length": 6 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + } + ], + "Geometries": [], + "Materials": [], + "Textures": [], + "Grids": null, + "Viewpoints": [], + "SectionPlanes": [], + "SectionBox": null, + "TimeLiner": null, + "ClashTests": [], + "LinkedFiles": [] +} \ No newline at end of file diff --git a/tests/fixtures/legacy-0.2/meshes-without-elements.json b/tests/fixtures/legacy-0.2/meshes-without-elements.json new file mode 100644 index 0000000..ce49adb --- /dev/null +++ b/tests/fixtures/legacy-0.2/meshes-without-elements.json @@ -0,0 +1,502 @@ +{ + "Title": "Meshes Without Elements", + "SourcePath": null, + "Author": "IOViewer", + "CreatedAt": "2026-11-12T12:00:00Z", + "Application": "IOViewer 0.2.0", + "BoundingBox": { + "Min": { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + }, + "Max": { + "X": 3, + "Y": 3, + "Z": 0, + "LengthSquared": 18, + "Length": 4.242640687119285 + }, + "IsEmpty": false, + "Center": { + "X": 1.5, + "Y": 1.5, + "Z": 0, + "LengthSquared": 4.5, + "Length": 2.1213203435596424 + }, + "Size": { + "X": 3, + "Y": 3, + "Z": 0, + "LengthSquared": 18, + "Length": 4.242640687119285 + } + }, + "Elements": [], + "Meshes": [ + { + "Id": 1, + "Name": "Pane A", + "ElementId": 100, + "Vertices": [ + { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "Normals": null, + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": 1, + "Color": null, + "Opacity": 0.8, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": 0, + "Y": 0, + "Z": 0, + "LengthSquared": 0, + "Length": 0 + }, + "Max": { + "X": 1, + "Y": 1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + }, + "IsEmpty": false, + "Center": { + "X": 0.5, + "Y": 0.5, + "Z": 0, + "LengthSquared": 0.5, + "Length": 0.7071067811865476 + }, + "Size": { + "X": 1, + "Y": 1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + } + } + }, + { + "Id": 2, + "Name": "Pane B", + "ElementId": 100, + "Vertices": [ + { + "X": 2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 3, + "Y": 0, + "Z": 0, + "LengthSquared": 9, + "Length": 3 + }, + { + "X": 3, + "Y": 1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 2, + "Y": 1, + "Z": 0, + "LengthSquared": 5, + "Length": 2.23606797749979 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "Normals": null, + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": 2, + "Color": null, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": 2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + "Max": { + "X": 3, + "Y": 1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + "IsEmpty": false, + "Center": { + "X": 2.5, + "Y": 0.5, + "Z": 0, + "LengthSquared": 6.5, + "Length": 2.5495097567963922 + }, + "Size": { + "X": 1, + "Y": 1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + } + } + }, + { + "Id": 3, + "Name": "Slab", + "ElementId": 200, + "Vertices": [ + { + "X": 0, + "Y": 2, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1, + "Y": 2, + "Z": 0, + "LengthSquared": 5, + "Length": 2.23606797749979 + }, + { + "X": 1, + "Y": 3, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 0, + "Y": 3, + "Z": 0, + "LengthSquared": 9, + "Length": 3 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "Normals": null, + "UVs": [ + { + "X": 0, + "Y": 0 + }, + { + "X": 1, + "Y": 0 + }, + { + "X": 1, + "Y": 1 + }, + { + "X": 0, + "Y": 1 + } + ], + "GeometryId": null, + "Transform": null, + "MaterialId": 2, + "Color": null, + "Opacity": 1, + "TextureIndex": 0, + "BoundingBox": { + "Min": { + "X": 0, + "Y": 2, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + "Max": { + "X": 1, + "Y": 3, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + "IsEmpty": false, + "Center": { + "X": 0.5, + "Y": 2.5, + "Z": 0, + "LengthSquared": 6.5, + "Length": 2.5495097567963922 + }, + "Size": { + "X": 1, + "Y": 1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + } + } + }, + { + "Id": 4, + "Name": "Loose Mesh", + "ElementId": null, + "Vertices": [ + { + "X": 2, + "Y": 2, + "Z": 0, + "LengthSquared": 8, + "Length": 2.8284271247461903 + }, + { + "X": 3, + "Y": 2, + "Z": 0, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 3, + "Y": 3, + "Z": 0, + "LengthSquared": 18, + "Length": 4.242640687119285 + }, + { + "X": 2, + "Y": 3, + "Z": 0, + "LengthSquared": 13, + "Length": 3.605551275463989 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "Normals": null, + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 200, + "G": 60, + "B": 60, + "A": 255, + "Rf": 0.7843137254901961, + "Gf": 0.23529411764705882, + "Bf": 0.23529411764705882, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": 2, + "Y": 2, + "Z": 0, + "LengthSquared": 8, + "Length": 2.8284271247461903 + }, + "Max": { + "X": 3, + "Y": 3, + "Z": 0, + "LengthSquared": 18, + "Length": 4.242640687119285 + }, + "IsEmpty": false, + "Center": { + "X": 2.5, + "Y": 2.5, + "Z": 0, + "LengthSquared": 12.5, + "Length": 3.5355339059327378 + }, + "Size": { + "X": 1, + "Y": 1, + "Z": 0, + "LengthSquared": 2, + "Length": 1.4142135623730951 + } + } + } + ], + "Geometries": [], + "Materials": [ + { + "Id": 1, + "Name": "Glass", + "Color": { + "R": 150, + "G": 200, + "B": 230, + "A": 255, + "Rf": 0.5882352941176471, + "Gf": 0.7843137254901961, + "Bf": 0.9019607843137255, + "Af": 1 + }, + "Opacity": 0.5, + "Roughness": 0.5, + "Metalness": 0.1, + "TextureIndex": null + }, + { + "Id": 2, + "Name": "Concrete", + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "Roughness": 0.5, + "Metalness": 0.1, + "TextureIndex": null + } + ], + "Textures": [ + { + "Id": 0, + "MimeType": "image/png", + "Base64Data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", + "ScaleX": 2, + "ScaleY": 2 + } + ], + "Grids": null, + "Viewpoints": [], + "SectionPlanes": [], + "SectionBox": null, + "TimeLiner": { + "Name": "Schedule", + "Tasks": [ + { + "Id": "T1", + "Name": "Install", + "TaskType": 0, + "PlannedStart": "2026-01-05T00:00:00+00:00", + "PlannedEnd": "2026-01-16T00:00:00+00:00", + "ActualStart": null, + "ActualEnd": null, + "PlannedCost": null, + "ActualCost": null, + "Progress": 0, + "AttachedSetName": null, + "AttachedElementIds": [ + 100, + 200, + 4 + ], + "Children": [] + } + ] + }, + "ClashTests": [ + { + "Name": "Panes vs Slab", + "Clashes": [ + { + "Id": "C1", + "Name": "Clash 1", + "Element1Name": "Pane A", + "Element2Name": "Slab", + "Element1Id": 100, + "Element2Id": 200, + "Point": { + "X": 0.5, + "Y": 1.5, + "Z": 0, + "LengthSquared": 2.5, + "Length": 1.5811388300841898 + }, + "Distance": 0.02, + "Status": 0 + } + ] + } + ], + "LinkedFiles": [ + { + "Name": "Structure", + "Path": "links/structure.glb", + "Visible": false + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/legacy-0.2/textured.json b/tests/fixtures/legacy-0.2/textured.json new file mode 100644 index 0000000..fd08b4f --- /dev/null +++ b/tests/fixtures/legacy-0.2/textured.json @@ -0,0 +1,2687 @@ +{ + "Title": "Textured", + "SourcePath": null, + "Author": null, + "CreatedAt": null, + "Application": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + }, + "Elements": [ + { + "Id": 600001, + "Name": "Textured Tank", + "Category": "Equipment", + "TypeName": "Tank", + "Level": "Level 1", + "LinkName": null, + "Properties": {}, + "MeshIndices": [ + 0 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 600002, + "Name": "Plain Box", + "Category": "Walls", + "TypeName": "Generic 200", + "Level": "Level 1", + "LinkName": null, + "Properties": {}, + "MeshIndices": [ + 1 + ], + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + } + ], + "Meshes": [ + { + "Id": 0, + "Name": "Textured Tank", + "ElementId": 600001, + "Vertices": [ + { + "X": 2, + "Y": 0, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 2, + "Y": 0, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.9828897227476208, + "Y": 0.26105238444010315, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.9828897227476208, + "Y": 0.26105238444010315, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.9318516525781366, + "Y": 0.5176380902050415, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.9318516525781366, + "Y": 0.5176380902050415, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.8477590650225735, + "Y": 0.7653668647301796, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.8477590650225735, + "Y": 0.7653668647301796, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.7320508075688774, + "Y": 0.9999999999999999, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.7320508075688774, + "Y": 0.9999999999999999, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.5867066805824703, + "Y": 1.2175228580174413, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.5867066805824703, + "Y": 1.2175228580174413, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.4142135623730951, + "Y": 1.4142135623730951, + "Z": 0, + "LengthSquared": 4.000000000000001, + "Length": 2 + }, + { + "X": 1.4142135623730951, + "Y": 1.4142135623730951, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.2175228580174413, + "Y": 1.5867066805824703, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.2175228580174413, + "Y": 1.5867066805824703, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.0000000000000002, + "Y": 1.7320508075688772, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.0000000000000002, + "Y": 1.7320508075688772, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0.7653668647301797, + "Y": 1.8477590650225735, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0.7653668647301797, + "Y": 1.8477590650225735, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0.5176380902050415, + "Y": 1.9318516525781366, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0.5176380902050415, + "Y": 1.9318516525781366, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0.2610523844401034, + "Y": 1.9828897227476208, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0.2610523844401034, + "Y": 1.9828897227476208, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.2246467991473532E-16, + "Y": 2, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.2246467991473532E-16, + "Y": 2, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.2610523844401032, + "Y": 1.9828897227476208, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -0.2610523844401032, + "Y": 1.9828897227476208, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.5176380902050413, + "Y": 1.9318516525781366, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -0.5176380902050413, + "Y": 1.9318516525781366, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.765366864730179, + "Y": 1.8477590650225737, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -0.765366864730179, + "Y": 1.8477590650225737, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.9999999999999996, + "Y": 1.7320508075688774, + "Z": 0, + "LengthSquared": 3.9999999999999996, + "Length": 1.9999999999999998 + }, + { + "X": -0.9999999999999996, + "Y": 1.7320508075688774, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.2175228580174413, + "Y": 1.5867066805824703, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.2175228580174413, + "Y": 1.5867066805824703, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.414213562373095, + "Y": 1.4142135623730951, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.414213562373095, + "Y": 1.4142135623730951, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.58670668058247, + "Y": 1.2175228580174418, + "Z": 0, + "LengthSquared": 4.000000000000001, + "Length": 2 + }, + { + "X": -1.58670668058247, + "Y": 1.2175228580174418, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.7320508075688774, + "Y": 0.9999999999999999, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.7320508075688774, + "Y": 0.9999999999999999, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.8477590650225735, + "Y": 0.7653668647301798, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.8477590650225735, + "Y": 0.7653668647301798, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.9318516525781364, + "Y": 0.517638090205042, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.9318516525781364, + "Y": 0.517638090205042, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.9828897227476208, + "Y": 0.261052384440104, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.9828897227476208, + "Y": 0.261052384440104, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -2, + "Y": 2.4492935982947064E-16, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -2, + "Y": 2.4492935982947064E-16, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.9828897227476208, + "Y": -0.26105238444010354, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.9828897227476208, + "Y": -0.26105238444010354, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.9318516525781366, + "Y": -0.5176380902050416, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.9318516525781366, + "Y": -0.5176380902050416, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.8477590650225737, + "Y": -0.7653668647301793, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.8477590650225737, + "Y": -0.7653668647301793, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.7320508075688776, + "Y": -0.9999999999999994, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.7320508075688776, + "Y": -0.9999999999999994, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.5867066805824703, + "Y": -1.2175228580174413, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.5867066805824703, + "Y": -1.2175228580174413, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.4142135623730958, + "Y": -1.4142135623730943, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.4142135623730958, + "Y": -1.4142135623730943, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.2175228580174418, + "Y": -1.5867066805824699, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.2175228580174418, + "Y": -1.5867066805824699, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -1.0000000000000009, + "Y": -1.732050807568877, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -1.0000000000000009, + "Y": -1.732050807568877, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.765366864730179, + "Y": -1.8477590650225737, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -0.765366864730179, + "Y": -1.8477590650225737, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.5176380902050413, + "Y": -1.9318516525781366, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -0.5176380902050413, + "Y": -1.9318516525781366, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -0.26105238444010326, + "Y": -1.9828897227476208, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -0.26105238444010326, + "Y": -1.9828897227476208, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": -3.6739403974420594E-16, + "Y": -2, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": -3.6739403974420594E-16, + "Y": -2, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0.26105238444010254, + "Y": -1.982889722747621, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0.26105238444010254, + "Y": -1.982889722747621, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0.5176380902050406, + "Y": -1.9318516525781368, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0.5176380902050406, + "Y": -1.9318516525781368, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 0.7653668647301783, + "Y": -1.847759065022574, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 0.7653668647301783, + "Y": -1.847759065022574, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.0000000000000002, + "Y": -1.7320508075688772, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.0000000000000002, + "Y": -1.7320508075688772, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.2175228580174398, + "Y": -1.5867066805824714, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.2175228580174398, + "Y": -1.5867066805824714, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.4142135623730947, + "Y": -1.4142135623730954, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.4142135623730947, + "Y": -1.4142135623730954, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.5867066805824699, + "Y": -1.217522858017442, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.5867066805824699, + "Y": -1.217522858017442, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.7320508075688767, + "Y": -1.0000000000000009, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.7320508075688767, + "Y": -1.0000000000000009, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.8477590650225737, + "Y": -0.7653668647301791, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.8477590650225737, + "Y": -0.7653668647301791, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.9318516525781362, + "Y": -0.5176380902050431, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.9318516525781362, + "Y": -0.5176380902050431, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 1.9828897227476208, + "Y": -0.26105238444010337, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 1.9828897227476208, + "Y": -0.26105238444010337, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + }, + { + "X": 2, + "Y": -4.898587196589413E-16, + "Z": 0, + "LengthSquared": 4, + "Length": 2 + }, + { + "X": 2, + "Y": -4.898587196589413E-16, + "Z": 3, + "LengthSquared": 13, + "Length": 3.605551275463989 + } + ], + "Indices": [ + 0, + 2, + 3, + 0, + 3, + 1, + 2, + 4, + 5, + 2, + 5, + 3, + 4, + 6, + 7, + 4, + 7, + 5, + 6, + 8, + 9, + 6, + 9, + 7, + 8, + 10, + 11, + 8, + 11, + 9, + 10, + 12, + 13, + 10, + 13, + 11, + 12, + 14, + 15, + 12, + 15, + 13, + 14, + 16, + 17, + 14, + 17, + 15, + 16, + 18, + 19, + 16, + 19, + 17, + 18, + 20, + 21, + 18, + 21, + 19, + 20, + 22, + 23, + 20, + 23, + 21, + 22, + 24, + 25, + 22, + 25, + 23, + 24, + 26, + 27, + 24, + 27, + 25, + 26, + 28, + 29, + 26, + 29, + 27, + 28, + 30, + 31, + 28, + 31, + 29, + 30, + 32, + 33, + 30, + 33, + 31, + 32, + 34, + 35, + 32, + 35, + 33, + 34, + 36, + 37, + 34, + 37, + 35, + 36, + 38, + 39, + 36, + 39, + 37, + 38, + 40, + 41, + 38, + 41, + 39, + 40, + 42, + 43, + 40, + 43, + 41, + 42, + 44, + 45, + 42, + 45, + 43, + 44, + 46, + 47, + 44, + 47, + 45, + 46, + 48, + 49, + 46, + 49, + 47, + 48, + 50, + 51, + 48, + 51, + 49, + 50, + 52, + 53, + 50, + 53, + 51, + 52, + 54, + 55, + 52, + 55, + 53, + 54, + 56, + 57, + 54, + 57, + 55, + 56, + 58, + 59, + 56, + 59, + 57, + 58, + 60, + 61, + 58, + 61, + 59, + 60, + 62, + 63, + 60, + 63, + 61, + 62, + 64, + 65, + 62, + 65, + 63, + 64, + 66, + 67, + 64, + 67, + 65, + 66, + 68, + 69, + 66, + 69, + 67, + 68, + 70, + 71, + 68, + 71, + 69, + 70, + 72, + 73, + 70, + 73, + 71, + 72, + 74, + 75, + 72, + 75, + 73, + 74, + 76, + 77, + 74, + 77, + 75, + 76, + 78, + 79, + 76, + 79, + 77, + 78, + 80, + 81, + 78, + 81, + 79, + 80, + 82, + 83, + 80, + 83, + 81, + 82, + 84, + 85, + 82, + 85, + 83, + 84, + 86, + 87, + 84, + 87, + 85, + 86, + 88, + 89, + 86, + 89, + 87, + 88, + 90, + 91, + 88, + 91, + 89, + 90, + 92, + 93, + 90, + 93, + 91, + 92, + 94, + 95, + 92, + 95, + 93, + 94, + 96, + 97, + 94, + 97, + 95 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9914448613738104, + "Y": 0.13052619222005157, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9914448613738104, + "Y": 0.13052619222005157, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9659258262890683, + "Y": 0.25881904510252074, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9659258262890683, + "Y": 0.25881904510252074, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9238795325112867, + "Y": 0.3826834323650898, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9238795325112867, + "Y": 0.3826834323650898, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.8660254037844387, + "Y": 0.49999999999999994, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.8660254037844387, + "Y": 0.49999999999999994, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7933533402912352, + "Y": 0.6087614290087207, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7933533402912352, + "Y": 0.6087614290087207, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7071067811865476, + "Y": 0.7071067811865476, + "Z": 0, + "LengthSquared": 1.0000000000000002, + "Length": 1 + }, + { + "X": 0.7071067811865476, + "Y": 0.7071067811865476, + "Z": 0, + "LengthSquared": 1.0000000000000002, + "Length": 1 + }, + { + "X": 0.6087614290087207, + "Y": 0.7933533402912352, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.6087614290087207, + "Y": 0.7933533402912352, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.5000000000000001, + "Y": 0.8660254037844386, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.5000000000000001, + "Y": 0.8660254037844386, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.38268343236508984, + "Y": 0.9238795325112867, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.38268343236508984, + "Y": 0.9238795325112867, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.25881904510252074, + "Y": 0.9659258262890683, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.25881904510252074, + "Y": 0.9659258262890683, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.1305261922200517, + "Y": 0.9914448613738104, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.1305261922200517, + "Y": 0.9914448613738104, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 6.123233995736766E-17, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 6.123233995736766E-17, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.1305261922200516, + "Y": 0.9914448613738104, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.1305261922200516, + "Y": 0.9914448613738104, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.25881904510252063, + "Y": 0.9659258262890683, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.25881904510252063, + "Y": 0.9659258262890683, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.3826834323650895, + "Y": 0.9238795325112868, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.3826834323650895, + "Y": 0.9238795325112868, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.4999999999999998, + "Y": 0.8660254037844387, + "Z": 0, + "LengthSquared": 0.9999999999999999, + "Length": 0.9999999999999999 + }, + { + "X": -0.4999999999999998, + "Y": 0.8660254037844387, + "Z": 0, + "LengthSquared": 0.9999999999999999, + "Length": 0.9999999999999999 + }, + { + "X": -0.6087614290087207, + "Y": 0.7933533402912352, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.6087614290087207, + "Y": 0.7933533402912352, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.7071067811865475, + "Y": 0.7071067811865476, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.7071067811865475, + "Y": 0.7071067811865476, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.793353340291235, + "Y": 0.6087614290087209, + "Z": 0, + "LengthSquared": 1.0000000000000002, + "Length": 1 + }, + { + "X": -0.793353340291235, + "Y": 0.6087614290087209, + "Z": 0, + "LengthSquared": 1.0000000000000002, + "Length": 1 + }, + { + "X": -0.8660254037844387, + "Y": 0.49999999999999994, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.8660254037844387, + "Y": 0.49999999999999994, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9238795325112867, + "Y": 0.3826834323650899, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9238795325112867, + "Y": 0.3826834323650899, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9659258262890682, + "Y": 0.258819045102521, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9659258262890682, + "Y": 0.258819045102521, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9914448613738104, + "Y": 0.130526192220052, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9914448613738104, + "Y": 0.130526192220052, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": 1.2246467991473532E-16, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": 1.2246467991473532E-16, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9914448613738104, + "Y": -0.13052619222005177, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9914448613738104, + "Y": -0.13052619222005177, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9659258262890683, + "Y": -0.2588190451025208, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9659258262890683, + "Y": -0.2588190451025208, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9238795325112868, + "Y": -0.38268343236508967, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.9238795325112868, + "Y": -0.38268343236508967, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.8660254037844388, + "Y": -0.4999999999999997, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.8660254037844388, + "Y": -0.4999999999999997, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.7933533402912352, + "Y": -0.6087614290087207, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.7933533402912352, + "Y": -0.6087614290087207, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.7071067811865479, + "Y": -0.7071067811865471, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.7071067811865479, + "Y": -0.7071067811865471, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.6087614290087209, + "Y": -0.7933533402912349, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.6087614290087209, + "Y": -0.7933533402912349, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.5000000000000004, + "Y": -0.8660254037844385, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.5000000000000004, + "Y": -0.8660254037844385, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.3826834323650895, + "Y": -0.9238795325112868, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.3826834323650895, + "Y": -0.9238795325112868, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.25881904510252063, + "Y": -0.9659258262890683, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.25881904510252063, + "Y": -0.9659258262890683, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.13052619222005163, + "Y": -0.9914448613738104, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0.13052619222005163, + "Y": -0.9914448613738104, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1.8369701987210297E-16, + "Y": -1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1.8369701987210297E-16, + "Y": -1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.13052619222005127, + "Y": -0.9914448613738105, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.13052619222005127, + "Y": -0.9914448613738105, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.2588190451025203, + "Y": -0.9659258262890684, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.2588190451025203, + "Y": -0.9659258262890684, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.38268343236508917, + "Y": -0.923879532511287, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.38268343236508917, + "Y": -0.923879532511287, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.5000000000000001, + "Y": -0.8660254037844386, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.5000000000000001, + "Y": -0.8660254037844386, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.6087614290087199, + "Y": -0.7933533402912357, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.6087614290087199, + "Y": -0.7933533402912357, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7071067811865474, + "Y": -0.7071067811865477, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7071067811865474, + "Y": -0.7071067811865477, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7933533402912349, + "Y": -0.608761429008721, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.7933533402912349, + "Y": -0.608761429008721, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.8660254037844384, + "Y": -0.5000000000000004, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.8660254037844384, + "Y": -0.5000000000000004, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9238795325112868, + "Y": -0.38268343236508956, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9238795325112868, + "Y": -0.38268343236508956, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9659258262890681, + "Y": -0.25881904510252157, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9659258262890681, + "Y": -0.25881904510252157, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9914448613738104, + "Y": -0.13052619222005168, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0.9914448613738104, + "Y": -0.13052619222005168, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": -2.4492935982947064E-16, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": -2.4492935982947064E-16, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": [ + { + "X": 0, + "Y": 0 + }, + { + "X": 0, + "Y": 3 + }, + { + "X": 0.2617993877991494, + "Y": 0 + }, + { + "X": 0.2617993877991494, + "Y": 3 + }, + { + "X": 0.5235987755982988, + "Y": 0 + }, + { + "X": 0.5235987755982988, + "Y": 3 + }, + { + "X": 0.7853981633974483, + "Y": 0 + }, + { + "X": 0.7853981633974483, + "Y": 3 + }, + { + "X": 1.0471975511965976, + "Y": 0 + }, + { + "X": 1.0471975511965976, + "Y": 3 + }, + { + "X": 1.3089969389957472, + "Y": 0 + }, + { + "X": 1.3089969389957472, + "Y": 3 + }, + { + "X": 1.5707963267948966, + "Y": 0 + }, + { + "X": 1.5707963267948966, + "Y": 3 + }, + { + "X": 1.832595714594046, + "Y": 0 + }, + { + "X": 1.832595714594046, + "Y": 3 + }, + { + "X": 2.0943951023931953, + "Y": 0 + }, + { + "X": 2.0943951023931953, + "Y": 3 + }, + { + "X": 2.356194490192345, + "Y": 0 + }, + { + "X": 2.356194490192345, + "Y": 3 + }, + { + "X": 2.6179938779914944, + "Y": 0 + }, + { + "X": 2.6179938779914944, + "Y": 3 + }, + { + "X": 2.8797932657906435, + "Y": 0 + }, + { + "X": 2.8797932657906435, + "Y": 3 + }, + { + "X": 3.141592653589793, + "Y": 0 + }, + { + "X": 3.141592653589793, + "Y": 3 + }, + { + "X": 3.4033920413889427, + "Y": 0 + }, + { + "X": 3.4033920413889427, + "Y": 3 + }, + { + "X": 3.665191429188092, + "Y": 0 + }, + { + "X": 3.665191429188092, + "Y": 3 + }, + { + "X": 3.926990816987241, + "Y": 0 + }, + { + "X": 3.926990816987241, + "Y": 3 + }, + { + "X": 4.1887902047863905, + "Y": 0 + }, + { + "X": 4.1887902047863905, + "Y": 3 + }, + { + "X": 4.4505895925855405, + "Y": 0 + }, + { + "X": 4.4505895925855405, + "Y": 3 + }, + { + "X": 4.71238898038469, + "Y": 0 + }, + { + "X": 4.71238898038469, + "Y": 3 + }, + { + "X": 4.974188368183839, + "Y": 0 + }, + { + "X": 4.974188368183839, + "Y": 3 + }, + { + "X": 5.235987755982989, + "Y": 0 + }, + { + "X": 5.235987755982989, + "Y": 3 + }, + { + "X": 5.497787143782138, + "Y": 0 + }, + { + "X": 5.497787143782138, + "Y": 3 + }, + { + "X": 5.759586531581287, + "Y": 0 + }, + { + "X": 5.759586531581287, + "Y": 3 + }, + { + "X": 6.021385919380436, + "Y": 0 + }, + { + "X": 6.021385919380436, + "Y": 3 + }, + { + "X": 6.283185307179586, + "Y": 0 + }, + { + "X": 6.283185307179586, + "Y": 3 + }, + { + "X": 6.544984694978736, + "Y": 0 + }, + { + "X": 6.544984694978736, + "Y": 3 + }, + { + "X": 6.806784082777885, + "Y": 0 + }, + { + "X": 6.806784082777885, + "Y": 3 + }, + { + "X": 7.0685834705770345, + "Y": 0 + }, + { + "X": 7.0685834705770345, + "Y": 3 + }, + { + "X": 7.330382858376184, + "Y": 0 + }, + { + "X": 7.330382858376184, + "Y": 3 + }, + { + "X": 7.592182246175334, + "Y": 0 + }, + { + "X": 7.592182246175334, + "Y": 3 + }, + { + "X": 7.853981633974482, + "Y": 0 + }, + { + "X": 7.853981633974482, + "Y": 3 + }, + { + "X": 8.115781021773632, + "Y": 0 + }, + { + "X": 8.115781021773632, + "Y": 3 + }, + { + "X": 8.377580409572781, + "Y": 0 + }, + { + "X": 8.377580409572781, + "Y": 3 + }, + { + "X": 8.639379797371932, + "Y": 0 + }, + { + "X": 8.639379797371932, + "Y": 3 + }, + { + "X": 8.901179185171081, + "Y": 0 + }, + { + "X": 8.901179185171081, + "Y": 3 + }, + { + "X": 9.16297857297023, + "Y": 0 + }, + { + "X": 9.16297857297023, + "Y": 3 + }, + { + "X": 9.42477796076938, + "Y": 0 + }, + { + "X": 9.42477796076938, + "Y": 3 + }, + { + "X": 9.686577348568528, + "Y": 0 + }, + { + "X": 9.686577348568528, + "Y": 3 + }, + { + "X": 9.948376736367678, + "Y": 0 + }, + { + "X": 9.948376736367678, + "Y": 3 + }, + { + "X": 10.210176124166827, + "Y": 0 + }, + { + "X": 10.210176124166827, + "Y": 3 + }, + { + "X": 10.471975511965978, + "Y": 0 + }, + { + "X": 10.471975511965978, + "Y": 3 + }, + { + "X": 10.733774899765125, + "Y": 0 + }, + { + "X": 10.733774899765125, + "Y": 3 + }, + { + "X": 10.995574287564276, + "Y": 0 + }, + { + "X": 10.995574287564276, + "Y": 3 + }, + { + "X": 11.257373675363425, + "Y": 0 + }, + { + "X": 11.257373675363425, + "Y": 3 + }, + { + "X": 11.519173063162574, + "Y": 0 + }, + { + "X": 11.519173063162574, + "Y": 3 + }, + { + "X": 11.780972450961725, + "Y": 0 + }, + { + "X": 11.780972450961725, + "Y": 3 + }, + { + "X": 12.042771838760872, + "Y": 0 + }, + { + "X": 12.042771838760872, + "Y": 3 + }, + { + "X": 12.304571226560023, + "Y": 0 + }, + { + "X": 12.304571226560023, + "Y": 3 + }, + { + "X": 12.566370614359172, + "Y": 0 + }, + { + "X": 12.566370614359172, + "Y": 3 + } + ], + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 185, + "G": 183, + "B": 178, + "A": 255, + "Rf": 0.7254901960784313, + "Gf": 0.7176470588235294, + "Bf": 0.6980392156862745, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": 0, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + }, + { + "Id": 1, + "Name": "Plain Box", + "ElementId": 600002, + "Vertices": [ + { + "X": 5, + "Y": -1, + "Z": 0, + "LengthSquared": 26, + "Length": 5.0990195135927845 + }, + { + "X": 5, + "Y": 1, + "Z": 0, + "LengthSquared": 26, + "Length": 5.0990195135927845 + }, + { + "X": 5, + "Y": 1, + "Z": 2, + "LengthSquared": 30, + "Length": 5.477225575051661 + }, + { + "X": 5, + "Y": -1, + "Z": 2, + "LengthSquared": 30, + "Length": 5.477225575051661 + }, + { + "X": 3, + "Y": 1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 3, + "Y": -1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 3, + "Y": -1, + "Z": 2, + "LengthSquared": 14, + "Length": 3.7416573867739413 + }, + { + "X": 3, + "Y": 1, + "Z": 2, + "LengthSquared": 14, + "Length": 3.7416573867739413 + }, + { + "X": 5, + "Y": 1, + "Z": 0, + "LengthSquared": 26, + "Length": 5.0990195135927845 + }, + { + "X": 3, + "Y": 1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 3, + "Y": 1, + "Z": 2, + "LengthSquared": 14, + "Length": 3.7416573867739413 + }, + { + "X": 5, + "Y": 1, + "Z": 2, + "LengthSquared": 30, + "Length": 5.477225575051661 + }, + { + "X": 3, + "Y": -1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 5, + "Y": -1, + "Z": 0, + "LengthSquared": 26, + "Length": 5.0990195135927845 + }, + { + "X": 5, + "Y": -1, + "Z": 2, + "LengthSquared": 30, + "Length": 5.477225575051661 + }, + { + "X": 3, + "Y": -1, + "Z": 2, + "LengthSquared": 14, + "Length": 3.7416573867739413 + }, + { + "X": 3, + "Y": -1, + "Z": 2, + "LengthSquared": 14, + "Length": 3.7416573867739413 + }, + { + "X": 5, + "Y": -1, + "Z": 2, + "LengthSquared": 30, + "Length": 5.477225575051661 + }, + { + "X": 5, + "Y": 1, + "Z": 2, + "LengthSquared": 30, + "Length": 5.477225575051661 + }, + { + "X": 3, + "Y": 1, + "Z": 2, + "LengthSquared": 14, + "Length": 3.7416573867739413 + }, + { + "X": 3, + "Y": 1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + }, + { + "X": 5, + "Y": 1, + "Z": 0, + "LengthSquared": 26, + "Length": 5.0990195135927845 + }, + { + "X": 5, + "Y": -1, + "Z": 0, + "LengthSquared": 26, + "Length": 5.0990195135927845 + }, + { + "X": 3, + "Y": -1, + "Z": 0, + "LengthSquared": 10, + "Length": 3.1622776601683795 + } + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 1, + "Y": 0, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -1, + "Y": -0, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 1, + "Z": 0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -1, + "Z": -0, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": 0, + "Y": 0, + "Z": 1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + }, + { + "X": -0, + "Y": -0, + "Z": -1, + "LengthSquared": 1, + "Length": 1 + } + ], + "UVs": null, + "GeometryId": null, + "Transform": null, + "MaterialId": null, + "Color": { + "R": 110, + "G": 122, + "B": 140, + "A": 255, + "Rf": 0.43137254901960786, + "Gf": 0.47843137254901963, + "Bf": 0.5490196078431373, + "Af": 1 + }, + "Opacity": 1, + "TextureIndex": null, + "BoundingBox": { + "Min": { + "X": "Infinity", + "Y": "Infinity", + "Z": "Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "Max": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + }, + "IsEmpty": true, + "Center": { + "X": "NaN", + "Y": "NaN", + "Z": "NaN", + "LengthSquared": "NaN", + "Length": "NaN" + }, + "Size": { + "X": "-Infinity", + "Y": "-Infinity", + "Z": "-Infinity", + "LengthSquared": "Infinity", + "Length": "Infinity" + } + } + } + ], + "Geometries": [], + "Materials": [], + "Textures": [ + { + "Id": 0, + "MimeType": "image/bmp", + "Base64Data": "Qk02MAAAAAAAADYAAAAoAAAAQAAAAEAAAAABABgAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAA6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr", + "ScaleX": 0.5, + "ScaleY": 0.5 + } + ], + "Grids": null, + "Viewpoints": [], + "SectionPlanes": [], + "SectionBox": null, + "TimeLiner": null, + "ClashTests": [], + "LinkedFiles": [] +} \ No newline at end of file From e9c8b5269f9bbce054810340ace1f6775bf35aa0 Mon Sep 17 00:00:00 2001 From: mahach666 <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 14:45:14 +0300 Subject: [PATCH 06/19] Remove the IfcIO adapter from IOViewer The adapter moves to the IfcIO repository as IfcIO.Scene. Remove its project, its tests, EngineVersions.props, the IfcIO package version, the two projects in the solution, the docs workflow path filters, the docfx entry and the documentation that describes it. The solution restores from nuget.org alone again. --- .github/workflows/docs.yml | 4 - CHANGELOG.md | 6 - CONTRIBUTING.md | 36 +- Directory.Packages.props | 2 - EngineVersions.props | 9 - IOViewer.slnx | 2 - README.md | 20 - docfx.json | 3 +- docs/getting-started.md | 55 --- docs/public-api.md | 19 - src/IOViewer.IfcIO/IOViewer.IfcIO.csproj | 16 - src/IOViewer.IfcIO/IfcViewerAdapter.cs | 277 ------------- src/IOViewer.IfcIO/IfcViewerConversion.cs | 79 ---- src/IOViewer.IfcIO/IfcViewerOptions.cs | 15 - src/IOViewer.IfcIO/icon.png | Bin 1918 -> 0 bytes .../IOViewer.IfcIO.Tests.csproj | 20 - .../IfcViewerAdapterTests.cs | 383 ------------------ 17 files changed, 5 insertions(+), 941 deletions(-) delete mode 100644 EngineVersions.props delete mode 100644 src/IOViewer.IfcIO/IOViewer.IfcIO.csproj delete mode 100644 src/IOViewer.IfcIO/IfcViewerAdapter.cs delete mode 100644 src/IOViewer.IfcIO/IfcViewerConversion.cs delete mode 100644 src/IOViewer.IfcIO/IfcViewerOptions.cs delete mode 100644 src/IOViewer.IfcIO/icon.png delete mode 100644 tests/IOViewer.IfcIO.Tests/IOViewer.IfcIO.Tests.csproj delete mode 100644 tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d600d08..bc687dc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,8 +9,6 @@ on: - 'templates/**' - '.config/dotnet-tools.json' - 'src/IOViewer/**' - - 'src/IOViewer.IfcIO/**' - - 'EngineVersions.props' - '.github/workflows/docs.yml' pull_request: paths: @@ -19,8 +17,6 @@ on: - 'templates/**' - '.config/dotnet-tools.json' - 'src/IOViewer/**' - - 'src/IOViewer.IfcIO/**' - - 'EngineVersions.props' - '.github/workflows/docs.yml' workflow_dispatch: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d11c1b..18cd239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,6 @@ API between minor releases. HTML viewer shows it as its own row in the properties panel ("Source ID" / "ID источника") and the model tree search finds it. It is written to the page's element list and to JSON only when set, so the element data of documents without it is written as before. -- `IOViewer.IfcIO` package: `IfcViewerAdapter` converts an IfcIO `IfcModel` into a `ViewerDocument` in metres with - Z up, without reading the file again. Elements keep their STEP id, GlobalId (`SourceId`), IFC class, storey, property - sets and quantities; colours come from IfcIO materials, and work schedules become the TimeLiner. `Convert` also - reports the length unit, elements without geometry by reason and skipped 4D tasks. IfcIO bakes mapped items into - world meshes, so each element gets its own mesh. It needs IfcIO 0.11.0, which reads world placements; the `IOViewer` - package itself still has no dependencies. ### Changed - The documentation states the model's units and axes: all coordinates and lengths in a `ViewerDocument` are in diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1527151..2055bc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,37 +33,9 @@ dotnet build IOViewer.slnx -c Release dotnet test IOViewer.slnx -c Release ``` -The library is in `src/IOViewer`, the CLI in `src/IOViewer.Cli`, engine adapters -in `src/IOViewer.` (for example `src/IOViewer.IfcIO`), and tests in -`tests/IOViewer.Tests` and `tests/IOViewer..Tests`. Tests use small -synthetic models built in code. The repository ships no customer models; set -`IOVIEWER_IFC_FILE` to a private IFC file to run one more adapter test on it. - -### Engine adapters - -Engines (IfcIO, RvtIO and others) depend neither on IOViewer nor on each other; -an adapter from an engine's model to `ViewerDocument` lives in this repository, -depends on IOViewer and that one engine, and `src/IOViewer` takes no engine -dependency. Engine package versions are set in `EngineVersions.props`. - -To build against an engine release that is not on nuget.org yet (IfcIO 0.11.0 -at the time of writing, so restoring the solution fails without it), pack the -engine into a local folder and pass its version and the folder: - -```sh -dotnet build IOViewer.slnx -c Release -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= -dotnet test IOViewer.slnx -c Release --no-build -dotnet pack IOViewer.slnx -c Release -p:Version=0.3.0-local.1 -p:IfcIOVersion=0.11.0-local.1 -p:RestoreAdditionalProjectSources= -o -``` - -Pack the whole solution with a prerelease of the next version: NuGet refuses a -stable package that depends on a prerelease one, and the adapter package -depends on the `IOViewer` package of the same version, so both must be in the -output. Do not commit a `nuget.config` that points to a local feed. - -Until the IfcIO version in `EngineVersions.props` is on nuget.org, restoring -the solution fails in the CI build job, the Docs workflow and `release.yml` -(the CLI alone still restores), so do not tag a release before then. +The library is in `src/IOViewer`, the CLI in `src/IOViewer.Cli`, and tests in +`tests/IOViewer.Tests`. Tests use small synthetic models built in code. +The repository ships no customer models. ### Viewer @@ -133,7 +105,7 @@ Write commit messages in the imperative mood, for example, Push a tag matching the package version, such as `v0.2.0`, to run `release.yml`. The workflow builds and tests the solution, attaches packages, symbols, source and checksums to a GitHub release, -and publishes the NuGet packages, including the engine adapters such as `IOViewer.IfcIO`, through Trusted Publishing. The release notes are the section of `CHANGELOG.md` for the version, so add that section before the tag. +and publishes the NuGet packages through Trusted Publishing. The release notes are the section of `CHANGELOG.md` for the version, so add that section before the tag. The NuGet policy must name this repository, `release.yml`, the configured NuGet profile and the `IOViewer*` package pattern. ## Licensing contributions diff --git a/Directory.Packages.props b/Directory.Packages.props index e2fb820..eb93399 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,10 +1,8 @@ - true - diff --git a/EngineVersions.props b/EngineVersions.props deleted file mode 100644 index 0842e08..0000000 --- a/EngineVersions.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - 0.11.0 - - diff --git a/IOViewer.slnx b/IOViewer.slnx index b58cdf0..7ed60cb 100644 --- a/IOViewer.slnx +++ b/IOViewer.slnx @@ -1,7 +1,5 @@ - - diff --git a/README.md b/README.md index 9274573..0c053ca 100644 --- a/README.md +++ b/README.md @@ -140,26 +140,6 @@ var doc = GlbViewerImporter.Load("complex_building.glb"); HtmlViewerExporter.Save(doc, "complex_building.html"); ``` -### Engine adapters - -An adapter turns the model of an IOExtend engine into a `ViewerDocument`, keeping what a GLB loses: source ids, -storeys and properties. Each adapter is its own package that depends on IOViewer and one engine; IOViewer itself -depends on no engine. - -| Package | Engine | Input | -| --- | --- | --- | -| `IOViewer.IfcIO` | [IfcIO](https://github.com/IOExtend/IfcIO) 0.11.0 or later | `IfcModel` (IFC2x3, IFC4) | - -```csharp -using IfcIO; -using IOViewer.Exporters; -using IOViewer.IfcIO; - -var model = IfcModel.Open("building.ifc"); -var doc = IfcViewerAdapter.ToViewerDocument(model); -HtmlViewerExporter.Save(doc, "building.html"); -``` - See the [public API](docs/public-api.md) for the supported surface and [getting started guide](docs/getting-started.md) for detailed workflows. ## Documentation diff --git a/docfx.json b/docfx.json index a59ec0e..007fca4 100644 --- a/docfx.json +++ b/docfx.json @@ -2,8 +2,7 @@ "metadata": [ { "src": [ - { "src": "src/IOViewer", "files": ["IOViewer.csproj"] }, - { "src": "src/IOViewer.IfcIO", "files": ["IOViewer.IfcIO.csproj"] } + { "src": "src/IOViewer", "files": ["IOViewer.csproj"] } ], "dest": "docs/api", "properties": { "TargetFramework": "net10.0" }, diff --git a/docs/getting-started.md b/docs/getting-started.md index 2b47753..f1e9647 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -84,61 +84,6 @@ for (int i = 0; i < 100; i++) `GlbViewerImporter` fills these properties from glTF nodes and meshes. Meshes with their own `Vertices` keep working as before; the exporter also writes exact translated copies of such meshes once. -## From an IFC file - -The `IOViewer.IfcIO` package converts a model read by [IfcIO](https://github.com/IOExtend/IfcIO) (0.11.0 or later) -into a `ViewerDocument`. Unlike an IFC to GLB to HTML route, it keeps GlobalIds, storeys, property sets and quantities. - -```sh -dotnet add package IOViewer.IfcIO -``` - -```csharp -using IfcIO; -using IOViewer.Exporters; -using IOViewer.IfcIO; - -var model = IfcModel.Open("building.ifc"); -var conversion = IfcViewerAdapter.Convert(model); - -foreach (var skipped in conversion.ElementsWithoutGeometry) -{ - Console.Error.WriteLine($"#{skipped.ElementId} ({skipped.GlobalId}): {skipped.Reason}"); -} - -HtmlViewerExporter.Save(conversion.Document, "building.html"); -``` - -`IfcViewerAdapter.ToViewerDocument(model)` returns the document alone. The adapter works on the model in memory and -does not read the file again. It maps the model as follows: - -- Geometry: IfcIO's world mesh of each element, converted from `IfcModel.LengthUnit` to metres by an exact factor - (`conversion.MetresPerUnit`). IFC is Z up like the viewer, so axes are not swapped. Coordinates stay doubles until the - HTML exporter makes them relative to the model centre, so georeferenced models keep their precision. -- Elements: one per element of `IfcModel.AllElements`, except the type objects (for example `IFCWALLTYPE`, - `IFCDOORSTYLE`) and door and window lining and panel properties that IfcIO also lists there. `Id` is the STEP entity - id (`#123` gives 123), `SourceId` the GlobalId, `Category` the IFC class as IfcIO reports it (for example `IFCWALL`), - `Level` the name of the IfcIO storey that holds it. - `IfcViewerOptions.IncludeSpaces` adds spaces, drawn translucent. Openings are not converted. -- Properties: each property set and quantity set becomes a property group with its name. Quantity values are written as - IfcIO reads them, in the file's units. -- Colours: the element's IfcIO material (surface style colour and its alpha as opacity), otherwise a default colour for - the IFC class. -- 4D: IfcIO work schedules become the TimeLiner, with each task's elements attached; tasks without planned dates are - counted in `conversion.SkippedTasks`. - -Current limits, all from what IfcIO exposes: - -- IfcIO bakes mapped items (`IfcMappedItem`) into each element's mesh and does not expose the shared representation, - so every element gets its own mesh (`conversion.OwnMeshes`) and repeated shapes are not stored once. -- IfcIO does not read type objects (`TypeName` stays empty), grids, transparency from files or task times from IFC2x3. -- IfcIO assigns storeys only from `IfcRelContainedInSpatialStructure` and puts everything else in the first storey, so - elements contained in the site or building, and spaces (which IFC aggregates to storeys with `IfcRelAggregates`), get - the first storey's name as `Level`. -- Clash results from `IfcModel.DetectClashes` are not converted. -- Element ids must be unique. They are for a model read with `IfcModel.Open`; a model built in memory numbers its - elements per storey, so save and reopen it before converting. - ## Using the viewer The model's up axis is +Z. The interface is in Russian when the browser language is Russian and in English otherwise. diff --git a/docs/public-api.md b/docs/public-api.md index 3dc9025..956b17e 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -30,22 +30,3 @@ - `GlbViewerImporter`: Reads binary glTF 2.0 (`.glb`) files into `ViewerDocument`. Each glTF mesh primitive becomes one `ViewerGeometry` and each node that uses it a `ViewerMesh` with the node's world transform, converted from glTF's Y-up to the viewer's Z-up. Each such node also becomes a `ViewerElement` (category `GLB`, named after the node, else the mesh) that all its primitives belong to. A node hierarchy that is not a tree (a node referenced twice or a cycle) is rejected with `InvalidDataException`. - `ObjViewerImporter`: Reads Wavefront OBJ geometry into `ViewerDocument` as is, without changing the up axis. Each object or group (`o`/`g`) becomes one `ViewerMesh` holding only the vertices its faces use and one `ViewerElement` (category `OBJ`) with its name. Material (`.mtl`) files are not read. - `JsonViewerImporter`: Reads JSON document snapshots into `ViewerDocument`. - -### IfcIO adapter (`IOViewer.IfcIO`, package `IOViewer.IfcIO`) - -A separate package that depends on IOViewer and [IfcIO](https://github.com/IOExtend/IfcIO) 0.11.0 or later; the -`IOViewer` package does not depend on IfcIO. - -- `IfcViewerAdapter.ToViewerDocument(IfcModel, IfcViewerOptions?)`: Converts a model in memory, without reading the file - again, into a `ViewerDocument` in metres with Z up. Each element becomes a `ViewerElement` with the STEP entity id as - `Id`, the GlobalId as `SourceId`, the IFC class as `Category`, the IfcIO storey as `Level` (elements outside a storey - and spaces get the first storey), and its property sets and quantity sets as property groups; type objects and lining - and panel properties that IfcIO lists as elements are skipped; an element with geometry gets one mesh of its own. Work schedules become the TimeLiner. Duplicate element - ids throw `ArgumentException`. -- `IfcViewerAdapter.Convert(IfcModel, IfcViewerOptions?)`: The same, returning an `IfcViewerConversion` with the - document and a report: the length unit and its exact factor to metres, the number of elements and of own meshes, - elements without geometry with an `IfcViewerGeometryIssue` (`NoGeometry`, `EmptyMesh`, `InvalidMesh`), and the number - of 4D tasks left out for lack of planned dates. -- `IfcViewerOptions.IncludeSpaces`: Also converts spaces; off by default. - -See [From an IFC file](getting-started.md#from-an-ifc-file) for the mapping and its current limits. diff --git a/src/IOViewer.IfcIO/IOViewer.IfcIO.csproj b/src/IOViewer.IfcIO/IOViewer.IfcIO.csproj deleted file mode 100644 index a2b1339..0000000 --- a/src/IOViewer.IfcIO/IOViewer.IfcIO.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - true - IOViewer.IfcIO - Converts IFC models read by IfcIO into IOViewer documents for standalone interactive 3D HTML viewers, keeping GlobalIds, storeys, property sets and quantities. - bim;ifc;ifcio;ioviewer;webgl;html-viewer;3d-viewer - true - - - - - - - - diff --git a/src/IOViewer.IfcIO/IfcViewerAdapter.cs b/src/IOViewer.IfcIO/IfcViewerAdapter.cs deleted file mode 100644 index 797158c..0000000 --- a/src/IOViewer.IfcIO/IfcViewerAdapter.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System.Globalization; -using IfcIO; -using IfcIO.Model; -using IfcIO.Model.Sequence; -using IfcIO.Schema; -using IOViewer.Model; - -namespace IOViewer.IfcIO; - -/// -/// Converts an read by IfcIO into a . -/// -/// -/// -/// The adapter works on the model in memory and never reads the file again. Geometry is IfcIO's world mesh of each -/// element (), scaled from to metres by an -/// exact factor; IFC is Z up like the viewer, so axes are not swapped. Coordinates stay doubles; the HTML exporter -/// moves them relative to the model centre. -/// -/// -/// Each element of (and of with -/// ) becomes one , except the type objects -/// (for example IFCWALLTYPE, IFCDOORSTYLE) and lining and panel properties that IfcIO also lists there: -/// is , which for a model opened with -/// is the element's STEP entity id (#123 gives 123), stable for a given file; -/// is the GlobalId; is the IFC class as IfcIO -/// reports it (upper case, for example IFCWALL); is the name of the IfcIO -/// storey that holds it. IfcIO places an element in a storey only through IfcRelContainedInSpatialStructure, so -/// elements contained in the site or building, and spaces aggregated to a storey (IfcRelAggregates, which IfcIO -/// does not read), get the first storey's name. Property sets and quantity sets become property groups named after the set; quantity values are -/// written as IfcIO reads them, in the file's units, with invariant formatting. -/// -/// -/// Colour and transparency come from (IfcIO reads the colour of -/// IfcSurfaceStyleShading); elements without one get a default colour for their IFC class. IfcIO's 4D work -/// schedules become . Openings are not converted. -/// -/// -public static class IfcViewerAdapter -{ - /// - /// Converts into a viewer document. - /// - /// The model, for example from . - /// Conversion options; null uses the defaults. - /// The document, in metres with Z up. - /// Two converted elements have the same . - public static ViewerDocument ToViewerDocument(IfcModel model, IfcViewerOptions? options = null) => - Convert(model, options).Document; - - /// - /// Converts into a viewer document and reports what was converted. - /// - /// The model, for example from . - /// Conversion options; null uses the defaults. - /// The document with a report of elements without geometry, meshes, units and skipped 4D tasks. - /// Two converted elements have the same . - public static IfcViewerConversion Convert(IfcModel model, IfcViewerOptions? options = null) - { - ArgumentNullException.ThrowIfNull(model); - options ??= new IfcViewerOptions(); - - double scale = MetresPerUnit(model.LengthUnit); - var doc = new ViewerDocument(model.ProjectName); - var withoutGeometry = new List(); - var materials = new Dictionary(ReferenceEqualityComparer.Instance); - var converted = new Dictionary(ReferenceEqualityComparer.Instance); - var ids = new HashSet(); - - foreach (var storey in model.Storeys) - { - var elements = options.IncludeSpaces ? storey.Elements.Concat(storey.Spaces) : storey.Elements; - foreach (var element in elements) - { - if (IsNotProduct(element.IfcType)) continue; - - // Mesh ElementIds, 4D tasks and clashes all key on Id, so a repeat would merge two elements. - if (!ids.Add(element.Id)) - { - throw new ArgumentException( - $"Element id {element.Id} ('{element.Name}') is used twice. Ids are unique in models read with IfcModel.Open; " - + "a model built in memory numbers elements per storey, so save and reopen it first.", - nameof(model)); - } - - converted.Add(element, element.Id); - var issue = AddElement(doc, element, storey.Name, scale, materials); - if (issue is { } reason) - { - withoutGeometry.Add(new IfcViewerElementWithoutGeometry(element.Id, element.GlobalId, reason)); - } - } - } - - int skippedTasks = AddSchedule(doc, model.WorkSchedules, converted); - doc.ComputeBoundingBox(); - return new IfcViewerConversion(doc, model.LengthUnit, scale, withoutGeometry, skippedTasks); - } - - // shortcut: IfcIO picks products by class-name prefix (IFCWALL, IFCDOOR, ...), so type objects such as IfcWallType or - // IfcDoorStyle and the door and window lining and panel properties arrive as elements without geometry; no product - // class it matches ends like these. Remove once IfcIO reads only IfcProduct subtypes. - private static bool IsNotProduct(string ifcType) => - ifcType.EndsWith("TYPE", StringComparison.OrdinalIgnoreCase) - || ifcType.EndsWith("STYLE", StringComparison.OrdinalIgnoreCase) - || ifcType.EndsWith("PROPERTIES", StringComparison.OrdinalIgnoreCase); - - // Exact lengths of the units IfcIO reports; it already converts any other file unit to metres while reading. - private static double MetresPerUnit(IfcLengthUnit unit) => unit switch - { - IfcLengthUnit.Millimetre => 0.001, - IfcLengthUnit.Metre => 1.0, - IfcLengthUnit.Foot => 0.3048, - IfcLengthUnit.Inch => 0.0254, - _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, "Unknown IFC length unit."), - }; - - private static IfcViewerGeometryIssue? AddElement( - ViewerDocument doc, IfcElement element, string level, double scale, Dictionary materials) - { - var viewerElement = new ViewerElement - { - Id = element.Id, - Name = element.Name, - Category = element.IfcType, - TypeName = element.TypeProduct?.Name ?? string.Empty, - Level = level, - SourceId = element.GlobalId, - }; - foreach (var pset in element.PropertySets) - { - foreach (var property in pset.Properties) - { - viewerElement.AddProperty(pset.Name, property.Name, property.Value); - } - } - foreach (var qset in element.QuantitySets) - { - foreach (var quantity in qset.Quantities) - { - viewerElement.AddProperty(qset.Name, quantity.Name, quantity.Value.ToString(CultureInfo.InvariantCulture)); - } - } - doc.Elements.Add(viewerElement); - - var source = element.GetEffectiveMesh(); - if (source is null) return IfcViewerGeometryIssue.NoGeometry; - if (source.Indices.Count == 0 || source.Vertices.Count == 0) return IfcViewerGeometryIssue.EmptyMesh; - - var vertices = new Vector3d[source.Vertices.Count]; - for (int i = 0; i < vertices.Length; i++) - { - var v = source.Vertices[i]; - if (!double.IsFinite(v.X) || !double.IsFinite(v.Y) || !double.IsFinite(v.Z)) return IfcViewerGeometryIssue.InvalidMesh; - vertices[i] = new Vector3d(v.X * scale, v.Y * scale, v.Z * scale); - } - if (source.Indices.Count % 3 != 0 || source.Indices.Any(index => (uint)index >= (uint)vertices.Length)) - { - return IfcViewerGeometryIssue.InvalidMesh; - } - - var mesh = new ViewerMesh - { - Id = doc.Meshes.Count + 1, - Name = element.Name, - ElementId = element.Id, - Vertices = vertices, - Indices = [.. source.Indices], - }; - if (element.Material is { } material) - { - mesh.MaterialId = MaterialId(doc, material, materials); - } - else - { - (mesh.Color, mesh.Opacity) = DefaultColor(element); - } - - viewerElement.MeshIndices.Add(doc.Meshes.Count); - doc.Meshes.Add(mesh); - return null; - } - - private static int MaterialId(ViewerDocument doc, IfcMaterial material, Dictionary materials) - { - if (materials.TryGetValue(material, out int id)) return id; - - id = doc.Materials.Count + 1; - doc.Materials.Add(new ViewerMaterial - { - Id = id, - Name = material.Name, - Color = new RgbaColor(Channel(material.R), Channel(material.G), Channel(material.B)), - Opacity = Math.Clamp(material.Alpha, 0, 1), - }); - materials.Add(material, id); - return id; - } - - private static byte Channel(double value) => (byte)Math.Round(Math.Clamp(value, 0, 1) * 255); - - // Fallback colours by IFC class family, matched on the class name prefix as IfcIO lists the classes it reads. - private static (RgbaColor Color, double Opacity) DefaultColor(IfcElement element) - { - string type = element.IfcType.ToUpperInvariant(); - if (element is IfcSpace) return (new RgbaColor(150, 200, 235), 0.3); - if (type.StartsWith("IFCWINDOW", StringComparison.Ordinal) || type.StartsWith("IFCCURTAINWALL", StringComparison.Ordinal)) - { - return (new RgbaColor(140, 190, 225), 0.5); - } - if (type.StartsWith("IFCWALL", StringComparison.Ordinal)) return (new RgbaColor(215, 213, 205), 1); - if (type.StartsWith("IFCSLAB", StringComparison.Ordinal) || type.StartsWith("IFCROOF", StringComparison.Ordinal) - || type.StartsWith("IFCFOOTING", StringComparison.Ordinal)) - { - return (new RgbaColor(190, 190, 190), 1); - } - if (type.StartsWith("IFCCOLUMN", StringComparison.Ordinal) || type.StartsWith("IFCBEAM", StringComparison.Ordinal) - || type.StartsWith("IFCMEMBER", StringComparison.Ordinal)) - { - return (new RgbaColor(120, 140, 185), 1); - } - if (type.StartsWith("IFCDOOR", StringComparison.Ordinal)) return (new RgbaColor(150, 105, 60), 1); - if (type.StartsWith("IFCPIPE", StringComparison.Ordinal) || type.StartsWith("IFCFLOW", StringComparison.Ordinal)) - { - return (new RgbaColor(225, 110, 60), 1); - } - if (type.StartsWith("IFCDUCT", StringComparison.Ordinal)) return (new RgbaColor(70, 190, 110), 1); - return (new RgbaColor(180, 180, 180), 1); - } - - private static int AddSchedule(ViewerDocument doc, List schedules, Dictionary converted) - { - var withTasks = schedules.Where(s => s.RootTasks.Count > 0).ToList(); - if (withTasks.Count == 0) return 0; - - // The viewer shows one schedule, so the tasks of several work schedules go into one, side by side. - var timeLiner = new ViewerTimeLinerSchedule(string.Join(", ", withTasks.Select(s => s.Name))); - int skipped = 0; - foreach (var task in withTasks.SelectMany(s => s.RootTasks)) - { - AddTask(task, timeLiner.Tasks); - } - doc.TimeLiner = timeLiner.Tasks.Count > 0 ? timeLiner : null; - return skipped; - - void AddTask(IfcTask task, List target) - { - if (task.Time?.PlannedStartDate is not { } start || task.Time.PlannedFinishDate is not { } finish) - { - skipped++; - foreach (var sub in task.SubTasks) AddTask(sub, target); - return; - } - - var viewerTask = new ViewerTimeLinerTask( - task.GlobalId, - task.Name, - Instant(start), - Instant(finish), - task.TaskType == IfcTaskType.Demolition ? ViewerTimeLinerTaskType.Demolish : ViewerTimeLinerTaskType.Construct) - { - ActualStart = task.Time.ActualStartDate is { } actualStart ? Instant(actualStart) : null, - ActualEnd = task.Time.ActualFinishDate is { } actualEnd ? Instant(actualEnd) : null, - }; - foreach (var element in task.AssignedElements) - { - if (converted.TryGetValue(element, out long id)) viewerTask.AttachedElementIds.Add(id); - } - target.Add(viewerTask); - foreach (var sub in task.SubTasks) AddTask(sub, viewerTask.Children); - } - } - - // IfcIO reads IFC date-times as UTC (converted to local time); a date set in memory without a kind is taken as UTC too. - private static DateTimeOffset Instant(DateTime value) => - value.Kind == DateTimeKind.Unspecified ? new DateTimeOffset(value, TimeSpan.Zero) : new DateTimeOffset(value).ToUniversalTime(); -} diff --git a/src/IOViewer.IfcIO/IfcViewerConversion.cs b/src/IOViewer.IfcIO/IfcViewerConversion.cs deleted file mode 100644 index af7308a..0000000 --- a/src/IOViewer.IfcIO/IfcViewerConversion.cs +++ /dev/null @@ -1,79 +0,0 @@ -using IfcIO; -using IfcIO.Schema; -using IOViewer.Model; - -namespace IOViewer.IfcIO; - -/// -/// The result of : the viewer document and what happened to the model's data. -/// -public sealed class IfcViewerConversion -{ - internal IfcViewerConversion( - ViewerDocument document, - IfcLengthUnit lengthUnit, - double metresPerUnit, - IReadOnlyList elementsWithoutGeometry, - int skippedTasks) - { - Document = document; - LengthUnit = lengthUnit; - MetresPerUnit = metresPerUnit; - ElementsWithoutGeometry = elementsWithoutGeometry; - SkippedTasks = skippedTasks; - } - - /// The converted document, in metres with Z up. - public ViewerDocument Document { get; } - - /// The model's length unit () the geometry was converted from. - public IfcLengthUnit LengthUnit { get; } - - /// The exact factor applied to every coordinate: the length of one in metres. - public double MetresPerUnit { get; } - - /// Number of converted elements, with or without geometry. - public int ElementCount => Document.Elements.Count; - - /// Number of converted elements that have a mesh. - public int ElementsWithGeometry => Document.Elements.Count - ElementsWithoutGeometry.Count; - - /// - /// Number of meshes with their own vertices. IfcIO bakes mapped items (IfcMappedItem) into each element's - /// world mesh and does not expose the shared representation, so every mesh is its own, one per element with - /// geometry, and stays empty. - /// - public int OwnMeshes => Document.Meshes.Count; - - /// Converted elements that have no mesh, with the reason; they still appear in the model tree. - public IReadOnlyList ElementsWithoutGeometry { get; } - - /// - /// Number of 4D tasks left out of because they have no planned start and finish - /// (IfcIO reads task times from IFC4 files only). Their subtasks are kept. - /// - public int SkippedTasks { get; } -} - -/// -/// A converted element that has no mesh. -/// -/// The element's . -/// The element's IFC GlobalId, if any. -/// Why the element has no mesh. -public sealed record IfcViewerElementWithoutGeometry(long ElementId, string? GlobalId, IfcViewerGeometryIssue Reason); - -/// -/// Why a converted element has no mesh. -/// -public enum IfcViewerGeometryIssue -{ - /// IfcIO returned no geometry: the element has no representation, or none IfcIO can read. - NoGeometry, - - /// The geometry has no triangles. - EmptyMesh, - - /// The geometry has a non-finite coordinate, an index out of range or an index count not divisible by 3. - InvalidMesh, -} diff --git a/src/IOViewer.IfcIO/IfcViewerOptions.cs b/src/IOViewer.IfcIO/IfcViewerOptions.cs deleted file mode 100644 index 47c7ab2..0000000 --- a/src/IOViewer.IfcIO/IfcViewerOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using IfcIO; - -namespace IOViewer.IfcIO; - -/// -/// Options for . -/// -public sealed class IfcViewerOptions -{ - /// - /// Also converts spaces (IfcSpace, ), drawn translucent unless their - /// material says otherwise. False by default, because closed room volumes hide the elements inside them. - /// - public bool IncludeSpaces { get; set; } -} diff --git a/src/IOViewer.IfcIO/icon.png b/src/IOViewer.IfcIO/icon.png deleted file mode 100644 index 8dd500b06ab17b4d4e66f15c242355fbd09b58ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1918 zcmZ{lc{JOJ7RP^nvJgZN)E>2$+R9X|v5r_1^kLN2(xNlSFxEz5Yfwc|vGb^1kfM*; zccxXXGIrV-t*!Pg)vD*sd*{q~=e+mFz4zRI?)~HYz4w!dxA~q6A&dY3fD31CVs~CC z{|YwPdG6Eqd;tJ(Q=EyRLsE$lS;nd6{Mbk(7+UTFWR? zAe$^ina3275if#7&=Jw)AVp`w@hQT>z*R(34421)tFSTC@_AtAW@9K z3ue_;Gp6ZmuIyHiys_aJg<8QmF8Fj*uVdGMulsiXR15L? z#>&2(t=sKfwM7F+;JRaw{PZXg98RDbNSMM zoC2~==XUEK_1zP@d}$hPkrk))Z5j;Lc!R9>+jqN8wCmNuFzfNU$M?2`qklSNq5AjC-99nMn;0u8-TDwB;YVbzB$4eq49&ePqs* zZ`BlI!9|uiU=J&PAO{!!iRmAQQ!b}C6*U3gJMc)hV zK=|OggTQM$*Euh;A4Ci^KQ-H4gZgjE^7O6ilK1R~*;z|z(Je$WUuP%Dz?`phmaF%q z96ALm)#Sf+_3sJnYMIOkxpf)tt`g9*9lrxzmD$J>aJstjYtsuykpD0JJXz5Hzp1F0 z#Lh3WC~4CSP5m#V55DR{WNv4QrQR8OYS%nB=ME1DVAVIL0gtIV9s{U^me~)If12XI zh9(}$5@sLfr3{%6`O=y48FP;QI^uJkE;g=h`Tt}2ly|XaIOofe(oP$Hv1_oX9tMs= zo0F(%Jyq%mWNqqoM`@B`hk(IFzsQ$?pMPlxVM5KH0O+r?H33G;$r3E02IEKg(GR8N zyHW>q{)iAVD3@$JHdn79`2po2e0p>xR;fc}t4>;60gCaW+~A52_v9o;1L@eM^6uspv#l~)No11a()LLB z;}KwCahzTsCFnqb24eZHtG7yO_=-MFA4I0+_G~nZ>kiHeI z;wWhAo2DAT;fJzyVS1^3U+z9~wzwvCb{$%|G4h$92wmDVn<(g>Uya-O!L<~$oE1`{ z!}O_D+_8zV7}akO3ulD{pzs^q!Li~FBs)UFajN(cdtBWN?-P>n{-&$66drc zYp&?{SIlT|nTjQ*2;#a@p9LWV{KMioM81SPZG3?#+-(j<`AmqjL%d@S-uv=*4_iL3 zXt44)zwZAC$%~N)mCK$U&HzRfiE0GGcpQo*bvFfvMn_s3FH2$x@<-XDHpsP8mi0s{ zKQ+aZhQBEb`XIZVv3h&xFN5m%cXsm~o)^o7f?R;}PYa^jh4C0HY{1$;jfYMNbayKUrKZkP$d+ENNP!cKk~93g&t$}cNIG;F zx|!zuW^4zKkt&%;*v$CEM;VV*=;zB)U8CMS30vQGLMjK$kHmJ*m`4mAh(|YuShdY? z - - - net8.0;net10.0 - false - $(NoWarn);CA1707 - - - - - - - - - - - - - - diff --git a/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs b/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs deleted file mode 100644 index 21093ef..0000000 --- a/tests/IOViewer.IfcIO.Tests/IfcViewerAdapterTests.cs +++ /dev/null @@ -1,383 +0,0 @@ -using System.Globalization; -using System.Text.RegularExpressions; -using IfcIO; -using IfcIO.Geometry; -using IfcIO.Model; -using IfcIO.Schema; -using IOViewer.Exporters; -using IOViewer.Model; -using IOViewer.Tests; -using Xunit; - -namespace IOViewer.IfcIO.Tests; - -public class IfcViewerAdapterTests -{ - private const string Millimetre = "#11=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);"; - private const string Foot = """ - #11=IFCCONVERSIONBASEDUNIT(#12,.LENGTHUNIT.,'FOOT',#13); - #12=IFCDIMENSIONALEXPONENTS(1,0,0,0,0,0,0); - #13=IFCMEASUREWITHUNIT(IFCLENGTHMEASURE(0.3048),#14); - #14=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); - """; - private const string Hostile = "\">"; - - // Site far from the origin; storey 3000 up; Panel placed at (10,20,0) in the storey and styled red. - // Fitting A and B draw one representation map (origin (0,0,100)); B's mapping moves it by (0,250,0). - private static string PlacedIfc(string lengthUnit, string extra = "") => $""" - ISO-10303-21; - HEADER; - FILE_DESCRIPTION((''),'2;1'); - FILE_NAME('adapter.ifc','2026-01-01T00:00:00',(''),(''),'','',''); - FILE_SCHEMA(('IFC2X3')); - ENDSEC; - DATA; - #1=IFCPROJECT('0AdapterProject0000001',$,'Adapter',$,$,$,$,(#20),#10); - #10=IFCUNITASSIGNMENT((#11)); - {lengthUnit} - #20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#21,$); - #21=IFCAXIS2PLACEMENT3D(#22,$,$); - #22=IFCCARTESIANPOINT((0.,0.,0.)); - #30=IFCSITE('0AdapterSite0000000001',$,'Site',$,$,#31,$,$,.ELEMENT.,$,$,$,$,$); - #31=IFCLOCALPLACEMENT($,#32); - #32=IFCAXIS2PLACEMENT3D(#33,$,$); - #33=IFCCARTESIANPOINT((412345678.,2000.,0.)); - #40=IFCBUILDINGSTOREY('0AdapterStorey00000001',$,'Level 1',$,$,#41,$,$,.ELEMENT.,3000.); - #41=IFCLOCALPLACEMENT(#31,#42); - #42=IFCAXIS2PLACEMENT3D(#43,$,$); - #43=IFCCARTESIANPOINT((0.,0.,3000.)); - #100=IFCBUILDINGELEMENTPROXY('1AdapterPanel000000001',$,'Panel',$,$,#101,#103,$,$); - #101=IFCLOCALPLACEMENT(#41,#102); - #102=IFCAXIS2PLACEMENT3D(#104,$,$); - #104=IFCCARTESIANPOINT((10.,20.,0.)); - #103=IFCPRODUCTDEFINITIONSHAPE($,$,(#105)); - #105=IFCSHAPEREPRESENTATION(#20,'Body','Brep',(#106)); - #106=IFCFACETEDBREP(#107); - #107=IFCCLOSEDSHELL((#108)); - #108=IFCFACE((#109)); - #109=IFCFACEOUTERBOUND(#110,.T.); - #110=IFCPOLYLOOP((#22,#111,#112)); - #111=IFCCARTESIANPOINT((100.,0.,0.)); - #112=IFCCARTESIANPOINT((0.,100.,0.)); - #120=IFCCOLOURRGB($,1.,0.,0.); - #121=IFCSURFACESTYLESHADING(#120); - #122=IFCSURFACESTYLE('Red',.BOTH.,(#121)); - #123=IFCSTYLEDITEM(#106,(#122),$); - #200=IFCREPRESENTATIONMAP(#201,#203); - #201=IFCAXIS2PLACEMENT3D(#202,$,$); - #202=IFCCARTESIANPOINT((0.,0.,100.)); - #203=IFCSHAPEREPRESENTATION(#20,'Body','Brep',(#204)); - #204=IFCFACETEDBREP(#107); - #300=IFCFLOWFITTING('1AdapterFittingA000001',$,'Fitting A',$,$,#301,#303,$); - #301=IFCLOCALPLACEMENT($,#302); - #302=IFCAXIS2PLACEMENT3D(#304,$,$); - #304=IFCCARTESIANPOINT((500.,0.,0.)); - #303=IFCPRODUCTDEFINITIONSHAPE($,$,(#305)); - #305=IFCSHAPEREPRESENTATION(#20,'Body','MappedRepresentation',(#306)); - #306=IFCMAPPEDITEM(#200,#307); - #307=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#22,$,$); - #310=IFCFLOWFITTING('1AdapterFittingB000001',$,'Fitting B',$,$,#301,#313,$); - #313=IFCPRODUCTDEFINITIONSHAPE($,$,(#315)); - #315=IFCSHAPEREPRESENTATION(#20,'Body','MappedRepresentation',(#316)); - #316=IFCMAPPEDITEM(#200,#317); - #317=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#318,$,$); - #318=IFCCARTESIANPOINT((0.,250.,0.)); - #400=IFCRELCONTAINEDINSPATIALSTRUCTURE('2AdapterContainment001',$,$,$,(#100,#300,#310),#40); - {extra} - ENDSEC; - END-ISO-10303-21; - """; - - private static readonly Vector3d[] Triangle = [new(0, 0, 0), new(100, 0, 0), new(0, 100, 0)]; - - [Theory] - [InlineData(Millimetre, IfcLengthUnit.Millimetre, 0.001)] - [InlineData(Foot, IfcLengthUnit.Foot, 0.3048)] - public void Convert_PlacedAndMappedElements_LandInWorldMetres(string lengthUnit, IfcLengthUnit expectedUnit, double metres) - { - var model = OpenText(PlacedIfc(lengthUnit)); - - var conversion = IfcViewerAdapter.Convert(model); - var doc = conversion.Document; - - Assert.Equal(expectedUnit, conversion.LengthUnit); - Assert.Equal(metres, conversion.MetresPerUnit); - AssertPoints(Offset(new(412345678 + 10, 2000 + 20, 3000), metres), MeshOf(doc, 100).Vertices); - AssertPoints(Offset(new(500, 0, 100), metres), MeshOf(doc, 300).Vertices); - AssertPoints(Offset(new(500, 250, 100), metres), MeshOf(doc, 310).Vertices); - - // IfcIO bakes mapped items into world meshes: the two fittings are two own meshes, nothing is shared. - Assert.Empty(doc.Geometries); - Assert.Equal(3, conversion.OwnMeshes); - Assert.All(doc.Meshes, m => Assert.Null(m.Transform)); - - var panel = Assert.Single(doc.Elements, e => e.Id == 100); - Assert.Equal("1AdapterPanel000000001", panel.SourceId); - Assert.Equal("IFCBUILDINGELEMENTPROXY", panel.Category); - Assert.Equal("Level 1", panel.Level); - var red = Assert.Single(doc.Materials); - Assert.Equal(new RgbaColor(255, 0, 0), red.Color); - Assert.Equal(red.Id, MeshOf(doc, 100).MaterialId); - } - - [Fact] - public void Convert_TypeObjectsAndLiningProperties_AreNotElements() - { - const string NonProducts = """ - #500=IFCWALLTYPE('1AdapterWallType000001',$,'WT',$,$,$,$,$,$,.STANDARD.); - #501=IFCDOORSTYLE('1AdapterDoorStyle00001',$,'DS',$,$,$,$,$,.SINGLE_SWING_LEFT.,.WOOD.,.F.,.F.); - #502=IFCDOORLININGPROPERTIES('1AdapterDoorLining0001',$,'DL',$,50.,$,$,$,$,$,$,$,$,$,$); - """; - var model = OpenText(PlacedIfc(Millimetre, NonProducts)); - long[] nonProducts = [500, 501, 502]; - // IfcIO lists them as elements, which is what the adapter has to undo. - Assert.All(nonProducts, id => Assert.Contains(model.AllElements, e => e.Id == id)); - - var conversion = IfcViewerAdapter.Convert(model); - - Assert.Equal(new long[] { 100, 300, 310 }, conversion.Document.Elements.Select(e => e.Id).Order()); - Assert.Empty(conversion.ElementsWithoutGeometry); - } - - [Theory] - [InlineData(IfcLengthUnit.Millimetre, 0.001)] - [InlineData(IfcLengthUnit.Metre, 1.0)] - public void Convert_SavedModel_ScalesFromItsLengthUnit(IfcLengthUnit unit, double metres) - { - var model = IfcModel.Create("Units"); - model.LengthUnit = unit; - model.AddMesh("Slab", [new(1000, 0, 0), new(2000, 0, 0), new(1000, 1500, 250)], [0, 1, 2], ifcType: "IFCSLAB"); - var opened = SaveAndOpen(model); - - var conversion = IfcViewerAdapter.Convert(opened); - - Assert.Equal(unit, conversion.LengthUnit); - var source = Assert.Single(opened.AllElements).Mesh!.Vertices; - var vertices = Assert.Single(conversion.Document.Meshes).Vertices; - Assert.Equal(source.Select(v => new Vector3d(v.X * metres, v.Y * metres, v.Z * metres)), vertices); - Assert.Equal(new Vector3d(2000 * metres, 1500 * metres, 250 * metres), conversion.Document.BoundingBox.Max); - } - - [Fact] - public void Convert_SavedModel_KeepsPropertiesQuantitiesGlobalIdsAndReportsElementsWithoutGeometry() - { - var model = IfcModel.Create("Properties"); - var storey = model.AddStorey("Level 2", 3000); - var wall = model.AddMesh("Wall", Mm(Triangle), [0, 1, 2], new IfcMaterial { Name = "Brick", R = 1, G = 0.5, B = 0 }, storey, "IFCWALL"); - wall.GetOrCreatePropertySet("Pset_WallCommon").Add("FireRating", "EI 60"); - wall.GetOrCreateQuantitySet("Qto_WallBaseQuantities").AddLength("Length", 3000.5); - model.AddElement(new IfcElement { Id = 2, Name = "Annotation" }, storey); - var opened = SaveAndOpen(model); - var openedWall = Assert.Single(opened.AllElements, e => e.Name == "Wall"); - var openedEmpty = Assert.Single(opened.AllElements, e => e.Name == "Annotation"); - - var conversion = IfcViewerAdapter.Convert(opened); - var doc = conversion.Document; - - var element = Assert.Single(doc.Elements, e => e.Id == openedWall.Id); - Assert.False(string.IsNullOrEmpty(openedWall.GlobalId)); - Assert.Equal(openedWall.GlobalId, element.SourceId); - Assert.Equal("IFCWALL", element.Category); - Assert.Equal("Level 2", element.Level); - Assert.Equal("EI 60", element.Properties["Pset_WallCommon"]["FireRating"]); - Assert.Equal("3000.5", element.Properties["Qto_WallBaseQuantities"]["Length"]); - var brick = Assert.Single(doc.Materials); - Assert.Equal(new RgbaColor(255, 128, 0), brick.Color); - Assert.Equal(1.0, brick.Opacity); - - Assert.Equal(2, conversion.ElementCount); - Assert.Equal(1, conversion.ElementsWithGeometry); - Assert.Equal( - new IfcViewerElementWithoutGeometry(openedEmpty.Id, openedEmpty.GlobalId, IfcViewerGeometryIssue.NoGeometry), - Assert.Single(conversion.ElementsWithoutGeometry)); - Assert.Empty(Assert.Single(doc.Elements, e => e.Id == openedEmpty.Id).MeshIndices); - - var view = ViewerHtml.Export(doc); - string id = openedWall.Id.ToString(CultureInfo.InvariantCulture); - Assert.Equal("EI 60", view.Props.GetProperty(id).GetProperty("Pset_WallCommon").GetProperty("FireRating").GetString()); - var meta = Assert.Single(view.Meta.GetProperty("elements").EnumerateArray(), e => e.GetProperty("id").GetInt64() == openedWall.Id); - Assert.Equal(openedWall.GlobalId, meta.GetProperty("sourceId").GetString()); - } - - [Fact] - public void Convert_HostileStrings_AreEscapedInHtml() - { - var model = IfcModel.Create("Hostile"); - var wall = model.AddMesh(Hostile, Mm(Triangle), [0, 1, 2], ifcType: "IFCWALL"); - wall.GetOrCreatePropertySet(Hostile).Add(Hostile, Hostile); - var opened = SaveAndOpen(model); - var openedWall = Assert.Single(opened.AllElements); - - var view = ViewerHtml.Export(IfcViewerAdapter.ToViewerDocument(opened)); - - string id = openedWall.Id.ToString(CultureInfo.InvariantCulture); - Assert.Equal(Hostile, view.Props.GetProperty(id).GetProperty(Hostile).GetProperty(Hostile).GetString()); - Assert.Equal(Hostile, Assert.Single(view.Meta.GetProperty("elements").EnumerateArray()).GetProperty("name").GetString()); - Assert.DoesNotContain(Hostile, view.Html, StringComparison.Ordinal); - } - - [Fact] - public void Convert_MaterialAlpha_BecomesOpacityAndElementsWithoutMaterialGetClassColour() - { - // IfcIO does not read transparency from files yet, so the material is set in memory. - var model = IfcModel.Create("Glass"); - model.AddMesh("Pane", Mm(Triangle), [0, 1, 2], new IfcMaterial { Name = "Glass", R = 0.2, G = 0.4, B = 0.6, Alpha = 0.35 }, ifcType: "IFCPLATE"); - model.AddMesh("Window", Mm(Triangle), [0, 1, 2], ifcType: "IFCWINDOW"); - - var doc = IfcViewerAdapter.ToViewerDocument(model); - - var glass = Assert.Single(doc.Materials); - Assert.Equal(0.35, glass.Opacity); - Assert.Equal(new RgbaColor(51, 102, 153), glass.Color); - var window = Assert.Single(doc.Meshes, m => m.Name == "Window"); - Assert.Null(window.MaterialId); - Assert.Equal(0.5, window.Opacity); - Assert.Contains( - ViewerHtml.Export(doc).Meta.GetProperty("materials").EnumerateArray(), - m => Math.Abs(m.GetProperty("opacity").GetDouble() - 0.35) < 1e-9); - } - - [Fact] - public void Convert_SpacesOnlyWhenIncluded() - { - var model = IfcModel.Create("Spaces"); - model.AddMesh("Wall", Mm(Triangle), [0, 1, 2], ifcType: "IFCWALL"); - var space = model.AddSpace("Room 1", mesh: IfcMesh.Create(1, "Room 1", Mm(Triangle), [0, 1, 2])); - - Assert.DoesNotContain(IfcViewerAdapter.ToViewerDocument(model).Elements, e => e.Id == space.Id); - - var doc = IfcViewerAdapter.ToViewerDocument(model, new IfcViewerOptions { IncludeSpaces = true }); - Assert.Equal("IFCSPACE", Assert.Single(doc.Elements, e => e.Id == space.Id).Category); - Assert.Equal(0.3, Assert.Single(doc.Meshes, m => m.ElementId == space.Id).Opacity); - } - - [Fact] - public void Convert_DuplicateElementIds_Throws() - { - // A model built in memory numbers elements per storey, so two storeys both start at 1. - var model = IfcModel.Create("Duplicates"); - model.AddMesh("A", Mm(Triangle), [0, 1, 2]); - model.AddMesh("B", Mm(Triangle), [0, 1, 2], storey: model.AddStorey("Level 2", 3000)); - - var ex = Assert.Throws(() => IfcViewerAdapter.Convert(model)); - Assert.Contains("used twice", ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void Convert_InvalidMesh_IsReportedNotDrawn() - { - var model = IfcModel.Create("Invalid"); - var bad = model.AddMesh("Bad", Mm(Triangle), [0, 1, 7]); - var empty = model.AddMesh("Empty", Mm(Triangle), []); - - var conversion = IfcViewerAdapter.Convert(model); - - Assert.Empty(conversion.Document.Meshes); - Assert.Equal( - [ - new IfcViewerElementWithoutGeometry(bad.Id, null, IfcViewerGeometryIssue.InvalidMesh), - new IfcViewerElementWithoutGeometry(empty.Id, null, IfcViewerGeometryIssue.EmptyMesh), - ], - conversion.ElementsWithoutGeometry); - } - - [Fact] - public void Convert_WorkSchedule_BecomesTimeLinerWithAttachedElements() - { - var model = IfcModel.Create("Schedule", IfcSchema.Ifc4); - var wall = model.AddMesh("Wall", Mm(Triangle), [0, 1, 2], ifcType: "IFCWALL"); - var schedule = model.AddWorkSchedule("Build"); - var start = new DateTime(2026, 3, 1, 8, 0, 0, DateTimeKind.Utc); - schedule.AddTask("Erect walls", "T1", start, start.AddDays(9)).AssignElement(wall); - schedule.AddTask("Undated", "T2"); - var opened = SaveAndOpen(model); - - var conversion = IfcViewerAdapter.Convert(opened); - - var timeLiner = conversion.Document.TimeLiner; - Assert.NotNull(timeLiner); - Assert.Equal("Build", timeLiner.Name); - var task = Assert.Single(timeLiner.Tasks); - Assert.Equal("Erect walls", task.Name); - Assert.Equal(new DateTimeOffset(start), task.PlannedStart); - Assert.Equal(new DateTimeOffset(start.AddDays(9)), task.PlannedEnd); - Assert.Equal([Assert.Single(opened.AllElements).Id], task.AttachedElementIds); - Assert.Equal(1, conversion.SkippedTasks); - } - - [IfcFileFact] - public void Convert_ConfiguredIfcFile_ConvertsEveryElementAndExports() - { - var model = IfcModel.Open(Environment.GetEnvironmentVariable(IfcFileFactAttribute.Variable)!); - - var conversion = IfcViewerAdapter.Convert(model); - - Assert.Equal( - model.AllElements.Count(e => !Regex.IsMatch(e.IfcType, "(TYPE|STYLE|PROPERTIES)$", RegexOptions.IgnoreCase)), - conversion.ElementCount); - Assert.Equal(conversion.ElementsWithGeometry, conversion.OwnMeshes); - Assert.All(conversion.Document.Elements, e => Assert.False(string.IsNullOrEmpty(e.SourceId))); - Assert.NotEmpty(HtmlViewerExporter.ExportToString(conversion.Document)); - } - - private static IfcModel OpenText(string ifc) - { - string path = Path.Combine(Path.GetTempPath(), $"ioviewer_ifc_{Guid.NewGuid():N}.ifc"); - try - { - File.WriteAllText(path, ifc); - return IfcModel.Open(path); - } - finally - { - File.Delete(path); - } - } - - // Models are saved as IFC2x3 (faceted breps) unless a test needs IFC4: IfcIO does not read back the - // IfcTriangulatedFaceSet it writes for IFC4. - private static IfcModel SaveAndOpen(IfcModel model) - { - string path = Path.Combine(Path.GetTempPath(), $"ioviewer_ifc_{Guid.NewGuid():N}.ifc"); - try - { - model.Save(path); - return IfcModel.Open(path); - } - finally - { - File.Delete(path); - } - } - - private static IfcVector3[] Mm(Vector3d[] points) => [.. points.Select(p => new IfcVector3(p.X, p.Y, p.Z))]; - - private static ViewerMesh MeshOf(ViewerDocument doc, long elementId) => Assert.Single(doc.Meshes, m => m.ElementId == elementId); - - private static Vector3d[] Offset(Vector3d origin, double metres) => - [.. Triangle.Select(p => (p + origin) * metres)]; - - private static void AssertPoints(Vector3d[] expected, IReadOnlyList actual) - { - Assert.Equal(expected.Length, actual.Count); - for (int i = 0; i < expected.Length; i++) - { - // 1e-6 m: far tighter than float precision at 400 km from the origin, so doubles must be kept. - Assert.True(Vector3d.Distance(expected[i], actual[i]) <= 1e-6, $"Vertex {i}: expected {expected[i]}, actual {actual[i]}"); - } - } -} - -/// Runs only when names an IFC file; the repository ships none. -public sealed class IfcFileFactAttribute : FactAttribute -{ - public const string Variable = "IOVIEWER_IFC_FILE"; - - public IfcFileFactAttribute() - { - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Variable))) - { - Skip = $"Set {Variable} to an IFC file to run this test."; - } - } -} From 2855bdd1110499ae80bcd06c86f6a1b62430fe01 Mon Sep 17 00:00:00 2001 From: mahach666 <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 15:06:40 +0300 Subject: [PATCH 07/19] Build the viewer on the IOScene scene model Reference IOScene [0.1.0-preview.1, 0.2.0-0) and drop the Viewer* model classes and math types. ViewerDocument now holds a SceneDocument, the section planes and box, and the model keys hidden at start; Title reads and writes the scene title. HtmlViewerExporter gains Save, Export and ExportToString overloads that take a SceneDocument. The payload writer reads the scene and keeps the page payload of the same content: a geometry placed once is baked on the global grid as own meshes were, a geometry placed several times is written once and placed by matrices. The instance colour and the material colour carry the only opacity. Instances of lines, points or an unknown topology are skipped and counted in META "skipped", and the CLI warns about them. GLB and OBJ imports produce one model with elements, geometries, instances and materials that pass Validate(). JsonViewerExporter writes viewer JSON format 2 with the scene encoded by SceneJson.CreateOptions(). JsonViewerImporter reads viewer JSON, scene files and IOViewer 0.1/0.2 snapshots; the 0.2.0 classes stay internal in IOViewer.Legacy and one mapper turns a snapshot into a scene, with stand-in elements for meshes without one. --- Directory.Packages.props | 2 + src/IOViewer.Cli/Program.cs | 16 +- src/IOViewer/Exporters/GeometryQuantizer.cs | 2 +- src/IOViewer/Exporters/HtmlViewerExporter.cs | 15 + src/IOViewer/Exporters/JsonViewerExporter.cs | 63 +- src/IOViewer/Exporters/ViewerPayloadWriter.cs | 300 +++++---- src/IOViewer/IOViewer.csproj | 6 + src/IOViewer/Importers/GlbViewerImporter.cs | 51 +- src/IOViewer/Importers/JsonViewerImporter.cs | 99 ++- src/IOViewer/Importers/ObjViewerImporter.cs | 33 +- src/IOViewer/Legacy/LegacyModel.cs | 202 +++++++ src/IOViewer/Legacy/LegacySnapshot.cs | 359 +++++++++++ src/IOViewer/Model/MathTypes.cs | 153 ----- src/IOViewer/Model/ViewerClash.cs | 82 --- src/IOViewer/Model/ViewerDocument.cs | 127 +--- src/IOViewer/Model/ViewerElement.cs | 49 -- src/IOViewer/Model/ViewerGeometry.cs | 33 - src/IOViewer/Model/ViewerGridSystem.cs | 34 -- src/IOViewer/Model/ViewerMaterial.cs | 27 - src/IOViewer/Model/ViewerMesh.cs | 42 -- src/IOViewer/Model/ViewerSection.cs | 2 + src/IOViewer/Model/ViewerTimeLiner.cs | 99 --- src/IOViewer/Model/ViewerViewpoint.cs | 39 -- .../IOViewer.Tests/GlbSharedGeometryTests.cs | 21 +- .../IOViewer.Tests/HtmlViewerExporterTests.cs | 568 ++++++++++-------- tests/IOViewer.Tests/JsonAndImportTests.cs | 241 +++++--- tests/IOViewer.Tests/LegacyFixtureTests.cs | 223 ++++++- .../ModelAndCliIntegrationTests.cs | 109 ++-- .../SharedGeometryModelTests.cs | 110 ---- tests/IOViewer.Tests/TestScenes.cs | 51 ++ 30 files changed, 1759 insertions(+), 1399 deletions(-) create mode 100644 src/IOViewer/Legacy/LegacyModel.cs create mode 100644 src/IOViewer/Legacy/LegacySnapshot.cs delete mode 100644 src/IOViewer/Model/MathTypes.cs delete mode 100644 src/IOViewer/Model/ViewerClash.cs delete mode 100644 src/IOViewer/Model/ViewerElement.cs delete mode 100644 src/IOViewer/Model/ViewerGeometry.cs delete mode 100644 src/IOViewer/Model/ViewerGridSystem.cs delete mode 100644 src/IOViewer/Model/ViewerMaterial.cs delete mode 100644 src/IOViewer/Model/ViewerMesh.cs delete mode 100644 src/IOViewer/Model/ViewerTimeLiner.cs delete mode 100644 src/IOViewer/Model/ViewerViewpoint.cs delete mode 100644 tests/IOViewer.Tests/SharedGeometryModelTests.cs create mode 100644 tests/IOViewer.Tests/TestScenes.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index eb93399..6b6e989 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,6 +3,8 @@ true + + diff --git a/src/IOViewer.Cli/Program.cs b/src/IOViewer.Cli/Program.cs index cf3cfab..29d9984 100644 --- a/src/IOViewer.Cli/Program.cs +++ b/src/IOViewer.Cli/Program.cs @@ -14,8 +14,9 @@ internal static class Program ioviewer --help | --version Generates standalone interactive 3D WebGL / HTML BIM & CAD model viewers - from 3D models (glTF/GLB, Wavefront OBJ, JSON snapshots). The HTML file has - no external dependencies and works offline. + from 3D models (glTF/GLB, Wavefront OBJ, JSON). The HTML file has no + external dependencies and works offline. JSON input is viewer JSON, an + IOScene scene file or an IOViewer 0.1/0.2 snapshot. Arguments: Path to input 3D model (.glb, .obj, or .json). @@ -30,7 +31,7 @@ no external dependencies and works offline. --no-properties Exclude element properties to reduce the file size. --no-grids Exclude spatial grid lines. --no-timeliner Exclude 4D TimeLiner schedule simulation. - --json Export structured model snapshot to JSON. + --json Export the document as viewer JSON. --serve [port] Start local HTTP preview server and open in default browser. -h, --help Show this help message and exit. -v, --version Show version information and exit. @@ -194,7 +195,7 @@ public static int Main(string[] args) if (outputPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { JsonViewerExporter.Save(doc, outputPath); - Console.WriteLine($"Exported JSON snapshot to '{outputPath}' ({doc.Meshes.Count} meshes, {doc.Elements.Count} elements) in {sw.ElapsedMilliseconds} ms."); + Console.WriteLine($"Exported JSON snapshot to '{outputPath}' ({doc.Scene.Instances.Count} meshes, {doc.Scene.Elements.Count} elements) in {sw.ElapsedMilliseconds} ms."); } else { @@ -208,7 +209,12 @@ public static int Main(string[] args) return 3; } var fi = new FileInfo(outputPath); - Console.WriteLine($"Exported standalone 3D HTML viewer to '{outputPath}' ({fi.Length / 1024:N0} KB, {doc.Meshes.Count} meshes) in {sw.ElapsedMilliseconds} ms."); + Console.WriteLine($"Exported standalone 3D HTML viewer to '{outputPath}' ({fi.Length / 1024:N0} KB, {doc.Scene.Instances.Count} meshes) in {sw.ElapsedMilliseconds} ms."); + int undrawn = ViewerPayloadWriter.UndrawnInstances(doc.Scene); + if (undrawn > 0) + { + Console.Error.WriteLine($"Warning: {undrawn} meshes of lines, points or an unknown kind are not shown; the viewer draws triangles only."); + } } if (!string.IsNullOrEmpty(explicitJsonPath)) diff --git a/src/IOViewer/Exporters/GeometryQuantizer.cs b/src/IOViewer/Exporters/GeometryQuantizer.cs index 74de381..609c232 100644 --- a/src/IOViewer/Exporters/GeometryQuantizer.cs +++ b/src/IOViewer/Exporters/GeometryQuantizer.cs @@ -1,4 +1,4 @@ -using IOViewer.Model; +using IOScene; namespace IOViewer.Exporters; diff --git a/src/IOViewer/Exporters/HtmlViewerExporter.cs b/src/IOViewer/Exporters/HtmlViewerExporter.cs index 79e7368..450334e 100644 --- a/src/IOViewer/Exporters/HtmlViewerExporter.cs +++ b/src/IOViewer/Exporters/HtmlViewerExporter.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.RegularExpressions; +using IOScene; using IOViewer.Model; namespace IOViewer.Exporters; @@ -9,9 +10,22 @@ namespace IOViewer.Exporters; /// public static class HtmlViewerExporter { + /// Writes the viewer page of a scene, with no section and every model shown, to a file. + public static void Save(SceneDocument scene, string filePath, HtmlViewerOptions? options = null) => + Save(new ViewerDocument(scene), filePath, options); + + /// Writes the viewer page of a scene, with no section and every model shown, to a stream. + public static void Export(SceneDocument scene, Stream stream, HtmlViewerOptions? options = null) => + Export(new ViewerDocument(scene), stream, options); + + /// Returns the viewer page of a scene, with no section and every model shown. + public static string ExportToString(SceneDocument scene, HtmlViewerOptions? options = null) => + ExportToString(new ViewerDocument(scene), options); + public static void Save(ViewerDocument doc, string filePath, HtmlViewerOptions? options = null) { ArgumentNullException.ThrowIfNull(doc); + ArgumentNullException.ThrowIfNull(doc.Scene); ArgumentNullException.ThrowIfNull(filePath); // Validated before the file is opened, so invalid data never truncates an existing page. @@ -23,6 +37,7 @@ public static void Save(ViewerDocument doc, string filePath, HtmlViewerOptions? public static void Export(ViewerDocument doc, Stream stream, HtmlViewerOptions? options = null) { ArgumentNullException.ThrowIfNull(doc); + ArgumentNullException.ThrowIfNull(doc.Scene); ArgumentNullException.ThrowIfNull(stream); Write(ViewerPayloadWriter.Create(doc, options ?? new HtmlViewerOptions()), stream); diff --git a/src/IOViewer/Exporters/JsonViewerExporter.cs b/src/IOViewer/Exporters/JsonViewerExporter.cs index c9cb6db..078e74f 100644 --- a/src/IOViewer/Exporters/JsonViewerExporter.cs +++ b/src/IOViewer/Exporters/JsonViewerExporter.cs @@ -1,27 +1,19 @@ -using System.Text.Encodings.Web; using System.Text.Json; +using IOScene; using IOViewer.Model; namespace IOViewer.Exporters; /// -/// Serializes a ViewerDocument snapshot into structured JSON. +/// Writes a as viewer JSON: { "Kind": "ioviewer", "Format": 2, "Scene": {...}, +/// "SectionPlanes", "SectionBox", "HiddenModelKeys" }, with the scene in the encoding of scene JSON. /// +/// A scene alone is written as a scene file with . public static class JsonViewerExporter { - private static readonly JsonSerializerOptions DefaultOptions = new() - { - WriteIndented = true, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals - }; + private static readonly JsonSerializerOptions DefaultOptions = Options(indented: true); - private static readonly JsonSerializerOptions CompactOptions = new() - { - WriteIndented = false, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals - }; + private static readonly JsonSerializerOptions CompactOptions = Options(indented: false); public static void Save(ViewerDocument doc, string filePath, bool indented = true) { @@ -37,13 +29,48 @@ public static void Export(ViewerDocument doc, Stream stream, bool indented = tru ArgumentNullException.ThrowIfNull(doc); ArgumentNullException.ThrowIfNull(stream); - var options = indented ? DefaultOptions : CompactOptions; - JsonSerializer.Serialize(stream, doc, options); + JsonSerializer.Serialize(stream, ViewerJsonFile.From(doc), indented ? DefaultOptions : CompactOptions); } public static string ExportToString(ViewerDocument doc, bool indented = true) { - var options = indented ? DefaultOptions : CompactOptions; - return JsonSerializer.Serialize(doc, options); + ArgumentNullException.ThrowIfNull(doc); + return JsonSerializer.Serialize(ViewerJsonFile.From(doc), indented ? DefaultOptions : CompactOptions); } + + private static JsonSerializerOptions Options(bool indented) + { + var options = SceneJson.CreateOptions(); + options.WriteIndented = indented; + return options; + } +} + +/// The root of a viewer JSON file, in the order the file lists its members. +internal sealed class ViewerJsonFile +{ + public const string FileKind = "ioviewer"; + public const int CurrentFormat = 2; + + public string Kind { get; set; } = FileKind; + public int Format { get; set; } = CurrentFormat; + public SceneDocument Scene { get; set; } = new(); + public List SectionPlanes { get; set; } = []; + public ViewerSectionBox? SectionBox { get; set; } + public List HiddenModelKeys { get; set; } = []; + + public static ViewerJsonFile From(ViewerDocument doc) => new() + { + Scene = doc.Scene, + SectionPlanes = doc.SectionPlanes, + SectionBox = doc.SectionBox, + HiddenModelKeys = doc.HiddenModelKeys, + }; + + public ViewerDocument ToDocument() => new(Scene) + { + SectionPlanes = SectionPlanes, + SectionBox = SectionBox, + HiddenModelKeys = HiddenModelKeys, + }; } diff --git a/src/IOViewer/Exporters/ViewerPayloadWriter.cs b/src/IOViewer/Exporters/ViewerPayloadWriter.cs index 6c2d305..75551e0 100644 --- a/src/IOViewer/Exporters/ViewerPayloadWriter.cs +++ b/src/IOViewer/Exporters/ViewerPayloadWriter.cs @@ -1,22 +1,31 @@ using System.Globalization; using System.Runtime.InteropServices; using System.Text.Json; +using IOScene; using IOViewer.Model; namespace IOViewer.Exporters; /// -/// Builds the META, BIN and PROPS blocks of the HTML viewer (format 3) from a . +/// Builds the META, BIN and PROPS blocks of the HTML viewer (format 3) from the scene of a . /// Geometry positions are written as u16 steps on the grid (with META q) when /// they span at most 65535 steps, else as f32; normals as octahedral i16 pairs. /// All input is validated and the binary blob is assembled by , before anything is written. /// +/// +/// A geometry that one instance places is baked into scene coordinates on the global grid, and translated twins of +/// such geometries share one written geometry; a geometry that several instances place is written once in local +/// coordinates and placed by each instance's matrix. Only triangle geometries are drawn: instances of lines, points +/// or a topology this version does not know are skipped and counted in META skipped. The first model of the +/// scene is the host; the elements of the other models carry a link, and those models are listed as linked files. +/// internal sealed class ViewerPayloadWriter { private const int FlushThreshold = 1 << 16; private static readonly RgbaColor DefaultColor = new(200, 200, 200); - private readonly ViewerDocument _doc; + private readonly SceneDocument _scene; + private readonly HashSet _hiddenModels; private readonly HtmlViewerOptions _options; private readonly BinaryBlobBuilder _blob = new(); private readonly List _geometries = []; @@ -24,30 +33,28 @@ internal sealed class ViewerPayloadWriter private readonly Dictionary> _ownTwinsByHash = []; private readonly List _materials = []; private readonly Dictionary _materialIndex = []; - private readonly Dictionary _materialsById = []; + private readonly Dictionary _materialsById = []; + private readonly Dictionary _textureIndex = []; private readonly List _instanceGeometry = []; private readonly List _instanceMaterial = []; private readonly List _instanceElement = []; private readonly List _instanceMatrix = []; - // The META elements: the document's, or, when it has none, stand-ins for the written meshes (0.1.0 listed meshes), - // so documents built without elements can still be listed, selected, hidden and isolated: one per mesh ElementId, - // grouping its meshes as 4D tasks and clashes refer to them, and one per mesh without an ElementId (written with - // "mesh": true, as its id is a mesh id that tasks and clashes must not match). - private readonly List _elements = []; - private readonly HashSet _meshStandIns = []; private BoundingBox3d _bounds; private Vector3d _rtcOffset; + private int _drawable; + private int _skipped; private int _writtenMeshes; private InstanceViews _instances; private ViewerPayloadWriter(ViewerDocument doc, HtmlViewerOptions options) { - _doc = doc; + _scene = doc.Scene; + _hiddenModels = [.. doc.HiddenModelKeys]; _options = options; } - /// Validates and lays out all geometry and instance arrays. - /// The document references missing geometry or has invalid indices. + /// Validates the scene of and lays out all geometry and instance arrays. + /// The scene references missing geometry or has invalid indices. public static ViewerPayloadWriter Create(ViewerDocument doc, HtmlViewerOptions options) { var writer = new ViewerPayloadWriter(doc, options); @@ -55,68 +62,74 @@ public static ViewerPayloadWriter Create(ViewerDocument doc, HtmlViewerOptions o return writer; } + /// Counts the instances the viewer does not draw because their geometry is not made of triangles. + public static int UndrawnInstances(SceneDocument scene) + { + var topology = new Dictionary(); + foreach (var geometry in scene.Geometries) topology.TryAdd(geometry.Id, geometry.Topology); + return scene.Instances.Count(i => topology.TryGetValue(i.GeometryId, out var t) && t != SceneTopology.Triangles); + } + private void Build() { - var shared = new Dictionary(); - foreach (var geometry in _doc.Geometries) + var geometries = new Dictionary(); + foreach (var geometry in _scene.Geometries) { - if (!shared.TryAdd(geometry.Id, geometry)) + if (!geometries.TryAdd(geometry.Id, geometry)) { throw new InvalidDataException($"Geometry id {geometry.Id} is used by more than one geometry."); } - CheckIndices(geometry.Indices, geometry.Vertices.Count, $"Geometry {geometry.Id}"); + if (geometry.Topology == SceneTopology.Triangles) CheckIndices(geometry.Indices, geometry.Vertices.Count, $"Geometry {geometry.Id}"); } - foreach (var mesh in _doc.Meshes) + // Instances per geometry, and the largest axis length of any of them: grid steps in local units grow by it. + var uses = new Dictionary(); + var maxScale = new Dictionary(); + foreach (var instance in _scene.Instances) { - if (mesh.GeometryId is int id) - { - if (!shared.ContainsKey(id)) - { - throw new InvalidDataException($"Mesh {mesh.Id} references geometry {id}, which is not in the document."); - } - if (mesh.Vertices.Count > 0) - { - throw new InvalidDataException($"Mesh {mesh.Id} has both geometry {id} and its own vertices."); - } - } - else + if (!geometries.ContainsKey(instance.GeometryId)) { - CheckIndices(mesh.Indices, mesh.Vertices.Count, $"Mesh {mesh.Id}"); + throw new InvalidDataException( + $"An instance of element {instance.ElementId} references geometry {instance.GeometryId}, which is not in the document."); } + uses[instance.GeometryId] = uses.GetValueOrDefault(instance.GeometryId) + 1; + if (instance.Transform is not { } t) continue; + double scale = Math.Max(t.XAxis.Length, Math.Max(t.YAxis.Length, t.ZAxis.Length)); + maxScale[instance.GeometryId] = Math.Max(maxScale.GetValueOrDefault(instance.GeometryId), scale); } - foreach (var material in _doc.Materials) _materialsById.TryAdd(material.Id, material); - bool meshElements = _doc.Elements.Count == 0; - _elements.AddRange(_doc.Elements); + foreach (var material in _scene.Materials) _materialsById.TryAdd(material.Id, material); + for (int i = 0; i < _scene.Textures.Count; i++) _textureIndex.TryAdd(_scene.Textures[i].Id, i); var elementIndex = new Dictionary(); - for (int i = 0; i < _elements.Count; i++) elementIndex.TryAdd(_elements[i].Id, i); + for (int i = 0; i < _scene.Elements.Count; i++) elementIndex.TryAdd(_scene.Elements[i].Id, i); - _bounds = _doc.ComputeBoundingBox(); + _bounds = _scene.ComputeBounds(); _rtcOffset = _bounds.IsEmpty ? Vector3d.Zero : _bounds.Center; - // Largest axis length of any instance per shared geometry: grid steps in local units grow by it in the world. - var maxScale = new Dictionary(); - foreach (var mesh in _doc.Meshes) - { - if (mesh.GeometryId is not int id || mesh.Transform is not { } t) continue; - double scale = Math.Max(t.XAxis.Length, Math.Max(t.YAxis.Length, t.ZAxis.Length)); - maxScale[id] = Math.Max(maxScale.GetValueOrDefault(id), scale); - } - var sharedOutput = new Dictionary(); long totalVertices = 0; - foreach (var mesh in _doc.Meshes) + bool full = false; + foreach (var instance in _scene.Instances) { - var geometry = mesh.GeometryId is int id ? shared[id] : null; - int vertexCount = geometry?.Vertices.Count ?? mesh.Vertices.Count; - if (totalVertices + vertexCount > _options.MaxMeshVertices) break; - totalVertices += vertexCount; + var geometry = geometries[instance.GeometryId]; + if (geometry.Topology != SceneTopology.Triangles) + { + _skipped++; + continue; + } + _drawable++; + // Instances are written in order while they fit; the rest are left out. + if (full || totalVertices + geometry.Vertices.Count > _options.MaxMeshVertices) + { + full = true; + continue; + } + totalVertices += geometry.Vertices.Count; - var transform = mesh.Transform ?? Transform3d.Identity; + var transform = instance.Transform ?? Transform3d.Identity; int geometryIndex; Vector3d translation; // in RTC - if (geometry is not null) + if (uses[geometry.Id] > 1) { if (!sharedOutput.TryGetValue(geometry.Id, out var written)) { @@ -128,29 +141,13 @@ private void Build() } else { - (geometryIndex, translation) = AddOwnGeometry(mesh); + (geometryIndex, translation) = AddOwnGeometry(geometry, instance.Transform); transform = Transform3d.Identity; } _instanceGeometry.Add((uint)geometryIndex); - _instanceMaterial.Add((uint)MaterialIndex(mesh)); - if (meshElements) - { - long? standInId = mesh.ElementId; - if (standInId is not long known || !elementIndex.TryGetValue(known, out int index)) - { - index = _elements.Count; - string fallback = standInId is long e ? $"Element {e}" : $"Mesh {mesh.Id}"; - _elements.Add(new ViewerElement { Id = standInId ?? mesh.Id, Name = string.IsNullOrEmpty(mesh.Name) ? fallback : mesh.Name }); - if (standInId is long grouped) elementIndex.Add(grouped, index); - else _meshStandIns.Add(index); - } - _instanceElement.Add(index); - } - else - { - _instanceElement.Add(mesh.ElementId is long e && elementIndex.TryGetValue(e, out int index) ? index : -1); - } + _instanceMaterial.Add((uint)MaterialIndex(instance)); + _instanceElement.Add(elementIndex.TryGetValue(instance.ElementId, out int index) ? index : -1); AddMatrix(transform, translation); _writtenMeshes++; } @@ -177,32 +174,38 @@ private static void CheckIndices(IReadOnlyList indices, int vertexCount, st // Written relative to Origin = Snap(local minimum), which each instance's double translation absorbs: quantized // (with q.o = 0) when it fits and no instance scales it up (which would scale the Step/2 error with it), else f32. // Geometry in world coordinates thus never reaches f32 or the viewer's GPU decode as large numbers. - private (int Index, Vector3d Origin) AddSharedGeometry(ViewerGeometry geometry, bool quantize) + private (int Index, Vector3d Origin) AddSharedGeometry(SceneGeometry geometry, bool quantize) { var vertices = geometry.Vertices.ToArray(); + var indices = TriangleIndices(geometry); var normals = geometry.Normals is { } n && n.Count == vertices.Length ? GeometryQuantizer.OctEncode(n) : null; var uvs = Uvs(geometry.UVs, vertices.Length); - var bounds = geometry.ComputeBoundingBox(); + var bounds = BoundingBox3d.Empty; + foreach (var v in vertices) bounds = bounds.Encapsulate(v); BoundingBox3d Local(Vector3d origin) => bounds.IsEmpty ? bounds : new BoundingBox3d(bounds.Min - origin, bounds.Max - origin); if (quantize && GeometryQuantizer.TryQuantize(vertices, out var origin, out var q)) { - return (AddGeometry(q, null, Vector3d.Zero, geometry.Indices, normals, uvs, Local(origin)), origin); + return (AddGeometry(q, null, Vector3d.Zero, indices, normals, uvs, Local(origin)), origin); } var min = bounds.IsEmpty ? Vector3d.Zero : bounds.Min; origin = new Vector3d(GeometryQuantizer.Snap(min.X), GeometryQuantizer.Snap(min.Y), GeometryQuantizer.Snap(min.Z)); - return (AddGeometry(null, Floats(vertices, origin), null, geometry.Indices, normals, uvs, Local(origin)), origin); + return (AddGeometry(null, Floats(vertices, origin), null, indices, normals, uvs, Local(origin)), origin); } - // Returns the geometry of a mesh with its own vertices and the instance translation (RTC). + // Empty indices mean non-indexed: every three vertices are a triangle. + private static IReadOnlyList TriangleIndices(SceneGeometry geometry) => + geometry.Indices.Count > 0 ? geometry.Indices : [.. Enumerable.Range(0, geometry.Vertices.Count - geometry.Vertices.Count % 3)]; + + // Returns the geometry of a geometry placed by one instance (an "own mesh") and the instance translation (RTC). // A translated twin of an earlier own mesh (format 2's dedupe: equal float32 positions relative to the exact minimum // corner, equal indices, normals and UVs) reuses that geometry with an exact, unsnapped translation; its vertices // are then within Step/2 plus float32 rounding of the truth, but no longer bit-identical to coinciding vertices of // other meshes. Otherwise the mesh gets its own geometry: quantized, the translation is the snapped minimum corner, // a point of the global RTC grid, and q has origin 0, so vertices that coincide in the world decode to the same // grid point in every mesh; over 64 m, the vertices are f32 relative to the exact minimum corner, as in format 2. - private (int Index, Vector3d Translation) AddOwnGeometry(ViewerMesh mesh) + private (int Index, Vector3d Translation) AddOwnGeometry(SceneGeometry mesh, Transform3d? transform) { - var shape = Bake(mesh); + var shape = Bake(mesh, transform); var hasher = new HashCode(); hasher.AddBytes(Bytes(shape.Relative)); foreach (int index in shape.Indices) hasher.Add(index); @@ -216,7 +219,7 @@ private static void CheckIndices(IReadOnlyList indices, int vertexCount, st } foreach (var twin in twins) { - var other = Bake(twin.Mesh); // rebaked on a hash match instead of keeping every mesh's floats in memory + var other = Bake(twin.Mesh, twin.Transform); // rebaked on a hash match instead of keeping every mesh's floats in memory if (SameBytes(shape.Relative, other.Relative) && shape.Indices.SequenceEqual(other.Indices) && SameBytes(shape.Normals, other.Normals) && SameBytes(shape.Uvs, other.Uvs)) { @@ -237,14 +240,14 @@ private static void CheckIndices(IReadOnlyList indices, int vertexCount, st var bounds = new BoundingBox3d(Vector3d.Zero, box.Max - shape.Min); // not empty: no vertices always quantize (geometry, translation) = (AddGeometry(null, shape.Relative, null, shape.Indices, shape.Normals, shape.Uvs, bounds), shape.Min); } - twins.Add(new OwnTwin(mesh, geometry, translation)); + twins.Add(new OwnTwin(mesh, transform, geometry, translation)); return (geometry, translation); } - // Bakes the mesh transform into its vertices (in RTC), normals and, for a mirroring transform, the winding. - private OwnShape Bake(ViewerMesh mesh) + // Bakes the instance transform into the vertices (in RTC), normals and, for a mirroring transform, the winding. + private OwnShape Bake(SceneGeometry mesh, Transform3d? placement) { - var transform = mesh.Transform ?? Transform3d.Identity; + var transform = placement ?? Transform3d.Identity; var vertices = mesh.Vertices; var rtc = new Vector3d[vertices.Count]; var box = BoundingBox3d.Empty; @@ -258,12 +261,12 @@ private OwnShape Bake(ViewerMesh mesh) short[]? normals = null; if (mesh.Normals is { } n && n.Count == vertices.Count) { - normals = GeometryQuantizer.OctEncode(mesh.Transform is null ? n : [.. n.Select(v => TransformNormal(transform, v))]); + normals = GeometryQuantizer.OctEncode(placement is null ? n : [.. n.Select(v => TransformNormal(transform, v))]); } // A mirroring transform (det < 0, as in TransformNormal) turns the triangles inside out unless their winding is // reversed too; the viewer only does that for shared geometry, from the instance matrix. - var indices = mesh.Indices; + var indices = TriangleIndices(mesh); if (Vector3d.Dot(transform.XAxis, Vector3d.Cross(transform.YAxis, transform.ZAxis)) < 0) { int[] flipped = [.. indices]; @@ -374,20 +377,14 @@ private bool Same(T[]? values, BlobView? view) where T : struct => private static ReadOnlySpan Bytes(T[]? values) where T : struct => values is null ? default : MemoryMarshal.AsBytes(values.AsSpan()); - private int MaterialIndex(ViewerMesh mesh) + // The instance colour overrides the material colour; the alpha of the colour drawn is the only opacity, and the + // texture is the material's. + private int MaterialIndex(SceneInstance instance) { - MaterialKey key; - if (mesh.Color is null && mesh.MaterialId is int id && _materialsById.TryGetValue(id, out var material)) - { - var c = material.Color; - key = new MaterialKey(c.R, c.G, c.B, Math.Clamp(c.Af * material.Opacity * mesh.Opacity, 0, 1), - mesh.TextureIndex ?? material.TextureIndex); - } - else - { - var c = mesh.Color ?? DefaultColor; - key = new MaterialKey(c.R, c.G, c.B, Math.Clamp(c.Af * mesh.Opacity, 0, 1), mesh.TextureIndex); - } + var material = instance.MaterialId is int id ? _materialsById.GetValueOrDefault(id) : null; + var c = instance.Color ?? material?.Color ?? DefaultColor; + int? texture = material?.TextureId is int t && _textureIndex.TryGetValue(t, out int i) ? i : null; + var key = new MaterialKey(c.R, c.G, c.B, c.Af, texture); if (!_materialIndex.TryGetValue(key, out int index)) { @@ -417,14 +414,18 @@ public void WriteProps(Stream output) w.WriteStartObject(); if (_options.IncludeProperties) { - foreach (var el in _doc.Elements) + var sets = new Dictionary(); + foreach (var set in _scene.PropertySets) sets.TryAdd(set.Id, set); + foreach (var el in _scene.Elements) { - if (el.Properties.Count == 0) continue; + if (el.PropertySetIds.Count == 0) continue; w.WriteStartObject(el.Id.ToString(CultureInfo.InvariantCulture)); - foreach (var group in el.Properties) + foreach (int id in el.PropertySetIds) { - w.WriteStartObject(group.Key); - foreach (var prop in group.Value) w.WriteString(prop.Key, prop.Value); + if (!sets.TryGetValue(id, out var set)) continue; + w.WriteStartObject(set.Name); + // The display string: a value of a kind this version does not know is shown as text. + foreach (var prop in set.Properties) w.WriteString(prop.Name, prop.Value.Text); w.WriteEndObject(); } w.WriteEndObject(); @@ -440,8 +441,9 @@ public void WriteMeta(Stream output) w.WriteStartObject(); w.WriteNumber("format", 3); w.WriteString("title", _options.Title); - w.WriteString("author", _doc.Author ?? "IOViewer"); - w.WriteString("createdAt", _doc.CreatedAt ?? DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)); + var host = _scene.Models.Count > 0 ? _scene.Models[0] : null; + w.WriteString("author", host?.Author ?? "IOViewer"); + w.WriteString("createdAt", host?.CreatedAt ?? DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)); WriteVector(w, "rtcOffset", _rtcOffset); WriteBox(w, "bbox", _bounds, _rtcOffset); @@ -450,11 +452,11 @@ public void WriteMeta(Stream output) w.WriteBoolean("initialWhiteMode", _options.InitialWhiteMode); w.WriteEndObject(); - if (_writtenMeshes < _doc.Meshes.Count) + if (_writtenMeshes < _drawable) { w.WriteStartObject("truncated"); w.WriteNumber("meshes", _writtenMeshes); - w.WriteNumber("totalMeshes", _doc.Meshes.Count); + w.WriteNumber("totalMeshes", _drawable); w.WriteEndObject(); } else @@ -462,6 +464,13 @@ public void WriteMeta(Stream output) w.WriteNull("truncated"); } + if (_skipped > 0) + { + w.WriteStartObject("skipped"); + w.WriteNumber("instances", _skipped); + w.WriteEndObject(); + } + w.WriteStartArray("materials"); foreach (var m in _materials) { @@ -479,11 +488,11 @@ public void WriteMeta(Stream output) w.WriteEndArray(); w.WriteStartArray("textures"); - foreach (var tex in _doc.Textures) + foreach (var tex in _scene.Textures) { w.WriteStartObject(); w.WriteString("mime", tex.MimeType); - w.WriteString("data", tex.Base64Data); + w.WriteString("data", Convert.ToBase64String(tex.Data)); w.WriteNumber("sx", tex.ScaleX); w.WriteNumber("sy", tex.ScaleY); w.WriteEndObject(); @@ -526,19 +535,27 @@ public void WriteMeta(Stream output) _instances.Matrix.WriteTo(w); w.WriteEndObject(); + // An element of a linked model carries the model's path, else its name, as the linked file list shows it. + var links = new Dictionary(); + for (int i = 1; i < _scene.Models.Count; i++) + { + var model = _scene.Models[i]; + links.TryAdd(model.Id, string.IsNullOrEmpty(model.Path) ? model.Name : model.Path); + } + var levels = new Dictionary(); + foreach (var level in _scene.Levels) levels.TryAdd(level.Id, level.Name); + w.WriteStartArray("elements"); - for (int i = 0; i < _elements.Count; i++) + foreach (var el in _scene.Elements) { - var el = _elements[i]; w.WriteStartObject(); w.WriteNumber("id", el.Id); - if (_meshStandIns.Contains(i)) w.WriteBoolean("mesh", true); w.WriteString("name", el.Name); w.WriteString("category", string.IsNullOrEmpty(el.Category) ? "General" : el.Category); w.WriteString("type", el.TypeName); - w.WriteString("level", el.Level); - if (string.IsNullOrEmpty(el.LinkName)) w.WriteNull("link"); - else w.WriteString("link", el.LinkName); + w.WriteString("level", el.LevelId is int level ? levels.GetValueOrDefault(level, string.Empty) : string.Empty); + if (host is null || el.ModelId == host.Id || links.GetValueOrDefault(el.ModelId) is not { Length: > 0 } link) w.WriteNull("link"); + else w.WriteString("link", link); if (!string.IsNullOrEmpty(el.SourceId)) w.WriteString("sourceId", el.SourceId); w.WriteEndObject(); FlushIfLarge(w); @@ -559,14 +576,14 @@ private void WriteViewpoints(Utf8JsonWriter w) w.WriteStartArray("viewpoints"); if (_options.IncludeViewpoints) { - foreach (var vp in _doc.Viewpoints) + foreach (var vp in _scene.Viewpoints) { w.WriteStartObject(); w.WriteString("name", vp.Name); w.WriteStartObject("camera"); WriteVector(w, "position", vp.Camera.Position - _rtcOffset); WriteVector(w, "target", vp.Camera.Target - _rtcOffset); - w.WriteString("projection", vp.Camera.Projection == ViewerProjection.Orthographic ? "orthographic" : "perspective"); + w.WriteString("projection", vp.Camera.Projection == SceneProjection.Orthographic ? "orthographic" : "perspective"); w.WriteEndObject(); w.WriteEndObject(); } @@ -577,9 +594,9 @@ private void WriteViewpoints(Utf8JsonWriter w) private void WriteGrids(Utf8JsonWriter w) { w.WriteStartArray("grids"); - if (_options.IncludeGrids && _doc.Grids is not null) + if (_options.IncludeGrids) { - foreach (var line in _doc.Grids.Lines) + foreach (var line in _scene.Grids.SelectMany(g => g.Lines)) { w.WriteStartObject(); w.WriteString("name", line.Name); @@ -593,21 +610,37 @@ private void WriteGrids(Utf8JsonWriter w) private void WriteTimeLiner(Utf8JsonWriter w) { - var schedule = _doc.TimeLiner; - if (!_options.IncludeTimeLiner || schedule is null || schedule.Tasks.Count == 0) + // The viewer shows one schedule: the tasks of every schedule, under the name of the first one with tasks. + var schedules = _scene.Schedules.Where(s => s.Tasks.Count > 0).ToList(); + if (!_options.IncludeTimeLiner || schedules.Count == 0) { w.WriteNull("timeliner"); return; } + var start = DateTimeOffset.MaxValue; + var end = DateTimeOffset.MinValue; + void Span(IEnumerable tasks) + { + foreach (var t in tasks) + { + if (t.PlannedStart < start) start = t.PlannedStart; + if (t.PlannedEnd > end) end = t.PlannedEnd; + Span(t.Children); + } + } + foreach (var s in schedules) Span(s.Tasks); + if (start == DateTimeOffset.MaxValue) start = DateTimeOffset.UtcNow; + if (end == DateTimeOffset.MinValue) end = DateTimeOffset.UtcNow.AddDays(30); + const string dateFormat = "yyyy-MM-ddTHH:mm:ssZ"; w.WriteStartObject("timeliner"); - w.WriteString("name", schedule.Name); - w.WriteString("start", schedule.GetOverallStart().ToString(dateFormat, CultureInfo.InvariantCulture)); - w.WriteString("end", schedule.GetOverallEnd().ToString(dateFormat, CultureInfo.InvariantCulture)); + w.WriteString("name", schedules[0].Name); + w.WriteString("start", start.ToString(dateFormat, CultureInfo.InvariantCulture)); + w.WriteString("end", end.ToString(dateFormat, CultureInfo.InvariantCulture)); w.WriteStartArray("tasks"); - void WriteTask(ViewerTimeLinerTask t) + void WriteTask(SceneTask t) { w.WriteStartObject(); w.WriteString("id", t.Id); @@ -618,12 +651,12 @@ void WriteTask(ViewerTimeLinerTask t) if (t.PlannedCost.HasValue) w.WriteNumber("cost", t.PlannedCost.Value); w.WriteNumber("progress", t.Progress); w.WriteStartArray("attachedElements"); - foreach (var id in t.AttachedElementIds) w.WriteNumberValue(id); + foreach (var id in t.ElementIds) w.WriteNumberValue(id); w.WriteEndArray(); w.WriteEndObject(); foreach (var child in t.Children) WriteTask(child); } - foreach (var task in schedule.Tasks) WriteTask(task); + foreach (var task in schedules.SelectMany(s => s.Tasks)) WriteTask(task); w.WriteEndArray(); w.WriteEndObject(); } @@ -631,12 +664,12 @@ void WriteTask(ViewerTimeLinerTask t) private void WriteLinkedFiles(Utf8JsonWriter w) { w.WriteStartArray("linkedFiles"); - foreach (var file in _doc.LinkedFiles) + foreach (var model in _scene.Models.Skip(1)) { w.WriteStartObject(); - w.WriteString("name", file.Name); - w.WriteString("path", file.Path); - w.WriteBoolean("visible", file.Visible); + w.WriteString("name", model.Name); + w.WriteString("path", model.Path ?? string.Empty); + w.WriteBoolean("visible", !_hiddenModels.Contains(model.Key)); w.WriteEndObject(); } w.WriteEndArray(); @@ -647,7 +680,7 @@ private void WriteClashTests(Utf8JsonWriter w) w.WriteStartArray("clashTests"); if (_options.IncludeClashes) { - foreach (var test in _doc.ClashTests) + foreach (var test in _scene.ClashTests) { w.WriteStartObject(); w.WriteString("name", test.Name); @@ -663,7 +696,8 @@ private void WriteClashTests(Utf8JsonWriter w) WriteNullableNumber(w, "element2Id", clash.Element2Id); WriteVector(w, "point", clash.Point - _rtcOffset); w.WriteNumber("distance", clash.Distance); - w.WriteString("status", clash.Status.ToString()); + // A status this version does not know is shown as Active. + w.WriteString("status", Enum.IsDefined(clash.Status) ? clash.Status.ToString() : nameof(SceneClashStatus.Active)); w.WriteEndObject(); } w.WriteEndArray(); @@ -719,7 +753,7 @@ private sealed record GeometryEntry(BlobView Positions, Vector3d? Origin, BlobVi private sealed record OwnShape(Vector3d[] Rtc, BoundingBox3d Box, Vector3d Min, float[] Relative, IReadOnlyList Indices, short[]? Normals, float[]? Uvs); // The first own mesh of a shape, the geometry written for it and its instance translation (RTC). - private readonly record struct OwnTwin(ViewerMesh Mesh, int Geometry, Vector3d Translation); + private readonly record struct OwnTwin(SceneGeometry Mesh, Transform3d? Transform, int Geometry, Vector3d Translation); private readonly record struct InstanceViews(BlobView Geometry, BlobView Material, BlobView Element, BlobView Matrix); } diff --git a/src/IOViewer/IOViewer.csproj b/src/IOViewer/IOViewer.csproj index adfa165..e301b35 100644 --- a/src/IOViewer/IOViewer.csproj +++ b/src/IOViewer/IOViewer.csproj @@ -5,8 +5,14 @@ IOViewer true $(DefaultItemExcludes);Viewer/node_modules/** + + $(NoWarn);NU5104 + + + + diff --git a/src/IOViewer/Importers/GlbViewerImporter.cs b/src/IOViewer/Importers/GlbViewerImporter.cs index 7d86c74..628e217 100644 --- a/src/IOViewer/Importers/GlbViewerImporter.cs +++ b/src/IOViewer/Importers/GlbViewerImporter.cs @@ -1,12 +1,15 @@ using System.Buffers.Binary; using System.Text; using System.Text.Json; +using IOScene; using IOViewer.Model; namespace IOViewer.Importers; /// -/// Imports standard glTF 2.0 Binary (.glb) files into a ViewerDocument. +/// Imports standard glTF 2.0 Binary (.glb) files into a ViewerDocument whose scene has one model: one geometry per +/// mesh primitive, one element per node that draws a mesh, and one instance per primitive of that node, with the +/// node's world transform converted from glTF's Y-up to the scene's Z-up. /// public static class GlbViewerImporter { @@ -18,12 +21,17 @@ public static ViewerDocument Load(string filePath) { ArgumentNullException.ThrowIfNull(filePath); using var fs = File.OpenRead(filePath); - return Load(fs, Path.GetFileNameWithoutExtension(filePath)); + return Load(fs, Path.GetFileNameWithoutExtension(filePath), Path.GetFileName(filePath), filePath); } public static ViewerDocument Load(Stream stream, string documentTitle = "Imported GLB Model") { ArgumentNullException.ThrowIfNull(stream); + return Load(stream, documentTitle, "model", null); + } + + private static ViewerDocument Load(Stream stream, string documentTitle, string modelKey, string? path) + { using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); @@ -65,7 +73,8 @@ public static ViewerDocument Load(Stream stream, string documentTitle = "Importe } } - var doc = new ViewerDocument(documentTitle); + var doc = new SceneDocument { Title = documentTitle }; + doc.Models.Add(new SceneModel { Id = 1, Key = modelKey, Name = documentTitle, Path = path, SourceFormat = "glb" }); using var jsonDoc = JsonDocument.Parse(jsonText); var root = jsonDoc.RootElement; @@ -75,20 +84,20 @@ public static ViewerDocument Load(Stream stream, string documentTitle = "Importe !root.TryGetProperty("bufferViews", out var bufferViewsEl) || binBuffer is null) { - return doc; + return new ViewerDocument(doc); } var bufferViews = bufferViewsEl.EnumerateArray().ToArray(); var accessors = accessorsEl.EnumerateArray().ToArray(); // Parse Materials - var materials = new List(); + var materials = new List(); if (root.TryGetProperty("materials", out var materialsEl)) { int matIdx = 0; foreach (var matEl in materialsEl.EnumerateArray()) { - var mat = new ViewerMaterial + var mat = new SceneMaterial { Id = matIdx++, Name = matEl.TryGetProperty("name", out var mn) ? mn.GetString() ?? "Material" : $"Material {matIdx}" @@ -104,7 +113,6 @@ public static ViewerDocument Load(Stream stream, string documentTitle = "Importe byte b = (byte)Math.Clamp((int)(arr[2].GetDouble() * 255), 0, 255); byte a = (byte)Math.Clamp((int)(arr[3].GetDouble() * 255), 0, 255); mat.Color = new RgbaColor(r, g, b, a); - mat.Opacity = arr[3].GetDouble(); } } materials.Add(mat); @@ -112,7 +120,7 @@ public static ViewerDocument Load(Stream stream, string documentTitle = "Importe } } - // Parse meshes lazily: one ViewerGeometry per (mesh, primitive), one ViewerMesh per node occurrence. + // Parse meshes lazily: one geometry per (mesh, primitive), one instance per primitive of a node occurrence. var meshElements = root.TryGetProperty("meshes", out var meshesEl) ? meshesEl.EnumerateArray().ToArray() : []; var geometryIds = new Dictionary<(int Mesh, int Primitive), int?>(); int meshCounter = 0; @@ -129,7 +137,7 @@ void Instantiate(int meshIndex, Transform3d world, string? nodeName) string meshName = meshEl.TryGetProperty("name", out var mn) ? mn.GetString() ?? $"Mesh {meshCounter + 1}" : $"Mesh {meshCounter + 1}"; if (!meshEl.TryGetProperty("primitives", out var primsEl)) return; - ViewerElement? element = null; + SceneElement? element = null; int primIndex = -1; foreach (var prim in primsEl.EnumerateArray()) { @@ -167,7 +175,7 @@ void Instantiate(int meshIndex, Transform3d world, string? nodeName) } geometryId = doc.Geometries.Count; - doc.Geometries.Add(new ViewerGeometry + doc.Geometries.Add(new SceneGeometry { Id = geometryId.Value, Vertices = positions, @@ -182,33 +190,27 @@ void Instantiate(int meshIndex, Transform3d world, string? nodeName) if (geometryId is null) continue; int? matId = null; - RgbaColor? col = null; if (prim.TryGetProperty("material", out var mProp)) { int mid = mProp.GetInt32(); - if (mid >= 0 && mid < materials.Count) - { - matId = mid; - col = materials[mid].Color; - } + if (mid >= 0 && mid < materials.Count) matId = mid; } if (element is null) { - element = new ViewerElement { Id = doc.Elements.Count + 1, Name = string.IsNullOrEmpty(nodeName) ? meshName : nodeName, Category = "GLB" }; + element = new SceneElement { Id = doc.Elements.Count + 1, ModelId = 1, Name = string.IsNullOrEmpty(nodeName) ? meshName : nodeName, Category = "GLB" }; doc.Elements.Add(element); } - element.MeshIndices.Add(doc.Meshes.Count); - doc.Meshes.Add(new ViewerMesh + ++meshCounter; + doc.Instances.Add(new SceneInstance { - Id = ++meshCounter, - Name = meshName, ElementId = element.Id, - GeometryId = geometryId, + GeometryId = geometryId.Value, Transform = world.Then(YupToZup), MaterialId = matId, - Color = col ?? new RgbaColor(200, 200, 200) + Color = matId is null ? new RgbaColor(200, 200, 200) : null, + Name = meshName, }); } } @@ -259,8 +261,7 @@ void Instantiate(int meshIndex, Transform3d world, string? nodeName) for (int i = 0; i < meshElements.Length; i++) Instantiate(i, Transform3d.Identity, null); } - doc.ComputeBoundingBox(); - return doc; + return new ViewerDocument(doc); } // glTF is Y-up; the viewer model is Z-up: (x, y, z) becomes (x, -z, y). diff --git a/src/IOViewer/Importers/JsonViewerImporter.cs b/src/IOViewer/Importers/JsonViewerImporter.cs index 7c541be..06203c2 100644 --- a/src/IOViewer/Importers/JsonViewerImporter.cs +++ b/src/IOViewer/Importers/JsonViewerImporter.cs @@ -1,18 +1,18 @@ +using System.Reflection; using System.Text.Json; +using IOScene; +using IOViewer.Exporters; using IOViewer.Model; namespace IOViewer.Importers; /// -/// Deserializes a ViewerDocument snapshot from JSON. +/// Reads JSON into a : viewer JSON ("Kind": "ioviewer", format 2), a scene file +/// ("Kind": "ioscene"), which gives a document with no sections, and a snapshot of IOViewer 0.1 or 0.2 (no +/// Kind), which is turned into a scene. /// public static class JsonViewerImporter { - private static readonly JsonSerializerOptions JsonOptions = new() - { - NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals - }; - public static ViewerDocument Load(string filePath) { ArgumentNullException.ThrowIfNull(filePath); @@ -20,11 +20,92 @@ public static ViewerDocument Load(string filePath) return Load(fs); } + /// + /// The JSON is damaged, of another kind, or of a format newer than this version reads. + /// public static ViewerDocument Load(Stream stream) { ArgumentNullException.ThrowIfNull(stream); - var doc = JsonSerializer.Deserialize(stream, JsonOptions); - if (doc is null) throw new InvalidDataException("Failed to deserialize ViewerDocument from JSON stream."); - return doc; + using var buffer = new MemoryStream(); + stream.CopyTo(buffer); + byte[] json = buffer.ToArray(); + if (json.AsSpan().StartsWith((ReadOnlySpan)[0xEF, 0xBB, 0xBF])) json = json[3..]; + + var (kind, format) = ReadRoot(json); + try + { + return kind switch + { + null => Legacy.LegacySnapshot.Read(json), + SceneDocument.DocumentKind => new ViewerDocument(SceneJson.Read(new MemoryStream(json, writable: false))), + ViewerJsonFile.FileKind => ReadViewerJson(json, format), + _ => throw new InvalidDataException( + $"Unknown JSON kind '{kind}': IOViewer reads 'ioviewer' and 'ioscene' files and IOViewer 0.1 and 0.2 snapshots."), + }; + } + catch (JsonException e) + { + throw new InvalidDataException($"Not a readable IOViewer JSON file: {e.Message}", e); + } + catch (TargetInvocationException e) when (e.InnerException is InvalidDataException rejected) + { + // Without dynamic code the serialiser calls the scene's Kind and Format setters through reflection. + throw new InvalidDataException(rejected.Message, e); + } + } + + private static ViewerDocument ReadViewerJson(byte[] json, int? format) + { + if (format != ViewerJsonFile.CurrentFormat) + { + throw new InvalidDataException(format > ViewerJsonFile.CurrentFormat + ? $"IOViewer JSON format {format} is newer than this version reads (format {ViewerJsonFile.CurrentFormat}); use a newer IOViewer." + : format is null + ? "Not a readable IOViewer JSON file: Format is missing." + : $"Not a readable IOViewer JSON file: Format is {format}, expected {ViewerJsonFile.CurrentFormat}."); + } + var file = JsonSerializer.Deserialize(json, SceneJson.CreateOptions()) + ?? throw new InvalidDataException("Not a readable IOViewer JSON file: the root is null."); + return file.ToDocument(); + } + + // Kind and Format of the root object, wherever they stand in it; a file without Kind is a 0.1 or 0.2 snapshot. + private static (string? Kind, int? Format) ReadRoot(byte[] json) + { + var reader = new Utf8JsonReader(json, new JsonReaderOptions { MaxDepth = 128 }); + string? kind = null; + int? format = null; + try + { + if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidDataException("Not a readable IOViewer JSON file: the root is not an object."); + } + while (reader.Read() && reader.TokenType == JsonTokenType.PropertyName) + { + bool isKind = reader.ValueTextEquals("Kind"u8); + bool isFormat = reader.ValueTextEquals("Format"u8); + reader.Read(); + if (isKind) + { + kind = reader.TokenType == JsonTokenType.String + ? reader.GetString() + : throw new InvalidDataException("Not a readable IOViewer JSON file: Kind is not a string."); + } + else if (isFormat && reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out int number)) + { + format = number; + } + else + { + reader.Skip(); + } + } + } + catch (JsonException e) + { + throw new InvalidDataException($"Not a readable IOViewer JSON file: {e.Message}", e); + } + return (kind, format); } } diff --git a/src/IOViewer/Importers/ObjViewerImporter.cs b/src/IOViewer/Importers/ObjViewerImporter.cs index e4b1ca0..78034fc 100644 --- a/src/IOViewer/Importers/ObjViewerImporter.cs +++ b/src/IOViewer/Importers/ObjViewerImporter.cs @@ -1,10 +1,13 @@ using System.Globalization; +using IOScene; using IOViewer.Model; namespace IOViewer.Importers; /// -/// Imports Wavefront OBJ models (.obj) into a ViewerDocument. +/// Imports Wavefront OBJ models (.obj) into a ViewerDocument whose scene has one model: each object or group +/// (o/g) becomes one element drawn by one geometry with the vertices its faces use, without changing +/// the up axis. Material (.mtl) files are not read. /// public static class ObjViewerImporter { @@ -12,12 +15,17 @@ public static ViewerDocument Load(string filePath) { ArgumentNullException.ThrowIfNull(filePath); using var fs = File.OpenRead(filePath); - return Load(fs, Path.GetFileNameWithoutExtension(filePath)); + return Load(fs, Path.GetFileNameWithoutExtension(filePath), Path.GetFileName(filePath), filePath); } public static ViewerDocument Load(Stream stream, string documentTitle = "Imported OBJ Model") { ArgumentNullException.ThrowIfNull(stream); + return Load(stream, documentTitle, "model", null); + } + + private static ViewerDocument Load(Stream stream, string documentTitle, string modelKey, string? path) + { using var reader = new StreamReader(stream, System.Text.Encoding.UTF8); @@ -25,7 +33,8 @@ public static ViewerDocument Load(Stream stream, string documentTitle = "Importe var normals = new List(); var meshIndices = new List(); var currentMeshName = "DefaultMesh"; - var doc = new ViewerDocument(documentTitle); + var doc = new SceneDocument { Title = documentTitle }; + doc.Models.Add(new SceneModel { Id = 1, Key = modelKey, Name = documentTitle, Path = path, SourceFormat = "obj" }); int meshCounter = 0; string? line; @@ -87,8 +96,7 @@ public static ViewerDocument Load(Stream stream, string documentTitle = "Importe AddMesh(); - doc.ComputeBoundingBox(); - return doc; + return new ViewerDocument(doc); // One mesh and one element per OBJ object or group, so the viewer can list, select and hide it. void AddMesh() @@ -118,18 +126,9 @@ void AddMesh() indices[i] = local; } - var element = new ViewerElement { Id = id, Name = currentMeshName, Category = "OBJ" }; - element.MeshIndices.Add(doc.Meshes.Count); - doc.Elements.Add(element); - doc.Meshes.Add(new ViewerMesh - { - Id = id, - Name = currentMeshName, - ElementId = id, - Vertices = meshVertices, - Indices = indices, - Color = new RgbaColor(180, 180, 180) - }); + doc.Elements.Add(new SceneElement { Id = id, ModelId = 1, Name = currentMeshName, Category = "OBJ" }); + doc.Geometries.Add(new SceneGeometry { Id = id, Vertices = meshVertices, Indices = indices }); + doc.Instances.Add(new SceneInstance { ElementId = id, GeometryId = id, Color = new RgbaColor(180, 180, 180), Name = currentMeshName }); meshIndices.Clear(); } } diff --git a/src/IOViewer/Legacy/LegacyModel.cs b/src/IOViewer/Legacy/LegacyModel.cs new file mode 100644 index 0000000..de0146b --- /dev/null +++ b/src/IOViewer/Legacy/LegacyModel.cs @@ -0,0 +1,202 @@ +using System.Text.Json.Serialization; + +// The JSON shape of IOViewer 0.1 and 0.2 snapshots: the 0.2.0 model classes, kept only to read old files. Members the +// mapper does not use (stored bounding boxes, MeshIndices) are left out, so the reader skips them. +namespace IOViewer.Legacy; + +internal readonly record struct Vector2d(double X, double Y); + +internal readonly record struct Vector3d(double X, double Y, double Z); + +internal readonly record struct Transform3d(Vector3d XAxis, Vector3d YAxis, Vector3d ZAxis, Vector3d Translation); + +internal readonly record struct RgbaColor(byte R, byte G, byte B, byte A = 255) +{ + public double Af => A / 255.0; +} + +internal sealed class ViewerDocument +{ + public string Title { get; set; } = "3D BIM Model"; + public string? SourcePath { get; set; } + public string? Author { get; set; } + public string? CreatedAt { get; set; } + public string? Application { get; set; } + public List Elements { get; set; } = []; + public List Meshes { get; set; } = []; + public List Geometries { get; set; } = []; + public List Materials { get; set; } = []; + public List Textures { get; set; } = []; + public ViewerGridSystem? Grids { get; set; } + public List Viewpoints { get; set; } = []; + public List SectionPlanes { get; set; } = []; + public ViewerSectionBox? SectionBox { get; set; } + public ViewerTimeLinerSchedule? TimeLiner { get; set; } + public List ClashTests { get; set; } = []; + public List LinkedFiles { get; set; } = []; +} + +internal sealed class ViewerElement +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Category { get; set; } = "General"; + public string TypeName { get; set; } = string.Empty; + public string Level { get; set; } = string.Empty; + public string? LinkName { get; set; } + + [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] + public Dictionary> Properties { get; } = new(StringComparer.OrdinalIgnoreCase); +} + +internal sealed class ViewerMesh +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public long? ElementId { get; set; } + public IReadOnlyList Vertices { get; set; } = []; + public IReadOnlyList Indices { get; set; } = []; + public IReadOnlyList? Normals { get; set; } + public IReadOnlyList? UVs { get; set; } + public int? GeometryId { get; set; } + public Transform3d? Transform { get; set; } + public int? MaterialId { get; set; } + public RgbaColor? Color { get; set; } + public double Opacity { get; set; } = 1.0; + public int? TextureIndex { get; set; } +} + +internal sealed class ViewerGeometry +{ + public int Id { get; set; } + public IReadOnlyList Vertices { get; set; } = []; + public IReadOnlyList Indices { get; set; } = []; + public IReadOnlyList? Normals { get; set; } + public IReadOnlyList? UVs { get; set; } +} + +internal sealed class ViewerMaterial +{ + public int Id { get; set; } + public string Name { get; set; } = "Default"; + public RgbaColor Color { get; set; } = new(200, 200, 200); + public double Opacity { get; set; } = 1.0; + public double Roughness { get; set; } = 0.5; + public double Metalness { get; set; } = 0.1; + public int? TextureIndex { get; set; } +} + +internal sealed class ViewerTexture +{ + public string MimeType { get; set; } = "image/png"; + public string Base64Data { get; set; } = string.Empty; + public double ScaleX { get; set; } = 1.0; + public double ScaleY { get; set; } = 1.0; +} + +internal sealed class ViewerGridSystem +{ + public string Name { get; set; } = "Grids"; + + [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] + public List Lines { get; } = []; +} + +internal sealed class ViewerGridLine +{ + public string Name { get; set; } = string.Empty; + public Vector3d Start { get; set; } + public Vector3d End { get; set; } + public double Elevation { get; set; } +} + +internal sealed class ViewerCamera +{ + public Vector3d Position { get; set; } = new(10, 10, 10); + public Vector3d Target { get; set; } + public Vector3d Up { get; set; } = new(0, 0, 1); + public double FieldOfView { get; set; } = 0.785; + public double AspectRatio { get; set; } = 1.6; + public double NearDistance { get; set; } = 0.1; + public double FarDistance { get; set; } = 1000.0; + public int Projection { get; set; } +} + +internal sealed class ViewerViewpoint +{ + public string Name { get; set; } = "Viewpoint"; + public ViewerCamera Camera { get; set; } = new(); +} + +internal sealed class ViewerSectionPlane +{ + public int Axis { get; set; } + public Vector3d Normal { get; set; } = new(1, 0, 0); + public double Offset { get; set; } + public bool Enabled { get; set; } = true; + public bool Inverted { get; set; } +} + +internal sealed class ViewerSectionBox +{ + public Vector3d Min { get; set; } = new(-10, -10, -10); + public Vector3d Max { get; set; } = new(10, 10, 10); + public bool Enabled { get; set; } = true; + public bool Inverted { get; set; } +} + +internal sealed class ViewerTimeLinerTask +{ + public string Id { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public int TaskType { get; set; } + public DateTimeOffset PlannedStart { get; set; } + public DateTimeOffset PlannedEnd { get; set; } + public DateTimeOffset? ActualStart { get; set; } + public DateTimeOffset? ActualEnd { get; set; } + public decimal? PlannedCost { get; set; } + public decimal? ActualCost { get; set; } + public double Progress { get; set; } + + [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] + public List AttachedElementIds { get; } = []; + + [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] + public List Children { get; } = []; +} + +internal sealed class ViewerTimeLinerSchedule +{ + public string Name { get; set; } = "TimeLiner Schedule"; + + [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] + public List Tasks { get; } = []; +} + +internal sealed class ViewerClashResult +{ + public string Id { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Element1Name { get; set; } = string.Empty; + public string Element2Name { get; set; } = string.Empty; + public long? Element1Id { get; set; } + public long? Element2Id { get; set; } + public Vector3d Point { get; set; } + public double Distance { get; set; } + public int Status { get; set; } = 1; +} + +internal sealed class ViewerClashTest +{ + public string Name { get; set; } = "Clash Test"; + + [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] + public List Clashes { get; } = []; +} + +internal sealed class ViewerLinkedFile +{ + public string Name { get; set; } = string.Empty; + public string Path { get; set; } = string.Empty; + public bool Visible { get; set; } = true; +} diff --git a/src/IOViewer/Legacy/LegacySnapshot.cs b/src/IOViewer/Legacy/LegacySnapshot.cs new file mode 100644 index 0000000..13ba10f --- /dev/null +++ b/src/IOViewer/Legacy/LegacySnapshot.cs @@ -0,0 +1,359 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using IOScene; +using S = IOScene; + +namespace IOViewer.Legacy; + +/// +/// Reads an IOViewer 0.1 or 0.2 JSON snapshot (a file without Kind) and turns it into a scene. +/// +/// +/// The mapping: the document metadata becomes the first model, each linked file and each other link name a model +/// (a linked file listed as not visible is hidden at start), level names a level table, property groups property sets +/// of text values, a mesh with its own vertices a geometry placed once, the product of the colour alpha and the +/// material and mesh opacities the instance colour alpha, a mesh texture a copy of the material that carries it, +/// the grid system, the TimeLiner and the clash tests one-item lists. A mesh without vertex indices draws nothing in +/// 0.2.0 and becomes a geometry of points, which the viewer does not draw either. Stored bounding boxes and +/// MeshIndices are ignored. A mesh without an element gets one (see ). +/// +internal static class LegacySnapshot +{ + private static readonly JsonSerializerOptions Options = new() + { + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals + }; + + private static readonly S.RgbaColor DefaultColor = new(200, 200, 200); + + public static Model.ViewerDocument Read(byte[] json) + { + var old = JsonSerializer.Deserialize(json, Options) + ?? throw new InvalidDataException("Not an IOViewer snapshot: the JSON root is null."); + return ToViewerDocument(old); + } + + /// Maps a snapshot to a document whose scene passes when the snapshot is consistent. + /// + /// Every instance of a scene belongs to an element, so a mesh without one gets a stand-in element: meshes that + /// share an ElementId that no element has get one element with that id, named after the first such mesh + /// (else Element {id}); a mesh without an ElementId gets its own element with the mesh id, named + /// after the mesh (else Mesh {id}). Stand-ins follow the elements in mesh order. In 0.2.0 a 4D task or + /// clash id meant an element or an ElementId, never a mesh id, so those keep their ids and a mesh stand-in + /// whose id is taken is renumbered above every id in the snapshot; the task and clash references then still name + /// what they named in 0.2.0. References to an id that names nothing are dropped. + /// + internal static Model.ViewerDocument ToViewerDocument(ViewerDocument old) + { + var scene = new SceneDocument { Title = old.Title ?? string.Empty }; + var result = new Model.ViewerDocument(scene) + { + SectionPlanes = [.. old.SectionPlanes.Select(p => new Model.ViewerSectionPlane + { + Axis = (Model.ViewerSectionAxis)p.Axis, + Normal = Map(p.Normal), + Offset = p.Offset, + Enabled = p.Enabled, + Inverted = p.Inverted, + })], + SectionBox = old.SectionBox is { } box ? new Model.ViewerSectionBox(Map(box.Min), Map(box.Max), box.Enabled, box.Inverted) : null, + }; + + // Models: the document, its linked files, then link names that match no linked file. + var keys = new HashSet(StringComparer.Ordinal); + string UniqueKey(string? path, string? fallback) + { + string key = FileName(path) is { Length: > 0 } name ? name : string.IsNullOrEmpty(fallback) ? "model" : fallback; + string unique = key; + for (int n = 2; !keys.Add(unique); n++) unique = FormattableString.Invariant($"{key}#{n}"); + return unique; + } + + scene.Models.Add(new SceneModel + { + Id = 1, + Key = UniqueKey(old.SourcePath, null), + Name = old.Title ?? string.Empty, + Path = Empty(old.SourcePath), + Application = old.Application, + Author = old.Author, + CreatedAt = old.CreatedAt, + }); + foreach (var file in old.LinkedFiles) + { + var model = new SceneModel { Id = scene.Models.Count + 1, Key = UniqueKey(file.Path, file.Name), Name = file.Name ?? string.Empty, Path = Empty(file.Path) }; + scene.Models.Add(model); + if (!file.Visible) result.HiddenModelKeys.Add(model.Key); + } + + var linkModels = new Dictionary(StringComparer.Ordinal); + int ModelOf(string? linkName) + { + if (string.IsNullOrEmpty(linkName)) return 1; + if (linkModels.TryGetValue(linkName, out int id)) return id; + // As the 0.2.0 viewer matched them: a link name is a linked file's name or path. + var model = scene.Models.Skip(1).FirstOrDefault(m => m.Name == linkName || m.Path == linkName); + if (model is null) + { + model = new SceneModel { Id = scene.Models.Count + 1, Key = UniqueKey(linkName, linkName), Name = linkName }; + scene.Models.Add(model); + } + linkModels.Add(linkName, model.Id); + return model.Id; + } + + var levels = new Dictionary<(int Model, string Name), int>(); + int? LevelOf(int modelId, string? name) + { + if (string.IsNullOrEmpty(name)) return null; + if (!levels.TryGetValue((modelId, name), out int id)) + { + id = scene.Levels.Count + 1; + scene.Levels.Add(new SceneLevel { Id = id, ModelId = modelId, Name = name }); + levels.Add((modelId, name), id); + } + return id; + } + + var elementIds = new HashSet(); + foreach (var source in old.Elements) + { + int modelId = ModelOf(source.LinkName); + var element = new SceneElement + { + Id = source.Id, + ModelId = modelId, + Name = source.Name ?? string.Empty, + Category = source.Category ?? string.Empty, + TypeName = source.TypeName ?? string.Empty, + LevelId = LevelOf(modelId, source.Level), + }; + foreach (var (group, values) in source.Properties) + { + var set = new ScenePropertySet { Id = scene.PropertySets.Count + 1, Name = group }; + foreach (var (name, value) in values) set.Properties.Add(new SceneProperty { Name = name, Value = SceneValue.FromText(value ?? string.Empty) }); + scene.PropertySets.Add(set); + element.PropertySetIds.Add(set.Id); + } + scene.Elements.Add(element); + elementIds.Add(element.Id); + } + + // Stand-in elements for meshes without one; ElementId groups keep their ids, mesh ids give way (see remarks). + var taken = new HashSet(elementIds); + foreach (var mesh in old.Meshes) + { + if (mesh.ElementId is long id) taken.Add(id); + } + long next = taken.Count == 0 ? 1 : taken.Max() + 1; + var groups = new HashSet(); + long ElementOf(ViewerMesh mesh) + { + if (mesh.ElementId is long id) + { + if (!elementIds.Contains(id) && groups.Add(id)) + { + scene.Elements.Add(new SceneElement { Id = id, ModelId = 1, Name = string.IsNullOrEmpty(mesh.Name) ? $"Element {id}" : mesh.Name }); + } + return id; + } + long own = mesh.Id; + if (!taken.Add(own)) + { + while (!taken.Add(next)) next++; + own = next; + } + scene.Elements.Add(new SceneElement { Id = own, ModelId = 1, Name = string.IsNullOrEmpty(mesh.Name) ? $"Mesh {mesh.Id}" : mesh.Name }); + return own; + } + + for (int i = 0; i < old.Textures.Count; i++) + { + var texture = old.Textures[i]; + byte[] data; + try + { + data = Convert.FromBase64String(texture.Base64Data ?? string.Empty); + } + catch (FormatException e) + { + throw new InvalidDataException($"Texture {i}: Base64Data is not base64.", e); + } + scene.Textures.Add(new SceneTexture { Id = i + 1, MimeType = texture.MimeType ?? "image/png", Data = data, ScaleX = texture.ScaleX, ScaleY = texture.ScaleY }); + } + int? TextureOf(int? index) => index is int i && (uint)i < (uint)old.Textures.Count ? i + 1 : null; + + // 0.2.0 drew a mesh with the first material of its id; the material opacity becomes the colour alpha. + var materials = new Dictionary(); + foreach (var material in old.Materials) + { + if (materials.ContainsKey(material.Id)) continue; + var mapped = new SceneMaterial + { + Id = material.Id, + Name = material.Name ?? string.Empty, + Color = Map(material.Color, material.Color.Af * material.Opacity), + Roughness = material.Roughness, + Metalness = material.Metalness, + TextureId = TextureOf(material.TextureIndex), + }; + scene.Materials.Add(mapped); + materials.Add(material.Id, (material, mapped)); + } + int nextMaterial = materials.Count == 0 ? 1 : materials.Keys.Max() + 1; + var copies = new Dictionary<(int? Material, int? Texture), int>(); + int CopyWithTexture(SceneMaterial? material, int? texture) + { + if (!copies.TryGetValue((material?.Id, texture), out int id)) + { + id = nextMaterial++; + scene.Materials.Add(new SceneMaterial + { + Id = id, + Name = material?.Name ?? "Texture", + Color = material?.Color ?? DefaultColor, + Roughness = material?.Roughness ?? 0.5, + Metalness = material?.Metalness ?? 0.1, + TextureId = texture, + }); + copies.Add((material?.Id, texture), id); + } + return id; + } + + int nextGeometry = old.Geometries.Count == 0 ? 1 : old.Geometries.Max(g => g.Id) + 1; + foreach (var geometry in old.Geometries) + { + scene.Geometries.Add(Geometry(geometry.Id, geometry.Vertices, geometry.Indices, geometry.Normals, geometry.UVs)); + } + + foreach (var mesh in old.Meshes) + { + int geometryId; + if (mesh.GeometryId is int shared) + { + if (mesh.Vertices.Count > 0) throw new InvalidDataException($"Mesh {mesh.Id} has both geometry {shared} and its own vertices."); + geometryId = shared; + } + else + { + geometryId = nextGeometry++; + scene.Geometries.Add(Geometry(geometryId, mesh.Vertices, mesh.Indices, mesh.Normals, mesh.UVs)); + } + + // The colour and opacity 0.2.0 drew: the mesh colour, else its material's, times the opacities. + (ViewerMaterial Old, SceneMaterial New)? material = mesh.Color is null && mesh.MaterialId is int m && materials.TryGetValue(m, out var found) ? found : null; + var color = mesh.Color ?? material?.Old.Color ?? new RgbaColor(200, 200, 200); + var drawn = Map(color, color.Af * (material?.Old.Opacity ?? 1) * mesh.Opacity); + int? texture = TextureOf(material is { } used ? mesh.TextureIndex ?? used.Old.TextureIndex : mesh.TextureIndex); + int? materialId = material is { } drawnWith + ? texture == drawnWith.New.TextureId ? drawnWith.New.Id : CopyWithTexture(drawnWith.New, texture) + : texture is null ? null : CopyWithTexture(null, texture); + + scene.Instances.Add(new SceneInstance + { + ElementId = ElementOf(mesh), + GeometryId = geometryId, + Transform = mesh.Transform is { } t ? new S.Transform3d(Map(t.XAxis), Map(t.YAxis), Map(t.ZAxis), Map(t.Translation)) : null, + MaterialId = materialId, + Color = mesh.Color is null && drawn == (material?.New.Color ?? DefaultColor) ? null : drawn, + Name = Empty(mesh.Name), + }); + } + + var ids = scene.Elements.Select(e => e.Id).ToHashSet(); + long? Known(long? id) => id is long known && ids.Contains(known) ? known : null; + + if (old.Grids is { } grids) + { + scene.Grids.Add(new SceneGridSystem + { + ModelId = 1, + Name = grids.Name ?? string.Empty, + Lines = [.. grids.Lines.Select(l => new SceneGridLine { Name = l.Name ?? string.Empty, Start = Map(l.Start), End = Map(l.End), Elevation = l.Elevation })], + }); + } + + foreach (var viewpoint in old.Viewpoints) + { + var c = viewpoint.Camera ?? new ViewerCamera(); + scene.Viewpoints.Add(new SceneViewpoint + { + Name = viewpoint.Name ?? string.Empty, + Camera = new SceneCamera + { + Position = Map(c.Position), + Target = Map(c.Target), + Up = Map(c.Up), + FieldOfView = c.FieldOfView, + AspectRatio = c.AspectRatio, + NearDistance = c.NearDistance, + FarDistance = c.FarDistance, + Projection = (SceneProjection)c.Projection, + }, + }); + } + + if (old.TimeLiner is { } schedule) + { + SceneTask Task(ViewerTimeLinerTask t) => new() + { + Id = t.Id ?? string.Empty, + Name = t.Name ?? string.Empty, + TaskType = (SceneTaskType)t.TaskType, + PlannedStart = t.PlannedStart, + PlannedEnd = t.PlannedEnd, + ActualStart = t.ActualStart, + ActualEnd = t.ActualEnd, + PlannedCost = t.PlannedCost, + ActualCost = t.ActualCost, + Progress = t.Progress, + ElementIds = [.. t.AttachedElementIds.Where(ids.Contains)], + Children = [.. t.Children.Select(Task)], + }; + scene.Schedules.Add(new SceneSchedule { Name = schedule.Name ?? string.Empty, Tasks = [.. schedule.Tasks.Select(Task)] }); + } + + foreach (var test in old.ClashTests) + { + scene.ClashTests.Add(new SceneClashTest + { + Name = test.Name ?? string.Empty, + Clashes = [.. test.Clashes.Select(c => new SceneClashResult + { + Id = c.Id ?? string.Empty, + Name = c.Name ?? string.Empty, + Element1Id = Known(c.Element1Id), + Element2Id = Known(c.Element2Id), + Element1Name = c.Element1Name ?? string.Empty, + Element2Name = c.Element2Name ?? string.Empty, + Point = Map(c.Point), + Distance = c.Distance, + Status = (SceneClashStatus)c.Status, + })], + }); + } + + return result; + } + + private static SceneGeometry Geometry(int id, IReadOnlyList vertices, IReadOnlyList indices, IReadOnlyList? normals, IReadOnlyList? uvs) => new() + { + Id = id, + Topology = indices.Count == 0 && vertices.Count > 0 ? SceneTopology.Points : SceneTopology.Triangles, + Vertices = [.. vertices.Select(Map)], + Indices = [.. indices], + Normals = normals is null ? null : [.. normals.Select(Map)], + UVs = uvs is null ? null : [.. uvs.Select(v => new S.Vector2d(v.X, v.Y))], + }; + + private static S.Vector3d Map(Vector3d v) => new(v.X, v.Y, v.Z); + + // The alpha byte nearest to an opacity from 0 to 1. + private static S.RgbaColor Map(RgbaColor c, double opacity) => + new(c.R, c.G, c.B, (byte)Math.Round(Math.Clamp(opacity, 0, 1) * 255, MidpointRounding.AwayFromZero)); + + private static string? Empty(string? text) => string.IsNullOrEmpty(text) ? null : text; + + private static string? FileName(string? path) => path?[(path.LastIndexOfAny(['/', '\\']) + 1)..]; +} diff --git a/src/IOViewer/Model/MathTypes.cs b/src/IOViewer/Model/MathTypes.cs deleted file mode 100644 index ec8a2fd..0000000 --- a/src/IOViewer/Model/MathTypes.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Globalization; - -namespace IOViewer.Model; - -/// -/// 2D double-precision vector. -/// -public readonly record struct Vector2d(double X, double Y) -{ - public static readonly Vector2d Zero = new(0, 0); - - public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"({X}, {Y})"); -} - -/// -/// 3D double-precision vector. -/// -public readonly record struct Vector3d(double X, double Y, double Z) -{ - public static readonly Vector3d Zero = new(0, 0, 0); - public static readonly Vector3d UnitX = new(1, 0, 0); - public static readonly Vector3d UnitY = new(0, 1, 0); - public static readonly Vector3d UnitZ = new(0, 0, 1); - - public double LengthSquared => X * X + Y * Y + Z * Z; - public double Length => Math.Sqrt(LengthSquared); - - public Vector3d Normalized() - { - double len = Length; - return len > 1e-12 ? new Vector3d(X / len, Y / len, Z / len) : Zero; - } - - public static Vector3d operator +(Vector3d a, Vector3d b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z); - public static Vector3d operator -(Vector3d a, Vector3d b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z); - public static Vector3d operator -(Vector3d v) => new(-v.X, -v.Y, -v.Z); - public static Vector3d operator *(Vector3d v, double s) => new(v.X * s, v.Y * s, v.Z * s); - public static Vector3d operator *(double s, Vector3d v) => new(v.X * s, v.Y * s, v.Z * s); - public static Vector3d operator /(Vector3d v, double s) => new(v.X / s, v.Y / s, v.Z / s); - - public static double Dot(Vector3d a, Vector3d b) => a.X * b.X + a.Y * b.Y + a.Z * b.Z; - public static Vector3d Cross(Vector3d a, Vector3d b) => new( - a.Y * b.Z - a.Z * b.Y, - a.Z * b.X - a.X * b.Z, - a.X * b.Y - a.Y * b.X); - - public static double Distance(Vector3d a, Vector3d b) => (a - b).Length; - - public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"({X}, {Y}, {Z})"); -} - -/// -/// 4D double-precision vector. -/// -public readonly record struct Vector4d(double X, double Y, double Z, double W) -{ - public static readonly Vector4d Zero = new(0, 0, 0, 0); - - public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"({X}, {Y}, {Z}, {W})"); -} - -/// -/// Double-precision quaternion representing 3D rotation. -/// -public readonly record struct Quaterniond(double X, double Y, double Z, double W) -{ - public static readonly Quaterniond Identity = new(0, 0, 0, 1); - - public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"({X}, {Y}, {Z}, {W})"); -} - -/// -/// 3D Axis-Aligned Bounding Box (AABB). -/// -public readonly record struct BoundingBox3d(Vector3d Min, Vector3d Max) -{ - public static readonly BoundingBox3d Empty = new( - new Vector3d(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity), - new Vector3d(double.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity)); - - public bool IsEmpty => Min.X > Max.X || Min.Y > Max.Y || Min.Z > Max.Z; - - public Vector3d Center => (Min + Max) * 0.5; - public Vector3d Size => Max - Min; - - public BoundingBox3d Encapsulate(Vector3d point) - { - if (IsEmpty) return new BoundingBox3d(point, point); - return new BoundingBox3d( - new Vector3d(Math.Min(Min.X, point.X), Math.Min(Min.Y, point.Y), Math.Min(Min.Z, point.Z)), - new Vector3d(Math.Max(Max.X, point.X), Math.Max(Max.Y, point.Y), Math.Max(Max.Z, point.Z))); - } - - public BoundingBox3d Encapsulate(BoundingBox3d other) - { - if (IsEmpty) return other; - if (other.IsEmpty) return this; - return new BoundingBox3d( - new Vector3d(Math.Min(Min.X, other.Min.X), Math.Min(Min.Y, other.Min.Y), Math.Min(Min.Z, other.Min.Z)), - new Vector3d(Math.Max(Max.X, other.Max.X), Math.Max(Max.Y, other.Max.Y), Math.Max(Max.Z, other.Max.Z))); - } -} - -/// -/// Affine 3D transform stored by columns: the result of is -/// XAxis * p.X + YAxis * p.Y + ZAxis * p.Z + Translation. -/// -/// The translation is in metres, in the document's Z-up coordinates. -/// Image of the unit X vector (first matrix column). -/// Image of the unit Y vector (second matrix column). -/// Image of the unit Z vector (third matrix column). -/// Translation applied after the linear part. -public readonly record struct Transform3d(Vector3d XAxis, Vector3d YAxis, Vector3d ZAxis, Vector3d Translation) -{ - /// The transform that leaves every point unchanged. - public static readonly Transform3d Identity = new(Vector3d.UnitX, Vector3d.UnitY, Vector3d.UnitZ, Vector3d.Zero); - - /// Creates a pure translation. - /// Translation vector. - public static Transform3d Translate(Vector3d t) => new(Vector3d.UnitX, Vector3d.UnitY, Vector3d.UnitZ, t); - - /// Transforms a point. - /// Point in local coordinates. - public Vector3d Apply(Vector3d p) => XAxis * p.X + YAxis * p.Y + ZAxis * p.Z + Translation; - - /// Composes two transforms: this one is applied first, then . - /// Transform applied after this one. - public Transform3d Then(Transform3d next) => new( - next.Apply(XAxis) - next.Translation, - next.Apply(YAxis) - next.Translation, - next.Apply(ZAxis) - next.Translation, - next.Apply(Translation)); -} - -/// -/// 8-bit per channel RGBA color with double precision conversion. -/// -public readonly record struct RgbaColor(byte R, byte G, byte B, byte A = 255) -{ - public static readonly RgbaColor White = new(255, 255, 255); - public static readonly RgbaColor Black = new(0, 0, 0); - public static readonly RgbaColor Gray = new(180, 180, 180); - - public double Rf => R / 255.0; - public double Gf => G / 255.0; - public double Bf => B / 255.0; - public double Af => A / 255.0; - - public string ToHex() => string.Create(CultureInfo.InvariantCulture, $"#{R:x2}{G:x2}{B:x2}"); - - public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"rgba({R}, {G}, {B}, {Af:F2})"); -} diff --git a/src/IOViewer/Model/ViewerClash.cs b/src/IOViewer/Model/ViewerClash.cs deleted file mode 100644 index 1508303..0000000 --- a/src/IOViewer/Model/ViewerClash.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Text.Json.Serialization; - -namespace IOViewer.Model; - -public enum ViewerClashStatus -{ - New = 0, - Active = 1, - Reviewed = 2, - Approved = 3, - Resolved = 4 -} - -/// -/// A detected geometric collision between two BIM/CAD elements. -/// -public sealed class ViewerClashResult -{ - public string Id { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public string Element1Name { get; set; } = string.Empty; - public string Element2Name { get; set; } = string.Empty; - - /// Id of the first clashing element, if known. - public long? Element1Id { get; set; } - - /// Id of the second clashing element, if known. - public long? Element2Id { get; set; } - - public Vector3d Point { get; set; } - public double Distance { get; set; } - public ViewerClashStatus Status { get; set; } = ViewerClashStatus.Active; - - public ViewerClashResult() { } - - public ViewerClashResult(string id, string name, string el1, string el2, Vector3d point, double distance, ViewerClashStatus status = ViewerClashStatus.Active) - { - Id = id; - Name = name; - Element1Name = el1; - Element2Name = el2; - Point = point; - Distance = distance; - Status = status; - } -} - -/// -/// A clash detection coordination test suite. -/// -public sealed class ViewerClashTest -{ - public string Name { get; set; } = "Clash Test"; - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public List Clashes { get; } = []; - - public ViewerClashTest() { } - - public ViewerClashTest(string name) - { - Name = name; - } -} - -/// -/// External linked model reference. -/// -public sealed class ViewerLinkedFile -{ - public string Name { get; set; } = string.Empty; - public string Path { get; set; } = string.Empty; - public bool Visible { get; set; } = true; - - public ViewerLinkedFile() { } - - public ViewerLinkedFile(string name, string path, bool visible = true) - { - Name = name; - Path = path; - Visible = visible; - } -} diff --git a/src/IOViewer/Model/ViewerDocument.cs b/src/IOViewer/Model/ViewerDocument.cs index 33b3bab..8b345e9 100644 --- a/src/IOViewer/Model/ViewerDocument.cs +++ b/src/IOViewer/Model/ViewerDocument.cs @@ -1,124 +1,51 @@ using System.Text.Json.Serialization; +using IOScene; namespace IOViewer.Model; /// -/// Root container representing a 3D BIM/CAD model scene graph ready for interactive 3D WebGL viewing. +/// What the HTML viewer shows: a scene, plus the viewer's own state at start (section planes, section box and the +/// models hidden at start). /// /// -/// All coordinates and lengths in the document are in metres, in a right-handed system with Z up: vertices, -/// transforms, bounding boxes, cameras, grids, sections and clash points. Producers convert to this on the way in -/// (for example, converts glTF's Y-up). +/// The model data is , in metres, right-handed, Z up, as the IOScene contract defines it. Section +/// planes and the section box are in the same coordinates. /// public sealed class ViewerDocument { - public string Title { get; set; } = "3D BIM Model"; - public string? SourcePath { get; set; } - public string? Author { get; set; } - public string? CreatedAt { get; set; } - public string? Application { get; set; } - - /// - /// Calculated or specified bounding box encompassing all model geometry. - /// - public BoundingBox3d BoundingBox { get; set; } = BoundingBox3d.Empty; - - /// - /// List of BIM elements with hierarchical category, type, and parameter properties. - /// - public List Elements { get; set; } = []; - - /// - /// List of 3D polygonal meshes and geometries. - /// - public List Meshes { get; set; } = []; - - /// - /// Geometries shared between meshes via . - /// - public List Geometries { get; set; } = []; - - /// - /// Material definitions with colors, opacity, and surface properties. - /// - public List Materials { get; set; } = []; + public ViewerDocument() { } - /// - /// Diffuse textures embedded as Base64 raster data with real-world dimensions. - /// - public List Textures { get; set; } = []; + public ViewerDocument(string title) + { + Title = title; + } - /// - /// Optional spatial grid coordinate system (building grid lines). - /// - public ViewerGridSystem? Grids { get; set; } + public ViewerDocument(SceneDocument scene) + { + ArgumentNullException.ThrowIfNull(scene); + Scene = scene; + } - /// - /// List of saved camera viewpoints for guided walkthroughs and inspections. - /// - public List Viewpoints { get; set; } = []; + /// The model data. + public SceneDocument Scene { get; set; } = new(); - /// - /// Active or predefined cutting planes. - /// + /// Cutting planes active at start. public List SectionPlanes { get; set; } = []; - /// - /// Optional 6-sided Section Box (bounding volume clipping). - /// + /// Section box (clipping volume) at start, or null. public ViewerSectionBox? SectionBox { get; set; } /// - /// Optional 4D TimeLiner construction schedule simulation. - /// - public ViewerTimeLinerSchedule? TimeLiner { get; set; } - - /// - /// List of clash detection coordination test results. - /// - public List ClashTests { get; set; } = []; - - /// - /// Optional list of linked/federated external reference models. + /// Keys () of the models hidden at start. The first model of the scene is the host + /// and is always shown; the others are listed as linked files. /// - public List LinkedFiles { get; set; } = []; - - public ViewerDocument() { } - - public ViewerDocument(string title) - { - Title = title; - } + public List HiddenModelKeys { get; set; } = []; - /// - /// Computes and updates the overall 3D bounding box from all meshes, including shared geometry and mesh transforms. - /// - public BoundingBox3d ComputeBoundingBox() + /// Title of the scene: reads and writes . + [JsonIgnore] + public string Title { - var geometries = new Dictionary(); - foreach (var g in Geometries) - { - geometries[g.Id] = g; - } - - var box = BoundingBox3d.Empty; - foreach (var mesh in Meshes) - { - mesh.ComputeBoundingBox(); - var transform = mesh.Transform ?? Transform3d.Identity; - foreach (var v in mesh.Vertices) - { - box = box.Encapsulate(transform.Apply(v)); - } - if (mesh.GeometryId is int id && geometries.TryGetValue(id, out var geometry)) - { - foreach (var v in geometry.Vertices) - { - box = box.Encapsulate(transform.Apply(v)); - } - } - } - BoundingBox = box; - return box; + get => Scene.Title; + set => Scene.Title = value; } } diff --git a/src/IOViewer/Model/ViewerElement.cs b/src/IOViewer/Model/ViewerElement.cs deleted file mode 100644 index 20985c0..0000000 --- a/src/IOViewer/Model/ViewerElement.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Text.Json.Serialization; - -namespace IOViewer.Model; - -/// -/// Represents a semantic BIM/CAD element (e.g. Wall, Column, Pipe, Equipment, Door). -/// -public sealed class ViewerElement -{ - public long Id { get; set; } - public string Name { get; set; } = string.Empty; - public string Category { get; set; } = "General"; - public string TypeName { get; set; } = string.Empty; - public string Level { get; set; } = string.Empty; - public string? LinkName { get; set; } - - /// - /// The element's id in its source format, e.g. a Revit UniqueId or an IFC GlobalId; null when there is none. - /// stays the key the viewer, meshes, 4D tasks and clashes use. - /// - // Left out of JSON when null, so snapshots of documents without source ids stay as they were. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? SourceId { get; set; } - - /// - /// Parameters grouped by property category (e.g. "Identity Data", "Dimensions", "Phasing"). - /// - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public Dictionary> Properties { get; } = new(StringComparer.OrdinalIgnoreCase); - - /// - /// Indices of associated meshes in ViewerDocument.Meshes. - /// - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public List MeshIndices { get; } = []; - - public BoundingBox3d BoundingBox { get; set; } = BoundingBox3d.Empty; - - public void AddProperty(string category, string name, string value) - { - if (string.IsNullOrEmpty(category)) category = "General"; - if (!Properties.TryGetValue(category, out var dict)) - { - dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - Properties[category] = dict; - } - dict[name] = value; - } -} diff --git a/src/IOViewer/Model/ViewerGeometry.cs b/src/IOViewer/Model/ViewerGeometry.cs deleted file mode 100644 index 9693ef9..0000000 --- a/src/IOViewer/Model/ViewerGeometry.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace IOViewer.Model; - -/// -/// Triangle geometry shared by several meshes; each mesh places it with its own . -/// -public sealed class ViewerGeometry -{ - /// Identifier referenced by . - public int Id { get; set; } - - /// Vertex positions in the geometry's local coordinates, in metres with Z up. - public IReadOnlyList Vertices { get; set; } = []; - - /// Triangle vertex indices (three per triangle). - public IReadOnlyList Indices { get; set; } = []; - - /// Optional per-vertex normals. - public IReadOnlyList? Normals { get; set; } - - /// Optional per-vertex texture coordinates. - public IReadOnlyList? UVs { get; set; } - - /// Computes the bounding box of in local coordinates. - public BoundingBox3d ComputeBoundingBox() - { - var box = BoundingBox3d.Empty; - foreach (var v in Vertices) - { - box = box.Encapsulate(v); - } - return box; - } -} diff --git a/src/IOViewer/Model/ViewerGridSystem.cs b/src/IOViewer/Model/ViewerGridSystem.cs deleted file mode 100644 index bb9150b..0000000 --- a/src/IOViewer/Model/ViewerGridSystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text.Json.Serialization; - -namespace IOViewer.Model; - -/// -/// Represents a building spatial grid coordinate system. -/// -public sealed class ViewerGridSystem -{ - public string Name { get; set; } = "Grids"; - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public List Lines { get; } = []; -} - -/// -/// A single 3D grid line / axis (e.g., Axis 1, Axis 2, Axis A, Axis B). -/// -public sealed class ViewerGridLine -{ - public string Name { get; set; } = string.Empty; - public Vector3d Start { get; set; } - public Vector3d End { get; set; } - public double Elevation { get; set; } - - public ViewerGridLine() { } - - public ViewerGridLine(string name, Vector3d start, Vector3d end, double elevation = 0) - { - Name = name; - Start = start; - End = end; - Elevation = elevation; - } -} diff --git a/src/IOViewer/Model/ViewerMaterial.cs b/src/IOViewer/Model/ViewerMaterial.cs deleted file mode 100644 index 19a7f60..0000000 --- a/src/IOViewer/Model/ViewerMaterial.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace IOViewer.Model; - -/// -/// Defines visual material appearance. -/// -public sealed class ViewerMaterial -{ - public int Id { get; set; } - public string Name { get; set; } = "Default"; - public RgbaColor Color { get; set; } = new(200, 200, 200); - public double Opacity { get; set; } = 1.0; - public double Roughness { get; set; } = 0.5; - public double Metalness { get; set; } = 0.1; - public int? TextureIndex { get; set; } -} - -/// -/// Diffuse texture data with real-world dimensions for UV box/triplanar mapping. -/// -public sealed class ViewerTexture -{ - public int Id { get; set; } - public string MimeType { get; set; } = "image/png"; - public string Base64Data { get; set; } = string.Empty; - public double ScaleX { get; set; } = 1.0; - public double ScaleY { get; set; } = 1.0; -} diff --git a/src/IOViewer/Model/ViewerMesh.cs b/src/IOViewer/Model/ViewerMesh.cs deleted file mode 100644 index 7e1e610..0000000 --- a/src/IOViewer/Model/ViewerMesh.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace IOViewer.Model; - -/// -/// Represents a 3D polygonal triangle mesh. -/// -public sealed class ViewerMesh -{ - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - public long? ElementId { get; set; } - - /// Vertex positions in metres with Z up, placed by . - public IReadOnlyList Vertices { get; set; } = []; - public IReadOnlyList Indices { get; set; } = []; - public IReadOnlyList? Normals { get; set; } - public IReadOnlyList? UVs { get; set; } - - /// Id of a in drawn by this mesh, if any. - public int? GeometryId { get; set; } - - /// Placement of this mesh's own and of the shared geometry; null means identity. - public Transform3d? Transform { get; set; } - - public int? MaterialId { get; set; } - public RgbaColor? Color { get; set; } - public double Opacity { get; set; } = 1.0; - public int? TextureIndex { get; set; } - - public BoundingBox3d BoundingBox { get; set; } = BoundingBox3d.Empty; - - public BoundingBox3d ComputeBoundingBox() - { - if (Vertices.Count == 0) return BoundingBox3d.Empty; - var box = BoundingBox3d.Empty; - foreach (var v in Vertices) - { - box = box.Encapsulate(v); - } - BoundingBox = box; - return box; - } -} diff --git a/src/IOViewer/Model/ViewerSection.cs b/src/IOViewer/Model/ViewerSection.cs index d56bede..74809d7 100644 --- a/src/IOViewer/Model/ViewerSection.cs +++ b/src/IOViewer/Model/ViewerSection.cs @@ -1,3 +1,5 @@ +using IOScene; + namespace IOViewer.Model; public enum ViewerSectionAxis diff --git a/src/IOViewer/Model/ViewerTimeLiner.cs b/src/IOViewer/Model/ViewerTimeLiner.cs deleted file mode 100644 index 06a0100..0000000 --- a/src/IOViewer/Model/ViewerTimeLiner.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Text.Json.Serialization; - -namespace IOViewer.Model; - -public enum ViewerTimeLinerTaskType -{ - Construct = 0, - Demolish = 1, - Temporary = 2 -} - -/// -/// A 4D construction schedule task. -/// -public sealed class ViewerTimeLinerTask -{ - public string Id { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public ViewerTimeLinerTaskType TaskType { get; set; } = ViewerTimeLinerTaskType.Construct; - - public DateTimeOffset PlannedStart { get; set; } - public DateTimeOffset PlannedEnd { get; set; } - public DateTimeOffset? ActualStart { get; set; } - public DateTimeOffset? ActualEnd { get; set; } - - public decimal? PlannedCost { get; set; } - public decimal? ActualCost { get; set; } - public double Progress { get; set; } - - public string? AttachedSetName { get; set; } - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public List AttachedElementIds { get; } = []; - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public List Children { get; } = []; - - public ViewerTimeLinerTask() { } - - public ViewerTimeLinerTask(string id, string name, DateTimeOffset start, DateTimeOffset end, ViewerTimeLinerTaskType taskType = ViewerTimeLinerTaskType.Construct) - { - Id = id; - Name = name; - PlannedStart = start; - PlannedEnd = end; - TaskType = taskType; - } -} - -/// -/// A complete 4D TimeLiner schedule. -/// -public sealed class ViewerTimeLinerSchedule -{ - public string Name { get; set; } = "TimeLiner Schedule"; - [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] - public List Tasks { get; } = []; - - public ViewerTimeLinerSchedule() { } - - public ViewerTimeLinerSchedule(string name) - { - Name = name; - } - - /// - /// Gets earliest planned start date across all tasks. - /// - public DateTimeOffset GetOverallStart() - { - var min = DateTimeOffset.MaxValue; - void Recurse(IEnumerable tasks) - { - foreach (var t in tasks) - { - if (t.PlannedStart < min) min = t.PlannedStart; - if (t.Children.Count > 0) Recurse(t.Children); - } - } - Recurse(Tasks); - return min == DateTimeOffset.MaxValue ? DateTimeOffset.UtcNow : min; - } - - /// - /// Gets latest planned finish date across all tasks. - /// - public DateTimeOffset GetOverallEnd() - { - var max = DateTimeOffset.MinValue; - void Recurse(IEnumerable tasks) - { - foreach (var t in tasks) - { - if (t.PlannedEnd > max) max = t.PlannedEnd; - if (t.Children.Count > 0) Recurse(t.Children); - } - } - Recurse(Tasks); - return max == DateTimeOffset.MinValue ? DateTimeOffset.UtcNow.AddDays(30) : max; - } -} diff --git a/src/IOViewer/Model/ViewerViewpoint.cs b/src/IOViewer/Model/ViewerViewpoint.cs deleted file mode 100644 index 9acf0f8..0000000 --- a/src/IOViewer/Model/ViewerViewpoint.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace IOViewer.Model; - -public enum ViewerProjection -{ - Perspective = 0, - Orthographic = 1 -} - -/// -/// Camera position, orientation, and lens parameters. -/// -public sealed class ViewerCamera -{ - public Vector3d Position { get; set; } = new(10, 10, 10); - public Vector3d Target { get; set; } = Vector3d.Zero; - public Vector3d Up { get; set; } = Vector3d.UnitZ; - public double FieldOfView { get; set; } = 0.785; // ~45 deg in rad - public double AspectRatio { get; set; } = 1.6; - public double NearDistance { get; set; } = 0.1; - public double FarDistance { get; set; } = 1000.0; - public ViewerProjection Projection { get; set; } = ViewerProjection.Perspective; -} - -/// -/// Saved viewpoint representation. -/// -public sealed class ViewerViewpoint -{ - public string Name { get; set; } = "Viewpoint"; - public ViewerCamera Camera { get; set; } = new(); - - public ViewerViewpoint() { } - - public ViewerViewpoint(string name, ViewerCamera camera) - { - Name = name; - Camera = camera; - } -} diff --git a/tests/IOViewer.Tests/GlbSharedGeometryTests.cs b/tests/IOViewer.Tests/GlbSharedGeometryTests.cs index 2618eb7..5268459 100644 --- a/tests/IOViewer.Tests/GlbSharedGeometryTests.cs +++ b/tests/IOViewer.Tests/GlbSharedGeometryTests.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Text; +using IOScene; using IOViewer.Importers; using IOViewer.Model; using Xunit; @@ -60,23 +61,23 @@ private static void AssertNear(Vector3d expected, Vector3d actual) Assert.True(Vector3d.Distance(expected, actual) < 1e-6, $"expected {expected}, got {actual}"); } - private static Vector3d World(ViewerDocument doc, int meshIndex) + private static Vector3d World(ViewerDocument doc, int instanceIndex) { - var mesh = doc.Meshes[meshIndex]; - var geometry = doc.Geometries[mesh.GeometryId!.Value]; - return (mesh.Transform ?? Transform3d.Identity).Apply(geometry.Vertices[0]); + var instance = doc.Scene.Instances[instanceIndex]; + var geometry = Assert.Single(doc.Scene.Geometries, g => g.Id == instance.GeometryId); + return (instance.Transform ?? Transform3d.Identity).Apply(geometry.Vertices[0]); } [Fact] - public void TwoNodesSharingMesh_ProduceOneGeometryTwoMeshes() + public void TwoNodesSharingMesh_ProduceOneGeometryTwoInstances() { var doc = Import("""{ "mesh": 0, "translation": [0,0,0] }, { "mesh": 0, "translation": [10,0,0] }""", "0, 1"); - Assert.Single(doc.Geometries); - Assert.Equal(2, doc.Meshes.Count); - Assert.Equal(doc.Meshes[0].GeometryId, doc.Meshes[1].GeometryId); - Assert.Empty(doc.Meshes[0].Vertices); + Assert.Single(doc.Scene.Geometries); + Assert.Equal(2, doc.Scene.Instances.Count); + Assert.Equal(doc.Scene.Instances[0].GeometryId, doc.Scene.Instances[1].GeometryId); AssertNear(new Vector3d(10, 0, 0), World(doc, 1)); + Assert.Empty(doc.Scene.Validate()); } [Fact] @@ -140,6 +141,6 @@ public void DeepHierarchy_DoesNotOverflow() var doc = Import(nodes.ToString(), "0"); - Assert.Single(doc.Meshes); + Assert.Single(doc.Scene.Instances); } } diff --git a/tests/IOViewer.Tests/HtmlViewerExporterTests.cs b/tests/IOViewer.Tests/HtmlViewerExporterTests.cs index 20e87fc..3d0d443 100644 --- a/tests/IOViewer.Tests/HtmlViewerExporterTests.cs +++ b/tests/IOViewer.Tests/HtmlViewerExporterTests.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using IOScene; using IOViewer.Exporters; using IOViewer.Model; using Xunit; @@ -14,17 +15,29 @@ public class HtmlViewerExporterTests // A georeferenced slab with one element, viewpoint, grids, 4D schedule and a clash. private static ViewerDocument GeoreferencedModel() { - var doc = new ViewerDocument("Commercial Center 3D") + var scene = new SceneDocument { Title = "Commercial Center 3D" }; + scene.Models.Add(new SceneModel { Id = 1, Key = "center", Name = "Commercial Center 3D", Author = "IOExtend", CreatedAt = "2026-11-11T12:00:00Z" }); + scene.Models.Add(new SceneModel { Id = 2, Key = "structure.rvt", Name = "Structure", Path = "structure.rvt" }); + scene.Levels.Add(new SceneLevel { Id = 1, ModelId = 1, Name = "Level 1" }); + scene.PropertySets.Add(new ScenePropertySet { - Author = "IOExtend", - CreatedAt = "2026-11-11T12:00:00Z" - }; - - doc.Meshes.Add(new ViewerMesh + Id = 1, + Name = "Dimensions", + Properties = [new SceneProperty { Name = "Thickness", Value = SceneValue.FromNumber(0.3, "m", "300 mm") }], + }); + scene.Elements.Add(new SceneElement + { + Id = 101, + ModelId = 1, + Name = "Ground Floor Slab", + Category = "Floors", + TypeName = "Generic 300mm", + LevelId = 1, + PropertySetIds = [1], + }); + scene.Geometries.Add(new SceneGeometry { Id = 1, - Name = "Structural Slab", - ElementId = 101, Vertices = [ new(OffsetX + 0, OffsetY + 0, 0), new(OffsetX + 10, OffsetY + 0, 0), @@ -32,47 +45,56 @@ private static ViewerDocument GeoreferencedModel() new(OffsetX + 0, OffsetY + 15, 0) ], Indices = [0, 1, 2, 0, 2, 3], - Color = new RgbaColor(180, 180, 190) }); + scene.Instances.Add(new SceneInstance { ElementId = 101, GeometryId = 1, Color = new RgbaColor(180, 180, 190), Name = "Structural Slab" }); - var el = new ViewerElement + scene.Viewpoints.Add(new SceneViewpoint { - Id = 101, - Name = "Ground Floor Slab", - Category = "Floors", - TypeName = "Generic 300mm", - Level = "Level 1" - }; - el.AddProperty("Dimensions", "Thickness", "300 mm"); - el.MeshIndices.Add(0); - doc.Elements.Add(el); + Name = "Isometric Overview", + Camera = new SceneCamera { Position = new Vector3d(OffsetX + 25, OffsetY - 25, 30), Target = new Vector3d(OffsetX + 5, OffsetY + 7.5, 0) }, + }); - doc.Viewpoints.Add(new ViewerViewpoint("Isometric Overview", new ViewerCamera + scene.Grids.Add(new SceneGridSystem { - Position = new Vector3d(OffsetX + 25, OffsetY - 25, 30), - Target = new Vector3d(OffsetX + 5, OffsetY + 7.5, 0) - })); - - var grids = new ViewerGridSystem { Name = "Structural Grids" }; - grids.Lines.Add(new ViewerGridLine("Axis 1", new(OffsetX, OffsetY, 0), new(OffsetX + 10, OffsetY, 0))); - doc.Grids = grids; - - var schedule = new ViewerTimeLinerSchedule("4D Construction"); - var task = new ViewerTimeLinerTask("T1", "Pour Slab", - DateTimeOffset.Parse("2026-06-01T08:00:00Z", CultureInfo.InvariantCulture), - DateTimeOffset.Parse("2026-06-10T17:00:00Z", CultureInfo.InvariantCulture), - ViewerTimeLinerTaskType.Construct); - task.AttachedElementIds.Add(101); - schedule.Tasks.Add(task); - doc.TimeLiner = schedule; + ModelId = 1, + Name = "Structural Grids", + Lines = [new SceneGridLine { Name = "Axis 1", Start = new(OffsetX, OffsetY, 0), End = new(OffsetX + 10, OffsetY, 0) }], + }); - var clashTest = new ViewerClashTest("Hard Clashes"); - clashTest.Clashes.Add(new ViewerClashResult("C1", "Slab vs Column", "Ground Floor Slab", "Column C1", - new Vector3d(OffsetX + 5, OffsetY + 5, 0), -0.02)); - doc.ClashTests.Add(clashTest); + scene.Schedules.Add(new SceneSchedule + { + Name = "4D Construction", + Tasks = + [ + new SceneTask + { + Id = "T1", + Name = "Pour Slab", + PlannedStart = DateTimeOffset.Parse("2026-06-01T08:00:00Z", CultureInfo.InvariantCulture), + PlannedEnd = DateTimeOffset.Parse("2026-06-10T17:00:00Z", CultureInfo.InvariantCulture), + TaskType = SceneTaskType.Construct, + ElementIds = [101], + }, + ], + }); - doc.LinkedFiles.Add(new ViewerLinkedFile("Structure", "structure.rvt")); - return doc; + scene.ClashTests.Add(new SceneClashTest + { + Name = "Hard Clashes", + Clashes = + [ + new SceneClashResult + { + Id = "C1", + Name = "Slab vs Column", + Element1Name = "Ground Floor Slab", + Element2Name = "Column C1", + Point = new Vector3d(OffsetX + 5, OffsetY + 5, 0), + Distance = -0.02, + }, + ], + }); + return new ViewerDocument(scene); } private static readonly Vector3d[] CubeVertices = @@ -87,12 +109,8 @@ private static ViewerDocument GeoreferencedModel() 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 3, 0, 4, 3, 4, 7 ]; - private static ViewerMesh Cube(int id, Vector3d at) => new() - { - Id = id, - Vertices = CubeVertices.Select(v => v + at).ToArray(), - Indices = CubeIndices - }; + private static SceneInstance Cube(SceneDocument scene, Vector3d at) => + scene.Mesh(CubeVertices.Select(v => v + at).ToArray(), CubeIndices); private static double[] Vec(System.Text.Json.JsonElement array) => array.EnumerateArray().Select(e => e.GetDouble()).ToArray(); @@ -129,6 +147,7 @@ public void Meta_HasFormat3AndRtcOffset() Assert.Equal(101, element.GetProperty("id").GetInt64()); Assert.Equal("Ground Floor Slab", element.GetProperty("name").GetString()); Assert.Equal("Generic 300mm", element.GetProperty("type").GetString()); + Assert.Equal("Level 1", element.GetProperty("level").GetString()); Assert.Equal(System.Text.Json.JsonValueKind.Null, element.GetProperty("link").ValueKind); Assert.Equal([0], view.Instances("element")); Assert.Equal("300 mm", view.Props.GetProperty("101").GetProperty("Dimensions").GetProperty("Thickness").GetString()); @@ -140,20 +159,22 @@ public void Meta_HasFormat3AndRtcOffset() var clash = meta.GetProperty("clashTests")[0].GetProperty("clashes")[0]; Assert.Equal("Slab vs Column", clash.GetProperty("name").GetString()); Assert.Equal([0, -2.5, 0], Vec(clash.GetProperty("point"))); - Assert.Equal("structure.rvt", meta.GetProperty("linkedFiles")[0].GetProperty("path").GetString()); + var linked = meta.GetProperty("linkedFiles")[0]; + Assert.Equal(("Structure", "structure.rvt", true), (linked.GetProperty("name").GetString(), linked.GetProperty("path").GetString(), linked.GetProperty("visible").GetBoolean())); + Assert.False(meta.TryGetProperty("skipped", out _)); } [Fact] public void Meta_WritesSourceIdOnlyWhenSet() { const string hostile = "\">"; - var doc = new ViewerDocument("Source ids"); - doc.Elements.Add(new ViewerElement { Id = 1, Name = "Wall", SourceId = "2xQ$n5SLP5MBLyL442paFx" }); - doc.Elements.Add(new ViewerElement { Id = 2, Name = "Door" }); - doc.Elements.Add(new ViewerElement { Id = 3, Name = "Empty", SourceId = "" }); - doc.Elements.Add(new ViewerElement { Id = 4, Name = "Hostile", SourceId = hostile }); + var scene = TestScenes.Empty("Source ids"); + scene.Elements.Add(new SceneElement { Id = 1, ModelId = 1, Name = "Wall", SourceId = "2xQ$n5SLP5MBLyL442paFx" }); + scene.Elements.Add(new SceneElement { Id = 2, ModelId = 1, Name = "Door" }); + scene.Elements.Add(new SceneElement { Id = 3, ModelId = 1, Name = "Empty", SourceId = "" }); + scene.Elements.Add(new SceneElement { Id = 4, ModelId = 1, Name = "Hostile", SourceId = hostile }); - var view = ViewerHtml.Export(doc); + var view = ViewerHtml.Export(new ViewerDocument(scene)); var elements = view.Meta.GetProperty("elements"); Assert.Equal("2xQ$n5SLP5MBLyL442paFx", elements[0].GetProperty("sourceId").GetString()); @@ -173,6 +194,14 @@ public void Save_WritesSameHtmlAsExportToString() { HtmlViewerExporter.Save(doc, tempPath); Assert.Equal(HtmlViewerExporter.ExportToString(doc), File.ReadAllText(tempPath)); + + // A scene alone is the document with no sections and every model shown. + HtmlViewerExporter.Save(doc.Scene, tempPath); + Assert.Equal(HtmlViewerExporter.ExportToString(doc), File.ReadAllText(tempPath)); + Assert.Equal(HtmlViewerExporter.ExportToString(doc), HtmlViewerExporter.ExportToString(doc.Scene)); + using var stream = new MemoryStream(); + HtmlViewerExporter.Export(doc.Scene, stream); + Assert.Equal(HtmlViewerExporter.ExportToString(doc), System.Text.Encoding.UTF8.GetString(stream.ToArray())); } finally { @@ -183,13 +212,13 @@ public void Save_WritesSameHtmlAsExportToString() [Fact] public void TranslatedDuplicates_ShareOneGeometry() { - var doc = new ViewerDocument(); + var scene = TestScenes.Empty(); for (int i = 0; i < 100; i++) { - doc.Meshes.Add(Cube(i, new Vector3d(i * 3, i * 7 % 11, 0))); + Cube(scene, new Vector3d(i * 3, i * 7 % 11, 0)); } - var view = ViewerHtml.Export(doc); + var view = ViewerHtml.Export(new ViewerDocument(scene)); Assert.Equal(1, view.Geometries.GetArrayLength()); Assert.Equal(100, view.InstanceCount); @@ -199,13 +228,11 @@ public void TranslatedDuplicates_ShareOneGeometry() [Fact] public void DifferentShapes_AreNotMerged() { - var doc = new ViewerDocument(); - doc.Meshes.Add(Cube(0, Vector3d.Zero)); - var stretched = Cube(1, Vector3d.Zero); - stretched.Vertices = stretched.Vertices.Select(v => new Vector3d(v.X * 2, v.Y, v.Z)).ToArray(); - doc.Meshes.Add(stretched); + var scene = TestScenes.Empty(); + Cube(scene, Vector3d.Zero); + scene.Mesh(CubeVertices.Select(v => new Vector3d(v.X * 2, v.Y, v.Z)).ToArray(), CubeIndices); - var view = ViewerHtml.Export(doc); + var view = ViewerHtml.Export(new ViewerDocument(scene)); Assert.Equal(2, view.Geometries.GetArrayLength()); Assert.Equal([0, 1], view.Instances("geometry")); @@ -214,12 +241,12 @@ public void DifferentShapes_AreNotMerged() [Fact] public void SharedGeometry_WritesTransformMatrixInRtc() { - var doc = new ViewerDocument(); - doc.Geometries.Add(new ViewerGeometry { Id = 7, Vertices = [new(0, 0, 0), new(1, 0, 0), new(0, 1, 0)], Indices = [0, 1, 2] }); - doc.Meshes.Add(new ViewerMesh { Id = 1, GeometryId = 7, Transform = Transform3d.Translate(new(1_000_000, 2_000_000, 0)) }); - doc.Meshes.Add(new ViewerMesh { Id = 2, GeometryId = 7, Transform = Transform3d.Translate(new(1_000_010, 2_000_000, 0)) }); + var scene = TestScenes.Empty(); + var triangle = scene.Geometry([new(0, 0, 0), new(1, 0, 0), new(0, 1, 0)], [0, 1, 2]); + scene.Place(triangle, Transform3d.Translate(new(1_000_000, 2_000_000, 0))); + scene.Place(triangle, Transform3d.Translate(new(1_000_010, 2_000_000, 0))); - var view = ViewerHtml.Export(doc); + var view = ViewerHtml.Export(new ViewerDocument(scene)); Assert.Equal(1, view.Geometries.GetArrayLength()); Assert.Equal(2, view.InstanceCount); @@ -237,17 +264,11 @@ public void OwnVertices_WithTransform_AreBakedAndNormalsRotated() { // Rotation by 90 degrees around Z: X -> Y. var rotate = new Transform3d(Vector3d.UnitY, -Vector3d.UnitX, Vector3d.UnitZ, new Vector3d(5, 0, 0)); - var doc = new ViewerDocument(); - doc.Meshes.Add(new ViewerMesh - { - Vertices = [new(0, 0, 0), new(1, 0, 0), new(0, 0, 1)], - Indices = [0, 1, 2], - Normals = [new(0, -1, 0), new(0, -1, 0), new(0, -1, 0)], - UVs = [new(0, 0), new(1, 0), new(0, 1)], - Transform = rotate - }); + var scene = TestScenes.Empty(); + scene.Mesh([new(0, 0, 0), new(1, 0, 0), new(0, 0, 1)], [0, 1, 2], rotate, + normals: [new(0, -1, 0), new(0, -1, 0), new(0, -1, 0)], uvs: [new(0, 0), new(1, 0), new(0, 1)]); - var view = ViewerHtml.Export(doc); + var view = ViewerHtml.Export(new ViewerDocument(scene)); var geometry = view.Geometries[0]; Assert.Equal([0, 0, 0, 0, 1, 0, 0, 0, 1], view.Positions(geometry)); @@ -258,12 +279,10 @@ public void OwnVertices_WithTransform_AreBakedAndNormalsRotated() [Fact] public void MissingNormals_AreNull() { - var doc = new ViewerDocument(); - var cube = Cube(0, Vector3d.Zero); - cube.Normals = [Vector3d.UnitZ]; - doc.Meshes.Add(cube); + var scene = TestScenes.Empty(); + scene.Mesh(CubeVertices, CubeIndices, normals: [Vector3d.UnitZ]); - var geometry = ViewerHtml.Export(doc).Geometries[0]; + var geometry = ViewerHtml.Export(new ViewerDocument(scene)).Geometries[0]; Assert.Equal(System.Text.Json.JsonValueKind.Null, geometry.GetProperty("normals").ValueKind); Assert.Equal(System.Text.Json.JsonValueKind.Null, geometry.GetProperty("uvs").ValueKind); @@ -273,13 +292,13 @@ public void MissingNormals_AreNull() public void ScriptInNames_IsEscaped() { const string evil = ""; - var doc = new ViewerDocument(); - doc.Meshes.Add(new ViewerMesh { ElementId = 1, Vertices = [new(0, 0, 0), new(1, 0, 0), new(0, 1, 0)], Indices = [0, 1, 2] }); - var el = new ViewerElement { Id = 1, Name = evil }; - el.AddProperty(evil, evil, evil); - doc.Elements.Add(el); + var scene = TestScenes.Empty(); + scene.Mesh([new(0, 0, 0), new(1, 0, 0), new(0, 1, 0)], [0, 1, 2]); + scene.Elements[0].Name = evil; + scene.PropertySets.Add(new ScenePropertySet { Id = 1, Name = evil, Properties = [new SceneProperty { Name = evil, Value = SceneValue.FromText(evil) }] }); + scene.Elements[0].PropertySetIds.Add(1); - var view = ViewerHtml.Export(doc, new HtmlViewerOptions { Title = evil }); + var view = ViewerHtml.Export(new ViewerDocument(scene), new HtmlViewerOptions { Title = evil }); Assert.DoesNotContain("": { - "": "" - } - }, - "MeshIndices": [ - 0 - ], - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + "Kind": "ioviewer", + "Format": 2, + "Scene": { + "Kind": "ioscene", + "Format": 1, + "Title": "Escaping", + "Models": [ + { + "Id": 1, + "Key": "model", + "Name": "Escaping" } - }, - { - "Id": 500002, - "Name": "Plain Wall", - "Category": "Walls", - "TypeName": "Generic 200", - "Level": "Level 1", - "LinkName": null, - "Properties": { - "Identity Data": { - "Mark": "Plain Wall" - } + ], + "Materials": [], + "Textures": [], + "PropertySets": [ + { + "Id": 1, + "Name": "\u003C/script\u003E\u003Cscript\u003Ewindow.__xss=2\u003C/script\u003E", + "Properties": [ + { + "Name": "\u003C/script\u003E\u003Cscript\u003Ewindow.__xss=2\u003C/script\u003E", + "Value": { + "Kind": 0, + "Text": "\u003C/script\u003E\u003Cscript\u003Ewindow.__xss=2\u003C/script\u003E" + } + } + ] }, - "MeshIndices": [ - 1 - ], - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } - } - }, - { - "Id": 500003, - "Name": "Plain Slab", - "Category": "Walls", - "TypeName": "Generic 200", - "Level": "Level 1", - "LinkName": null, - "Properties": { - "Identity Data": { - "Mark": "Plain Slab" - } + { + "Id": 2, + "Name": "Identity Data", + "Properties": [ + { + "Name": "Mark", + "Value": { + "Kind": 0, + "Text": "Plain Wall" + } + } + ] }, - "MeshIndices": [ - 2 - ], - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + { + "Id": 3, + "Name": "Identity Data", + "Properties": [ + { + "Name": "Mark", + "Value": { + "Kind": 0, + "Text": "Plain Slab" + } + } + ] + } + ], + "Levels": [ + { + "Id": 1, + "ModelId": 1, + "Name": "Level 1" } - } - ], - "Meshes": [ - { - "Id": 0, - "Name": "", - "ElementId": 500001, - "Vertices": [ - { - "X": 2, - "Y": 0, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 2, - "Y": 0.3, - "Z": 0, - "LengthSquared": 4.09, - "Length": 2.0223748416156684 - }, - { - "X": 2, - "Y": 0.3, - "Z": 3, - "LengthSquared": 13.09, - "Length": 3.6180105030251086 - }, - { - "X": 2, - "Y": 0, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0, - "Y": 0.3, - "Z": 0, - "LengthSquared": 0.09, - "Length": 0.3 - }, - { - "X": 0, - "Y": 0, - "Z": 0, - "LengthSquared": 0, - "Length": 0 - }, - { - "X": 0, - "Y": 0, - "Z": 3, - "LengthSquared": 9, - "Length": 3 - }, - { - "X": 0, - "Y": 0.3, - "Z": 3, - "LengthSquared": 9.09, - "Length": 3.014962686336267 - }, - { - "X": 2, - "Y": 0.3, - "Z": 0, - "LengthSquared": 4.09, - "Length": 2.0223748416156684 - }, - { - "X": 0, - "Y": 0.3, - "Z": 0, - "LengthSquared": 0.09, - "Length": 0.3 - }, - { - "X": 0, - "Y": 0.3, - "Z": 3, - "LengthSquared": 9.09, - "Length": 3.014962686336267 - }, - { - "X": 2, - "Y": 0.3, - "Z": 3, - "LengthSquared": 13.09, - "Length": 3.6180105030251086 - }, - { - "X": 0, - "Y": 0, - "Z": 0, - "LengthSquared": 0, - "Length": 0 - }, - { - "X": 2, - "Y": 0, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 2, - "Y": 0, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0, - "Y": 0, - "Z": 3, - "LengthSquared": 9, - "Length": 3 - }, - { - "X": 0, - "Y": 0, - "Z": 3, - "LengthSquared": 9, - "Length": 3 - }, - { - "X": 2, - "Y": 0, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 2, - "Y": 0.3, - "Z": 3, - "LengthSquared": 13.09, - "Length": 3.6180105030251086 - }, - { - "X": 0, - "Y": 0.3, - "Z": 3, - "LengthSquared": 9.09, - "Length": 3.014962686336267 - }, - { - "X": 0, - "Y": 0.3, - "Z": 0, - "LengthSquared": 0.09, - "Length": 0.3 - }, - { - "X": 2, - "Y": 0.3, - "Z": 0, - "LengthSquared": 4.09, - "Length": 2.0223748416156684 - }, - { - "X": 2, - "Y": 0, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0, - "Y": 0, - "Z": 0, - "LengthSquared": 0, - "Length": 0 - } - ], - "Indices": [ - 0, - 1, - 2, - 0, - 2, - 3, - 4, - 5, - 6, - 4, - 6, - 7, - 8, - 9, - 10, - 8, - 10, - 11, - 12, - 13, - 14, - 12, - 14, - 15, - 16, - 17, - 18, - 16, - 18, - 19, - 20, - 21, - 22, - 20, - 22, - 23 - ], - "Normals": [ - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - } - ], - "UVs": null, - "GeometryId": null, - "Transform": null, - "MaterialId": null, - "Color": { - "R": 185, - "G": 183, - "B": 178, - "A": 255, - "Rf": 0.7254901960784313, - "Gf": 0.7176470588235294, - "Bf": 0.6980392156862745, - "Af": 1 + ], + "Geometries": [ + { + "Id": 1, + "Topology": 0, + "Vertices": [ + 2, + 0, + 0, + 2, + 0.3, + 0, + 2, + 0.3, + 3, + 2, + 0, + 3, + 0, + 0.3, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0.3, + 3, + 2, + 0.3, + 0, + 0, + 0.3, + 0, + 0, + 0.3, + 3, + 2, + 0.3, + 3, + 0, + 0, + 0, + 2, + 0, + 0, + 2, + 0, + 3, + 0, + 0, + 3, + 0, + 0, + 3, + 2, + 0, + 3, + 2, + 0.3, + 3, + 0, + 0.3, + 3, + 0, + 0.3, + 0, + 2, + 0.3, + 0, + 2, + 0, + 0, + 0, + 0, + 0 + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1 + ] }, - "Opacity": 1, - "TextureIndex": null, - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + { + "Id": 2, + "Topology": 0, + "Vertices": [ + 5, + 0, + 0, + 5, + 0.3, + 0, + 5, + 0.3, + 3, + 5, + 0, + 3, + 3, + 0.3, + 0, + 3, + 0, + 0, + 3, + 0, + 3, + 3, + 0.3, + 3, + 5, + 0.3, + 0, + 3, + 0.3, + 0, + 3, + 0.3, + 3, + 5, + 0.3, + 3, + 3, + 0, + 0, + 5, + 0, + 0, + 5, + 0, + 3, + 3, + 0, + 3, + 3, + 0, + 3, + 5, + 0, + 3, + 5, + 0.3, + 3, + 3, + 0.3, + 3, + 3, + 0.3, + 0, + 5, + 0.3, + 0, + 5, + 0, + 0, + 3, + 0, + 0 + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1 + ] + }, + { + "Id": 3, + "Topology": 0, + "Vertices": [ + 8, + 0, + 0, + 8, + 0.3, + 0, + 8, + 0.3, + 3, + 8, + 0, + 3, + 6, + 0.3, + 0, + 6, + 0, + 0, + 6, + 0, + 3, + 6, + 0.3, + 3, + 8, + 0.3, + 0, + 6, + 0.3, + 0, + 6, + 0.3, + 3, + 8, + 0.3, + 3, + 6, + 0, + 0, + 8, + 0, + 0, + 8, + 0, + 3, + 6, + 0, + 3, + 6, + 0, + 3, + 8, + 0, + 3, + 8, + 0.3, + 3, + 6, + 0.3, + 3, + 6, + 0.3, + 0, + 8, + 0.3, + 0, + 8, + 0, + 0, + 6, + 0, + 0 + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1 + ] } - }, - { - "Id": 1, - "Name": "Plain Wall", - "ElementId": 500002, - "Vertices": [ - { - "X": 5, - "Y": 0, - "Z": 0, - "LengthSquared": 25, - "Length": 5 - }, - { - "X": 5, - "Y": 0.3, - "Z": 0, - "LengthSquared": 25.09, - "Length": 5.008991914547277 - }, - { - "X": 5, - "Y": 0.3, - "Z": 3, - "LengthSquared": 34.09, - "Length": 5.838664230798 - }, - { - "X": 5, - "Y": 0, - "Z": 3, - "LengthSquared": 34, - "Length": 5.830951894845301 - }, - { - "X": 3, - "Y": 0.3, - "Z": 0, - "LengthSquared": 9.09, - "Length": 3.014962686336267 - }, - { - "X": 3, - "Y": 0, - "Z": 0, - "LengthSquared": 9, - "Length": 3 - }, - { - "X": 3, - "Y": 0, - "Z": 3, - "LengthSquared": 18, - "Length": 4.242640687119285 - }, - { - "X": 3, - "Y": 0.3, - "Z": 3, - "LengthSquared": 18.09, - "Length": 4.253234063627348 - }, - { - "X": 5, - "Y": 0.3, - "Z": 0, - "LengthSquared": 25.09, - "Length": 5.008991914547277 - }, - { - "X": 3, - "Y": 0.3, - "Z": 0, - "LengthSquared": 9.09, - "Length": 3.014962686336267 - }, - { - "X": 3, - "Y": 0.3, - "Z": 3, - "LengthSquared": 18.09, - "Length": 4.253234063627348 - }, - { - "X": 5, - "Y": 0.3, - "Z": 3, - "LengthSquared": 34.09, - "Length": 5.838664230798 - }, - { - "X": 3, - "Y": 0, - "Z": 0, - "LengthSquared": 9, - "Length": 3 - }, - { - "X": 5, - "Y": 0, - "Z": 0, - "LengthSquared": 25, - "Length": 5 - }, - { - "X": 5, - "Y": 0, - "Z": 3, - "LengthSquared": 34, - "Length": 5.830951894845301 - }, - { - "X": 3, - "Y": 0, - "Z": 3, - "LengthSquared": 18, - "Length": 4.242640687119285 - }, - { - "X": 3, - "Y": 0, - "Z": 3, - "LengthSquared": 18, - "Length": 4.242640687119285 - }, - { - "X": 5, - "Y": 0, - "Z": 3, - "LengthSquared": 34, - "Length": 5.830951894845301 - }, - { - "X": 5, - "Y": 0.3, - "Z": 3, - "LengthSquared": 34.09, - "Length": 5.838664230798 - }, - { - "X": 3, - "Y": 0.3, - "Z": 3, - "LengthSquared": 18.09, - "Length": 4.253234063627348 - }, - { - "X": 3, - "Y": 0.3, - "Z": 0, - "LengthSquared": 9.09, - "Length": 3.014962686336267 - }, - { - "X": 5, - "Y": 0.3, - "Z": 0, - "LengthSquared": 25.09, - "Length": 5.008991914547277 - }, - { - "X": 5, - "Y": 0, - "Z": 0, - "LengthSquared": 25, - "Length": 5 - }, - { - "X": 3, - "Y": 0, - "Z": 0, - "LengthSquared": 9, - "Length": 3 - } - ], - "Indices": [ - 0, - 1, - 2, - 0, - 2, - 3, - 4, - 5, - 6, - 4, - 6, - 7, - 8, - 9, - 10, - 8, - 10, - 11, - 12, - 13, - 14, - 12, - 14, - 15, - 16, - 17, - 18, - 16, - 18, - 19, - 20, - 21, - 22, - 20, - 22, - 23 - ], - "Normals": [ - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - } - ], - "UVs": null, - "GeometryId": null, - "Transform": null, - "MaterialId": null, - "Color": { - "R": 185, - "G": 183, - "B": 178, - "A": 255, - "Rf": 0.7254901960784313, - "Gf": 0.7176470588235294, - "Bf": 0.6980392156862745, - "Af": 1 + ], + "Elements": [ + { + "Id": 500001, + "ModelId": 1, + "SourceId": "\u0022\u003E\u003Csvg onload=\u0022window.__xss=3\u0022\u003E", + "Kind": 0, + "Name": "\u003Cimg src=x onerror=\u0022window.__xss=1\u0022\u003E", + "Category": "Walls", + "TypeName": "Generic 200", + "LevelId": 1, + "PropertySetIds": [ + 1 + ] + }, + { + "Id": 500002, + "ModelId": 1, + "Kind": 0, + "Name": "Plain Wall", + "Category": "Walls", + "TypeName": "Generic 200", + "LevelId": 1, + "PropertySetIds": [ + 2 + ] }, - "Opacity": 1, - "TextureIndex": null, - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + { + "Id": 500003, + "ModelId": 1, + "Kind": 0, + "Name": "Plain Slab", + "Category": "Walls", + "TypeName": "Generic 200", + "LevelId": 1, + "PropertySetIds": [ + 3 + ] } - }, - { - "Id": 2, - "Name": "Plain Slab", - "ElementId": 500003, - "Vertices": [ - { - "X": 8, - "Y": 0, - "Z": 0, - "LengthSquared": 64, - "Length": 8 - }, - { - "X": 8, - "Y": 0.3, - "Z": 0, - "LengthSquared": 64.09, - "Length": 8.005623023850173 - }, - { - "X": 8, - "Y": 0.3, - "Z": 3, - "LengthSquared": 73.09, - "Length": 8.5492689745966 - }, - { - "X": 8, - "Y": 0, - "Z": 3, - "LengthSquared": 73, - "Length": 8.54400374531753 - }, - { - "X": 6, - "Y": 0.3, - "Z": 0, - "LengthSquared": 36.09, - "Length": 6.007495318350236 - }, - { - "X": 6, - "Y": 0, - "Z": 0, - "LengthSquared": 36, - "Length": 6 - }, - { - "X": 6, - "Y": 0, - "Z": 3, - "LengthSquared": 45, - "Length": 6.708203932499369 - }, - { - "X": 6, - "Y": 0.3, - "Z": 3, - "LengthSquared": 45.09, - "Length": 6.7149087856798175 - }, - { - "X": 8, - "Y": 0.3, - "Z": 0, - "LengthSquared": 64.09, - "Length": 8.005623023850173 - }, - { - "X": 6, - "Y": 0.3, - "Z": 0, - "LengthSquared": 36.09, - "Length": 6.007495318350236 - }, - { - "X": 6, - "Y": 0.3, - "Z": 3, - "LengthSquared": 45.09, - "Length": 6.7149087856798175 - }, - { - "X": 8, - "Y": 0.3, - "Z": 3, - "LengthSquared": 73.09, - "Length": 8.5492689745966 - }, - { - "X": 6, - "Y": 0, - "Z": 0, - "LengthSquared": 36, - "Length": 6 - }, - { - "X": 8, - "Y": 0, - "Z": 0, - "LengthSquared": 64, - "Length": 8 - }, - { - "X": 8, - "Y": 0, - "Z": 3, - "LengthSquared": 73, - "Length": 8.54400374531753 - }, - { - "X": 6, - "Y": 0, - "Z": 3, - "LengthSquared": 45, - "Length": 6.708203932499369 - }, - { - "X": 6, - "Y": 0, - "Z": 3, - "LengthSquared": 45, - "Length": 6.708203932499369 - }, - { - "X": 8, - "Y": 0, - "Z": 3, - "LengthSquared": 73, - "Length": 8.54400374531753 - }, - { - "X": 8, - "Y": 0.3, - "Z": 3, - "LengthSquared": 73.09, - "Length": 8.5492689745966 - }, - { - "X": 6, - "Y": 0.3, - "Z": 3, - "LengthSquared": 45.09, - "Length": 6.7149087856798175 - }, - { - "X": 6, - "Y": 0.3, - "Z": 0, - "LengthSquared": 36.09, - "Length": 6.007495318350236 - }, - { - "X": 8, - "Y": 0.3, - "Z": 0, - "LengthSquared": 64.09, - "Length": 8.005623023850173 - }, - { - "X": 8, - "Y": 0, - "Z": 0, - "LengthSquared": 64, - "Length": 8 - }, - { - "X": 6, - "Y": 0, - "Z": 0, - "LengthSquared": 36, - "Length": 6 - } - ], - "Indices": [ - 0, - 1, - 2, - 0, - 2, - 3, - 4, - 5, - 6, - 4, - 6, - 7, - 8, - 9, - 10, - 8, - 10, - 11, - 12, - 13, - 14, - 12, - 14, - 15, - 16, - 17, - 18, - 16, - 18, - 19, - 20, - 21, - 22, - 20, - 22, - 23 - ], - "Normals": [ - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - } - ], - "UVs": null, - "GeometryId": null, - "Transform": null, - "MaterialId": null, - "Color": { - "R": 185, - "G": 183, - "B": 178, - "A": 255, - "Rf": 0.7254901960784313, - "Gf": 0.7176470588235294, - "Bf": 0.6980392156862745, - "Af": 1 + ], + "Instances": [ + { + "ElementId": 500001, + "GeometryId": 1, + "Color": [ + 185, + 183, + 178, + 255 + ], + "Name": "\u003Cimg src=x onerror=\u0022window.__xss=1\u0022\u003E" }, - "Opacity": 1, - "TextureIndex": null, - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + { + "ElementId": 500002, + "GeometryId": 2, + "Color": [ + 185, + 183, + 178, + 255 + ], + "Name": "Plain Wall" + }, + { + "ElementId": 500003, + "GeometryId": 3, + "Color": [ + 185, + 183, + 178, + 255 + ], + "Name": "Plain Slab" } - } - ], - "Geometries": [], - "Materials": [], - "Textures": [], - "Grids": null, - "Viewpoints": [], + ], + "Grids": [], + "Viewpoints": [], + "Schedules": [], + "ClashTests": [] + }, "SectionPlanes": [], - "SectionBox": null, - "TimeLiner": null, - "ClashTests": [], - "LinkedFiles": [] + "HiddenModelKeys": [] } \ No newline at end of file diff --git a/tests/fixtures/viewer/generate-fixtures.cs b/tests/fixtures/viewer/generate-fixtures.cs index 71866cb..c265aa6 100644 --- a/tests/fixtures/viewer/generate-fixtures.cs +++ b/tests/fixtures/viewer/generate-fixtures.cs @@ -2,6 +2,7 @@ #:project ../../../src/IOViewer/IOViewer.csproj #:property PublishAot=false #:property JsonSerializerIsReflectionEnabledByDefault=true +using IOScene; using IOViewer.Exporters; using IOViewer.Importers; using IOViewer.Model; @@ -15,10 +16,15 @@ string outDir = args[0]; Directory.CreateDirectory(outDir); -var doc = new ViewerDocument("Two-Storey Frame Building") { Author = "IOViewer", CreatedAt = "2026-11-11T12:00:00Z" }; +var scene = new SceneDocument { Title = "Two-Storey Frame Building" }; +// The host model, then the two linked files; elements of a linked file belong to its model. +scene.Models.Add(new SceneModel { Id = 1, Key = "model", Name = scene.Title, Author = "IOViewer", CreatedAt = "2026-11-11T12:00:00Z" }); +const int host = 1, structure = 2, mep = 3; +scene.Models.Add(new SceneModel { Id = structure, Key = "Structure.rvt", Name = "Structure", Path = "Structure.rvt" }); +scene.Models.Add(new SceneModel { Id = mep, Key = "MEP.rvt", Name = "MEP", Path = "MEP.rvt" }); var concrete = new RgbaColor(185, 183, 178); var steel = new RgbaColor(110, 122, 140); -var glass = new RgbaColor(150, 200, 230); +var glass = new RgbaColor(150, 200, 230, 89); // 35 % opaque var mullion = new RgbaColor(80, 82, 88); var ductColor = new RgbaColor(205, 215, 225); @@ -29,49 +35,49 @@ const double storey = 3.6, slab = 0.2, beamDepth = 0.3, beamWidth = 0.2; long nextId = 300001; -int nextMesh = 0; -ViewerElement Element(string name, string category, string type, string level, string? link) +var levels = new Dictionary<(int, string), int>(); +SceneElement Element(string name, string category, string type, string level, int model) { - var e = new ViewerElement { Id = nextId++, Name = name, Category = category, TypeName = type, Level = level, LinkName = link }; - e.AddProperty("Identity Data", "Mark", name); - e.AddProperty("Constraints", "Level", level); - doc.Elements.Add(e); + if (!levels.TryGetValue((model, level), out int levelId)) + { + levelId = scene.Levels.Count + 1; + scene.Levels.Add(new SceneLevel { Id = levelId, ModelId = model, Name = level }); + levels.Add((model, level), levelId); + } + var e = new SceneElement { Id = nextId++, ModelId = model, Name = name, Category = category, TypeName = type, LevelId = levelId }; + scene.Elements.Add(e); + Property(scene, e, "Identity Data", "Mark", name); + Property(scene, e, "Constraints", "Level", level); return e; } -void AddMesh(ViewerElement? e, ViewerMesh m) -{ - m.Id = nextMesh; - m.ElementId = e?.Id; - e?.MeshIndices.Add(nextMesh); - doc.Meshes.Add(m); - nextMesh++; -} +void Place(SceneElement e, int geometry, Transform3d? transform, RgbaColor color) => + scene.Instances.Add(new SceneInstance { ElementId = e.Id, GeometryId = geometry, Transform = transform, Color = color, Name = e.Name }); // Shared geometries: column (0.4 x 0.4 x storey-slab, centred in XY) and a 6 m beam along X (centred in Y). var (cv, ci, cn) = Box(new(-0.2, -0.2, 0), new(0.2, 0.2, storey - slab)); -doc.Geometries.Add(new ViewerGeometry { Id = 1, Vertices = cv, Indices = ci, Normals = cn }); +scene.Geometries.Add(new SceneGeometry { Id = 1, Vertices = cv, Indices = ci, Normals = cn }); var (bv, bi, bn) = Box(new(0, -beamWidth / 2, -beamDepth), new(6, beamWidth / 2, 0)); -doc.Geometries.Add(new ViewerGeometry { Id = 2, Vertices = bv, Indices = bi, Normals = bn }); +scene.Geometries.Add(new SceneGeometry { Id = 2, Vertices = bv, Indices = bi, Normals = bn }); -ViewerElement groundSlab = Element("Ground Slab", "Floors", "Concrete 300", "Level 1", null); -AddBox(groundSlab, new(-1, -1, -0.3), new(19, 13, 0), concrete); +SceneElement groundSlab = Element("Ground Slab", "Floors", "Concrete 300", "Level 1", host); +AddBox(scene, groundSlab, new(-1, -1, -0.3), new(19, 13, 0), concrete); -var levels = new[] { ("Level 1", 0.0), ("Level 2", storey) }; +var storeys = new[] { ("Level 1", 0.0), ("Level 2", storey) }; var columnsByLevel = new List[2]; var beamsByLevel = new List[2]; -var clashBeams = new List(); +var clashBeams = new List(); for (int l = 0; l < 2; l++) { - var (levelName, z0) = levels[l]; + var (levelName, z0) = storeys[l]; columnsByLevel[l] = []; beamsByLevel[l] = []; for (int i = 0; i < xs.Length; i++) { for (int j = 0; j < ys.Length; j++) { - var c = Element($"Column {xNames[i]}{yNames[j]} {levelName}", "Structural Columns", "Concrete 400x400", levelName, "Structure.rvt"); - c.AddProperty("Dimensions", "Section", "400 x 400 mm"); - AddMesh(c, new ViewerMesh { Name = c.Name, GeometryId = 1, Transform = Transform3d.Translate(new(xs[i], ys[j], z0)), Color = concrete }); + var c = Element($"Column {xNames[i]}{yNames[j]} {levelName}", "Structural Columns", "Concrete 400x400", levelName, structure); + Property(scene, c, "Dimensions", "Section", "400 x 400 mm"); + Place(c, 1, Transform3d.Translate(new(xs[i], ys[j], z0)), concrete); columnsByLevel[l].Add(c.Id); } } @@ -82,8 +88,8 @@ void AddMesh(ViewerElement? e, ViewerMesh m) { for (int i = 0; i < xs.Length - 1; i++) { - var b = Element($"Beam {xNames[i]}-{xNames[i + 1]}/{yNames[j]} {beamLevel}", "Structural Framing", "Steel I 300", beamLevel, "Structure.rvt"); - AddMesh(b, new ViewerMesh { Name = b.Name, GeometryId = 2, Transform = Transform3d.Translate(new(xs[i], ys[j], top)), Color = steel }); + var b = Element($"Beam {xNames[i]}-{xNames[i + 1]}/{yNames[j]} {beamLevel}", "Structural Framing", "Steel I 300", beamLevel, structure); + Place(b, 2, Transform3d.Translate(new(xs[i], ys[j], top)), steel); beamsByLevel[l].Add(b.Id); if (l == 0 && i == 1) clashBeams.Add(b); } @@ -93,69 +99,65 @@ void AddMesh(ViewerElement? e, ViewerMesh m) { for (int j = 0; j < ys.Length - 1; j++) { - var b = Element($"Beam {xNames[i]}/{yNames[j]}-{yNames[j + 1]} {beamLevel}", "Structural Framing", "Steel I 300", beamLevel, "Structure.rvt"); - AddMesh(b, new ViewerMesh { Name = b.Name, GeometryId = 2, Transform = alongY.Then(Transform3d.Translate(new(xs[i], ys[j], top))), Color = steel }); + var b = Element($"Beam {xNames[i]}/{yNames[j]}-{yNames[j + 1]} {beamLevel}", "Structural Framing", "Steel I 300", beamLevel, structure); + Place(b, 2, alongY.Then(Transform3d.Translate(new(xs[i], ys[j], top))), steel); beamsByLevel[l].Add(b.Id); } } } -var slab2 = Element("Floor Slab Level 2", "Floors", "Concrete 200", "Level 2", null); -AddBox(slab2, new(-0.5, -0.5, storey - slab), new(18.5, 12.5, storey), concrete); -var roof = Element("Roof Slab", "Roofs", "Concrete 200", "Roof", null); -AddBox(roof, new(-0.5, -0.5, 2 * storey - slab), new(18.5, 12.5, 2 * storey), concrete); +var slab2 = Element("Floor Slab Level 2", "Floors", "Concrete 200", "Level 2", host); +AddBox(scene, slab2, new(-0.5, -0.5, storey - slab), new(18.5, 12.5, storey), concrete); +var roof = Element("Roof Slab", "Roofs", "Concrete 200", "Roof", host); +AddBox(scene, roof, new(-0.5, -0.5, 2 * storey - slab), new(18.5, 12.5, 2 * storey), concrete); // Element made of two meshes: opaque mullion frame + transparent glazing. -var curtain = Element("Curtain Wall South", "Curtain Walls", "Storefront", "Level 1", null); -curtain.AddProperty("Materials", "Glazing", "Clear glass 35 %"); -AddBox(curtain, new(0, -0.75, 0), new(18, -0.65, 0.1), mullion); -AddBox(curtain, new(0, -0.72, 0.1), new(18, -0.68, 2 * storey), glass, 0.35); +var curtain = Element("Curtain Wall South", "Curtain Walls", "Storefront", "Level 1", host); +Property(scene, curtain, "Materials", "Glazing", "Clear glass 35 %"); +AddBox(scene, curtain, new(0, -0.75, 0), new(18, -0.65, 0.1), mullion); +AddBox(scene, curtain, new(0, -0.72, 0.1), new(18, -0.68, 2 * storey), glass); -var duct = Element("Supply Duct SA-1", "Ducts", "Rectangular 600x400", "Level 2", "MEP.rvt"); +var duct = Element("Supply Duct SA-1", "Ducts", "Rectangular 600x400", "Level 2", mep); duct.SourceId = "2xQ$n5SLP5MBLyL442paFx"; // IFC GlobalId style -duct.AddProperty("Mechanical", "System", "Supply Air"); -AddBox(duct, new(8.7, -1, storey - slab - 0.4), new(9.3, 13, storey - slab), ductColor); +Property(scene, duct, "Mechanical", "System", "Supply Air"); +AddBox(scene, duct, new(8.7, -1, storey - slab - 0.4), new(9.3, 13, storey - slab), ductColor); -// Mesh without ElementId and without normals: a survey marker pyramid. -AddMesh(null, new ViewerMesh +// A survey marker pyramid without normals, on an element with nothing but a name, as a 0.2 snapshot's mesh without +// an element now loads. +var marker = new SceneElement { Id = nextId++, ModelId = host, Name = "Survey Marker" }; +scene.Elements.Add(marker); +scene.Geometries.Add(new SceneGeometry { - Name = "Survey Marker", + Id = scene.Geometries.Count + 1, Vertices = [new(-3.3, -3.3, 0), new(-2.7, -3.3, 0), new(-2.7, -2.7, 0), new(-3.3, -2.7, 0), new(-3, -3, 0.8)], Indices = [0, 2, 1, 0, 3, 2, 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4], - Color = new RgbaColor(220, 60, 40) }); +Place(marker, scene.Geometries.Count, null, new RgbaColor(220, 60, 40)); // Twin columns in front of the building; the second is mirrored (scale -1 along X) and must shade like the first. -var twin = Element("Column Twin", "Structural Columns", "Concrete 400x400", "Level 1", "Structure.rvt"); -AddMesh(twin, new ViewerMesh { Name = twin.Name, GeometryId = 1, Transform = Transform3d.Translate(new(22, -3, 0)), Color = concrete }); -var mirroredTwin = Element("Column Twin Mirrored", "Structural Columns", "Concrete 400x400", "Level 1", "Structure.rvt"); -AddMesh(mirroredTwin, new ViewerMesh -{ - Name = mirroredTwin.Name, - GeometryId = 1, - Transform = new Transform3d(-Vector3d.UnitX, Vector3d.UnitY, Vector3d.UnitZ, new(24, -3, 0)), - Color = concrete -}); +var twin = Element("Column Twin", "Structural Columns", "Concrete 400x400", "Level 1", structure); +Place(twin, 1, Transform3d.Translate(new(22, -3, 0)), concrete); +var mirroredTwin = Element("Column Twin Mirrored", "Structural Columns", "Concrete 400x400", "Level 1", structure); +Place(mirroredTwin, 1, new Transform3d(-Vector3d.UnitX, Vector3d.UnitY, Vector3d.UnitZ, new(24, -3, 0)), concrete); -var grids = new ViewerGridSystem { Name = "Structural Grids" }; -for (int i = 0; i < xs.Length; i++) grids.Lines.Add(new ViewerGridLine(xNames[i], new(xs[i], -2, 0), new(xs[i], 14, 0))); -for (int j = 0; j < ys.Length; j++) grids.Lines.Add(new ViewerGridLine(yNames[j], new(-2, ys[j], 0), new(20, ys[j], 0))); -doc.Grids = grids; +var grids = new SceneGridSystem { ModelId = host, Name = "Structural Grids" }; +for (int i = 0; i < xs.Length; i++) grids.Lines.Add(new SceneGridLine { Name = xNames[i], Start = new(xs[i], -2, 0), End = new(xs[i], 14, 0) }); +for (int j = 0; j < ys.Length; j++) grids.Lines.Add(new SceneGridLine { Name = yNames[j], Start = new(-2, ys[j], 0), End = new(20, ys[j], 0) }); +scene.Grids.Add(grids); -doc.Viewpoints.Add(new ViewerViewpoint("Isometric", new ViewerCamera { Position = new(36, -24, 24), Target = new(9, 6, 3.6) })); -doc.Viewpoints.Add(new ViewerViewpoint("Front", new ViewerCamera { Position = new(9, -40, 3.6), Target = new(9, 6, 3.6) })); -doc.Viewpoints.Add(new ViewerViewpoint("Top", new ViewerCamera { Position = new(9, 6, 50), Target = new(9, 6, 0), Up = Vector3d.UnitY })); -doc.Viewpoints.Add(new ViewerViewpoint("Duct Clash", new ViewerCamera { Position = new(14, -4, 5), Target = new(9, 3, 3.2) })); -doc.Viewpoints.Add(new ViewerViewpoint("Plan", new ViewerCamera { Position = new(9, 6, 40), Target = new(9, 6, 0), Projection = ViewerProjection.Orthographic })); +scene.Viewpoints.Add(new SceneViewpoint { Name = "Isometric", Camera = new SceneCamera { Position = new(36, -24, 24), Target = new(9, 6, 3.6) } }); +scene.Viewpoints.Add(new SceneViewpoint { Name = "Front", Camera = new SceneCamera { Position = new(9, -40, 3.6), Target = new(9, 6, 3.6) } }); +scene.Viewpoints.Add(new SceneViewpoint { Name = "Top", Camera = new SceneCamera { Position = new(9, 6, 50), Target = new(9, 6, 0), Up = Vector3d.UnitY } }); +scene.Viewpoints.Add(new SceneViewpoint { Name = "Duct Clash", Camera = new SceneCamera { Position = new(14, -4, 5), Target = new(9, 3, 3.2) } }); +scene.Viewpoints.Add(new SceneViewpoint { Name = "Plan", Camera = new SceneCamera { Position = new(9, 6, 40), Target = new(9, 6, 0), Projection = SceneProjection.Orthographic } }); -var schedule = new ViewerTimeLinerSchedule("Construction Schedule"); +var schedule = new SceneSchedule { Name = "Construction Schedule" }; DateTimeOffset start = new(2026, 3, 2, 8, 0, 0, TimeSpan.Zero); -void Task(string id, string name, int fromDay, int days, IEnumerable ids) +void Task(string id, string name, int fromDay, int days, IEnumerable ids) => schedule.Tasks.Add(new SceneTask { - var t = new ViewerTimeLinerTask(id, name, start.AddDays(fromDay), start.AddDays(fromDay + days)) { PlannedCost = 10000m * days }; - t.AttachedElementIds.AddRange(ids); - schedule.Tasks.Add(t); -} + Id = id, Name = name, PlannedStart = start.AddDays(fromDay), PlannedEnd = start.AddDays(fromDay + days), PlannedCost = 10000m * days, + ElementIds = [.. ids], +}); Task("T1", "Ground slab", 0, 10, [groundSlab.Id]); Task("T2", "Columns Level 1", 10, 8, columnsByLevel[0]); Task("T3", "Beams and slab Level 2", 18, 12, [.. beamsByLevel[0], slab2.Id]); @@ -163,73 +165,94 @@ void Task(string id, string name, int fromDay, int days, IEnumerable ids) Task("T5", "Beams and roof slab", 38, 12, [.. beamsByLevel[1], roof.Id]); Task("T6", "Curtain wall", 50, 10, [curtain.Id]); Task("T7", "Supply duct", 55, 5, [duct.Id]); -doc.TimeLiner = schedule; +scene.Schedules.Add(schedule); -var clashTest = new ViewerClashTest("Duct vs Structure"); +var clashTest = new SceneClashTest { Name = "Duct vs Structure" }; for (int k = 0; k < clashBeams.Count; k++) { var beam = clashBeams[k]; - clashTest.Clashes.Add(new ViewerClashResult($"CL{k + 1}", $"Clash {k + 1}", duct.Name, beam.Name, - new(9, ys[k], storey - slab - 0.15), -0.15) - { Element1Id = duct.Id, Element2Id = beam.Id, Status = ViewerClashStatus.New }); + clashTest.Clashes.Add(new SceneClashResult + { + Id = $"CL{k + 1}", Name = $"Clash {k + 1}", Element1Name = duct.Name, Element2Name = beam.Name, + Point = new(9, ys[k], storey - slab - 0.15), Distance = -0.15, Element1Id = duct.Id, Element2Id = beam.Id, Status = SceneClashStatus.New, + }); } -doc.ClashTests.Add(clashTest); - -doc.LinkedFiles.Add(new ViewerLinkedFile("Structure", "Structure.rvt")); -doc.LinkedFiles.Add(new ViewerLinkedFile("MEP", "MEP.rvt")); +scene.ClashTests.Add(clashTest); -string buildingPath = Path.Combine(outDir, "building.json"); -JsonViewerExporter.Save(doc, buildingPath); -JsonViewerExporter.Save(new ViewerDocument("Empty Model"), Path.Combine(outDir, "empty.json")); -JsonViewerExporter.Save(Escaping(), Path.Combine(outDir, "escaping.json")); -JsonViewerExporter.Save(Textured(), Path.Combine(outDir, "textured.json")); +var empty = new SceneDocument { Title = "Empty Model" }; +empty.Models.Add(new SceneModel { Id = 1, Key = "model", Name = empty.Title }); +var files = new (string Name, SceneDocument Scene)[] { ("building", scene), ("empty", empty), ("escaping", Escaping()), ("textured", Textured()) }; +foreach (var (name, document) in files) +{ + var issues = document.Validate(); + if (issues.Count > 0) throw new InvalidOperationException($"{name}: {string.Join("; ", issues.Select(i => $"{i.Code} {i.Message}"))}"); + JsonViewerExporter.Save(new ViewerDocument(document), Path.Combine(outDir, $"{name}.json")); +} // Round-trip check: everything the viewer needs must survive JsonViewerImporter. -var back = JsonViewerImporter.Load(buildingPath); -Console.WriteLine($"elements {back.Elements.Count}, meshes {back.Meshes.Count}, geometries {back.Geometries.Count}, " + - $"grid lines {back.Grids?.Lines.Count}, viewpoints {back.Viewpoints.Count}, tasks {back.TimeLiner?.Tasks.Count}, " + - $"task0 attached {back.TimeLiner?.Tasks.FirstOrDefault()?.AttachedElementIds.Count}, clashes {back.ClashTests[0].Clashes.Count}, clash0 ids {back.ClashTests[0].Clashes.FirstOrDefault()?.Element1Id}/{back.ClashTests[0].Clashes.FirstOrDefault()?.Element2Id}, " + - $"linked {back.LinkedFiles.Count}, props0 {back.Elements[0].Properties.Count}, no-element meshes {back.Meshes.Count(m => m.ElementId is null)}"); +var back = JsonViewerImporter.Load(Path.Combine(outDir, "building.json")).Scene; +Console.WriteLine($"models {back.Models.Count}, elements {back.Elements.Count}, instances {back.Instances.Count}, geometries {back.Geometries.Count}, " + + $"levels {back.Levels.Count}, property sets {back.PropertySets.Count}, grid lines {back.Grids.Sum(g => g.Lines.Count)}, viewpoints {back.Viewpoints.Count}, " + + $"tasks {back.Schedules.Sum(s => s.Tasks.Count)}, task0 elements {back.Schedules[0].Tasks[0].ElementIds.Count}, clashes {back.ClashTests[0].Clashes.Count}, " + + $"clash0 ids {back.ClashTests[0].Clashes[0].Element1Id}/{back.ClashTests[0].Clashes[0].Element2Id}, validate {back.Validate().Count}"); return 0; -void AddBox(ViewerElement e, Vector3d min, Vector3d max, RgbaColor color, double opacity = 1) +static void Property(SceneDocument scene, SceneElement e, string group, string name, string value) +{ + var set = scene.PropertySets.Find(s => s.Name == group && e.PropertySetIds.Contains(s.Id)); + if (set is null) + { + set = new ScenePropertySet { Id = scene.PropertySets.Count + 1, Name = group }; + scene.PropertySets.Add(set); + e.PropertySetIds.Add(set.Id); + } + set.Properties.Add(new SceneProperty { Name = name, Value = SceneValue.FromText(value) }); +} + +// A box drawn by its own geometry, placed once. +static void AddBox(SceneDocument scene, SceneElement e, Vector3d min, Vector3d max, RgbaColor color) { var (v, i, n) = Box(min, max); - AddMesh(e, new ViewerMesh { Name = e.Name, Vertices = v, Indices = i, Normals = n, Color = color, Opacity = opacity }); + var geometry = new SceneGeometry { Id = scene.Geometries.Count + 1, Vertices = v, Indices = i, Normals = n }; + scene.Geometries.Add(geometry); + scene.Instances.Add(new SceneInstance { ElementId = e.Id, GeometryId = geometry.Id, Color = color, Name = e.Name }); } // Hostile markup in an element name, source id and in a property group, name and value: the viewer must show it as text. // No clashes, 4D, links or viewpoints, so the panels for them stay hidden. -ViewerDocument Escaping() +SceneDocument Escaping() { const string script = ""; const string sourceId = "\">"; - var hostile = new ViewerDocument("Escaping"); + var hostile = new SceneDocument { Title = "Escaping" }; + hostile.Models.Add(new SceneModel { Id = 1, Key = "model", Name = hostile.Title }); + hostile.Levels.Add(new SceneLevel { Id = 1, ModelId = 1, Name = "Level 1" }); string[] names = ["", "Plain Wall", "Plain Slab"]; for (int k = 0; k < names.Length; k++) { - var e = new ViewerElement { Id = 500001 + k, Name = names[k], Category = "Walls", TypeName = "Generic 200", Level = "Level 1" }; + var e = new SceneElement { Id = 500001 + k, ModelId = 1, Name = names[k], Category = "Walls", TypeName = "Generic 200", LevelId = 1 }; + hostile.Elements.Add(e); if (k == 0) { - e.AddProperty(script, script, script); + Property(hostile, e, script, script, script); e.SourceId = sourceId; } - else e.AddProperty("Identity Data", "Mark", names[k]); - var (v, i, n) = Box(new(k * 3, 0, 0), new(k * 3 + 2, 0.3, 3)); - e.MeshIndices.Add(k); - hostile.Elements.Add(e); - hostile.Meshes.Add(new ViewerMesh { Id = k, Name = names[k], ElementId = e.Id, Vertices = v, Indices = i, Normals = n, Color = concrete }); + else Property(hostile, e, "Identity Data", "Mark", names[k]); + AddBox(hostile, e, new(k * 3, 0, 0), new(k * 3 + 2, 0.3, 3), concrete); } return hostile; } // A smooth, checker-textured cylinder (cylindrical UVs in metres, 0.5 m per checker repeat) next to an untextured box, // for the textures display mode and smooth shading. -ViewerDocument Textured() +SceneDocument Textured() { - var textured = new ViewerDocument("Textured"); + var textured = new SceneDocument { Title = "Textured" }; + textured.Models.Add(new SceneModel { Id = 1, Key = "model", Name = textured.Title }); + textured.Levels.Add(new SceneLevel { Id = 1, ModelId = 1, Name = "Level 1" }); textured.Textures.Add(Checker()); + textured.Materials.Add(new SceneMaterial { Id = 1, Name = "Checker", Color = concrete, TextureId = 1 }); const int sides = 48; const double radius = 2, height = 3; var v = new List(); @@ -248,20 +271,18 @@ ViewerDocument Textured() } if (k < sides) idx.AddRange([k * 2, k * 2 + 2, k * 2 + 3, k * 2, k * 2 + 3, k * 2 + 1]); } - var tank = new ViewerElement { Id = 600001, Name = "Textured Tank", Category = "Equipment", TypeName = "Tank", Level = "Level 1" }; - tank.MeshIndices.Add(0); + var tank = new SceneElement { Id = 600001, ModelId = 1, Name = "Textured Tank", Category = "Equipment", TypeName = "Tank", LevelId = 1 }; textured.Elements.Add(tank); - textured.Meshes.Add(new ViewerMesh { Id = 0, Name = tank.Name, ElementId = tank.Id, Vertices = v, Indices = idx, Normals = n, UVs = uv, Color = concrete, TextureIndex = 0 }); - var plain = new ViewerElement { Id = 600002, Name = "Plain Box", Category = "Walls", TypeName = "Generic 200", Level = "Level 1" }; - plain.MeshIndices.Add(1); + textured.Geometries.Add(new SceneGeometry { Id = 1, Vertices = v, Indices = idx, Normals = n, UVs = uv }); + textured.Instances.Add(new SceneInstance { ElementId = tank.Id, GeometryId = 1, MaterialId = 1, Name = tank.Name }); + var plain = new SceneElement { Id = 600002, ModelId = 1, Name = "Plain Box", Category = "Walls", TypeName = "Generic 200", LevelId = 1 }; textured.Elements.Add(plain); - var (bv, bi, bn) = Box(new(3, -1, 0), new(5, 1, 2)); - textured.Meshes.Add(new ViewerMesh { Id = 1, Name = plain.Name, ElementId = plain.Id, Vertices = bv, Indices = bi, Normals = bn, Color = steel }); + AddBox(textured, plain, new(3, -1, 0), new(5, 1, 2), steel); return textured; } // 64 x 64 checker of 8 x 8 pixel squares as a 24-bit BMP, 0.5 m per repeat. -static ViewerTexture Checker() +static SceneTexture Checker() { const int size = 64, header = 54, pixels = size * size * 3; var bmp = new byte[header + pixels]; @@ -283,7 +304,7 @@ static ViewerTexture Checker() bmp.AsSpan(header + (y * size + x) * 3, 3).Fill(value); // rows of 192 bytes need no padding } } - return new ViewerTexture { MimeType = "image/bmp", Base64Data = Convert.ToBase64String(bmp), ScaleX = 0.5, ScaleY = 0.5 }; + return new SceneTexture { Id = 1, MimeType = "image/bmp", Data = bmp, ScaleX = 0.5, ScaleY = 0.5 }; } // Axis-aligned box with 4 vertices per face so each face has its own flat normal. diff --git a/tests/fixtures/viewer/textured.json b/tests/fixtures/viewer/textured.json index fd08b4f..927ecee 100644 --- a/tests/fixtures/viewer/textured.json +++ b/tests/fixtures/viewer/textured.json @@ -1,2687 +1,1371 @@ { - "Title": "Textured", - "SourcePath": null, - "Author": null, - "CreatedAt": null, - "Application": null, - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } - }, - "Elements": [ - { - "Id": 600001, - "Name": "Textured Tank", - "Category": "Equipment", - "TypeName": "Tank", - "Level": "Level 1", - "LinkName": null, - "Properties": {}, - "MeshIndices": [ - 0 - ], - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + "Kind": "ioviewer", + "Format": 2, + "Scene": { + "Kind": "ioscene", + "Format": 1, + "Title": "Textured", + "Models": [ + { + "Id": 1, + "Key": "model", + "Name": "Textured" + } + ], + "Materials": [ + { + "Id": 1, + "Name": "Checker", + "Color": [ + 185, + 183, + 178, + 255 + ], + "Roughness": 0.5, + "Metalness": 0.1, + "TextureId": 1 + } + ], + "Textures": [ + { + "Id": 1, + "MimeType": "image/bmp", + "Data": "Qk02MAAAAAAAADYAAAAoAAAAQAAAAEAAAAABABgAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAA6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr", + "ScaleX": 0.5, + "ScaleY": 0.5 } - }, - { - "Id": 600002, - "Name": "Plain Box", - "Category": "Walls", - "TypeName": "Generic 200", - "Level": "Level 1", - "LinkName": null, - "Properties": {}, - "MeshIndices": [ - 1 - ], - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + ], + "PropertySets": [], + "Levels": [ + { + "Id": 1, + "ModelId": 1, + "Name": "Level 1" } - } - ], - "Meshes": [ - { - "Id": 0, - "Name": "Textured Tank", - "ElementId": 600001, - "Vertices": [ - { - "X": 2, - "Y": 0, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 2, - "Y": 0, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.9828897227476208, - "Y": 0.26105238444010315, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.9828897227476208, - "Y": 0.26105238444010315, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.9318516525781366, - "Y": 0.5176380902050415, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.9318516525781366, - "Y": 0.5176380902050415, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.8477590650225735, - "Y": 0.7653668647301796, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.8477590650225735, - "Y": 0.7653668647301796, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.7320508075688774, - "Y": 0.9999999999999999, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.7320508075688774, - "Y": 0.9999999999999999, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.5867066805824703, - "Y": 1.2175228580174413, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.5867066805824703, - "Y": 1.2175228580174413, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.4142135623730951, - "Y": 1.4142135623730951, - "Z": 0, - "LengthSquared": 4.000000000000001, - "Length": 2 - }, - { - "X": 1.4142135623730951, - "Y": 1.4142135623730951, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.2175228580174413, - "Y": 1.5867066805824703, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.2175228580174413, - "Y": 1.5867066805824703, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.0000000000000002, - "Y": 1.7320508075688772, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.0000000000000002, - "Y": 1.7320508075688772, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0.7653668647301797, - "Y": 1.8477590650225735, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0.7653668647301797, - "Y": 1.8477590650225735, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0.5176380902050415, - "Y": 1.9318516525781366, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0.5176380902050415, - "Y": 1.9318516525781366, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0.2610523844401034, - "Y": 1.9828897227476208, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0.2610523844401034, - "Y": 1.9828897227476208, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.2246467991473532E-16, - "Y": 2, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.2246467991473532E-16, - "Y": 2, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.2610523844401032, - "Y": 1.9828897227476208, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -0.2610523844401032, - "Y": 1.9828897227476208, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.5176380902050413, - "Y": 1.9318516525781366, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -0.5176380902050413, - "Y": 1.9318516525781366, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.765366864730179, - "Y": 1.8477590650225737, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -0.765366864730179, - "Y": 1.8477590650225737, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.9999999999999996, - "Y": 1.7320508075688774, - "Z": 0, - "LengthSquared": 3.9999999999999996, - "Length": 1.9999999999999998 - }, - { - "X": -0.9999999999999996, - "Y": 1.7320508075688774, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.2175228580174413, - "Y": 1.5867066805824703, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.2175228580174413, - "Y": 1.5867066805824703, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.414213562373095, - "Y": 1.4142135623730951, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.414213562373095, - "Y": 1.4142135623730951, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.58670668058247, - "Y": 1.2175228580174418, - "Z": 0, - "LengthSquared": 4.000000000000001, - "Length": 2 - }, - { - "X": -1.58670668058247, - "Y": 1.2175228580174418, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.7320508075688774, - "Y": 0.9999999999999999, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.7320508075688774, - "Y": 0.9999999999999999, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.8477590650225735, - "Y": 0.7653668647301798, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.8477590650225735, - "Y": 0.7653668647301798, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.9318516525781364, - "Y": 0.517638090205042, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.9318516525781364, - "Y": 0.517638090205042, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.9828897227476208, - "Y": 0.261052384440104, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.9828897227476208, - "Y": 0.261052384440104, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -2, - "Y": 2.4492935982947064E-16, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -2, - "Y": 2.4492935982947064E-16, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.9828897227476208, - "Y": -0.26105238444010354, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.9828897227476208, - "Y": -0.26105238444010354, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.9318516525781366, - "Y": -0.5176380902050416, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.9318516525781366, - "Y": -0.5176380902050416, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.8477590650225737, - "Y": -0.7653668647301793, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.8477590650225737, - "Y": -0.7653668647301793, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.7320508075688776, - "Y": -0.9999999999999994, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.7320508075688776, - "Y": -0.9999999999999994, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.5867066805824703, - "Y": -1.2175228580174413, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.5867066805824703, - "Y": -1.2175228580174413, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.4142135623730958, - "Y": -1.4142135623730943, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.4142135623730958, - "Y": -1.4142135623730943, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.2175228580174418, - "Y": -1.5867066805824699, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.2175228580174418, - "Y": -1.5867066805824699, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -1.0000000000000009, - "Y": -1.732050807568877, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -1.0000000000000009, - "Y": -1.732050807568877, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.765366864730179, - "Y": -1.8477590650225737, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -0.765366864730179, - "Y": -1.8477590650225737, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.5176380902050413, - "Y": -1.9318516525781366, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -0.5176380902050413, - "Y": -1.9318516525781366, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -0.26105238444010326, - "Y": -1.9828897227476208, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -0.26105238444010326, - "Y": -1.9828897227476208, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": -3.6739403974420594E-16, - "Y": -2, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": -3.6739403974420594E-16, - "Y": -2, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0.26105238444010254, - "Y": -1.982889722747621, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0.26105238444010254, - "Y": -1.982889722747621, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0.5176380902050406, - "Y": -1.9318516525781368, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0.5176380902050406, - "Y": -1.9318516525781368, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 0.7653668647301783, - "Y": -1.847759065022574, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 0.7653668647301783, - "Y": -1.847759065022574, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.0000000000000002, - "Y": -1.7320508075688772, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.0000000000000002, - "Y": -1.7320508075688772, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.2175228580174398, - "Y": -1.5867066805824714, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.2175228580174398, - "Y": -1.5867066805824714, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.4142135623730947, - "Y": -1.4142135623730954, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.4142135623730947, - "Y": -1.4142135623730954, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.5867066805824699, - "Y": -1.217522858017442, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.5867066805824699, - "Y": -1.217522858017442, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.7320508075688767, - "Y": -1.0000000000000009, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.7320508075688767, - "Y": -1.0000000000000009, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.8477590650225737, - "Y": -0.7653668647301791, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.8477590650225737, - "Y": -0.7653668647301791, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.9318516525781362, - "Y": -0.5176380902050431, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.9318516525781362, - "Y": -0.5176380902050431, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 1.9828897227476208, - "Y": -0.26105238444010337, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 1.9828897227476208, - "Y": -0.26105238444010337, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - }, - { - "X": 2, - "Y": -4.898587196589413E-16, - "Z": 0, - "LengthSquared": 4, - "Length": 2 - }, - { - "X": 2, - "Y": -4.898587196589413E-16, - "Z": 3, - "LengthSquared": 13, - "Length": 3.605551275463989 - } - ], - "Indices": [ - 0, - 2, - 3, - 0, - 3, - 1, - 2, - 4, - 5, - 2, - 5, - 3, - 4, - 6, - 7, - 4, - 7, - 5, - 6, - 8, - 9, - 6, - 9, - 7, - 8, - 10, - 11, - 8, - 11, - 9, - 10, - 12, - 13, - 10, - 13, - 11, - 12, - 14, - 15, - 12, - 15, - 13, - 14, - 16, - 17, - 14, - 17, - 15, - 16, - 18, - 19, - 16, - 19, - 17, - 18, - 20, - 21, - 18, - 21, - 19, - 20, - 22, - 23, - 20, - 23, - 21, - 22, - 24, - 25, - 22, - 25, - 23, - 24, - 26, - 27, - 24, - 27, - 25, - 26, - 28, - 29, - 26, - 29, - 27, - 28, - 30, - 31, - 28, - 31, - 29, - 30, - 32, - 33, - 30, - 33, - 31, - 32, - 34, - 35, - 32, - 35, - 33, - 34, - 36, - 37, - 34, - 37, - 35, - 36, - 38, - 39, - 36, - 39, - 37, - 38, - 40, - 41, - 38, - 41, - 39, - 40, - 42, - 43, - 40, - 43, - 41, - 42, - 44, - 45, - 42, - 45, - 43, - 44, - 46, - 47, - 44, - 47, - 45, - 46, - 48, - 49, - 46, - 49, - 47, - 48, - 50, - 51, - 48, - 51, - 49, - 50, - 52, - 53, - 50, - 53, - 51, - 52, - 54, - 55, - 52, - 55, - 53, - 54, - 56, - 57, - 54, - 57, - 55, - 56, - 58, - 59, - 56, - 59, - 57, - 58, - 60, - 61, - 58, - 61, - 59, - 60, - 62, - 63, - 60, - 63, - 61, - 62, - 64, - 65, - 62, - 65, - 63, - 64, - 66, - 67, - 64, - 67, - 65, - 66, - 68, - 69, - 66, - 69, - 67, - 68, - 70, - 71, - 68, - 71, - 69, - 70, - 72, - 73, - 70, - 73, - 71, - 72, - 74, - 75, - 72, - 75, - 73, - 74, - 76, - 77, - 74, - 77, - 75, - 76, - 78, - 79, - 76, - 79, - 77, - 78, - 80, - 81, - 78, - 81, - 79, - 80, - 82, - 83, - 80, - 83, - 81, - 82, - 84, - 85, - 82, - 85, - 83, - 84, - 86, - 87, - 84, - 87, - 85, - 86, - 88, - 89, - 86, - 89, - 87, - 88, - 90, - 91, - 88, - 91, - 89, - 90, - 92, - 93, - 90, - 93, - 91, - 92, - 94, - 95, - 92, - 95, - 93, - 94, - 96, - 97, - 94, - 97, - 95 - ], - "Normals": [ - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9914448613738104, - "Y": 0.13052619222005157, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9914448613738104, - "Y": 0.13052619222005157, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9659258262890683, - "Y": 0.25881904510252074, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9659258262890683, - "Y": 0.25881904510252074, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9238795325112867, - "Y": 0.3826834323650898, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9238795325112867, - "Y": 0.3826834323650898, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.8660254037844387, - "Y": 0.49999999999999994, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.8660254037844387, - "Y": 0.49999999999999994, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7933533402912352, - "Y": 0.6087614290087207, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7933533402912352, - "Y": 0.6087614290087207, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7071067811865476, - "Y": 0.7071067811865476, - "Z": 0, - "LengthSquared": 1.0000000000000002, - "Length": 1 - }, - { - "X": 0.7071067811865476, - "Y": 0.7071067811865476, - "Z": 0, - "LengthSquared": 1.0000000000000002, - "Length": 1 - }, - { - "X": 0.6087614290087207, - "Y": 0.7933533402912352, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.6087614290087207, - "Y": 0.7933533402912352, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.5000000000000001, - "Y": 0.8660254037844386, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.5000000000000001, - "Y": 0.8660254037844386, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.38268343236508984, - "Y": 0.9238795325112867, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.38268343236508984, - "Y": 0.9238795325112867, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.25881904510252074, - "Y": 0.9659258262890683, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.25881904510252074, - "Y": 0.9659258262890683, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.1305261922200517, - "Y": 0.9914448613738104, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.1305261922200517, - "Y": 0.9914448613738104, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 6.123233995736766E-17, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 6.123233995736766E-17, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.1305261922200516, - "Y": 0.9914448613738104, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.1305261922200516, - "Y": 0.9914448613738104, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.25881904510252063, - "Y": 0.9659258262890683, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.25881904510252063, - "Y": 0.9659258262890683, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.3826834323650895, - "Y": 0.9238795325112868, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.3826834323650895, - "Y": 0.9238795325112868, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.4999999999999998, - "Y": 0.8660254037844387, - "Z": 0, - "LengthSquared": 0.9999999999999999, - "Length": 0.9999999999999999 - }, - { - "X": -0.4999999999999998, - "Y": 0.8660254037844387, - "Z": 0, - "LengthSquared": 0.9999999999999999, - "Length": 0.9999999999999999 - }, - { - "X": -0.6087614290087207, - "Y": 0.7933533402912352, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.6087614290087207, - "Y": 0.7933533402912352, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.7071067811865475, - "Y": 0.7071067811865476, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.7071067811865475, - "Y": 0.7071067811865476, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.793353340291235, - "Y": 0.6087614290087209, - "Z": 0, - "LengthSquared": 1.0000000000000002, - "Length": 1 - }, - { - "X": -0.793353340291235, - "Y": 0.6087614290087209, - "Z": 0, - "LengthSquared": 1.0000000000000002, - "Length": 1 - }, - { - "X": -0.8660254037844387, - "Y": 0.49999999999999994, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.8660254037844387, - "Y": 0.49999999999999994, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9238795325112867, - "Y": 0.3826834323650899, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9238795325112867, - "Y": 0.3826834323650899, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9659258262890682, - "Y": 0.258819045102521, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9659258262890682, - "Y": 0.258819045102521, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9914448613738104, - "Y": 0.130526192220052, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9914448613738104, - "Y": 0.130526192220052, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": 1.2246467991473532E-16, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": 1.2246467991473532E-16, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9914448613738104, - "Y": -0.13052619222005177, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9914448613738104, - "Y": -0.13052619222005177, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9659258262890683, - "Y": -0.2588190451025208, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9659258262890683, - "Y": -0.2588190451025208, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9238795325112868, - "Y": -0.38268343236508967, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.9238795325112868, - "Y": -0.38268343236508967, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.8660254037844388, - "Y": -0.4999999999999997, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.8660254037844388, - "Y": -0.4999999999999997, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.7933533402912352, - "Y": -0.6087614290087207, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.7933533402912352, - "Y": -0.6087614290087207, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.7071067811865479, - "Y": -0.7071067811865471, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.7071067811865479, - "Y": -0.7071067811865471, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.6087614290087209, - "Y": -0.7933533402912349, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.6087614290087209, - "Y": -0.7933533402912349, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.5000000000000004, - "Y": -0.8660254037844385, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.5000000000000004, - "Y": -0.8660254037844385, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.3826834323650895, - "Y": -0.9238795325112868, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.3826834323650895, - "Y": -0.9238795325112868, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.25881904510252063, - "Y": -0.9659258262890683, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.25881904510252063, - "Y": -0.9659258262890683, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.13052619222005163, - "Y": -0.9914448613738104, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0.13052619222005163, - "Y": -0.9914448613738104, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1.8369701987210297E-16, - "Y": -1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1.8369701987210297E-16, - "Y": -1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.13052619222005127, - "Y": -0.9914448613738105, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.13052619222005127, - "Y": -0.9914448613738105, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.2588190451025203, - "Y": -0.9659258262890684, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.2588190451025203, - "Y": -0.9659258262890684, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.38268343236508917, - "Y": -0.923879532511287, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.38268343236508917, - "Y": -0.923879532511287, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.5000000000000001, - "Y": -0.8660254037844386, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.5000000000000001, - "Y": -0.8660254037844386, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.6087614290087199, - "Y": -0.7933533402912357, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.6087614290087199, - "Y": -0.7933533402912357, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7071067811865474, - "Y": -0.7071067811865477, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7071067811865474, - "Y": -0.7071067811865477, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7933533402912349, - "Y": -0.608761429008721, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.7933533402912349, - "Y": -0.608761429008721, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.8660254037844384, - "Y": -0.5000000000000004, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.8660254037844384, - "Y": -0.5000000000000004, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9238795325112868, - "Y": -0.38268343236508956, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9238795325112868, - "Y": -0.38268343236508956, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9659258262890681, - "Y": -0.25881904510252157, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9659258262890681, - "Y": -0.25881904510252157, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9914448613738104, - "Y": -0.13052619222005168, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0.9914448613738104, - "Y": -0.13052619222005168, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": -2.4492935982947064E-16, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": -2.4492935982947064E-16, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - } - ], - "UVs": [ - { - "X": 0, - "Y": 0 - }, - { - "X": 0, - "Y": 3 - }, - { - "X": 0.2617993877991494, - "Y": 0 - }, - { - "X": 0.2617993877991494, - "Y": 3 - }, - { - "X": 0.5235987755982988, - "Y": 0 - }, - { - "X": 0.5235987755982988, - "Y": 3 - }, - { - "X": 0.7853981633974483, - "Y": 0 - }, - { - "X": 0.7853981633974483, - "Y": 3 - }, - { - "X": 1.0471975511965976, - "Y": 0 - }, - { - "X": 1.0471975511965976, - "Y": 3 - }, - { - "X": 1.3089969389957472, - "Y": 0 - }, - { - "X": 1.3089969389957472, - "Y": 3 - }, - { - "X": 1.5707963267948966, - "Y": 0 - }, - { - "X": 1.5707963267948966, - "Y": 3 - }, - { - "X": 1.832595714594046, - "Y": 0 - }, - { - "X": 1.832595714594046, - "Y": 3 - }, - { - "X": 2.0943951023931953, - "Y": 0 - }, - { - "X": 2.0943951023931953, - "Y": 3 - }, - { - "X": 2.356194490192345, - "Y": 0 - }, - { - "X": 2.356194490192345, - "Y": 3 - }, - { - "X": 2.6179938779914944, - "Y": 0 - }, - { - "X": 2.6179938779914944, - "Y": 3 - }, - { - "X": 2.8797932657906435, - "Y": 0 - }, - { - "X": 2.8797932657906435, - "Y": 3 - }, - { - "X": 3.141592653589793, - "Y": 0 - }, - { - "X": 3.141592653589793, - "Y": 3 - }, - { - "X": 3.4033920413889427, - "Y": 0 - }, - { - "X": 3.4033920413889427, - "Y": 3 - }, - { - "X": 3.665191429188092, - "Y": 0 - }, - { - "X": 3.665191429188092, - "Y": 3 - }, - { - "X": 3.926990816987241, - "Y": 0 - }, - { - "X": 3.926990816987241, - "Y": 3 - }, - { - "X": 4.1887902047863905, - "Y": 0 - }, - { - "X": 4.1887902047863905, - "Y": 3 - }, - { - "X": 4.4505895925855405, - "Y": 0 - }, - { - "X": 4.4505895925855405, - "Y": 3 - }, - { - "X": 4.71238898038469, - "Y": 0 - }, - { - "X": 4.71238898038469, - "Y": 3 - }, - { - "X": 4.974188368183839, - "Y": 0 - }, - { - "X": 4.974188368183839, - "Y": 3 - }, - { - "X": 5.235987755982989, - "Y": 0 - }, - { - "X": 5.235987755982989, - "Y": 3 - }, - { - "X": 5.497787143782138, - "Y": 0 - }, - { - "X": 5.497787143782138, - "Y": 3 - }, - { - "X": 5.759586531581287, - "Y": 0 - }, - { - "X": 5.759586531581287, - "Y": 3 - }, - { - "X": 6.021385919380436, - "Y": 0 - }, - { - "X": 6.021385919380436, - "Y": 3 - }, - { - "X": 6.283185307179586, - "Y": 0 - }, - { - "X": 6.283185307179586, - "Y": 3 - }, - { - "X": 6.544984694978736, - "Y": 0 - }, - { - "X": 6.544984694978736, - "Y": 3 - }, - { - "X": 6.806784082777885, - "Y": 0 - }, - { - "X": 6.806784082777885, - "Y": 3 - }, - { - "X": 7.0685834705770345, - "Y": 0 - }, - { - "X": 7.0685834705770345, - "Y": 3 - }, - { - "X": 7.330382858376184, - "Y": 0 - }, - { - "X": 7.330382858376184, - "Y": 3 - }, - { - "X": 7.592182246175334, - "Y": 0 - }, - { - "X": 7.592182246175334, - "Y": 3 - }, - { - "X": 7.853981633974482, - "Y": 0 - }, - { - "X": 7.853981633974482, - "Y": 3 - }, - { - "X": 8.115781021773632, - "Y": 0 - }, - { - "X": 8.115781021773632, - "Y": 3 - }, - { - "X": 8.377580409572781, - "Y": 0 - }, - { - "X": 8.377580409572781, - "Y": 3 - }, - { - "X": 8.639379797371932, - "Y": 0 - }, - { - "X": 8.639379797371932, - "Y": 3 - }, - { - "X": 8.901179185171081, - "Y": 0 - }, - { - "X": 8.901179185171081, - "Y": 3 - }, - { - "X": 9.16297857297023, - "Y": 0 - }, - { - "X": 9.16297857297023, - "Y": 3 - }, - { - "X": 9.42477796076938, - "Y": 0 - }, - { - "X": 9.42477796076938, - "Y": 3 - }, - { - "X": 9.686577348568528, - "Y": 0 - }, - { - "X": 9.686577348568528, - "Y": 3 - }, - { - "X": 9.948376736367678, - "Y": 0 - }, - { - "X": 9.948376736367678, - "Y": 3 - }, - { - "X": 10.210176124166827, - "Y": 0 - }, - { - "X": 10.210176124166827, - "Y": 3 - }, - { - "X": 10.471975511965978, - "Y": 0 - }, - { - "X": 10.471975511965978, - "Y": 3 - }, - { - "X": 10.733774899765125, - "Y": 0 - }, - { - "X": 10.733774899765125, - "Y": 3 - }, - { - "X": 10.995574287564276, - "Y": 0 - }, - { - "X": 10.995574287564276, - "Y": 3 - }, - { - "X": 11.257373675363425, - "Y": 0 - }, - { - "X": 11.257373675363425, - "Y": 3 - }, - { - "X": 11.519173063162574, - "Y": 0 - }, - { - "X": 11.519173063162574, - "Y": 3 - }, - { - "X": 11.780972450961725, - "Y": 0 - }, - { - "X": 11.780972450961725, - "Y": 3 - }, - { - "X": 12.042771838760872, - "Y": 0 - }, - { - "X": 12.042771838760872, - "Y": 3 - }, - { - "X": 12.304571226560023, - "Y": 0 - }, - { - "X": 12.304571226560023, - "Y": 3 - }, - { - "X": 12.566370614359172, - "Y": 0 - }, - { - "X": 12.566370614359172, - "Y": 3 - } - ], - "GeometryId": null, - "Transform": null, - "MaterialId": null, - "Color": { - "R": 185, - "G": 183, - "B": 178, - "A": 255, - "Rf": 0.7254901960784313, - "Gf": 0.7176470588235294, - "Bf": 0.6980392156862745, - "Af": 1 + ], + "Geometries": [ + { + "Id": 1, + "Topology": 0, + "Vertices": [ + 2, + 0, + 0, + 2, + 0, + 3, + 1.9828897227476208, + 0.26105238444010315, + 0, + 1.9828897227476208, + 0.26105238444010315, + 3, + 1.9318516525781366, + 0.5176380902050415, + 0, + 1.9318516525781366, + 0.5176380902050415, + 3, + 1.8477590650225735, + 0.7653668647301796, + 0, + 1.8477590650225735, + 0.7653668647301796, + 3, + 1.7320508075688774, + 0.9999999999999999, + 0, + 1.7320508075688774, + 0.9999999999999999, + 3, + 1.5867066805824703, + 1.2175228580174413, + 0, + 1.5867066805824703, + 1.2175228580174413, + 3, + 1.4142135623730951, + 1.4142135623730951, + 0, + 1.4142135623730951, + 1.4142135623730951, + 3, + 1.2175228580174413, + 1.5867066805824703, + 0, + 1.2175228580174413, + 1.5867066805824703, + 3, + 1.0000000000000002, + 1.7320508075688772, + 0, + 1.0000000000000002, + 1.7320508075688772, + 3, + 0.7653668647301797, + 1.8477590650225735, + 0, + 0.7653668647301797, + 1.8477590650225735, + 3, + 0.5176380902050415, + 1.9318516525781366, + 0, + 0.5176380902050415, + 1.9318516525781366, + 3, + 0.2610523844401034, + 1.9828897227476208, + 0, + 0.2610523844401034, + 1.9828897227476208, + 3, + 1.2246467991473532E-16, + 2, + 0, + 1.2246467991473532E-16, + 2, + 3, + -0.2610523844401032, + 1.9828897227476208, + 0, + -0.2610523844401032, + 1.9828897227476208, + 3, + -0.5176380902050413, + 1.9318516525781366, + 0, + -0.5176380902050413, + 1.9318516525781366, + 3, + -0.765366864730179, + 1.8477590650225737, + 0, + -0.765366864730179, + 1.8477590650225737, + 3, + -0.9999999999999996, + 1.7320508075688774, + 0, + -0.9999999999999996, + 1.7320508075688774, + 3, + -1.2175228580174413, + 1.5867066805824703, + 0, + -1.2175228580174413, + 1.5867066805824703, + 3, + -1.414213562373095, + 1.4142135623730951, + 0, + -1.414213562373095, + 1.4142135623730951, + 3, + -1.58670668058247, + 1.2175228580174418, + 0, + -1.58670668058247, + 1.2175228580174418, + 3, + -1.7320508075688774, + 0.9999999999999999, + 0, + -1.7320508075688774, + 0.9999999999999999, + 3, + -1.8477590650225735, + 0.7653668647301798, + 0, + -1.8477590650225735, + 0.7653668647301798, + 3, + -1.9318516525781364, + 0.517638090205042, + 0, + -1.9318516525781364, + 0.517638090205042, + 3, + -1.9828897227476208, + 0.261052384440104, + 0, + -1.9828897227476208, + 0.261052384440104, + 3, + -2, + 2.4492935982947064E-16, + 0, + -2, + 2.4492935982947064E-16, + 3, + -1.9828897227476208, + -0.26105238444010354, + 0, + -1.9828897227476208, + -0.26105238444010354, + 3, + -1.9318516525781366, + -0.5176380902050416, + 0, + -1.9318516525781366, + -0.5176380902050416, + 3, + -1.8477590650225737, + -0.7653668647301793, + 0, + -1.8477590650225737, + -0.7653668647301793, + 3, + -1.7320508075688776, + -0.9999999999999994, + 0, + -1.7320508075688776, + -0.9999999999999994, + 3, + -1.5867066805824703, + -1.2175228580174413, + 0, + -1.5867066805824703, + -1.2175228580174413, + 3, + -1.4142135623730958, + -1.4142135623730943, + 0, + -1.4142135623730958, + -1.4142135623730943, + 3, + -1.2175228580174418, + -1.5867066805824699, + 0, + -1.2175228580174418, + -1.5867066805824699, + 3, + -1.0000000000000009, + -1.732050807568877, + 0, + -1.0000000000000009, + -1.732050807568877, + 3, + -0.765366864730179, + -1.8477590650225737, + 0, + -0.765366864730179, + -1.8477590650225737, + 3, + -0.5176380902050413, + -1.9318516525781366, + 0, + -0.5176380902050413, + -1.9318516525781366, + 3, + -0.26105238444010326, + -1.9828897227476208, + 0, + -0.26105238444010326, + -1.9828897227476208, + 3, + -3.6739403974420594E-16, + -2, + 0, + -3.6739403974420594E-16, + -2, + 3, + 0.26105238444010254, + -1.982889722747621, + 0, + 0.26105238444010254, + -1.982889722747621, + 3, + 0.5176380902050406, + -1.9318516525781368, + 0, + 0.5176380902050406, + -1.9318516525781368, + 3, + 0.7653668647301783, + -1.847759065022574, + 0, + 0.7653668647301783, + -1.847759065022574, + 3, + 1.0000000000000002, + -1.7320508075688772, + 0, + 1.0000000000000002, + -1.7320508075688772, + 3, + 1.2175228580174398, + -1.5867066805824714, + 0, + 1.2175228580174398, + -1.5867066805824714, + 3, + 1.4142135623730947, + -1.4142135623730954, + 0, + 1.4142135623730947, + -1.4142135623730954, + 3, + 1.5867066805824699, + -1.217522858017442, + 0, + 1.5867066805824699, + -1.217522858017442, + 3, + 1.7320508075688767, + -1.0000000000000009, + 0, + 1.7320508075688767, + -1.0000000000000009, + 3, + 1.8477590650225737, + -0.7653668647301791, + 0, + 1.8477590650225737, + -0.7653668647301791, + 3, + 1.9318516525781362, + -0.5176380902050431, + 0, + 1.9318516525781362, + -0.5176380902050431, + 3, + 1.9828897227476208, + -0.26105238444010337, + 0, + 1.9828897227476208, + -0.26105238444010337, + 3, + 2, + -4.898587196589413E-16, + 0, + 2, + -4.898587196589413E-16, + 3 + ], + "Indices": [ + 0, + 2, + 3, + 0, + 3, + 1, + 2, + 4, + 5, + 2, + 5, + 3, + 4, + 6, + 7, + 4, + 7, + 5, + 6, + 8, + 9, + 6, + 9, + 7, + 8, + 10, + 11, + 8, + 11, + 9, + 10, + 12, + 13, + 10, + 13, + 11, + 12, + 14, + 15, + 12, + 15, + 13, + 14, + 16, + 17, + 14, + 17, + 15, + 16, + 18, + 19, + 16, + 19, + 17, + 18, + 20, + 21, + 18, + 21, + 19, + 20, + 22, + 23, + 20, + 23, + 21, + 22, + 24, + 25, + 22, + 25, + 23, + 24, + 26, + 27, + 24, + 27, + 25, + 26, + 28, + 29, + 26, + 29, + 27, + 28, + 30, + 31, + 28, + 31, + 29, + 30, + 32, + 33, + 30, + 33, + 31, + 32, + 34, + 35, + 32, + 35, + 33, + 34, + 36, + 37, + 34, + 37, + 35, + 36, + 38, + 39, + 36, + 39, + 37, + 38, + 40, + 41, + 38, + 41, + 39, + 40, + 42, + 43, + 40, + 43, + 41, + 42, + 44, + 45, + 42, + 45, + 43, + 44, + 46, + 47, + 44, + 47, + 45, + 46, + 48, + 49, + 46, + 49, + 47, + 48, + 50, + 51, + 48, + 51, + 49, + 50, + 52, + 53, + 50, + 53, + 51, + 52, + 54, + 55, + 52, + 55, + 53, + 54, + 56, + 57, + 54, + 57, + 55, + 56, + 58, + 59, + 56, + 59, + 57, + 58, + 60, + 61, + 58, + 61, + 59, + 60, + 62, + 63, + 60, + 63, + 61, + 62, + 64, + 65, + 62, + 65, + 63, + 64, + 66, + 67, + 64, + 67, + 65, + 66, + 68, + 69, + 66, + 69, + 67, + 68, + 70, + 71, + 68, + 71, + 69, + 70, + 72, + 73, + 70, + 73, + 71, + 72, + 74, + 75, + 72, + 75, + 73, + 74, + 76, + 77, + 74, + 77, + 75, + 76, + 78, + 79, + 76, + 79, + 77, + 78, + 80, + 81, + 78, + 81, + 79, + 80, + 82, + 83, + 80, + 83, + 81, + 82, + 84, + 85, + 82, + 85, + 83, + 84, + 86, + 87, + 84, + 87, + 85, + 86, + 88, + 89, + 86, + 89, + 87, + 88, + 90, + 91, + 88, + 91, + 89, + 90, + 92, + 93, + 90, + 93, + 91, + 92, + 94, + 95, + 92, + 95, + 93, + 94, + 96, + 97, + 94, + 97, + 95 + ], + "Normals": [ + 1, + 0, + 0, + 1, + 0, + 0, + 0.9914448613738104, + 0.13052619222005157, + 0, + 0.9914448613738104, + 0.13052619222005157, + 0, + 0.9659258262890683, + 0.25881904510252074, + 0, + 0.9659258262890683, + 0.25881904510252074, + 0, + 0.9238795325112867, + 0.3826834323650898, + 0, + 0.9238795325112867, + 0.3826834323650898, + 0, + 0.8660254037844387, + 0.49999999999999994, + 0, + 0.8660254037844387, + 0.49999999999999994, + 0, + 0.7933533402912352, + 0.6087614290087207, + 0, + 0.7933533402912352, + 0.6087614290087207, + 0, + 0.7071067811865476, + 0.7071067811865476, + 0, + 0.7071067811865476, + 0.7071067811865476, + 0, + 0.6087614290087207, + 0.7933533402912352, + 0, + 0.6087614290087207, + 0.7933533402912352, + 0, + 0.5000000000000001, + 0.8660254037844386, + 0, + 0.5000000000000001, + 0.8660254037844386, + 0, + 0.38268343236508984, + 0.9238795325112867, + 0, + 0.38268343236508984, + 0.9238795325112867, + 0, + 0.25881904510252074, + 0.9659258262890683, + 0, + 0.25881904510252074, + 0.9659258262890683, + 0, + 0.1305261922200517, + 0.9914448613738104, + 0, + 0.1305261922200517, + 0.9914448613738104, + 0, + 6.123233995736766E-17, + 1, + 0, + 6.123233995736766E-17, + 1, + 0, + -0.1305261922200516, + 0.9914448613738104, + 0, + -0.1305261922200516, + 0.9914448613738104, + 0, + -0.25881904510252063, + 0.9659258262890683, + 0, + -0.25881904510252063, + 0.9659258262890683, + 0, + -0.3826834323650895, + 0.9238795325112868, + 0, + -0.3826834323650895, + 0.9238795325112868, + 0, + -0.4999999999999998, + 0.8660254037844387, + 0, + -0.4999999999999998, + 0.8660254037844387, + 0, + -0.6087614290087207, + 0.7933533402912352, + 0, + -0.6087614290087207, + 0.7933533402912352, + 0, + -0.7071067811865475, + 0.7071067811865476, + 0, + -0.7071067811865475, + 0.7071067811865476, + 0, + -0.793353340291235, + 0.6087614290087209, + 0, + -0.793353340291235, + 0.6087614290087209, + 0, + -0.8660254037844387, + 0.49999999999999994, + 0, + -0.8660254037844387, + 0.49999999999999994, + 0, + -0.9238795325112867, + 0.3826834323650899, + 0, + -0.9238795325112867, + 0.3826834323650899, + 0, + -0.9659258262890682, + 0.258819045102521, + 0, + -0.9659258262890682, + 0.258819045102521, + 0, + -0.9914448613738104, + 0.130526192220052, + 0, + -0.9914448613738104, + 0.130526192220052, + 0, + -1, + 1.2246467991473532E-16, + 0, + -1, + 1.2246467991473532E-16, + 0, + -0.9914448613738104, + -0.13052619222005177, + 0, + -0.9914448613738104, + -0.13052619222005177, + 0, + -0.9659258262890683, + -0.2588190451025208, + 0, + -0.9659258262890683, + -0.2588190451025208, + 0, + -0.9238795325112868, + -0.38268343236508967, + 0, + -0.9238795325112868, + -0.38268343236508967, + 0, + -0.8660254037844388, + -0.4999999999999997, + 0, + -0.8660254037844388, + -0.4999999999999997, + 0, + -0.7933533402912352, + -0.6087614290087207, + 0, + -0.7933533402912352, + -0.6087614290087207, + 0, + -0.7071067811865479, + -0.7071067811865471, + 0, + -0.7071067811865479, + -0.7071067811865471, + 0, + -0.6087614290087209, + -0.7933533402912349, + 0, + -0.6087614290087209, + -0.7933533402912349, + 0, + -0.5000000000000004, + -0.8660254037844385, + 0, + -0.5000000000000004, + -0.8660254037844385, + 0, + -0.3826834323650895, + -0.9238795325112868, + 0, + -0.3826834323650895, + -0.9238795325112868, + 0, + -0.25881904510252063, + -0.9659258262890683, + 0, + -0.25881904510252063, + -0.9659258262890683, + 0, + -0.13052619222005163, + -0.9914448613738104, + 0, + -0.13052619222005163, + -0.9914448613738104, + 0, + -1.8369701987210297E-16, + -1, + 0, + -1.8369701987210297E-16, + -1, + 0, + 0.13052619222005127, + -0.9914448613738105, + 0, + 0.13052619222005127, + -0.9914448613738105, + 0, + 0.2588190451025203, + -0.9659258262890684, + 0, + 0.2588190451025203, + -0.9659258262890684, + 0, + 0.38268343236508917, + -0.923879532511287, + 0, + 0.38268343236508917, + -0.923879532511287, + 0, + 0.5000000000000001, + -0.8660254037844386, + 0, + 0.5000000000000001, + -0.8660254037844386, + 0, + 0.6087614290087199, + -0.7933533402912357, + 0, + 0.6087614290087199, + -0.7933533402912357, + 0, + 0.7071067811865474, + -0.7071067811865477, + 0, + 0.7071067811865474, + -0.7071067811865477, + 0, + 0.7933533402912349, + -0.608761429008721, + 0, + 0.7933533402912349, + -0.608761429008721, + 0, + 0.8660254037844384, + -0.5000000000000004, + 0, + 0.8660254037844384, + -0.5000000000000004, + 0, + 0.9238795325112868, + -0.38268343236508956, + 0, + 0.9238795325112868, + -0.38268343236508956, + 0, + 0.9659258262890681, + -0.25881904510252157, + 0, + 0.9659258262890681, + -0.25881904510252157, + 0, + 0.9914448613738104, + -0.13052619222005168, + 0, + 0.9914448613738104, + -0.13052619222005168, + 0, + 1, + -2.4492935982947064E-16, + 0, + 1, + -2.4492935982947064E-16, + 0 + ], + "UVs": [ + 0, + 0, + 0, + 3, + 0.2617993877991494, + 0, + 0.2617993877991494, + 3, + 0.5235987755982988, + 0, + 0.5235987755982988, + 3, + 0.7853981633974483, + 0, + 0.7853981633974483, + 3, + 1.0471975511965976, + 0, + 1.0471975511965976, + 3, + 1.3089969389957472, + 0, + 1.3089969389957472, + 3, + 1.5707963267948966, + 0, + 1.5707963267948966, + 3, + 1.832595714594046, + 0, + 1.832595714594046, + 3, + 2.0943951023931953, + 0, + 2.0943951023931953, + 3, + 2.356194490192345, + 0, + 2.356194490192345, + 3, + 2.6179938779914944, + 0, + 2.6179938779914944, + 3, + 2.8797932657906435, + 0, + 2.8797932657906435, + 3, + 3.141592653589793, + 0, + 3.141592653589793, + 3, + 3.4033920413889427, + 0, + 3.4033920413889427, + 3, + 3.665191429188092, + 0, + 3.665191429188092, + 3, + 3.926990816987241, + 0, + 3.926990816987241, + 3, + 4.1887902047863905, + 0, + 4.1887902047863905, + 3, + 4.4505895925855405, + 0, + 4.4505895925855405, + 3, + 4.71238898038469, + 0, + 4.71238898038469, + 3, + 4.974188368183839, + 0, + 4.974188368183839, + 3, + 5.235987755982989, + 0, + 5.235987755982989, + 3, + 5.497787143782138, + 0, + 5.497787143782138, + 3, + 5.759586531581287, + 0, + 5.759586531581287, + 3, + 6.021385919380436, + 0, + 6.021385919380436, + 3, + 6.283185307179586, + 0, + 6.283185307179586, + 3, + 6.544984694978736, + 0, + 6.544984694978736, + 3, + 6.806784082777885, + 0, + 6.806784082777885, + 3, + 7.0685834705770345, + 0, + 7.0685834705770345, + 3, + 7.330382858376184, + 0, + 7.330382858376184, + 3, + 7.592182246175334, + 0, + 7.592182246175334, + 3, + 7.853981633974482, + 0, + 7.853981633974482, + 3, + 8.115781021773632, + 0, + 8.115781021773632, + 3, + 8.377580409572781, + 0, + 8.377580409572781, + 3, + 8.639379797371932, + 0, + 8.639379797371932, + 3, + 8.901179185171081, + 0, + 8.901179185171081, + 3, + 9.16297857297023, + 0, + 9.16297857297023, + 3, + 9.42477796076938, + 0, + 9.42477796076938, + 3, + 9.686577348568528, + 0, + 9.686577348568528, + 3, + 9.948376736367678, + 0, + 9.948376736367678, + 3, + 10.210176124166827, + 0, + 10.210176124166827, + 3, + 10.471975511965978, + 0, + 10.471975511965978, + 3, + 10.733774899765125, + 0, + 10.733774899765125, + 3, + 10.995574287564276, + 0, + 10.995574287564276, + 3, + 11.257373675363425, + 0, + 11.257373675363425, + 3, + 11.519173063162574, + 0, + 11.519173063162574, + 3, + 11.780972450961725, + 0, + 11.780972450961725, + 3, + 12.042771838760872, + 0, + 12.042771838760872, + 3, + 12.304571226560023, + 0, + 12.304571226560023, + 3, + 12.566370614359172, + 0, + 12.566370614359172, + 3 + ] }, - "Opacity": 1, - "TextureIndex": 0, - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + { + "Id": 2, + "Topology": 0, + "Vertices": [ + 5, + -1, + 0, + 5, + 1, + 0, + 5, + 1, + 2, + 5, + -1, + 2, + 3, + 1, + 0, + 3, + -1, + 0, + 3, + -1, + 2, + 3, + 1, + 2, + 5, + 1, + 0, + 3, + 1, + 0, + 3, + 1, + 2, + 5, + 1, + 2, + 3, + -1, + 0, + 5, + -1, + 0, + 5, + -1, + 2, + 3, + -1, + 2, + 3, + -1, + 2, + 5, + -1, + 2, + 5, + 1, + 2, + 3, + 1, + 2, + 3, + 1, + 0, + 5, + 1, + 0, + 5, + -1, + 0, + 3, + -1, + 0 + ], + "Indices": [ + 0, + 1, + 2, + 0, + 2, + 3, + 4, + 5, + 6, + 4, + 6, + 7, + 8, + 9, + 10, + 8, + 10, + 11, + 12, + 13, + 14, + 12, + 14, + 15, + 16, + 17, + 18, + 16, + 18, + 19, + 20, + 21, + 22, + 20, + 22, + 23 + ], + "Normals": [ + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1, + -0, + -0, + -1 + ] } - }, - { - "Id": 1, - "Name": "Plain Box", - "ElementId": 600002, - "Vertices": [ - { - "X": 5, - "Y": -1, - "Z": 0, - "LengthSquared": 26, - "Length": 5.0990195135927845 - }, - { - "X": 5, - "Y": 1, - "Z": 0, - "LengthSquared": 26, - "Length": 5.0990195135927845 - }, - { - "X": 5, - "Y": 1, - "Z": 2, - "LengthSquared": 30, - "Length": 5.477225575051661 - }, - { - "X": 5, - "Y": -1, - "Z": 2, - "LengthSquared": 30, - "Length": 5.477225575051661 - }, - { - "X": 3, - "Y": 1, - "Z": 0, - "LengthSquared": 10, - "Length": 3.1622776601683795 - }, - { - "X": 3, - "Y": -1, - "Z": 0, - "LengthSquared": 10, - "Length": 3.1622776601683795 - }, - { - "X": 3, - "Y": -1, - "Z": 2, - "LengthSquared": 14, - "Length": 3.7416573867739413 - }, - { - "X": 3, - "Y": 1, - "Z": 2, - "LengthSquared": 14, - "Length": 3.7416573867739413 - }, - { - "X": 5, - "Y": 1, - "Z": 0, - "LengthSquared": 26, - "Length": 5.0990195135927845 - }, - { - "X": 3, - "Y": 1, - "Z": 0, - "LengthSquared": 10, - "Length": 3.1622776601683795 - }, - { - "X": 3, - "Y": 1, - "Z": 2, - "LengthSquared": 14, - "Length": 3.7416573867739413 - }, - { - "X": 5, - "Y": 1, - "Z": 2, - "LengthSquared": 30, - "Length": 5.477225575051661 - }, - { - "X": 3, - "Y": -1, - "Z": 0, - "LengthSquared": 10, - "Length": 3.1622776601683795 - }, - { - "X": 5, - "Y": -1, - "Z": 0, - "LengthSquared": 26, - "Length": 5.0990195135927845 - }, - { - "X": 5, - "Y": -1, - "Z": 2, - "LengthSquared": 30, - "Length": 5.477225575051661 - }, - { - "X": 3, - "Y": -1, - "Z": 2, - "LengthSquared": 14, - "Length": 3.7416573867739413 - }, - { - "X": 3, - "Y": -1, - "Z": 2, - "LengthSquared": 14, - "Length": 3.7416573867739413 - }, - { - "X": 5, - "Y": -1, - "Z": 2, - "LengthSquared": 30, - "Length": 5.477225575051661 - }, - { - "X": 5, - "Y": 1, - "Z": 2, - "LengthSquared": 30, - "Length": 5.477225575051661 - }, - { - "X": 3, - "Y": 1, - "Z": 2, - "LengthSquared": 14, - "Length": 3.7416573867739413 - }, - { - "X": 3, - "Y": 1, - "Z": 0, - "LengthSquared": 10, - "Length": 3.1622776601683795 - }, - { - "X": 5, - "Y": 1, - "Z": 0, - "LengthSquared": 26, - "Length": 5.0990195135927845 - }, - { - "X": 5, - "Y": -1, - "Z": 0, - "LengthSquared": 26, - "Length": 5.0990195135927845 - }, - { - "X": 3, - "Y": -1, - "Z": 0, - "LengthSquared": 10, - "Length": 3.1622776601683795 - } - ], - "Indices": [ - 0, - 1, - 2, - 0, - 2, - 3, - 4, - 5, - 6, - 4, - 6, - 7, - 8, - 9, - 10, - 8, - 10, - 11, - 12, - 13, - 14, - 12, - 14, - 15, - 16, - 17, - 18, - 16, - 18, - 19, - 20, - 21, - 22, - 20, - 22, - 23 - ], - "Normals": [ - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 1, - "Y": 0, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -1, - "Y": -0, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 1, - "Z": 0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -1, - "Z": -0, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": 0, - "Y": 0, - "Z": 1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - }, - { - "X": -0, - "Y": -0, - "Z": -1, - "LengthSquared": 1, - "Length": 1 - } - ], - "UVs": null, - "GeometryId": null, - "Transform": null, - "MaterialId": null, - "Color": { - "R": 110, - "G": 122, - "B": 140, - "A": 255, - "Rf": 0.43137254901960786, - "Gf": 0.47843137254901963, - "Bf": 0.5490196078431373, - "Af": 1 + ], + "Elements": [ + { + "Id": 600001, + "ModelId": 1, + "Kind": 0, + "Name": "Textured Tank", + "Category": "Equipment", + "TypeName": "Tank", + "LevelId": 1, + "PropertySetIds": [] }, - "Opacity": 1, - "TextureIndex": null, - "BoundingBox": { - "Min": { - "X": "Infinity", - "Y": "Infinity", - "Z": "Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "Max": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - }, - "IsEmpty": true, - "Center": { - "X": "NaN", - "Y": "NaN", - "Z": "NaN", - "LengthSquared": "NaN", - "Length": "NaN" - }, - "Size": { - "X": "-Infinity", - "Y": "-Infinity", - "Z": "-Infinity", - "LengthSquared": "Infinity", - "Length": "Infinity" - } + { + "Id": 600002, + "ModelId": 1, + "Kind": 0, + "Name": "Plain Box", + "Category": "Walls", + "TypeName": "Generic 200", + "LevelId": 1, + "PropertySetIds": [] } - } - ], - "Geometries": [], - "Materials": [], - "Textures": [ - { - "Id": 0, - "MimeType": "image/bmp", - "Base64Data": "Qk02MAAAAAAAADYAAAAoAAAAQAAAAEAAAAABABgAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAA6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vrPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw86+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr", - "ScaleX": 0.5, - "ScaleY": 0.5 - } - ], - "Grids": null, - "Viewpoints": [], + ], + "Instances": [ + { + "ElementId": 600001, + "GeometryId": 1, + "MaterialId": 1, + "Name": "Textured Tank" + }, + { + "ElementId": 600002, + "GeometryId": 2, + "Color": [ + 110, + 122, + 140, + 255 + ], + "Name": "Plain Box" + } + ], + "Grids": [], + "Viewpoints": [], + "Schedules": [], + "ClashTests": [] + }, "SectionPlanes": [], - "SectionBox": null, - "TimeLiner": null, - "ClashTests": [], - "LinkedFiles": [] + "HiddenModelKeys": [] } \ No newline at end of file diff --git a/tools/SyntheticModel/SyntheticModel.cs b/tools/SyntheticModel/SyntheticModel.cs index 51fe0df..d208dbf 100644 --- a/tools/SyntheticModel/SyntheticModel.cs +++ b/tools/SyntheticModel/SyntheticModel.cs @@ -5,15 +5,16 @@ // Writes a deterministic synthetic pipe network as an exported HTML viewer, for performance measurements: // dotnet run tools/SyntheticModel/SyntheticModel.cs -- --elements 200000 --out synthetic-200k.html // Without --out it writes /ioviewer-perf/synthetic-.html. -// Pipes and joints share 20 geometries (about 100 triangles each) placed by rotated transforms; one element per mesh, -// five systems (one of them transparent wells), a few properties per element and a 4D schedule over the network. -// --unique gives every mesh its own copy of its geometry, already placed, as exporters without instancing write it. +// Pipes and joints share 20 geometries (about 100 triangles each) placed by rotated transforms; one element per +// instance, five systems (one material each; the wells are transparent), a few properties per element and a 4D +// schedule over the network. +// --unique gives every instance its own copy of its geometry, already placed, as exporters without instancing write it. // --textured gives every geometry cylindrical UVs (metres) and the first system a 64 x 64 checker texture. using System.Diagnostics; using System.Globalization; +using IOScene; using IOViewer.Exporters; -using IOViewer.Model; int elements = 0; string? output = null; @@ -55,7 +56,7 @@ var watch = Stopwatch.StartNew(); var doc = PipeNetwork.Build(elements, textured); if (unique) PipeNetwork.Unshare(doc); -Console.WriteLine($"Built {doc.Elements.Count} elements, {doc.Meshes.Count} meshes, {doc.Geometries.Count} geometries, " + +Console.WriteLine($"Built {doc.Elements.Count} elements, {doc.Instances.Count} instances, {doc.Geometries.Count} geometries, " + $"{PipeNetwork.Triangles(doc):N0} triangles in {watch.Elapsed.TotalSeconds:F1} s"); watch.Restart(); HtmlViewerExporter.Save(doc, output, new HtmlViewerOptions { Title = doc.Title }); @@ -67,26 +68,27 @@ static class PipeNetwork private const int Segments = 24; // pipe: 24 side quads + two 22-triangle caps = 92 triangles private static readonly double[] Diameters = [0.1, 0.15, 0.2, 0.3, 0.5]; private static readonly double[] Lengths = [2, 4, 6]; - private static readonly (string Name, RgbaColor Color, double Opacity)[] Systems = + private static readonly (string Name, RgbaColor Color)[] Systems = [ - ("Water supply", new RgbaColor(40, 120, 220), 1), - ("Sewer", new RgbaColor(150, 100, 60), 1), - ("Storm water", new RgbaColor(60, 170, 90), 1), - ("Heating", new RgbaColor(210, 60, 50), 1), - ("Wells", new RgbaColor(170, 170, 180), 0.5), + ("Water supply", new RgbaColor(40, 120, 220)), + ("Sewer", new RgbaColor(150, 100, 60)), + ("Storm water", new RgbaColor(60, 170, 90)), + ("Heating", new RgbaColor(210, 60, 50)), + ("Wells", new RgbaColor(170, 170, 180, 128)), // half transparent ]; - public static long Triangles(ViewerDocument doc) + public static long Triangles(SceneDocument doc) { var perGeometry = doc.Geometries.ToDictionary(g => g.Id, g => g.Indices.Count / 3); - return doc.Meshes.Sum(m => (long)perGeometry[m.GeometryId!.Value]); + return doc.Instances.Sum(m => (long)perGeometry[m.GeometryId]); } // Random walks of pipes with a joint after each one, mostly horizontal, sometimes a riser; seeded, so every run is equal. - public static ViewerDocument Build(int count, bool textured) + public static SceneDocument Build(int count, bool textured) { var random = new Random(20261111); - var doc = new ViewerDocument($"Synthetic pipe network ({count.ToString("N0", CultureInfo.InvariantCulture)} elements)"); + var doc = new SceneDocument { Title = $"Synthetic pipe network ({count.ToString("N0", CultureInfo.InvariantCulture)} elements)" }; + doc.Models.Add(new SceneModel { Id = 1, Key = "model", Name = doc.Title }); for (int d = 0; d < Diameters.Length; d++) { for (int l = 0; l < Lengths.Length; l++) doc.Geometries.Add(Pipe(d * Lengths.Length + l, Diameters[d] / 2, Lengths[l])); @@ -95,17 +97,22 @@ public static ViewerDocument Build(int count, bool textured) for (int d = 0; d < Diameters.Length; d++) doc.Geometries.Add(Joint(jointBase + d, Diameters[d] * 0.7)); if (textured) doc.Textures.Add(Checker()); else foreach (var g in doc.Geometries) g.UVs = null; // the untextured page stays as it was + for (int s = 0; s < Systems.Length; s++) + { + doc.Materials.Add(new SceneMaterial { Id = s + 1, Name = Systems[s].Name, Color = Systems[s].Color, TextureId = textured && s == 0 ? 1 : null }); + } + var levels = new Dictionary(); // zone number -> level id double side = Math.Sqrt(count / 2.0) * 6; // metres; keeps the density about the same at any count Vector3d[] directions = [Vector3d.UnitX, -Vector3d.UnitX, Vector3d.UnitY, -Vector3d.UnitY]; - var schedule = new ViewerTimeLinerSchedule("Construction"); + var schedule = new SceneSchedule { Name = "Construction" }; int tasks = Math.Clamp(count / 5000, 1, 40); var start = new DateTimeOffset(2027, 1, 1, 0, 0, 0, TimeSpan.Zero); for (int t = 0; t < tasks; t++) { - schedule.Tasks.Add(new ViewerTimeLinerTask($"T{t + 1}", $"Stage {t + 1}", start.AddDays(t * 10), start.AddDays(t * 10 + 14))); + schedule.Tasks.Add(new SceneTask { Id = $"T{t + 1}", Name = $"Stage {t + 1}", PlannedStart = start.AddDays(t * 10), PlannedEnd = start.AddDays(t * 10 + 14) }); } - doc.TimeLiner = schedule; + doc.Schedules.Add(schedule); var point = Vector3d.Zero; var direction = Vector3d.UnitX; @@ -146,38 +153,47 @@ public static ViewerDocument Build(int count, bool textured) point += direction * Lengths[length]; type = $"Pipe DN{Diameters[diameter] * 1000:F0}"; } - var (systemName, color, opacity) = Systems[system]; long id = 1_000_000 + i; - doc.Meshes.Add(new ViewerMesh + doc.Instances.Add(new SceneInstance { ElementId = id, GeometryId = geometry, Transform = transform, MaterialId = system + 1 }); + int zone = (i / 10000) + 1; + if (!levels.TryGetValue(zone, out int level)) { - Id = i, ElementId = id, GeometryId = geometry, Transform = transform, Color = color, Opacity = opacity, - TextureIndex = textured && system == 0 ? 0 : null, - }); - var element = new ViewerElement + level = levels[zone] = doc.Levels.Count + 1; + doc.Levels.Add(new SceneLevel { Id = level, ModelId = 1, Name = $"Zone {zone}" }); + } + var element = new SceneElement { - Id = id, Name = $"{type} {i}", Category = systemName, TypeName = type, Level = $"Zone {(i / 10000) + 1}", + Id = id, ModelId = 1, Name = $"{type} {i}", Category = Systems[system].Name, TypeName = type, LevelId = level, }; - element.MeshIndices.Add(i); - element.AddProperty("Identity", "Mark", $"M-{i}"); - element.AddProperty("Dimensions", "Diameter", $"{Diameters[diameter] * 1000:F0} mm"); + element.PropertySetIds.Add(Set(doc, "Identity", "Mark", $"M-{i}")); + element.PropertySetIds.Add(Set(doc, "Dimensions", "Diameter", $"{Diameters[diameter] * 1000:F0} mm")); doc.Elements.Add(element); - schedule.Tasks[(int)((long)i * tasks / count)].AttachedElementIds.Add(id); + schedule.Tasks[(int)((long)i * tasks / count)].ElementIds.Add(id); } return doc; } - // Replaces each mesh's shared geometry by its own placed copy (positions and normals transformed, no Transform). - public static void Unshare(ViewerDocument doc) + private static int Set(SceneDocument doc, string name, string property, string value) + { + var set = new ScenePropertySet { Id = doc.PropertySets.Count + 1, Name = name }; + set.Properties.Add(new SceneProperty { Name = property, Value = SceneValue.FromText(value) }); + doc.PropertySets.Add(set); + return set.Id; + } + + // Replaces each instance's shared geometry by its own placed copy (positions and normals transformed, no Transform). + public static void Unshare(SceneDocument doc) { var shared = doc.Geometries.ToDictionary(g => g.Id); doc.Geometries = []; - foreach (var mesh in doc.Meshes) + for (int i = 0; i < doc.Instances.Count; i++) { - var source = shared[mesh.GeometryId!.Value]; + var mesh = doc.Instances[i]; + var source = shared[mesh.GeometryId]; var t = mesh.Transform!.Value; - doc.Geometries.Add(new ViewerGeometry + doc.Geometries.Add(new SceneGeometry { - Id = mesh.Id, + Id = i, Vertices = source.Vertices.Select(t.Apply).ToList(), Normals = source.Normals!.Select(n => (t.Apply(n) - t.Translation).Normalized()).ToList(), UVs = source.UVs, @@ -186,7 +202,7 @@ public static void Unshare(ViewerDocument doc) ? source.Indices.Chunk(3).SelectMany(f => new[] { f[0], f[2], f[1] }).ToList() : source.Indices, }); - mesh.GeometryId = mesh.Id; + mesh.GeometryId = i; mesh.Transform = null; } } @@ -199,7 +215,7 @@ private static Transform3d Place(Vector3d axis, Vector3d up, Vector3d origin, bo } // Capped cylinder along +X from 0 to length; caps have their own vertices so their normals stay flat. - private static ViewerGeometry Pipe(int id, double radius, double length) + private static SceneGeometry Pipe(int id, double radius, double length) { var vertices = new List(); var normals = new List(); @@ -234,11 +250,11 @@ private static ViewerGeometry Pipe(int id, double radius, double length) } for (int s = 1; s < Segments - 1; s++) indices.AddRange([first, first + s, first + s + 1]); } - return new ViewerGeometry { Id = id, Vertices = vertices, Normals = normals, Indices = indices, UVs = uvs }; + return new SceneGeometry { Id = id, Vertices = vertices, Normals = normals, Indices = indices, UVs = uvs }; } // UV sphere, 10 x 6 bands: 100 triangles. - private static ViewerGeometry Joint(int id, double radius) + private static SceneGeometry Joint(int id, double radius) { const int around = 10, bands = 6; var vertices = new List(); @@ -261,7 +277,7 @@ private static ViewerGeometry Joint(int id, double radius) if (b < bands - 1) indices.AddRange([p + 1, q, q + 1]); } } - return new ViewerGeometry + return new SceneGeometry { Id = id, Vertices = vertices.Select(v => v * radius).ToList(), Normals = vertices, Indices = indices, // Cylindrical about the local Z axis, in metres. @@ -270,7 +286,7 @@ private static ViewerGeometry Joint(int id, double radius) } // 64 x 64 checker of 8 x 8 pixel squares as a 24-bit BMP, 0.5 m per repeat. - private static ViewerTexture Checker() + private static SceneTexture Checker() { const int size = 64, header = 54, pixels = size * size * 3; var bmp = new byte[header + pixels]; @@ -292,6 +308,6 @@ private static ViewerTexture Checker() bmp.AsSpan(header + (y * size + x) * 3, 3).Fill(v); // rows of 192 bytes need no padding } } - return new ViewerTexture { MimeType = "image/bmp", Base64Data = Convert.ToBase64String(bmp), ScaleX = 0.5, ScaleY = 0.5 }; + return new SceneTexture { Id = 1, MimeType = "image/bmp", Data = bmp, ScaleX = 0.5, ScaleY = 0.5 }; } } From 77729c26b93bde925d6d9508a06b4764294a61ba Mon Sep 17 00:00:00 2001 From: mahach666 <79989386+mahach666@users.noreply.github.com> Date: Thu, 12 Nov 2026 15:17:34 +0300 Subject: [PATCH 09/19] Document the scene-based API in the README and docs --- README.md | 55 +++++++++++++++++------------------------ docs/getting-started.md | 50 ++++++++++++++++--------------------- docs/public-api.md | 48 +++++++++++++++++++++-------------- 3 files changed, 73 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 0c053ca..bc7883d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Platform](https://img.shields.io/badge/.NET-8.0%20%7C%2010.0-512BD4?style=flat-square&logo=dotnet)](https://dotnet.microsoft.com/) -IOViewer is a fast, 100% managed .NET library and command line tool that generates standalone interactive 3D WebGL / HTML BIM & CAD model viewers from code, glTF/GLB, Wavefront OBJ, or JSON. Geometry read by sibling libraries ([RvtIO](https://github.com/IOExtend/RvtIO), [IfcIO](https://github.com/IOExtend/IfcIO), and [NwcIO](https://github.com/IOExtend/NwcIO)) can be passed in by building a `ViewerDocument` in code; IOViewer has no direct dependency on them. +IOViewer is a fast, 100% managed .NET library and command line tool that generates standalone interactive 3D WebGL / HTML BIM & CAD model viewers from code, glTF/GLB, Wavefront OBJ, or JSON. Geometry read by sibling libraries ([RvtIO](https://github.com/IOExtend/RvtIO), [IfcIO](https://github.com/IOExtend/IfcIO), and [NwcIO](https://github.com/IOExtend/NwcIO)) can be passed in by building a `SceneDocument` in code. The scene model comes from the [IOScene](https://github.com/IOExtend/IOScene) package; IOViewer has no direct dependency on the format libraries. The generated HTML is a single file with no external dependencies that works offline: it opens from disk (`file://`) in a current browser with WebGL 2 and makes no network requests. The viewer is built on [three.js](https://threejs.org/), which is embedded in the file (see [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)). @@ -19,7 +19,7 @@ Built clean-room under the [IOExtend](https://github.com/IOExtend) organization. ## What it does - Writes one self-contained HTML file per model; the viewer, its styles and the model data are all inlined. -- Stores geometry once per shape: meshes can share a `ViewerGeometry` placed by a `Transform3d`, and the exporter also finds meshes that are exact translated copies of each other. +- Stores geometry once per shape: instances can share a `SceneGeometry` placed by a `Transform3d`, and the exporter also finds geometries that are exact translated copies of each other. - Packs geometry compactly: positions quantized to 16-bit steps of 2⁻¹⁰ m (error at most 0.49 mm, 0.85 mm for rotated shared geometry; geometry larger than about 64 m, or shared geometry that an instance scales up, stays 32-bit float), octahedral 16-bit normals, 16- or 32-bit indices, all in one base64 block. - Shifts coordinates relative to the model centre (RTC), so georeferenced models with coordinates around 10⁶ m do not jitter. - Draws large models with few draw calls: geometry is merged into spatial chunks per material (very large models draw their repeated shapes as instanced copies instead), and hiding, selection and colouring are per-object data, so they never rebuild geometry. @@ -31,7 +31,7 @@ Built clean-room under the [IOExtend](https://github.com/IOExtend) organization. - section planes (up to six, moved and rotated with an in-scene gizmo) or a section box (keep inside or cut inside, face handles, move by the centre, box around the selection); - point-to-point distance measurement, display modes (materials, textures, white, wireframe), grid lines and a global opacity slider; - saved viewpoints with a viewpoint tour, clash results, a 4D schedule player and linked-file visibility, each shown only when the document has that data. -- Imports binary glTF 2.0 (`.glb`), Wavefront OBJ (`.obj`), and JSON snapshots. +- Imports binary glTF 2.0 (`.glb`), Wavefront OBJ (`.obj`), and JSON: viewer JSON, IOScene scene files and IOViewer 0.1/0.2 snapshots. ### Mouse and keyboard @@ -64,6 +64,9 @@ Add the library to a .NET project: dotnet add package IOViewer ``` +The library examples below are for 0.3.0, which builds on the IOScene scene model and is in preview; 0.2.0 has the +earlier model, and [CHANGELOG.md](CHANGELOG.md) lists how its types map to the new ones. + The library targets .NET 8 and .NET 10. It uses managed code without native dependencies or Windows-only APIs and supports Windows, Linux, and macOS. ## Command line @@ -92,41 +95,26 @@ Diagnostics and progress go to standard error. Run `ioviewer --help` for the ful ## Library -Create a viewer document programmatically and export to HTML: +Build a scene in code and export it to HTML: ```csharp -using IOViewer; -using IOViewer.Model; +using IOScene; using IOViewer.Exporters; -// Create viewer document -var doc = new ViewerDocument -{ - Title = "Commercial Building 3D Showcase", - Author = "IOExtend" -}; - -// Add mesh geometry -var mesh = new ViewerMesh -{ - Id = 1, - Name = "Curtain Wall Panel", - Vertices = [new(0, 0, 0), new(3, 0, 0), new(3, 3, 0), new(0, 3, 0)], - Indices = [0, 1, 2, 0, 2, 3], - Color = new RgbaColor(100, 149, 237, 200), - Opacity = 0.8 -}; -doc.Meshes.Add(mesh); - -// Export to self-contained HTML -HtmlViewerExporter.Save(doc, "showcase.html", new HtmlViewerOptions -{ - Title = "Commercial Building 3D Showcase", - IncludeProperties = true, - IncludeViewpoints = true -}); +var scene = new SceneDocument { Title = "Curtain wall panel" }; +scene.AddMesh("Panel", + [new(0, 0, 0), new(3, 0, 0), new(3, 0, 3), new(0, 0, 3)], + [0, 1, 2, 0, 2, 3], + new RgbaColor(100, 149, 237, 200)); + +HtmlViewerExporter.Save(scene, "panel.html"); ``` +`AddMesh` adds the model, the geometry, the element and the instance that links them. A real importer fills the scene +lists directly (`Models`, `Elements`, `Geometries`, `Instances`, `Materials`, `PropertySets` and the rest); the +[IOScene contract](https://github.com/IOExtend/IOScene/blob/main/docs/contract.md) describes them. To open the page with +section planes, a section box or linked models hidden, wrap the scene in a `ViewerDocument` (namespace `IOViewer.Model`). + Import a glTF / GLB model and convert to HTML viewer: ```csharp @@ -154,7 +142,8 @@ The documentation site with API references and guides is at