PaperJS SymbolItems lose properties after hit result search - javascript

I'm creating a SymbolDefinition for a circle, and placing it on the canvas using new SymbolItem(definition). The circle object used to create the definition had a default position of (0,0), a default color, and no additional information because whatever defaults paper sets.
When I instance the symbol and place it at a certain position (lets say (20,30) for example), I also append some other information such as a name, some supplementary info in its data property, etc.
If I click on the placed circle, and have a callback search for that item using project#hitResultAll, the item contained within the hit result no longer possesses any of the information mentioned above. In fact, it appears to be the original circle object used to create the definition; its position is in fact (0,0), it has no name, and the data property is empty.
Here is a jsFiddle with an example (open dev console to see output).
In case the link doesn't work, here is the example code:
var canvas = paper.createCanvas(100, 100);
document.getElementById("canvasDiv").appendChild(canvas);
paper.setup(canvas);
var circle = paper.Path.Circle([0,0], 10);
circle.fillColor = 'red';
var definition = new paper.SymbolDefinition(circle);
var tool = new paper.Tool();
tool.onMouseUp = function(event){
var hitResult = paper.project.hitTestAll(event.point);
if(hitResult.length > 0){
var result = hitResult[0];
var item = result.item;
console.log("Item name should be 'Bob', but is actually " + item.name);
// Output is: Item name should be 'Bob', but is actually null
}
};
var actualCircle = new paper.SymbolItem(definition);
actualCircle.position = [20,30];
actualCircle.name = 'Bob';
console.log("Symbol Item was created with the name " + actualCircle.name);
// Output is: Symbol Item was created with the name Bob
Does anyone know why this is happening, and how I can go about solving it?

This turned out to be a bug in the paperJS hit test code. Thanks to Lehni, this has been resolved. At the time of this post, a new release has not been deployed yet, but so long as you use any version above 0.10.2, you should be safe.
If you are stuck with an older version, according to Lehni:
If you use hitTest() instead of hitTestAll(), it returns the expected result.

Related

I want to push unnamed or dynamically named objects (class instances) to an array

Context: "Umo Space" is a javascript game I'm working on Umo Space is a javascript game I'm working on, with a class "Umo" (universal moving object) which includes the needed variables and member functions to do stuff with an object moving around in 2d space. Currently the game world has predefined planets and enemy ships, but I'm trying to create randomized systems. In doing so, I need to fill the "planets" array with a random number of "Umo" instances. I thought I could just push() an unnamed new "Umo" instance into the array, and it seems that I can when I create the first Umo (the sun basically) in the center of the system before the loop for the randomized planets. However, inside the loop, I do what seems to be the same thing, and shortly thereafter I get an error when I try to use member functions or assign to member variables of the Umo instance.
Uncaught TypeError: Cannot read property 'setorbit' of undefined
at System.randomplanets (UmoSpace68.html:567)
at UmoSpace68.html:591
If I comment out that particular line (which uses a member function of Umo), I instead will get an error for assigning the ".name" or ".parentid" member variables. I thought maybe I needed to assign the Umo dynamic variable names using an "eval()" rather than pushing it unnamed, but that didn't fix anything (and seems to be an... unfavored practice). In any case, it seems like the push() failed without generating an error message, inside the loop.
This problem also somehow causes another error downstream, in seemingly unrelated code, un-defining my "ships" array (not a member variable of anything, just an array of predefined "Umo"s).
UmoSpace68.html:789 Uncaught ReferenceError: Cannot access 'ships'
before initialization
at update (UmoSpace68.html:789)
If I comment out the function call "testsystem.randomplanets();" all the problems disappear, and all of the Umo functionality is used elsewhere without issue, so the problem is almost certainly in the randomplanets() member function or at least the "System" class (which is not yet used meaningfully).
Here is the smallest relevant code snippet I could muster:
class System{
constructor(index, name){
this.index = index; //integer identifying system
this.name = name; //name of system for display
this.planets = []; //list of planets (to be generated)
this.ships = []; //list of ships (to be generated)
this.bombs = []; //list of bombs used in system
this.difficulty = 1; //Scales ship generation attributes
}
randomplanets(){
var numplanets = Math.floor(Math.random()*16+2);//random number of planets, 2-17
var orbitradius = 0; //randomized in the loop
var planetsize = 0; //randomized in the loop
this.planets.push( new Umo(0,0,Math.floor(Math.random()*3000+1000), "orange") ); //make the sun
this.planets[0].name = this.name; // Star name is same as system name
i=0;
while (i<numplanets-1){
i=i+1; //planets[0] is already the sun, so we can skip index 0;
orbitradius = Math.floor( (Math.random()*500)*(Math.random()*500) + 2000); //Minimum radius 2000, 0-500^2 more concentrated in center
planetsize = Math.floor( Math.random()*1000 + 80); //Even distribution of sizes, 80-1080
this.planets.push( new Umo(0,0,planetsize, randcolor() ) );//this is where the planet gets added to the array
this.planets[i].name = randname(4);//random 4 character name
this.planets[i].setorbit(this.planets[0], orbitradius, Math.random()*6.28, 1);
this.planets[i].parentid = 0; //establishes star (planet[0] as parent planet
//this.randommoons(i);
}
}
randommoons(index){//index is of planet
var nummoons = Math.floor(Math.random()*planets[index].s/150 )//Planets < 150 in size have 0 chance of a moon, planet 300 in size has 50% chance of 1 moon, etc.
var moonsize = 0; //randomized in loop
var moonorbitr = 0;//randomized in loop
var moonindex = 0; //set in loop
i = nummoons;
while (i>0){
i=i-1;
moonsize = Math.floor(Math.random()*this.planets[index].s/3+10);//radius is 10 plus up to 1/3 of parent planet
moonorbitr = Math.floor(this.planets[index].s*(Math.random()*3+1.5)); //orbit radius is 1.5x parent planet radius + up to 3x parent planet radius
moonindex = this.planets.length;
this.planets.push( new Umo(0,0,moonsize, randcolor()) );
this.planets[moonindex].name = randname(4);
this.planets[moonindex].parentid = index;
this.planets[moonindex].setorbit(this.planets[index],moonorbitr,Math.random()*6.28, 1);//orbit direction is 1, not random
}
}
}//end of system class////////////////////////////////////////////////////////////////////////////////////////////////////////////////
let testsystem = new System(2,"thetestsystem");
testsystem.randomplanets();
It seems the problem was not in the randomplanets() function after all. #Sal suggested changing the while loop to a for loop, which DID fix the problem, but more importantly led me to later discover a more fundamental problem while developing random ship generation. I eventually isolated the problem to the randname(namelength) function, which initiated it's own while loop with:
i=namelength;
which i changed to
var i=namelength;
I think that leaving out the "var" caused the randname function to operate on the "i" immediately outside the randname function, inside the randomplanets function (perhaps globally as well). I think that using a for loop must have protected the "i" used in the for loop from this scope error.
Moral of the story: Remember to declare your iterator with a "var" each time you use it, especially if you habitually use i,j,k, etc.

Change color of shape without recreating

I have a project using easelJS in that I'm trying to simply change the color of a shape object. I have a couple of examples but they both seem to indicate I would need to completely redraw the shape with a graphics.beginFill() call again. This seems completely overkill considering that I've stored my shape object already and am able to perform other manipulations on it.
I've also tried to use ColorFilter and ColorMatrix but have not had luck making a clean color change. In some cases the color essentially "overwrites" the detail of the shape.
I have an array of ShapeObject that I store the createjs.Shape() object in.
var ShapeObject = function()
{
this.name;
this.shape;
this.rotation;
this.color;
};
sObject = new ShapeObject();
myShape = new createjs.Shape();
sObject.shape = myShape;
shapes = new Array();
shapes.push(sObject);
Later I am able to retrieve the shape from the array and apply a filter, for example,
s = shapes[i];
filter = new createjs.ColorFilter(0,0,0,1, 255,128,0,0);
s.shape.filters = [ filter ];
Using this same example I'd like to avoid having to completely recreate the shape. I have tried the following but, while it changes color, I lose all the other details of the shape that was originally applied.
s.shape.graphics.clear().beginFill(color);
Does anyone have an idea of how to simply change the color without completely recreating the original shape?
EDIT
Following the answer regarding .command and the createjs command blog post I have created the following.
var theShape = new createjs.Shape();
var fillCommand = theShape.graphics.beginFill("yellow").command;
theShape.graphics.setStrokeStyle(1)
.beginStroke(createjs.Graphics.getRGB(0, 0, 0))
.drawCircle(0, 0, 20)
.moveTo(0,-20)
.lineTo(0,0)
.moveTo(0,0)
.lineTo(20,0);
fillCommand.style = "orange";
Despite being nearly identical to the examples I am receiving the error Uncaught TypeError: Cannot set property 'style' of undefined at the last line above.
I wouldn't use ColorFilter or ColorMatrix, it will likely be much slower. But you could use the Graphics command object, check out the docs: http://www.createjs.com/docs/easeljs/classes/Graphics.html
var fillCommand = myGraphics.beginFill("red").command;
// ... later, update the fill style/color:
fillCommand.style = "blue";

Photoshop scripting - how to create text layer in one history state

This question Photoshop Script to create text in a bitmap image in Photoshop already has answer how to create text layer in photoshop, but as result in history tab you can see 8-10 history state changes. Is there a way to do the same job, but while changing history only once?
Obviously problem is that mentioned answer first adds a layer to document and then edits it 8-10 times and solution would be to create a layer and edit it's properties and then after it's ready add it to document.
I have searched in Photoshop CC 2014 Javascript Scripting Reference and it lists only 3 methods for ArtLayers methods: add(), getByName() and removeAll().
Function add() creates new layers, adds it and then returns it. So according to Javascript Scripting Reference I don't see how it would be possible to first create a layer and then add it.
I am still asking because possibly I have missed something or there is an official way to do it, but has not made it into the official docs for some reason.
Better late than never, but yes it is possible. You just have to suspend the history state with suspendHistory
Just pass your function as a string in the form of
app.activeDocument.suspendHistory("string", "myFunction()");
The first parameter, as far as I'm aware is the history state string.
As in the example,
// Switch off any dialog boxes
displayDialogs = DialogModes.ERROR; // OFF
app.activeDocument.suspendHistory ("history state", "createText('Arial-BoldMT', 48, 0, 128, 0, 'Hello World', 100, 50)");
function createText(fface, size, colR, colG, colB, content, tX, tY)
{
// Add a new layer in the new document
var artLayerRef = app.activeDocument.artLayers.add()
// Specify that the layer is a text layer
artLayerRef.kind = LayerKind.TEXT
//This section defines the color of the hello world text
textColor = new SolidColor();
textColor.rgb.red = colR;
textColor.rgb.green = colG;
textColor.rgb.blue = colB;
//Get a reference to the text item so that we can add the text and format it a bit
textItemRef = artLayerRef.textItem
textItemRef.font = fface;
textItemRef.contents = content;
textItemRef.color = textColor;
textItemRef.size = size
textItemRef.position = new Array(tX, tY) //pixels from the left, pixels from the top
activeDocument.activeLayer.name = "Text";
activeDocument.activeLayer.textItem.justification = Justification.CENTER;
}
// Set Display Dialogs back to normal
displayDialogs = DialogModes.ALL; // NORMAL

Generic: Naming structure and drawing KineticJS

This one takes a bit more to describe, sorry for the shorter title.
I currently do not have my code in front of me but might update this with the full details of how it works and where the problem is located.
Basically I notice that without doing a more or less global name (window.whatever) for the shapes/groups it will never draw them. I have done logs of the objects to see if they are proper and have seen nothing wrong with them.
The full scope is a layer first, that is passed to a function that i then create shapes and groups in a logical way, without passing the groups back and instead sending the layer as a parameter to add it from within the function. When I do it this way it seems that I can never get it to draw to the container (stage).
To add to it, I am looping to create the shapes, as I am making them variable and more percentage based then exact pixels, so I am holding the objects in an array that is generated through the loop.
I have done this flat first and it worked, however after moving it to the function it stopped working, is there any reason for this I may be missing?
Another note if it is relevant, I am using Adobe AIR.
If anyone has any ideas let me know, I will post the actual code when I can (few hours from this posting).
UPDATE:
Basically the issue I am having is that when i separate the shapes/groups into their own function it does not want to draw them at all. I have tried to call the draw function inline and also just add them to the stage later, both to my understanding will trigger the draw.
here is the code
var forms = {
selector:function(params,bind){
//globals
var parent,obj,lastWidth=0,items=[];
//setup defaults
var params = params || {};
params.x = params.x || 0;
params.y = params.y || 0;
params.active = params.active || 0;
params.height = params.height || 200;
params.children = params.children || [{
fill:'yellow'
}];
params.width = params.width || bind.getWidth();
params.margins = params.margins || 5;
//container for selector
parent = new Kinetic.Group({
x:params.x,
y:params.y,
height:params.height,
width:params.width
});
air.Introspector.Console.log(params);
var totalMargins = params.margins*(params.children.length+1),
activeWidth = (params.width-totalMargins)/2,
subItems = params.children.length-1,
subWidth = activeWidth/subItems,
itemHeight = (params.height-params.margins)/2;
//loop all children
for(var i=0;i<params.children.length;i++){
//use an array to store objects
items[i]={};
//create default object for rectangle
obj = {};
obj.y = params.margins;
obj.fill = params.children[i].fill;
if(params.active==i){
obj.width = activeWidth;
}else{
obj.width = subWidth;
}
obj.x = params.margins+lastWidth;
obj.height = itemHeight;
lastWidth = lastWidth+(params.margins+obj.width);
//create group for text
items[i].text = new Kinetic.Group({
x:0,
y:itemHeight+params.margins
});
//create box for text
items[i].box = new Kinetic.Rect({
x: params.margins,
y: params.margins,
width:params.width-(params.margins*2),
height:(params.height-itemHeight)-(params.margins*2),
fill:'yellow'
});
air.Introspector.Console.log(items[i].box);
//add box to text groups
items[i].text.add(items[i].box);
//create item
items[i].item = new Kinetic.Rect(obj);
//add groups to parent
parent
.add(items[i].item)
.add(items[i].text);
}
//add parent to the bound container
bind.add(parent);
}
}
From your question, I'm assuming bind is your Kinetic.Layer.
Make sure bind has been added to the stage: stage.add(bind);
After adding your groups/rects to bind, also do bind.draw();
I have since figured it out.
The issue is that the layer must be added to the stage before anything else is added it it. It was not in my code because I did not see it as an issue as it worked elsewhere.
Basically if you add anything to a layer, then add the layer to the stage it will fail. it must go like this:
CREATE STAGE
CREATE LAYER
ADD LAYER TO STAGE
ADD GROUPS TO LAYER
DRAW IF NEEDED
The way it was done originally is like so:
CREATE STAGE
CREATE LAYER
ADD GROUPS TO LAYER
ADD LAYER TO STAGE
DRAW IF NEEDED
The question will be updated to make this apparent, this is something that is not in the documentation as a problem (as far as I have seen) and I can see it as confusing some.

How to get the last shape of a specific type?

I need to find the last path or circle in a paper, in order to perform further calculations to draw more elements, and calling paper.bottom only gets the last element. Is there any way to get shapes of specific types, e.g. bottom.path, bottom.circle or to traverse for the n'th child?
I want to avoid using jQuery selectors, as i can't retrieve any properties from those.
An example of a paper populated with shapes:
var paper = Raphael('paper',500,500);
var c1 = paper.circle(100,100,50)
var p1 = paper.path("M10 20l70 0")
var c2 = paper.circle(200,100,50)
Checking for the bottom element will get you, well, the bottom element (along the Z axis) - which is undesired. Consider a case where the Z ordering of elements is altered - perhaps by calling Element.toBack() - that will invalidate the check altogether.
Instead you may want to aim for a more semantic approach by iterating over the elements array and retrieving the last element of a certain type (e.g. circle).
Raphaël also allows for extending the built-in set of functions, so we can attach a function to the paper element solely for that purpose. The following snippet shows an example of such a function which returns the last element of the passed type from the paper, or false if no such element was found:
Raphael.fn.last = function(type) {
var element;
this.forEach(function(el) {
if (el.type == type) {
element = el;
}
});
return element || !!element;
};
Be sure to attach this function before any instanciation of paper elements. Than it's only a matter of calling it, passing the desired element type:
var r = Raphael(paper, '100%', '100%');
// ...
// add elements etcetera
// ...
var lastCircle = r.last('circle');
Note that for the suggested implementation this can result in heavy operations - if the paper contains many elements, but the purpose here is to grant you with the notion of functionality extension, and kick-start the thinking process. Feel free to improve the function to increase its efficiency.
Live Demo
Regarding how to traverse for the nth child: Just put your paths in a set and they're indexed like any ordinary array. You can easily call out any number from that set, and get full access to its properties:
var myPaper = Raphael('container', '800', '600');
myPaper.setStart();
var circ1 = myPaper.circle(50, 50, 40);
var circ2 = myPaper.circle(150, 50, 40);
var circ3 = myPaper.circle(250, 50, 40);
var circ4 = myPaper.circle(350, 50, 40);
var allCircles = myPaper.setFinish();
allCircles[2].attr({fill: "blue"});
​
http://jsfiddle.net/eE7xS/

Categories

Resources