<div class="threedview"> <img class="ih screenshot" style="width: 300px"/> <div class="progressbars"> <div class="waiting"> <span class="badge">3</span><span class="text"> Waiting...</span> </div> </div> <div class="overlay-controls"> <div class="buttons"> <button class="ih btn btnScreenShot">Screenshot</button> <button class="btn btn-default glyphicon viewbutton glyphicon-resize-full btnResize" title="Show "full screen""></button> <button class="btn btn-default glyphicon viewbutton glyphicon-wrench btnViewOptions" data-toggle="dropdown" title="Change between triangles/lines"></button> </div> <div class="panel panel-default visualization ih"> <div class="panel-heading"> <h3 class="panel-title">Visualization options</h3> </div> <div class="panel-body"></div> </div> </div> <div id="viewport"></div> <div class="dropdown viewOptions"> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </div> </div> <script> function ThreeDView(containerDiv, parent, main) { var o = this; o.selected = null; // IfcProduct.oid -> GeometryInfo.oid o.loadedOids = {}; this.load = function(){ var promise = new BimServerApiPromise(); o.viewer = new BIMSURFER.Viewer(Global.bimServerApi, 'viewport'); $(window).resize(o.resize); o.resize(); o.viewer.loadScene(function(){ Global.scene = o.viewer.scene; var clickSelect = o.viewer.getControl("BIMSURFER.Control.ClickSelect"); clickSelect.activate(); clickSelect.events.register('select', o.nodeSelected); clickSelect.events.register('unselect', o.nodeUnselected); promise.fire(); }, {useCapture: true}); return promise; }; this.nodeSelected = function(groupId, node){ o.selected = node.data.object; parent.selected(o, groupId, node.data.object); }; this.nodeUnselected = function(groupId, node){ parent.unselected(o, groupId, node.data.object); o.selected = null; }; this.selected = function(origin, groupId, object){ if (o.selected == object) { return; } o.selected = object; // todo use groupid var sceneJsNode = o.viewer.scene.findNode(object.gid); if (sceneJsNode != null) { sceneJsNode.nodeId = sceneJsNode.id; o.viewer.getControl("BIMSURFER.Control.ClickSelect").pick(sceneJsNode); } }; this.unselected = function(groupId, id){ }; this.setAlternativeColor = function(object, color){ o.updateVisibility(object); }; this.unsetAlternativeColor = function(object){ var threeDObject = o.viewer.scene.findNode(object.gid); if (threeDObject != null) { var matrix = threeDObject.nodes[0]; var geometryNode = matrix.nodes[0]; if (geometryNode.coreId != null && ("" + geometryNode.coreId).endsWith("_Visualization")) { // This is a complex-material object which had been modified, return it to the old state if (geometryNode._core.arrays.colors != null) { matrix.removeNode(geometryNode); var newGeometry = { type: "geometry", coreId: geometryNode.getCoreId().replace("_Visualization", "") } matrix.addNode(newGeometry); } } else { o.setObjectColor(object); } } }; this.setObjectColor = function(object){ var threeDObject = o.viewer.scene.findNode(object.gid); var mode = object.trans.mode; if (threeDObject != null) { var matrix = threeDObject.nodes[0]; var geometryNode = matrix.nodes[0]; var material = BIMSURFER.Constants.materials[object.getType()]; // Hack to get the roof to be red..... if (object.getType() == "IfcSlab") { if (object.getPredefinedType() == "ROOF") { material = BIMSURFER.Constants.materials["IfcRoof"]; } } var color = {r: material.r, g: material.g, b: material.b, a: material.a}; if (mode == 0) { threeDObject.findParentByType("enable").setEnabled(true); } else if (mode == 1) { threeDObject.findParentByType("enable").setEnabled(true); color.a = 0.5; color.r = 0.5; color.g = 0.5; color.b = 0.5; if (o.selectedId == object.oid) { threeDObject.getNode("highlight").set("alpha", 0.5); } } else if (mode == 2) { threeDObject.findParentByType("enable").setEnabled(false); } o.changeColorOfObject(object, color); } }; this.changeColorOfObject = function(object, color){ var threeDObject = o.viewer.scene.findNode(object.gid); if (threeDObject != null) { var matrix = threeDObject.nodes[0]; var geometryNode = matrix.nodes[0]; if (geometryNode._core.arrays != null && geometryNode._core.arrays.colors != null) { var groupId = threeDObject.findParentByType("translate").data.groupId; var geometry = { type: "geometry", primitive: "triangles" }; geometry.coreId = geometryNode.getCoreId() + "_Visualization"; geometry.indices = geometryNode._core.arrays.indices; geometry.positions = geometryNode._core.arrays.positions; geometry.normals = geometryNode._core.arrays.normals; geometry.colors = []; for (var i=0; i<geometryNode._core.arrays.colors.length; i+=4) { geometry.colors[i] = color.r; geometry.colors[i+1] = color.g; geometry.colors[i+2] = color.b; geometry.colors[i+3] = color.a; } var library = o.viewer.scene.findNode("library-" + groupId); library.add("node", geometry); var newGeometry = { type: "geometry", coreId: geometryNode.getCoreId() + "_Visualization" } matrix.removeNode(geometryNode); matrix.addNode(newGeometry); } else { threeDObject.findParentByType("flags").set("flags", {transparent: color.a < 1}); threeDObject.parent.set("alpha", color.a); threeDObject.parent.set("baseColor", {r: color.r, g: color.g, b: color.b}); } } }; this.updateVisibility = function(object) { if (object.gid == null) { return; } var threeDObject = o.viewer.scene.findNode(object.gid); var mode = object.trans.mode; if (threeDObject != null) { var matrix = threeDObject.nodes[0]; var geometryNode = matrix.nodes[0]; if (geometryNode != null) { if (mode == 0) { threeDObject.findParentByType("enable").setEnabled(true); if (geometryNode._core.arrays != null && geometryNode._core.arrays.colors != null) { if (object.trans.colorOverride != null) { o.changeColorOfObject(object, object.trans.colorOverride); } else { if (geometryNode.coreId != null && ("" + geometryNode.coreId).endsWith("_Visualization")) { // This is a complex-material object which had been modified, return it to the old state if (geometryNode._core.arrays.colors != null) { matrix.removeNode(geometryNode); var newGeometry = { type: "geometry", coreId: geometryNode.getCoreId().replace("_Visualization", "") } matrix.addNode(newGeometry); } } } } else { if (object.trans.colorOverride != null) { o.changeColorOfObject(object, object.trans.colorOverride); } else { var material = BIMSURFER.Constants.materials[object.getType()]; // Hack to get the roof to be red..... if (object.getType() == "IfcSlab") { if (object.getPredefinedType() == "ROOF") { material = BIMSURFER.Constants.materials["IfcRoof"]; } } if (material == null) { material = BIMSURFER.Constants.materials.DEFAULT; } var color = {r: material.r, g: material.g, b: material.b, a: material.a}; o.changeColorOfObject(object, color); } } } else if (mode == 1) { threeDObject.findParentByType("enable").setEnabled(true); var color = {}; color.a = 0.5; color.r = 0.5; color.g = 0.5; color.b = 0.5; if (o.selectedId == object.oid) { threeDObject.getNode("highlight").set("alpha", 0.5); } o.changeColorOfObject(object, color); } else if (mode == 2) { threeDObject.findParentByType("enable").setEnabled(false); } } } else { console.log("Object not found: " + object.gid); } }; this.objectVisibilityChanged = function(objects){ // todo use groupid var uniqueRoids = []; if (!Array.isArray(objects)) { objects = [objects]; } var oidsNotLoaded = []; var count = 0; for (var i=0; i<objects.length; i++) { var object = objects[i]; if (i == 0) { // TODO actually do something useful here uniqueRoids.push(object.model.roid); } o.updateVisibility(object); if (o.loadedOids[object.oid] == null) { if (object.isA("IfcProduct")) { if (object.object._rRepresentation != -1 && object.object._rRepresentation != null) { if (Settings.getDefaultHiddenTypes()[object.getType()] == null || object.trans.mode == 0) { if (object.object._rgeometry != null) { if (object.model.objects[object.object._rgeometry] != null) { // Only if this data is preloaded, otherwise just don't include any gi object.getGeometry(function(geometryInfo){ oidsNotLoaded.push({gid: object.object._rgeometry._i, oid: object.oid, object: object, info: geometryInfo.object}); count++; }); } else { oidsNotLoaded.push({gid: object.object._rgeometry._i, oid: object.oid, object: object}); count++; } } } } } } } oidsNotLoaded.sort(function(a, b){ if (a.info != null && b.info != null) { var topa = (a.info._emaxBounds.z + a.info._eminBounds.z) / 2; var topb = (b.info._emaxBounds.z + b.info._eminBounds.z) / 2; return topa - topb; } else { // Resort back to type // TODO this is dodgy when some objects do have info, and others don't return a.object.getType().localeCompare(b.object.getType()); } }); if (count > 0) { var models = {}; var lastModel; uniqueRoids.forEach(function(roid){ models[roid] = parent.models[roid]; lastModel = parent.models[roid]; }); var geometryType = $.cookie(main.user.oid + "geometrytype"); if (geometryType == null) { geometryType = "triangles"; } var geometryLoader = new GeometryLoader(Global.bimServerApi, models, o.viewer, geometryType); var first = true; var progressdiv = null; var progressbar = null; var text = null; geometryLoader.addProgressListener(function(title, progressPercent){ if (first) { progressdiv = $("<div class=\"progressdiv\">"); text = $("<div class=\"text\">"); text.html("Blaat"); var progress = $("<div class=\"progress progress-striped\">"); progressbar = $("<div class=\"progress-bar\" style=\"width: 100%\">"); progressdiv.append(text) progressdiv.append(progress); progress.append(progressbar); containerDiv.find(".progressbars").append(progressdiv); first = false; } //text.html("Loading... (" + nrObjectsRead + "/" + totalNrObjects + ")"); if (title == "done") { progressdiv.fadeOut(400); } else { text.html(title); progressbar.css("width", progressPercent + "%"); } }); geometryLoader.setLoadOids(uniqueRoids, oidsNotLoaded); geometryLoader.setTitle(lastModel.name); // This might be needed when the geometryloader comes up with more objects than requested... geometryLoader.objectAddedListeners.push(function(oid){ // models[uniqueRoids[0]].get(oid, function(object){ // o.updateVisibility(object); // }); o.loadedOids[oid] = true; }); // geometryLoader.progressListeners.push(function(state){ // if (state == "done") { // for (var i=0; i<oidsNotLoaded.length; i++) { // o.loadedOids[oidsNotLoaded[i]] = true; // } // o.resize(); // } // }); o.viewer.loadGeometry(geometryLoader); } }; this.resize = function(){ var resizeBtn = containerDiv.find(".btnResize"); var fullscreen = !resizeBtn.hasClass("glyphicon-resize-full"); var width = Math.round($(window).width() / 3 * 2 - 29); $(".overlay-controls").css("top", fullscreen ? 10 : 120); var height = ($(window).height() - $(".navbar").outerHeight() - $(".navbar-header").outerHeight() - $(".rightpanel .panel-heading").outerHeight() - 10) + (fullscreen ? 113 : 0); $("div#viewport").width(width + "px"); $("div#viewport").height((height - 1) + "px"); o.viewer.resize($('div#viewport').width(), $('div#viewport').height()); }; this.unloadRevision = function(poid, roid) { if (roid != -1) { var node = o.viewer.scene.findNode("model_node_" + roid); if (node != null) { node.destroy(); var boundsTranslate = o.viewer.scene.findNode("bounds_translate"); if (boundsTranslate.nodes.length == 0) { // If this was the last model, let's clear the bound_translate as well, so we can start with a fresh one after this boundsTranslate.destroy(); } } } }; this.loadRevision = function(project, roid){ Global.bimServerApi.registerNewExtendedDataOnRevisionHandler(roid, function(){ o.updateVisualisationOverride(roid); }, function(){ o.updateVisualisationOverride(roid); }); }; this.updateVisualisationOverride = function(roid){ /* This checks whether there are visualization overrides, example: { "name": "Test", "changes": [{ "selector": { "guids": ["16DNNqzfP2thtfaOflvsKA"] }, "effect": { "color": { "r": 0, "g": 1, "b": 0, "a": 1 } } }] } */ Global.bimServerApi.callWithNoIndication("ServiceInterface", "getExtendedDataSchemaByName", {name: "VIS_3D_JSON_1_0"}, function(schema){ if (schema != null) { var lastExtendedData = null; Global.bimServerApi.call("ServiceInterface", "getAllExtendedDataOfRevision", {roid: roid}, function(list){ list.forEach(function(extendedData){ if (extendedData.schemaId == schema.oid) { if (lastExtendedData == null || extendedData.added > lastExtendedData.added) { lastExtendedData = extendedData; } } }); if (lastExtendedData != null) { Global.bimServerApi.call("ServiceInterface", "getFile", {fileId: lastExtendedData.fileId}, function(file){ var data = atob(file.data); var vis = JSON.parse(data); var div = $("<div class=\"checkbox\">"); var label = $("<label></label>"); div.append(label); var checkbox = $("<input type=\"checkbox\"/>"); var change = function(checked){ var guids = []; var map = {}; vis.changes.forEach(function(change){ guids = guids.concat(change.selector.guids); change.selector.guids.forEach(function(guid){ map[guid] = change; }); }); var model = parent.models[roid]; var objects = []; model.getByGuids(guids, function(object){ var change = map[object.getGlobalId()]; if (object.object._rRepresentation != -1 && object.object._rRepresentation != null) { objects.push(object); object.trans.mode = 0; if (checked) { object.trans.colorOverride = change.effect.color; } else { object.trans.colorOverride = null; } } // Go 1 level deep for decomposition, this 1 level is quite arbitrarily chosen, but makes models with IfcAssembly work if (object.getIsDecomposedBy != null) { object.getIsDecomposedBy(function(ifcRelAggregates){ ifcRelAggregates.getRelatedObjects(function(relatedObject){ if (relatedObject.object._rRepresentation != -1 && relatedObject.object._rRepresentation != null) { objects.push(relatedObject); relatedObject.trans.mode = 0; if (checked) { relatedObject.trans.colorOverride = change.effect.color; } else { relatedObject.trans.colorOverride = null; } } }); }); } }).done(function(){ o.objectVisibilityChanged(objects); }); }; checkbox.change(function(){ var checked = $(this).is(":checked"); change(checked); }); label.append(checkbox); label.append(vis.name); containerDiv.find(".visualization").show(); containerDiv.find(".visualization .panel-body div").remove(); containerDiv.find(".visualization .panel-body").append(div); }); } }); } }); }; this.resizeClick = function(){ var resizeBtn = containerDiv.find(".btnResize"); if (resizeBtn.hasClass("glyphicon-resize-full")) { $(".navbar").hide(); $(".panel-heading").hide(); resizeBtn.removeClass("glyphicon-resize-full").addClass("glyphicon-resize-small"); resizeBtn.attr("title", "Back to normal view"); } else { $(".navbar").show(); $(".panel-heading").show(); resizeBtn.removeClass("glyphicon-resize-small").addClass("glyphicon-resize-full"); resizeBtn.attr("title", "Show \"full screen\""); } parent.navigator.resize(); o.resize(); }; this.close = function(){ // console.log("close"); // parent.selectListeners.unregister(o.selected); // parent.unselectListeners.unregister(o.unselected); // parent.objectVisibilityListeners.unregister(o.objectVisibilityChanged); // parent.modelLoadedListeners.unregister(o.loadRevision); // parent.modelUnloadedListeners.unregister(o.unloadRevision); }; this.viewOptionsClick = function(){ var geometryType = $.cookie(main.user.oid + "geometrytype"); if (geometryType == null) { geometryType = "triangles"; } $.cookie(main.user.oid + "geometrytype", geometryType == "lines" ? "triangles" : "lines"); alert("Reload for changes to take effect"); // containerDiv.find(".viewOptions").dropdown("toggle"); }; containerDiv.find(".waiting").hide(); parent.selectListeners.register(o.selected); parent.unselectListeners.register(o.unselected); parent.objectVisibilityListeners.register(o.objectVisibilityChanged); parent.modelLoadedListeners.register(o.loadRevision); parent.modelUnloadedListeners.register(o.unloadRevision); $("#viewport").on("contextmenu", function(e){ e.preventDefault(); }); $(".btnScreenShot").click(function(){ o.viewer.capture().done(function(test){ $(".screenshot").attr("src", test.src); }); }); containerDiv.find(".btnResize").click(o.resizeClick); containerDiv.find(".btnViewOptions").click(o.viewOptionsClick); } </script>