I have multiple groups of texture images (at different times) to load into a three js scene, and would like to get callbacks when each separate group is loaded. The groups may overlap in time.
If there was just one group, I could do this:
// FETCH BEADS NEEDED and rebuild display if needed. Return qty of beads requested
var manager = new THREE.LoadingManager( function() {
// this onLoad callback is executed when both textures are loaded
} );
var loader = new THREE.TextureLoader( manager );
var texture1 = loader.load( 'texture1.png' );
var texture2 = loader.load( 'texture2.png' );
This will work fine for the first group consisting of texture1.png and texture2.png. But now I have a separate group of texture3.png and texture4.png ...
What I need is two callbacks, one when texture1.png and texture2.png are both loaded, and a second callback when texture3 and texture4.png are loaded.
I could create a new TextureLoader for the second one, but in fact I will have many groups and don't want to have hundreds or thousands of TextureLoaders lying around and leaking memory.
Should I delete each one in its onLoad callback? Or what?
Thanks and best regards, jb
The loading manager has an .onLoad property where you can assign a callback for when its elements have finished loading. You can assign this at any time, it doesn't have to be set when initializing, so just re-assign it before you start loading a new group, and it should have the behavior you're looking for. You can read more about the .onLoad property in the docs:
var manager = new THREE.LoadingManager();
var texLoader = new THREE.TextureLoader(manager);
function loadGroup1() {
manager.onLoad = () => {
console.log("Group 1 complete");
};
texLoader.load("img1.png");
texLoader.load("img2.png");
}
function loadGroup2() {
manager.onLoad = () => {
console.log("Group 2 loaded");
};
texLoader.load("img3.png");
texLoader.load("img4.png");
}
function loadGroup3() {
manager.onLoad = () => {
console.log("Group 3 finished");
};
texLoader.load("img5.png");
texLoader.load("img6.png");
}
Just make sure the previous group has finished loading before starting the next group, otherwise you'll overwrite the onLoad function prematurely.
Edit
An alternative method that doesn't require staggering the loads:
var texLoader = new THREE.TextureLoader();
var loadTracker = [];
function loadTex(url, groupNo) {
// Start a new group if it doesn't exist
if (loadTracker[groupNo] === undefined) {
loadTracker[groupNo] = 1;
}
// Add 1 to group if it already exists
else {
loadTracker[groupNo] ++;
}
// Load texture
texLoader.load(url, () => {
// Subtract 1 from group when load is finished
loadTracker[groupNo] --;
// When count reaches 0, group has finished loading
if (loadTracker[groupNo] === 0) {
console.log("Group number " + groupNo + " has finished loading");
}
});
}
// Trigger loads with image URL, and group they belong to
loadTex("img1.png", 0);
loadTex("img2.png", 0);
loadTex("img3.png", 1);
loadTex("img4.png", 1);
loadTex("img5.png", 1);
loadTex("img6.png", 2);
// ...
Related
I have Photoshop CC2019 PSD document containing several smart objects that contains other smart objects that contains other smart objects. Some of these have linked layers. Normally, such images are not updated automatically (which is extremely annoying, Adobe!) but you have to manually update each of them once the linked image content has changed.
There is a .jsx script file named "Update All Modified Content.jsx" which auto-updates linked layers (PNG image in my case) but only if the smart object is in the top most document - that is no nested smart objects with linked layers are updated automatically.
My question is: does anyone know how to update the content of the above mentioned .jsx file so that it would auto-update all linked images across all the smart objects in PSD document including nested ones?
For those who care or would be willing to help updating the code here it is:
// Update all modified content
var idplacedLayerUpdateAllModified = stringIDToTypeID( "placedLayerUpdateAllModified" );
executeAction( idplacedLayerUpdateAllModified, undefined, DialogModes.NO );
So, after spending half a day with it I finally solved it myself. Here is the code:
#target photoshop
// SET INITIAL ACTIVE DOCUMENT
var mainDocument = app.activeDocument;
// SAVE THE DOCUMENT NAME FOR FUTURE USE
var mainDocName = mainDocument.name;
// RUN THE MAIN UPDATE FUNCTION
mainDocument.suspendHistory("processAllSmartObjects", "autoupdateAllSmartObjects(mainDocument, 0)");
// FINALLY SAVE THE MAIN DOCUMENT
mainDocument.save();
function autoupdateAllSmartObjects(theParent, prevVal) {
// FUNCTION TO TEST IF SMARTOBJECT IS LINKED
function isLinkedSO(obj) {
var localFilePath = "";
var ref = new ActionReference();
ref.putIdentifier(charIDToTypeID('Lyr '), obj.id);
var desc = executeActionGet(ref);
var smObj = desc.getObjectValue(stringIDToTypeID('smartObject'));
var isLinked = false;
// TEST IF IT HAS LINKED FILE
try {
var localFilePath = smObj.getPath(stringIDToTypeID('link'));
isLinked = true;
} catch(e) {
//
}
return isLinked;
}
// FUNCTION TO UPDATE LINKED SMART OBJECT
function doTheUpdate(LYR, stackNr) {
// SET ACTIVE LAYER TO ACTUALY ITERATED ONE
app.activeDocument.activeLayer = LYR;
// RUN IN "SILENT" MODE
app.displayDialogs = DialogModes.NO;
var layer = app.activeDocument.activeLayer;
// IF ACTIVE LAYER IS SMARTOBJECT
if (layer.kind == "LayerKind.SMARTOBJECT") {
//alert(layer);
// OPEN THE SMARTOBJECT
app.runMenuItem(stringIDToTypeID('placedLayerEditContents'));
// DO THE ACTUAL FILE UPDATE
var idplacedLayerUpdateAllModified = stringIDToTypeID( "placedLayerUpdateAllModified" );
executeAction( idplacedLayerUpdateAllModified, undefined, DialogModes.NO);
// IF IT IS NOT THE "CORE/MAIN" DOCUMENT
if(stackNr > 0) {
// SAVE CHANGES (UPDATE) AND CLOSE IT
app.activeDocument.close(SaveOptions.SAVECHANGES);
}
// CONTINUE INSIDE THIS ACTIVE SMARTOBJECT
autoupdateAllSmartObjects(app.activeDocument, stackNr);
}
return;
}
// FUNCTION TO PARSE GROUPS
function parseGroup(LYR) {
var groupLayers = LYR.layers;
// IF GROUP IS NOT EMPTY
if(groupLayers.length > 0) {
// PARSE ALL LAYERS IN THE GROUP
for (var i = groupLayers.length - 1; i >= 0; i--) {
var lyr = groupLayers[i];
// IF NOT LOCKED = NOT EDITABL:E
if(!lyr.allLocked) {
// YET ANOTHER GROUP?
if (lyr.typename == "LayerSet") {
// IF IT IS NOT EMPTY
if (lyr.layers.length > 0) {
// RE-RUN THE SCRIPT ANEW WITH THE SELECTED GROUP AS LAYERS SOURCE
autoupdateAllSmartObjects(lyr, 0);
}
// LAYERS
} else if (lyr.typename == "ArtLayer") {
// IF THE LAYER IS SMARTOBJECT
if (lyr.kind == LayerKind.SMARTOBJECT) {
// IF THE LAYER IS SET TO "visible" (THAT IS: NOT DISABLED)
if(lyr.visible){
// TEST IF THE SMARTOBJECT IS ACTUALLY LINKED
if(!isLinkedSO(lyr)) {
// RUN THE UPDATE SUB-FUNCTION
doTheUpdate(lyr, i);
}
}
}
}
}
}
}
}
// PARSE ALL THE LAYERS
for (var i = theParent.layers.length - 1 - prevVal; i >= 0; i--) {
var theLayer = theParent.layers[i];
// ONLY ArtLayers
if (theLayer.typename == "ArtLayer") {
// IF THE LAYER IS SMARTOBJECT
if (theLayer.kind == LayerKind.SMARTOBJECT) {
// IF THE LAYER IS SET TO "visible" (THAT IS: NOT DISABLED)
if(theLayer.visible){
// TEST IF THE SMARTOBJECT IS ACTUALLY LINKED
if(!isLinkedSO(theLayer)){
// RUN THE UPDATE SUB-FUNCTION
doTheUpdate(theLayer, i);
// IF WE ARE AT THE LAST LAYER IN THE STACK AND IT IS NOT OUR MAIN DOCUMENT
if(i == 0 && app.activeDocument.name !== mainDocName) {
// SAVE CHANGES (UPDATE) AND CLOSE IT
app.activeDocument.close(SaveOptions.SAVECHANGES);
}
}
}
}
// ONLY Groups
} else if (theLayer.typename == "LayerSet") {
// RUN SUB-FUNCTION FOR GROUP PARSING
parseGroup(theLayer);
// ANYTHING ELSE
} else {
autoupdateAllSmartObjects(theLayer, m);
}
}
return;
};
OP's script works!! It was going in loops for me too but after some trial and error, I realised that my smart objects (SO) that are linked across artboards ( - e.g. if you change one SO, it changes on various artboards) - were the issue. I hid all such SO and it works.
so basically, it only works for Smart Objects + copies made via 'New smart object via copy' NOT 'duplicate layer' / copypaste smart objects. If your work contains a 'duplicate layer' SO - it will break the script. You need to hide these objects ( or not work like that all together) before running the script
I am working with an object which i loaded like this (I save it as a global variable such that I can change it later):
model1 = null;
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load("http://blabla.mtl", function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load("http://blabla.obj", function(object) {
object.scale.x = 0.0004;
object.scale.y = 0.0004;
object.scale.z = 0.0004;
object.rotateX(Math.PI / 2);
object.rotateZ(Math.PI / 2);
object.add(new THREE.AxisHelper(2))
model1 = object; //save in global variables
scene.add(model1);
});
});
When I am rendering I want to change the position of the model, based on a box's change in position:
function render(ms: number) {
if (lastTime) {
update((ms-lastTime)/1000)
}
lastTime = ms
requestAnimationFrame(render)
renderer.render( scene, camera )
}
var pos_current;
var pos_new;
var trans;
function update(dt: number) {
if (pause.on) return
//save position of box before it is transformed
pos_current = box.position.clone();
box.updatePosition()
//save new position of box
pos_new = box.position.clone();
//transform the 3D-model according to the box's transformation
trans = pos_current.sub(pos_new);
model1.position.add(trans);
console.log(model1.position)
I can in the console see that the position of model1 changes, but the object just doesn't move at all, and I can't really figure out why.
Any help is very appreciated :)
Got it to work - the problem was that I was trying to update the position of model1, before it was actually done loading in the obj-loader :D
I have a query. I too made a global variable and equaled it to object when loading. but it says in console log that variable in null. Is there a something I am missing.
Update:-
I understood the part where I am making the humanModel equal to object before it loaded but then how to handle it then?
let humanModel = null;
OBJLoader.load(
// resource URL
"/Models/Model_1(Malequins)/1/Cadnav.com_B0426014.obj",
// called when resource is loaded
function(object) {
scene.add(object);
humanModel = object; //adding the humanModel to the global variable
camera.lookAt(object.position);
console.log(object.position);
console.log(camera.position);
},
// called when loading is in progresses
function(xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function(error) {
console.log("An error happened " + error);
}
);
console.log(humanModel.position);
Thanks
you can keep the object(obj file) in x,y or z direction, for it you need to set its position as:
var loader = new THREE.OBJLoader();
loader.load(
'/models/female.obj',
function(object){
object.position.y=-20;
scene.add(object);
}
)
Can I call a function(one that will make another object visible/invisible) on a specific animation frame or time? I would like to have arrows describe the movement of the animation at certain times during the animation. While I can just make them visible when I start the animation and make them invisible when the animation stops, I would like to specify ranges inside the animation to do this
playPatientAnim: function (anim, callback) {
var pending = 1;
var me = this;
var finish = callback ? function () {
if (pending && !--pending) {
callback.call(me, anim);
}
} : null;
me.currentPatient.skinned.forEach(function (mesh) {
mesh.animations.forEach(function(anim){
anim.stop();
});
});
me.currentPatient.skinned.forEach(function (mesh) {
var animation = mesh.animations[anim];
animation.stop();
if (animation) {
pending++;
animation.onComplete = finish;
animation.play();
}
});
if (finish) {
finish();
}
}
You can make a mesh visible or invisible ( mesh.visible = false; //or true ). To change visibility at certain time you could use timestamp:
new Date().getTime() and calculate how you want to do the sequence of your animation.
Let's say I have three Views. AppView, MenuView and StripView. MenuView contains multiple StripViews and AppView contains one MenuView. How can I trigger event from StripView and listen on that event on AppView.
EDIT
Let's say I want to click on ImageSurface on StripView and reigster that event on AppView, and then do some transitionig.
MY SOLUTION
Everything is based on Timbre app, created in Famou.us Starter Kit Reference Tutorials
// StripView.js (_setListeners() is called in StripView constructor and bodySurface is defined in code)
function _setListeners() {
var eventScope = this._eventOutput;
this.backgroundSurface.on('click',function(){
eventScope.emit('test', { somedata:'some value'} );
}.bind(this));
}
// MenuView.js (_createStripViews() is called in MenuView constructor)
function _createStripViews() {
this.stripModifiers = [];
var yOffset = this.options.topOffset;
for (var i = 0; i < this.options.stripData.length; i++) {
var stripView = new StripView({
iconUrl: this.options.stripData[i].iconUrl,
title: this.options.stripData[i].title
});
var stripModifier = new StateModifier({
transform: Transform.translate(0, yOffset, 0)
});
this.stripModifiers.push(stripModifier);
this.add(stripModifier).add(stripView);
yOffset += this.options.stripOffset;
stripView.pipe(this._eventOutput);
}
}
//AppView.js (menuView is defined in code and _setListeners() is called in AppView constructor)
function _setListeners() {
this.menuView.on('test',function(){
console.log("IT WORKS");
}.bind(this));
}
You want to use Views built in handlers to achieve this. These are _eventInput and _eventOutput.. Here is an example using an ImageSurface in StripView and responding to a click in AppView..
Hope it helps!
// In StripView.js
var imageSurface = new ImageSurface();
imageSurface.on('click',function(){
this._eventOutput.trigger('image-click', { somedata:'some value'} );
}.bind(this));
// In AppView.js
var stripView = new StripView();
this.subscribe(stripView);
this._eventInput.on('image-click',function(data){
// Do Something
});
I'm starting to work using servicenow, and I've an issue with TimeLines and the doubleClick events.
I configured the schedule page and ScriptInclude (code as pseudo-code):
Schedule Page
glideTimeline.setReadOnly(true); glideTimeline.showLeftPane(true);
glideTimeline.registerEvent("getItems", "MyTimelineScriptInclude");
function doubleClickCustomFunction(evt) {
try {
alert('double click: ' + "evt: " + evt + ', target: ' + target );
action.setRedirectURL( 'my_application.do?sys_id=' + target );
catch (exception) {
gs.log(exception);
}
},
MyTimelineScriptInclude
var MyTimelineScriptInclude = Class.create();
MyTimelineScriptInclude.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {
_getTickets: function(){
tickets = foo();
return tickets;
}
getItems: function() {
try {
var ticket_list = this._getTickets();
for (var ticket in ticket_list) {
this._representTicket(ticket_list[ticket].sys_id);
}
} catch(exception) {
this._debugLog(exception, "getItemsException");
}
},
_representTicket: function(sys_id) {
// ticket Object;
ticket_object = getTicket(sys_id);
var timelineItem = new TimelineItem('my_application' , ticket_object.sys_id);
_representSpans( timelineItem , ticket_object );
this.add(timelineItem);
},
_representSpans: function( timelineItem , ticket_object ) {
var timelineItemSpan1 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan1.setTimeSpan( ticket_object.startDateTime1.getNumericValue() , ticket_object.endDateTime1.getNumericValue() );
timelineItemSpan1.setSpanText(ticket_object.spanText);
timelineItemSpan1.setSpanColor(ticket_object.spanColor);
timelineItemSpan1.setTooltip(ticket_object.spanTooltip);
var timelineItemSpan2 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan2.setTimeSpan( ticket_object.startDateTime2.getNumericValue() , ticket_object.endDateTime2.getNumericValue() );
timelineItemSpan2.setSpanText(ticket_object.spanText);
timelineItemSpan2.setSpanColor(ticket_object.spanColor);
timelineItemSpan2.setTooltip(ticket_object.spanTooltip);
var timelineItemSpan3 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan3.setTimeSpan( ticket_object.startDateTime2.getNumericValue() , ticket_object.endDateTime2.getNumericValue() );
timelineItemSpan3.setSpanText(ticket_object.spanText);
timelineItemSpan3.setSpanColor(ticket_object.spanColor);
timelineItemSpan3.setTooltip(ticket_object.spanTooltip);
},
});
The problem is when I double click on a timeline row, it triggers the doubleClickCustomFunction, but, it isn't able to get any evt data, so, It doesn't performs the redirection.
Best regards
Schedule Pages in ServiceNow use client-side script, so if the doubleClickCustomFunction is part of the schedule page client script, the server-side calls (action.setRedirect and gs.log) will fail.
The default double click function contains the following parameters: event, this, strRecordSysID, strUserSysID
I haven't used a custom doubleclick override, so I'm not sure if these parameters are automatically available. However, this is the case for other custom overrides written within the script include, such as elementMoveX
Other than that, you might try calling window.event within the function if it is a part of the client script