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
Related
Im trying to implement the XLS Extension. In the ModelData class, i cannot get objects leaf nodes because the viewer is undefined.
Here is the problematic method:
getAllLeafComponents(callback) {
// from https://learnforge.autodesk.io/#/viewer/extensions/panel?id=enumerate-leaf-nodes
viewer.getObjectTree(function (tree) {
let leaves = [];
tree.enumNodeChildren(tree.getRootId(), function (dbId) {
if (tree.getChildCount(dbId) === 0) {
leaves.push(dbId);
}
}, true);
callback(leaves);
});
}
Im getting Cannot read properties of undefined (reading 'getObjectTree') , meaning viewer is undefined.
However, viewer is working and displaying documents.
I tried to call it by window.viewer and this.viewer to no avail.
Thanks in advance for any help
It looks like it missed two lines. Could you try the revised one below?
// Model data in format for charts
class ModelData {
constructor(viewer) {
this._modelData = {};
this._viewer = viewer;
}
init(callback) {
var _this = this;
var viewer = _this._viewer;
_this.getAllLeafComponents(function (dbIds) {
var count = dbIds.length;
dbIds.forEach(function (dbId) {
viewer.getProperties(dbId, function (props) {
props.properties.forEach(function (prop) {
if (!isNaN(prop.displayValue)) return; // let's not categorize properties that store numbers
// some adjustments for revit:
prop.displayValue = prop.displayValue.replace('Revit ', ''); // remove this Revit prefix
if (prop.displayValue.indexOf('<') == 0) return; // skip categories that start with <
// ok, now let's organize the data into this hash table
if (_this._modelData[prop.displayName] == null) _this._modelData[prop.displayName] = {};
if (_this._modelData[prop.displayName][prop.displayValue] == null) _this._modelData[prop.displayName][prop.displayValue] = [];
_this._modelData[prop.displayName][prop.displayValue].push(dbId);
})
if ((--count) == 0) callback();
});
})
})
}
getAllLeafComponents(callback) {
var _this = this;
var viewer = _this._viewer;
// from https://learnforge.autodesk.io/#/viewer/extensions/panel?id=enumerate-leaf-nodes
viewer.getObjectTree(function (tree) {
var leaves = [];
tree.enumNodeChildren(tree.getRootId(), function (dbId) {
if (tree.getChildCount(dbId) === 0) {
leaves.push(dbId);
}
}, true);
callback(leaves);
});
}
hasProperty(propertyName){
return (this._modelData[propertyName] !== undefined);
}
getLabels(propertyName) {
return Object.keys(this._modelData[propertyName]);
}
getCountInstances(propertyName) {
return Object.keys(this._modelData[propertyName]).map(key => this._modelData[propertyName][key].length);
}
getIds(propertyName, propertyValue) {
return this._modelData[propertyName][propertyValue];
}
}
I am developing HTML + JS using the forge viewer of Autodesk-forge.
I am looking for a way to display text near a specific object in 2D on the viewer.
Please let me know if there is a fix for the code below, or any other way to do it.
What I tried and result
I practiced the contents of the following article
https://adndevblog.typepad.com/technology_perspective/2020/12/forge-viewer-markup-along-dbid.html
The markup could be displayed on the 3D model as expected.
However, it was not possible to set the markup position on the 2D model.
Code (excerpt)
Calling part
viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, () => {
createMarkUp(viewer, {
icons: [
{ dbId: 11146, label: 'コメントあり', css: 'iconWarning fas fa-exclamation-triangle fa-2x
faa-flash animated' },
],
onClick: (id) => {
viewer.select(id);
}
});
});
Logic part
function createMarkUp(viewer, options) {
let _group = null;
let _button = null;
let _icons = options.icons || [];
let _frags = null;
viewer._enabled = true;
load();
showIcons(true);
function load() {
const updateIconsCallback = () => {
if (viewer._enabled) {
updateIcons();
}
};
viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, updateIconsCallback);
viewer.addEventListener(Autodesk.Viewing.ISOLATE_EVENT, updateIconsCallback);
viewer.addEventListener(Autodesk.Viewing.HIDE_EVENT, updateIconsCallback);
viewer.addEventListener(Autodesk.Viewing.SHOW_EVENT, updateIconsCallback);
return true;
}
function showIcons(show) {
const $viewer = $('#' + viewer.clientContainer.id + ' div.adsk-viewing-viewer');
// remove previous...
$('#' + viewer.clientContainer.id + ' div.adsk-viewing-viewer label.markup').remove();
if (!show) return;
// do we have anything to show?
if (_icons === undefined || _icons === null) return;
// do we have access to the instance tree?
const tree = viewer.model.getInstanceTree();
if (tree === undefined) { console.log('Loading tree...'); return; }
const onClick = (e) => {
if (options.onClick)
options.onClick($(e.currentTarget).data('id'));
};
_frags = {}
for (var i = 0; i < _icons.length; i++) {
// we need to collect all the fragIds for a given dbId
const icon = _icons[i];
_frags['dbId' + icon.dbId] = []
// create the label for the dbId
const $label = $(`
<label class="markup update" data-id="${icon.dbId}">
<span class="${icon.css}"> ${icon.label || ''}</span>
</label>
`);
$label.css('display', viewer.isNodeVisible(icon.dbId) ? 'block' : 'none');
$label.on('click', onClick);
$viewer.append($label);
// now collect the fragIds
tree.enumNodeFragments(icon.dbId, function (fragId) {
_frags['dbId' + icon.dbId].push(fragId);
updateIcons(); // re-position of each fragId found
});
}
}
function getModifiedWorldBoundingBox(dbId) {
const fragList = viewer.model.getFragmentList();
const nodebBox = new THREE.Box3();
// for each fragId on the list, get the bounding box
for (const fragId of _frags['dbId' + dbId]) {
const fragbBox = new THREE.Box3();
fragList.getWorldBounds(fragId, fragbBox);
nodebBox.union(fragbBox); // create a unifed bounding box
}
return nodebBox
}
function updateIcons() {
for (const label of $('#' + viewer.clientContainer.id + ' div.adsk-viewing-viewer .update'))
{
const $label = $(label);
const id = $label.data('id');
// get the center of the dbId (based on its fragIds bounding boxes)
const pos = viewer.worldToClient(getModifiedWorldBoundingBox(id).center());
// position the label center to it
$label.css('left', Math.floor(pos.x - $label[0].offsetWidth / 2) + 'px');
$label.css('top', Math.floor(pos.y - $label[0].offsetHeight / 2) + 'px');
$label.css('display', viewer.isNodeVisible(id) ? 'block' : 'none');
}
}
}
Constitution
HTML+JS
Unfortunately, the code above cannot be applied to 2D drawings as those use a different internal data structure.
With that said, you should still be able to achieve what you want because the Viewer APIs provide some utilities for 2D designs as well. For example, based on this blog post, you can obtain the bounding box of a specific element in your 2D drawing like so:
function getBounds2D(viewer, model, dbId) {
const frags = model.getFragmentList();
let bounds = new THREE.Box3();
let boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bounds);
let fragIds = frags.fragments.dbId2fragId[dbId]; // Find all fragments including this object's primitives
if (!Array.isArray(fragIds)) {
fragIds = [fragIds];
}
for (const fragId of fragIds) {
// Get the actual mesh with all geometry data for given fragment
const mesh = frags.getVizmesh(fragId);
const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, viewer.impl.use2dInstancing);
vbr.enumGeomsForObject(dbId, boundsCallback); // Update bounds based on all primitives linked to our dbId
}
return bounds;
}
This will return a THREE.Box3 structure defining the extent of the 2D primitive. From there, you could grab the center of the bounding box, project it to screen coordinates, and use the screen coords to position your HTML overlay elements, for example:
const bounds = getBounds2D(viewer, viewer.model, someDbId);
const coords = viewer.worldToClient(bounds.center());
$label.css('left', Math.floor(coords.x + 'px');
$label.css('top', Math.floor(coords.y + 'px');
Is it common sense to use polymer build and then deploy the Application on your Web Server used for production?
Or does it make sense to actutally use polymer serve / polyserve as the Web Server?
The problem with polymer serve is that if it falls over it doesn't restart, leaving you with no web site. Its real use is in development because it maps directories for you when you are developing a single element.
Also, how will you be handling ajax calls?
IN the past I have previously run my code (a bespoke node web server) in PM2. These days I run using docker, and in particular docker-compose which also restarts the application if it fails.
EDIT The following is how I transpile on the fly code is copied (and then altered by me) from Google Polymer Teams "Polymer Server" and is therefore subject to the licence conditions given in that project.
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
The code consists of some supporting functions like these
const parse5 = require('parse5');
const dom5 = require('dom5');
const LRU = require('lru-cache');
const babelCore = require('babel-core');
const transformLog = require('debug')('web:transform');
const babelTransformers = [
'babel-plugin-transform-es2015-arrow-functions',
'babel-plugin-transform-es2015-block-scoped-functions',
'babel-plugin-transform-es2015-block-scoping',
'babel-plugin-transform-es2015-classes',
'babel-plugin-transform-es2015-computed-properties',
'babel-plugin-transform-es2015-destructuring',
'babel-plugin-transform-es2015-duplicate-keys',
'babel-plugin-transform-es2015-for-of',
'babel-plugin-transform-es2015-function-name',
'babel-plugin-transform-es2015-literals',
'babel-plugin-transform-es2015-object-super',
'babel-plugin-transform-es2015-parameters',
'babel-plugin-transform-es2015-shorthand-properties',
'babel-plugin-transform-es2015-spread',
'babel-plugin-transform-es2015-sticky-regex',
'babel-plugin-transform-es2015-template-literals',
'babel-plugin-transform-es2015-typeof-symbol',
'babel-plugin-transform-es2015-unicode-regex',
'babel-plugin-transform-regenerator',
].map((name) => require(name));
const isInlineJavaScript = dom5.predicates.AND(
dom5.predicates.hasTagName('script'),
dom5.predicates.NOT(dom5.predicates.hasAttr('src')));
const babelCompileCache = LRU({
length: (n, key) => n.length + key.length
});
function compileHtml(source, location) {
const document = parse5.parse(source);
const scriptTags = dom5.queryAll(document, isInlineJavaScript);
for (const scriptTag of scriptTags) {
try {
const script = dom5.getTextContent(scriptTag);
const compiledScriptResult = compileScript(script);
dom5.setTextContent(scriptTag, compiledScriptResult);
} catch (e) {
// By not setting textContent we keep the original script, which
// might work. We may want to fail the request so a better error
// shows up in the network panel of dev tools. If this is the main
// page we could also render a message in the browser.
//eslint-disable-next-line no-console
console.warn(`Error compiling script in ${location}: ${e.message}`);
}
}
return parse5.serialize(document);
}
function compileScript(script) {
return babelCore
.transform(script, {
plugins: babelTransformers,
}).code;
}
function transform(request, body, isHtml) {
const source = body;
const cached = babelCompileCache.get(source);
if (cached !== undefined) {
transformLog('using the cache');
return cached;
}
if (isHtml) {
transformLog('compiling html');
body = compileHtml(source, request.path);
} else {
transformLog('compiling js');
body = compileScript(source);
}
babelCompileCache.set(source, body);
return body;
}
The meat though is the middleware which effectively inserts itself in the outgoing stream captures all the chunks of outgoing html and js files and transforms them if necessary.
function transformResponse(transformNeeded) {
return (req, res, next) => {
let ended = false;
let _shouldTransform = null;
let isHtml = true;
// Note: this function memorizes its result.
function shouldTransform() {
if (_shouldTransform == null) {
const successful = res.statusCode >= 200 && res.statusCode < 300;
if (successful) {
const result = transformNeeded(req);
isHtml = result.isHtml;
_shouldTransform = !!result.transform;
} else {
_shouldTransform = false;
}
}
return _shouldTransform;
}
const chunks = [];
const _write = res.write;
res.write = function( chunk, enc, cb) {
if (ended) {
_write.call(this, chunk, enc, cb);
return false;
}
if (shouldTransform()) {
const buffer = (typeof chunk === 'string') ? new Buffer(chunk,enc) : chunk;
chunks.push(buffer);
return true;
}
return _write.call(this, chunk, enc, cb);
}.bind(res);
const _end = res.end;
res.end = function (chunk, enc, cb) {
if (ended)
return false;
ended = true;
if (shouldTransform()) {
if (chunk) {
const buffer = (typeof chunk === 'string') ? new Buffer(chunk,enc) : chunk;
chunks.push(buffer);
}
const body = Buffer.concat(chunks).toString('utf8');
let newBody = body;
try {
newBody = transform(req, body, isHtml);
} catch (e) {
//eslint-disable-next-line no-console
console.warn('Error', e);
}
// TODO(justinfagnani): re-enable setting of content-length when we know
// why it was causing truncated files. Could be multi-byte characters.
// Assumes single-byte code points!
// res.setHeader('Content-Length', `${newBody.length}`);
this.removeHeader('Content-Length');
return _end.call(this, newBody);
}
return _end.call(this,chunk, enc, cb);
}.bind(res);
next();
};
}
This routine called transformNeeded which is as follows (this is the bit that detects the brower)
function transformNeeded(req) {
const pathname = url.parse(req.url).pathname;
const isHtml = pathname === '/' || pathname.slice(-5) === '.html';
if (isHtml || pathname.slice(-3) === '.js') {
//see if we need to compile as we have a .html or .js file
const splitPathName = pathname.split('/');
const isPolyfill = splitPathName.includes('webcomponentsjs') ||
splitPathName.includes('promise-polyfill');
if (!isPolyfill) {
const browser = new UAParser(req.headers['user-agent']).getBrowser();
const versionSplit = (browser.version || '').split('.');
const [majorVersion, minorVersion] = versionSplit.map((v) => v ? parseInt(v, 10) : -1);
const supportsES2015 = (browser.name === 'Chrome' && majorVersion >= 49) ||
(browser.name === 'Chromium' && majorVersion >= 49) ||
(browser.name === 'OPR' && majorVersion >= 36) ||
(browser.name === 'Mobile Safari' && majorVersion >= 10) ||
(browser.name === 'Safari' && majorVersion >= 10) ||
// Note: The Edge user agent uses the EdgeHTML version, not the main
// release version (e.g. EdgeHTML 15 corresponds to Edge 40). See
// https://en.wikipedia.org/wiki/Microsoft_Edge#Release_history.
//
// Versions before 15.15063 may contain a JIT bug affecting ES6
// constructors (see #161).
(browser.name === 'Edge' &&
(majorVersion > 15 || (majorVersion === 15 && minorVersion >= 15063))) ||
(browser.name === 'Firefox' && majorVersion >= 51);
requestLog(
'Browser is %s version %d,%d - supports ES2015? ',
browser.name,
majorVersion,
minorVersion,
supportsES2015
);
return {transform: !supportsES2015, isHtml: isHtml};
}
}
return {transform: false, isHtml: isHtml};
}
Finally, I have to set up the routes before I establish the web server and then tell the web server to use the routes I have set up.
const Router = require('router');
//sets up my API routes
manager.setRoutes(router);
router.use('/', transformResponse(this.transformNeeded));
router.use('/', staticFiles(clientPath));
this._start(router);
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;
jQuery is firing on every mousemove event for me.
How do I get it to stop doing that?
It seems like jQuery 1.2.6 do not have this behavior, but 1.4 and 1.5 does.
Stackoverflow.com does the same thing. Wonder what changed.
The jQuery event code:
/*
* A number of helper functions used for managing events.
* Many of the ideas behind this code originated from
* Dean Edwards' addEvent library.
*/
jQuery.event = {
// Bind an event to an element
// Original by Dean Edwards
add: function( elem, types, handler, data ) {
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// TODO :: Use a try/catch until it's safe to pull this out (likely 1.6)
// Minor release fix for bug #8018
try {
// For whatever reason, IE has trouble passing the window object
// around, causing it to be cloned in the process
if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
elem = window;
}
}
catch ( e ) {}
if ( handler === false ) {
handler = returnFalse;
} else if ( !handler ) {
// Fixes bug #7229. Fix recommended by jdalton
return;
}
var handleObjIn, handleObj;
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
}
// Make sure that the function being executed has a unique ID
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// Init the element's event structure
var elemData = jQuery._data( elem );
// If no elemData is found then we must be trying to bind to one of the
// banned noData elements
if ( !elemData ) {
return;
}
var events = elemData.events,
eventHandle = elemData.handle;
if ( !events ) {
elemData.events = events = {};
}
if ( !eventHandle ) {
elemData.handle = eventHandle = function( e ) {
// Handle the second event of a trigger and when
// an event is called after a page has unloaded
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
jQuery.event.handle.apply( eventHandle.elem, arguments ) :
undefined;
};
}
// Add elem as a property of the handle function
// This is to prevent a memory leak with non-native events in IE.
eventHandle.elem = elem;
// Handle multiple events separated by a space
// jQuery(...).bind("mouseover mouseout", fn);
types = types.split(" ");
var type, i = 0, namespaces;
while ( (type = types[ i++ ]) ) {
handleObj = handleObjIn ?
jQuery.extend({}, handleObjIn) :
{ handler: handler, data: data };
// Namespaced event handlers
if ( type.indexOf(".") > -1 ) {
namespaces = type.split(".");
type = namespaces.shift();
handleObj.namespace = namespaces.slice(0).sort().join(".");
} else {
namespaces = [];
handleObj.namespace = "";
}
handleObj.type = type;
if ( !handleObj.guid ) {
handleObj.guid = handler.guid;
}
// Get the current list of functions bound to this event
var handlers = events[ type ],
special = jQuery.event.special[ type ] || {};
// Init the event handler queue
if ( !handlers ) {
handlers = events[ type ] = [];
// Check for a special event handler
// Only use addEventListener/attachEvent if the special
// events handler returns false
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// Add the function to the element's handler list
handlers.push( handleObj );
// Keep track of which events have been used, for global triggering
jQuery.event.global[ type ] = true;
}
// Nullify elem to prevent memory leaks in IE
elem = null;
},
global: {},
// Detach an event or set of events from an element
remove: function( elem, types, handler, pos ) {
// don't do events on text and comment nodes
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
if ( handler === false ) {
handler = returnFalse;
}
var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
events = elemData && elemData.events;
if ( !elemData || !events ) {
return;
}
// types is actually an event object here
if ( types && types.type ) {
handler = types.handler;
types = types.type;
}
// Unbind all events for the element
if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
types = types || "";
for ( type in events ) {
jQuery.event.remove( elem, type + types );
}
return;
}
// Handle multiple events separated by a space
// jQuery(...).unbind("mouseover mouseout", fn);
types = types.split(" ");
while ( (type = types[ i++ ]) ) {
origType = type;
handleObj = null;
all = type.indexOf(".") < 0;
namespaces = [];
if ( !all ) {
// Namespaced event handlers
namespaces = type.split(".");
type = namespaces.shift();
namespace = new RegExp("(^|\\.)" +
jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
}
eventType = events[ type ];
if ( !eventType ) {
continue;
}
if ( !handler ) {
for ( j = 0; j < eventType.length; j++ ) {
handleObj = eventType[ j ];
if ( all || namespace.test( handleObj.namespace ) ) {
jQuery.event.remove( elem, origType, handleObj.handler, j );
eventType.splice( j--, 1 );
}
}
continue;
}
special = jQuery.event.special[ type ] || {};
for ( j = pos || 0; j < eventType.length; j++ ) {
handleObj = eventType[ j ];
if ( handler.guid === handleObj.guid ) {
// remove the given handler for the given type
if ( all || namespace.test( handleObj.namespace ) ) {
if ( pos == null ) {
eventType.splice( j--, 1 );
}
if ( special.remove ) {
special.remove.call( elem, handleObj );
}
}
if ( pos != null ) {
break;
}
}
}
// remove generic event handler if no more handlers exist
if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
jQuery.removeEvent( elem, type, elemData.handle );
}
ret = null;
delete events[ type ];
}
}
// Remove the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {
var handle = elemData.handle;
if ( handle ) {
handle.elem = null;
}
delete elemData.events;
delete elemData.handle;
if ( jQuery.isEmptyObject( elemData ) ) {
jQuery.removeData( elem, undefined, true );
}
}
},
// bubbling is internal
trigger: function( event, data, elem /*, bubbling */ ) {
// Event object or event type
var type = event.type || event,
bubbling = arguments[3];
if ( !bubbling ) {
event = typeof event === "object" ?
// jQuery.Event object
event[ jQuery.expando ] ? event :
// Object literal
jQuery.extend( jQuery.Event(type), event ) :
// Just the event type (string)
jQuery.Event(type);
if ( type.indexOf("!") >= 0 ) {
event.type = type = type.slice(0, -1);
event.exclusive = true;
}
// Handle a global trigger
if ( !elem ) {
// Don't bubble custom events when global (to avoid too much overhead)
event.stopPropagation();
// Only trigger if we've ever bound an event for it
if ( jQuery.event.global[ type ] ) {
// XXX This code smells terrible. event.js should not be directly
// inspecting the data cache
jQuery.each( jQuery.cache, function() {
// internalKey variable is just used to make it easier to find
// and potentially change this stuff later; currently it just
// points to jQuery.expando
var internalKey = jQuery.expando,
internalCache = this[ internalKey ];
if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
jQuery.event.trigger( event, data, internalCache.handle.elem );
}
});
}
}
// Handle triggering a single element
// don't do events on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
return undefined;
}
// Clean up in case it is reused
event.result = undefined;
event.target = elem;
// Clone the incoming data, if any
data = jQuery.makeArray( data );
data.unshift( event );
}
event.currentTarget = elem;
// Trigger the event, it is assumed that "handle" is a function
var handle = jQuery._data( elem, "handle" );
if ( handle ) {
handle.apply( elem, data );
}
var parent = elem.parentNode || elem.ownerDocument;
// Trigger an inline bound script
try {
if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
event.result = false;
event.preventDefault();
}
}
// prevent IE from throwing an error for some elements with some event types, see #3533
} catch (inlineError) {}
if ( !event.isPropagationStopped() && parent ) {
jQuery.event.trigger( event, data, parent, true );
} else if ( !event.isDefaultPrevented() ) {
var old,
target = event.target,
targetType = type.replace( rnamespaces, "" ),
isClick = jQuery.nodeName( target, "a" ) && targetType === "click",
special = jQuery.event.special[ targetType ] || {};
if ( (!special._default || special._default.call( elem, event ) === false) &&
!isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
try {
if ( target[ targetType ] ) {
// Make sure that we don't accidentally re-trigger the onFOO events
old = target[ "on" + targetType ];
if ( old ) {
target[ "on" + targetType ] = null;
}
jQuery.event.triggered = event.type;
target[ targetType ]();
}
// prevent IE from throwing an error for some elements with some event types, see #3533
} catch (triggerError) {}
if ( old ) {
target[ "on" + targetType ] = old;
}
jQuery.event.triggered = undefined;
}
}
},
handle: function( event ) {
var all, handlers, namespaces, namespace_re, events,
namespace_sort = [],
args = jQuery.makeArray( arguments );
event = args[0] = jQuery.event.fix( event || window.event );
event.currentTarget = this;
// Namespaced event handlers
all = event.type.indexOf(".") < 0 && !event.exclusive;
if ( !all ) {
namespaces = event.type.split(".");
event.type = namespaces.shift();
namespace_sort = namespaces.slice(0).sort();
namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
}
event.namespace = event.namespace || namespace_sort.join(".");
events = jQuery._data(this, "events");
handlers = (events || {})[ event.type ];
if ( events && handlers ) {
// Clone the handlers to prevent manipulation
handlers = handlers.slice(0);
for ( var j = 0, l = handlers.length; j < l; j++ ) {
var handleObj = handlers[ j ];
// Filter the functions by class
if ( all || namespace_re.test( handleObj.namespace ) ) {
// Pass in a reference to the handler function itself
// So that we can later remove it
event.handler = handleObj.handler;
event.data = handleObj.data;
event.handleObj = handleObj;
var ret = handleObj.handler.apply( this, args );
if ( ret !== undefined ) {
event.result = ret;
if ( ret === false ) {
event.preventDefault();
event.stopPropagation();
}
}
if ( event.isImmediatePropagationStopped() ) {
break;
}
}
}
}
return event.result;
},
props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix: function( event ) {
if ( event[ jQuery.expando ] ) {
return event;
}
// store a copy of the original event object
// and "clone" to set read-only properties
var originalEvent = event;
event = jQuery.Event( originalEvent );
for ( var i = this.props.length, prop; i; ) {
prop = this.props[ --i ];
event[ prop ] = originalEvent[ prop ];
}
// Fix target property, if necessary
if ( !event.target ) {
// Fixes #1925 where srcElement might not be defined either
event.target = event.srcElement || document;
}
// check if target is a textnode (safari)
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
// Add relatedTarget, if necessary
if ( !event.relatedTarget && event.fromElement ) {
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
}
// Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && event.clientX != null ) {
var doc = document.documentElement,
body = document.body;
event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
}
// Add which for key events
if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
event.which = event.charCode != null ? event.charCode : event.keyCode;
}
// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
if ( !event.metaKey && event.ctrlKey ) {
event.metaKey = event.ctrlKey;
}
// Add which for click: 1 === left; 2 === middle; 3 === right
// Note: button is not normalized, so don't use it
if ( !event.which && event.button !== undefined ) {
event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
}
return event;
},
// Deprecated, use jQuery.guid instead
guid: 1E8,
// Deprecated, use jQuery.proxy instead
proxy: jQuery.proxy,
special: {
ready: {
// Make sure the ready event is setup
setup: jQuery.bindReady,
teardown: jQuery.noop
},
live: {
add: function( handleObj ) {
jQuery.event.add( this,
liveConvert( handleObj.origType, handleObj.selector ),
jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
},
remove: function( handleObj ) {
jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
}
},
beforeunload: {
setup: function( data, namespaces, eventHandle ) {
// We only want to do this special case on windows
if ( jQuery.isWindow( this ) ) {
this.onbeforeunload = eventHandle;
}
},
teardown: function( namespaces, eventHandle ) {
if ( this.onbeforeunload === eventHandle ) {
this.onbeforeunload = null;
}
}
}
}
};
To clarify, I don't like this behavior. I think it's unnecessary, especially if I never intend to use mousemove in the first place.
This kind of thing matters in some situations.
In the developer tools, go to the Sources tab, then on the right open up Event Listener Breakpoints from the accordion menu. Make sure nothing is selected. I had this problem because somehow mouse/click events were selected, so it was stopping there every time.
This is not an intrinsic part of jQuery's event handling code, but instead only occurs on some sites.
Presumably those sites are using some specific feature that ends up listening to all mouse moves, e.g. on the document object or the <body /> or the like. A good candidate for what could cause this would be the (mis)use of .live(); in general .delegate() is preferable, but sometimes .live() is unavoidable.
Regardless, whichever feature it is that causes this, StackOverflow et al. are willing to accept this cost. As you build up your own site, you can periodically check the Developer Tools timeline, and if this kind of behavior appears, you can make your own decision on cost/benefit.