Referencing unused materials from a JSON scene - json

I'm trying to create different materials in a JSON scene, assign a default one to a mesh, and let the user switch between the different materials within the user interface.
Is it possible to reference every materials, even the unused ones, after parsing a JSON ?

After inspecting the code of THREE.ObjectLoader, it turns out that it parses the entire JSON but explicitly returns a single reference to the scene object. I solved this issue by monkey patching the parse method at runtime. The code is taken from THREE.ObjectLoader.parse, only the return value is different (returning an Object and not a THREE.Object3D anymore).
function objectLoaderParseOverride(json, onLoad) {
var ret = {};
ret.geometries = this.parseGeometries( json.geometries );
ret.images = this.parseImages( json.images, function () {
if ( onLoad !== undefined ) onLoad( ret );
} );
ret.textures = this.parseTextures( json.textures, ret.images );
ret.materials = this.parseMaterials( json.materials, ret.textures );
ret.object = this.parseObject( json.object, ret.geometries, ret.materials );
if ( json.animations ) {
ret.object.animations = this.parseAnimations( json.animations );
}
if ( json.images === undefined || json.images.length === 0 ) {
if ( onLoad !== undefined ) onLoad( ret );
}
return ret;
}
var loader = new THREE.ObjectLoader();
loader.parse = objectLoaderParseOverride;

Related

Grouping Elements cannot find in forge viewer

i am working with Forge viewer, in Revit file we are creating some elements as groups. when that Revit file uploading in forge viewer we cannot find any grouping data which given in Revit, we can only saw total element wise groups. is there any way to get the Revit groups into forge viewer?..... please help us to solve this issue.
Except for those two methods advised by Jeremy. There is another way to achieve it by querying viewer property DB.
Currently, Revit groups are not a part of the model structure panel (Instance tree) and don't have a concrete mesh linked to them, so we cannot play with them in the viewer directly, but fortunately they can be found inside the viewer property DB.
Here is a small demo for proving this possibility, please have a try:
//
// Copyright (c) Autodesk, Inc. All rights reserved
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Forge Autodesk.ADN.RevitGroupPanel
// by Eason Kang - Autodesk Developer Network (ADN)
//
'use strict';
(function() {
function userFunction( pdb ) {
let _nameAttrId = pdb.getAttrName();
let _internalGroupRefAttrId = -1;
// Iterate over all attributes and find the index to the one we are interested in
pdb.enumAttributes(function(i, attrDef, attrRaw){
let category = attrDef.category;
let name = attrDef.name;
if( name === 'Group' && category === '__internalref__' ) {
_internalGroupRefAttrId = i;
return true; // to stop iterating over the remaining attributes.
}
});
//console.log( _internalGroupRefAttrId );
// Early return is the model doesn't contain data for "Group".
if( _internalGroupRefAttrId === -1 )
return null;
let _internalMemberRefAttrId = -1;
// Iterate over all attributes and find the index to the one we are interested in
pdb.enumAttributes(function(i, attrDef, attrRaw){
let category = attrDef.category;
let name = attrDef.name;
if( name === 'Member' && category === '__internalref__' ) {
_internalMemberRefAttrId = i;
return true; // to stop iterating over the remaining attributes.
}
});
//console.log( _internalMemberRefAttrId );
// Early return is the model doesn't contain data for "Member".
if( _internalMemberRefAttrId === -1 )
return null;
let _categoryAttrId = -1;
// Iterate over all attributes and find the index to the one we are interested in
pdb.enumAttributes(function(i, attrDef, attrRaw){
let category = attrDef.category;
let name = attrDef.name;
if( name === 'Category' && category === '__category__' ) {
_categoryAttrId = i;
return true; // to stop iterating over the remaining attributes.
}
});
//console.log( _categoryAttrId );
// Early return is the model doesn't contain data for "Member".
if( _categoryAttrId === -1 )
return null;
const groups = [];
// Now iterate over all parts to find all groups
pdb.enumObjects(function( dbId ) {
let isGroup = false;
// For each part, iterate over their properties.
pdb.enumObjectProperties( dbId, function( attrId, valId ) {
// Only process 'Caegory' property.
// The word "Property" and "Attribute" are used interchangeably.
if( attrId === _categoryAttrId ) {
const value = pdb.getAttrValue( attrId, valId );
if( value === 'Revit Group' ) {
isGroup = true;
// Stop iterating over additional properties when "Caegory: Revit Group" is found.
return true;
}
}
});
if( !isGroup ) return;
const children = [];
let groupName = '';
// For each part, iterate over their properties.
pdb.enumObjectProperties( dbId, function( attrId, valId ) {
// Only process 'Member' property.
// The word "Property" and "Attribute" are used interchangeably.
if( attrId === _internalMemberRefAttrId ) {
const value = pdb.getAttrValue( attrId, valId );
children.push( value );
}
if( attrId === _nameAttrId ) {
const value = pdb.getAttrValue( attrId, valId );
groupName = value;
}
});
groups.push({
dbId,
name: groupName,
children
});
});
return groups;
}
class AdnRevitGroupPanel extends Autodesk.Viewing.UI.DockingPanel {
constructor( viewer, title, options ) {
options = options || {};
// Height adjustment for scroll container, offset to height of the title bar and footer by default.
if( !options.heightAdjustment )
options.heightAdjustment = 70;
if( !options.marginTop )
options.marginTop = 0;
super( viewer.container, viewer.container.id + 'AdnRevitGroupPanel', title, options );
this.container.classList.add( 'adn-docking-panel' );
this.container.classList.add( 'adn-rvt-group-panel' );
this.createScrollContainer( options );
this.viewer = viewer;
this.options = options;
this.uiCreated = false;
this.addVisibilityListener(( show ) => {
if( !show ) return;
if( !this.uiCreated )
this.createUI();
});
}
async getGroupData() {
try {
return await this.viewer.model.getPropertyDb().executeUserFunction( userFunction );
} catch( ex ) {
console.error( ex );
return null;
}
}
async requestContent() {
const data = await this.getGroupData();
if( !data ) return;
for( let i=0; i<data.length; i++ ) {
const div = document.createElement( 'div' );
div.innerText = `${ data[i].name }(${ data[i].children.length })`;
div.title = `DbId: ${ data[i].dbId }`;
div.addEventListener(
'click',
( event ) => {
event.stopPropagation();
event.preventDefault();
this.viewer.clearSelection();
this.viewer.select( data[i].children );
this.viewer.fitToView( data[i].children );
});
this.scrollContainer.appendChild( div );
}
this.resizeToContent();
}
async createUI() {
this.uiCreated = true;
if( this.viewer.model.isLoadDone() ) {
this.requestContent();
} else {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
() => this.requestContent(),
{ once: true }
);
}
}
}
class AdnRevitGroupPanelExtension extends Autodesk.Viewing.Extension {
constructor( viewer, options ) {
super( viewer, options );
this.panel = null;
this.createUI = this.createUI.bind( this );
this.onToolbarCreated = this.onToolbarCreated.bind( this );
}
onToolbarCreated() {
this.viewer.removeEventListener(
Autodesk.Viewing.TOOLBAR_CREATED_EVENT,
this.onToolbarCreated
);
this.createUI();
}
createUI() {
const viewer = this.viewer;
const rvtGroupPanel = new AdnRevitGroupPanel( viewer, 'Revit Group' );
viewer.addPanel( rvtGroupPanel );
this.panel = rvtGroupPanel;
const rvtGroupButton = new Autodesk.Viewing.UI.Button( 'toolbar-adnRevitGroupTool' );
rvtGroupButton.setToolTip( 'Revit Group' );
rvtGroupButton.setIcon( 'adsk-icon-properties' );
rvtGroupButton.onClick = function() {
rvtGroupPanel.setVisible( !rvtGroupPanel.isVisible() );
};
const subToolbar = new Autodesk.Viewing.UI.ControlGroup( 'toolbar-adn-tools' );
subToolbar.addControl( rvtGroupButton );
subToolbar.adnRvtGroupButton = rvtGroupButton;
this.subToolbar = subToolbar;
viewer.toolbar.addControl( this.subToolbar );
rvtGroupPanel.addVisibilityListener(function( visible ) {
if( visible )
viewer.onPanelVisible( rvtGroupPanel, viewer );
rvtGroupButton.setState( visible ? Autodesk.Viewing.UI.Button.State.ACTIVE : Autodesk.Viewing.UI.Button.State.INACTIVE );
});
}
load() {
if( this.viewer.toolbar ) {
// Toolbar is already available, create the UI
this.createUI();
} else {
// Toolbar hasn't been created yet, wait until we get notification of its creation
this.viewer.addEventListener(
Autodesk.Viewing.TOOLBAR_CREATED_EVENT,
this.onToolbarCreated
);
}
return true;
}
unload() {
if( this.panel ) {
this.panel.uninitialize();
delete this.panel;
this.panel = null;
}
if( this.subToolbar ) {
this.viewer.toolbar.removeControl( this.subToolbar );
delete this.subToolbar;
this.subToolbar = null;
}
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension( 'Autodesk.ADN.RevitGroupPanel', AdnRevitGroupPanelExtension );
})();
viewer.loadExtension( 'Autodesk.ADN.RevitGroupPanel' );
Here is the snapshot:
As you certainly know, Forge is a fully generic CAD modelling environment.
Therefore, it mainly provides support for cross-domain CAD functionality.
The Revit grouping functionality that you seek is BIM specific and cannot be recreated in a useful manner for all domains in Forge.
There are numerous way that you can work around this.
Here are two main approaches you might want to consider:
Implement a Revit add-in to retrieve and export the grouping data and make it available to your Forge app
Implement an app in Forge Design Automation for Revit to gather the required data from the RVT hosted in Forge

Polymer - notify that a object.property changed in a array for DOM

In Polymer 1.*, I have a dom repeat. The obj.property is not updating in the DOM when I mutate array itemsCollection.
I tried 'this.notifyPath(itemCollection.${i}.value)' after 'this.set(itemCollection.${i}.value, existing.value);' but it did not update in the DOM.
Am I supposed to use this.notifySplices instead? And if so, how would I use it after this.set(itemCollection.${i}.value, existing.value);?
_populateAlerts: function(existingValues) {
this.itemCollection.forEach((question, i)=> {
const existing =
existingValues.find(value => value.name === question.name);
this.set(`itemCollection.${i}.picklist_value_id`,
existing.picklist_value_id);
this.set(`itemCollection.${i}.value`, existing.value);
});
},
this.notifyPath is never needed if you use this.set, and should probably only be used if another framework sets the variable.
It's weird code, with cubic looping, and setting subproperties in itemCollection, while looping through said array, through Polymer methods.
Anyway, I wonder if there is an Array reference problem. Where existingValues = itemCollection, so every time existingValues changes, itemCollection is changed as well but in a way that doesn't update the DOM. This means that itemCollection tries to set itself to an already existing value when being set through this.set, hence not updating the DOM through dirty checking.
A simple solution could be to just set itemCollection with a copy of itself.
_populateAlerts: function(existingValues) {
this.itemCollection.forEach((question, i)=> {
const existing =
existingValues.find(value => value.name === question.name);
this.set(`itemCollection.${i}.picklist_value_id`,
existing.picklist_value_id);
this.set(`itemCollection.${i}.value`, existing.value);
});
this.set('itemCollection', JSON.parse( JSON.stringify(this.itemCollection) );
// Alternatively
// const tempArr = JSON.parse( JSON.stringify(this.itemCollection) );
// this.set('itemCollection, []); // override dirty checking, as stated in the documentation
// this.set('itemCollection', tempArr);
},
Another solution could be to create a new array of existingValues, breaking the "reference chain" so existingValues != itemCollection. That is, if the issue is a reference problem.
_populateAlerts: function(existingValues) {
const copiedExistingValues = JSON.parse( JSON.stringify(existingValues) );
this.itemCollection.forEach((question, i)=> {
const existing =
copiedExistingValues.find(value => value.name === question.name);
this.set(`itemCollection.${i}.picklist_value_id`,
existing.picklist_value_id);
this.set(`itemCollection.${i}.value`, existing.value);
});
},
However, if you're only interested in the first occurance, I would create an object of existingArrays to avoid cubic looping while also breaking the reference chain.
_populateAlerts: function(existingValues) {
const existingValuesObj = this._createObjectFrom(existingValues, 'name');
this.itemCollection.forEach((question, i)=> {
this.set(`itemCollection.${i}.picklist_value_id`,
existingValuesObj[question.name].picklist_value_id);
this.set(`itemCollection.${i}.value`, existingValuesObj[question.name].value);
});
},
_createObjectFrom: function (arr, property, overwritePreviousObj) {
var obj = {};
var propertyName = '';
for (var i = 0; i < arr.length; i++) {
propertyName = arr[i][property];
if (overwritePreviousObj) {
obj[propertyName] = arr[i];
} else if (!obj.hasOwnProperty(propertyName) {
obj[propertyName] = arr[i];
}
}
return obj;
},

Assign JSON value to variable based on value of a different key

I have this function for extracting the timestamp from two JSON objects:
lineReader.on('line', function (line) {
var obj = JSON.parse(line);
if(obj.Event == "SparkListenerApplicationStart" || obj.Event == "SparkListenerApplicationEnd") {
console.log('Line from file:', obj.Timestamp);
}
});
The JSON comes from a log file(not JSON) where each line represents an entry in the log and each line also happens to be in JSON format on its own.
The two objects represent the start and finish of a job. These can be identified by the event key(SparkListenerApplicationStart and SparkListenerApplicationEnd). They also both contain a timestamp key. I want to subtract the end time from the start time to get the duration.
My thinking is to assign the timestamp from the JSON where Event key = SparkListenerApplicationStart to one variable and assign the timestamp from the JSON where Event key = SparkListenerApplicationEnd to another variable and subtract one from the other. How can I do this? I know I can't simply do anything like:
var startTime = if(obj.Event == "SparkListenerApplicationStart"){
return obj.Timestamp;
}
I'm not sure if I understood, but if are reading rows and want get the Timestamp of each row I would re-write a new object;
const collection = []
lineReader.on('line', function (line) {
var obj = JSON.parse(line);
if(obj.Event == "SparkListenerApplicationStart" || obj.Event == "SparkListenerApplicationEnd") {
// console.log('Line from file:', obj.Timestamp);
collection.push(obj.Timestamp)
}
});
console.log(collection);
Where collection could be a LocalStorage, Global Variable, or something alike.
Additional info
With regard to my comment where I queried how to identify the start and end times, I ended up setting start as the smallest value and end as the largest. Here is my final code:
const collection = []
lineReader.on('line', function (line) {
var obj = JSON.parse(line);
if((obj.Event == "SparkListenerApplicationStart" || obj.Event == "SparkListenerApplicationEnd")) {
collection.push(obj.Timestamp);
if(collection.length == 2){
startTime = Math.min.apply(null, collection);
finishTime = Math.max.apply(null, collection);
duration = finishTime - startTime;
console.log(duration);
}
}
});

ScriptDB object size calculation

I'm trying to estimate the limits of my current GAS project. I use ScriptDB to chunk out processing to get around the 6 min execution limit. If I have an object like
var userObj{
id: //user email address
count: //integer 1-1000
trigger: //trigger ID
label: //string ~30 char or less
folder: //Google Drive folder ID
sendto: //'true' or 'false'
shareto: //'true' or 'false'
}
How would I calculate the size that this object takes up in the DB? I would like to project how many of these objects can exist concurrently before I reach the 200MB limit for our domain.
Whenever you've got a question about google-apps-script that isn't about the API, try searching for javascript questions first. In this case, I found JavaScript object size, and tried out the accepted answer in apps-script. (Actually, the "improved" accepted answer.) I've made no changes at all, but have reproduced it here with a test function so you can just cut & paste to try it out.
Here's what I got with the test stud object, in the debugger.
Now, it's not perfect - for instance, it doesn't factor in the size of the keys you'll use in ScriptDB. Another answer took a stab at that. But since your object contains some potentially huge values, such as an email address which can be 256 characters long, the key lengths may be of little concern.
// https://stackoverflow.com/questions/1248302/javascript-object-size/11900218#11900218
function roughSizeOfObject( object ) {
var objectList = [];
var stack = [ object ];
var bytes = 0;
while ( stack.length ) {
var value = stack.pop();
if ( typeof value === 'boolean' ) {
bytes += 4;
}
else if ( typeof value === 'string' ) {
bytes += value.length * 2;
}
else if ( typeof value === 'number' ) {
bytes += 8;
}
else if
(
typeof value === 'object'
&& objectList.indexOf( value ) === -1
)
{
objectList.push( value );
for( i in value ) {
stack.push( value[ i ] );
}
}
}
return bytes;
}
function Marks()
{
this.maxMarks = 100;
}
function Student()
{
this.firstName = "firstName";
this.lastName = "lastName";
this.marks = new Marks();
}
function test () {
var stud = new Student();
var studSize = roughSizeOfObject(stud);
debugger;
}

Permutation of Actionscript 3 Array

Greetings,
This is my first post and I hope someone out there can help. I am an educator and I designed a quiz using Actionscript 3 (Adobe Flash) that is to determine all the different ways a family can have three children.
I have two buttons that enter either the letter B (for boy) or G (for girl) into an input text field named text_entry. I then have a submit button named enter_btn that checks to see if the entry into the input text is correct. If the input is correct, the timeline moves to the next problem (frame labeled checkmark); if it is incorrect the timeline moves to the end of the quiz (frame 62).
The following code works well for any particular correct single entry (ie: BGB). I need to write code in which all eight correct variations must be entered, but they can be entered in any order (permutation):
ie:
BBB,BBG,BGB,BGG,GBB,GBG,GGB,GGG; or
BGB,GGG,BBG,BBB,GGB,BGB,GGB,BGG; or
GGB,GGG,BBG,BBB,GGB,BGB,BGB,BGG; etc...
there are over 40,000 ways to enter these eight ways of having three children. Help!
baby_B.addEventListener(MouseEvent.CLICK, letterB);
function letterB(event:MouseEvent)
{
text_entry.appendText("B");
}
baby_G.addEventListener(MouseEvent.CLICK, letterG);
function letterG(event:MouseEvent)
{
text_entry.appendText("G");
}
enter_btn.addEventListener(MouseEvent.CLICK, check);
function check(event:MouseEvent):void {
var solution_S:Array=["BBB","BBG","BGB","BGG","GBB","GBG","GGB","GGG "];
if(solution_S.indexOf(text_entry.text)>=0)
{
gotoAndStop("checkmark");
}
else
{
gotoAndPlay(62);
}
}
If you know the correct code, please write it out for me. Thanks!
You will just need to keep a little bit of state to know what the user has entered so far. One possible way of doing that is to have a custom object/dictionary that you initialize outside all your functions, so that it is preserved during the transitions between frames/runs of the functions:
var solutionEntered:Object = {"BBB":false, "BBG":false, /*fill in rest */ };
Then in your function check you can perform an additional check, something like:
function check(event:MouseEvent):void {
var solution_S:Array=["BBB","BBG","BGB","BGG","GBB","GBG","GGB","GGG "];
if(solution_S.indexOf(text_entry.text)>=0) {
// We know the user entered a valid solution, let's check if
// then entered it before
if(solutionEntered[text_entry.text]) {
// yes they entered it before, do whatever you need to do
} else {
// no they haven't entered it, set the status as entered
solutionEntered[text_entry.text] = true;
}
// continue rest of your function
}
// continue the rest of your function
}
Note that this is not necessarily an optimal solution, but it keeps with the code style you already have.
Try this:
import flash.text.TextField;
import flash.events.MouseEvent;
import flash.display.Sprite;
var correctAnswers : Array = [ "BBB", "BBG", "BGB", "GBB", "BGG", "GGB", "GBG", "GGG" ];
var answersSoFar : Array;
var textField : TextField; //on stage
var submitButton : Sprite; //on stage
var correctAnswerCount : int;
//for testing
textField.text = "BBB,BBG,BGB,GBB,BGG,GGB,GBG,GGG";
//textField.text = "BGB,BBB,GGG,BBG,GBB,BGG,GGB,GBG,";
//textField.text = "BBB,BBG, BGB,GBB,BGG, GGB, GBG, GGG";
//textField.text = "BBB,BBG,BGB,GBB,BGG,GGB,GBG";
//textField.text = "BBB,BBG,BGB,GBB,BGG,GGB,GBG,GGG,BBG";
submitButton.addEventListener( MouseEvent.CLICK, onSubmit );
function onSubmit( event : MouseEvent ) : void
{
var answers : Array = getAnswersArray( textField.text );
answersSoFar = [];
correctAnswerCount = 0;
for each ( var answer : String in answers )
if ( answerIsCorrect( answer ) ) correctAnswerCount++;
if ( correctAnswerCount == correctAnswers.length ) trace( "correct" );
else trace( "incorrect" );
}
function getAnswersArray( string : String ) : Array
{
string = removeInvalidCharacters( string );
return string.split( "," );
}
function removeInvalidCharacters( string : String ) : String
{
var result : String = "";
for ( var i : int, len = string.length; i < len; i++ )
if ( string.charAt( i ) == "B" || string.charAt( i ) == "G" || string.charAt( i ) == "," )
result += string.charAt( i );
return result;
}
function answerIsCorrect( answer : String ) : Boolean
{
if ( answerIsADuplicate( answer ) ) return false;
else answersSoFar.push( answer );
if ( answerIsInListOfCorrectAnswers( answer ) ) return true;
return false;
}
function answerIsInListOfCorrectAnswers( answer : String ) : Boolean
{
if ( correctAnswers.indexOf( answer ) == -1 ) return false;
return true;
}
function answerIsADuplicate( answer : String ) : Boolean
{
if ( answersSoFar.indexOf( answer ) == -1 ) return false;
return true;
}
note that in the original code you pasted, you have an extra space in the last element of your correct answer list - "GGG " should be "GGG"
this works
baby_B.addEventListener(MouseEvent.CLICK, letterB);
function letterB(event:MouseEvent) {
text_entry.appendText("B");
}
baby_G.addEventListener(MouseEvent.CLICK, letterG);
function letterG(event:MouseEvent) {
text_entry.appendText("G");
}
var valid:Array = ["BBB","BBG","BGB","BGG","GBB","GBG","GGB","GGG"];
enter_btn.addEventListener(MouseEvent.CLICK, check);
function check(event:MouseEvent):void {
var parts:Array = text_entry.text.split(/,\s*/g); // split the text into component parts
var dup:Array = valid.slice(); // copy the correct list
while(parts.length){ // run through each answer component
var part:String = parts.pop(); // grab the last one
part = part.replace(/(^\s+|\s+$)/g, ''); // strip leading/trailing white space
var pos:int = dup.indexOf(part); // is it in the list of correct answers?
if(pos != -1){ // if it is...
dup.splice(pos, 1); // then remove that answer from the list
}
}
if(dup.length == 0) { // if it's 0, they got all the correct answers
gotoAndStop("checkmark");
} else { // otherwise, they didn't get one or more correct answers
gotoAndPlay(62);
}
}