Forge viewer display only part of element / node - autodesk-forge

we implemented a complex filter method to filter the elements of the model. Everything based on IFC. Following an example of a IfcFooting element with 2 sub-elements:
ID IfcType Description
--------------------------------------------------------
1 IfcFooting Base
2 > IfcBuildingElementPart Insulation
3 > IfcBuildingElementPart Reinforced Concrete
The filter method can find IfcFooting elements with special sub-element "Reinforced Concrete". Return value is ID 1 and 3. In the viewer we are using the following method to display only the filter results:
viewer3d.impl.visibilityManager.hide(rootId, model);
$.each(selection, function (k, v) {
viewer3d.impl.visibilityManager.show(v, model);
});
Problem is that calling this method with ID 1, the viewer will display 1, 2 and 3. Is it possible to disable this behaviour? We only need to display the sub-element 3, but it is not possible to ignore ID 1 in filter results... thx!

It's the expected behavior, if the id is not a leaf component but a logical component that owns children, then it's normal that showing/hiding this component will impact all its children.
What you should do is make sure all the ids you collect are leaf components (check that instanceTree.enumNodeChildren(dbId) has no children). And perform the logic on leaf only.
In your case you would filter out dbId 1 because it has children and only show dbId 3.
Here is an example:
function isLeafComponent (dbId) {
var instanceTree = viewer.model.getData().instanceTree
var childCount = 0
instanceTree.enumNodeChildren(dbId, function(childId) {
++childCount
})
return (childCount < 2) // Handles IFC with "Body" child
}

Related

Is there a way of searching div element by class in GAS? [duplicate]

Is there a simple method to locate an XML node by its attribute in Google Apps Script? Here's an XML snippet:
<hd:components>
<hd:text name="ADM Custom admissions TE">
<hd:prompt>Admission</hd:prompt>
<hd:columnWidth widthType="minimum" minWidth="100"/>
</hd:text>
<hd:text name="ADM Insufficient heat end date TE">
<hd:prompt>To</hd:prompt>
</hd:text>
<hd:text name="ADM Insufficient heat start date TE">
<hd:prompt>From</hd:prompt>
</hd:text>
<hd:text name="ADM Third party payment period TE">
<hd:defMergeProps unansweredText="__________"/>
<hd:prompt>When (date or period)?</hd:prompt>
</hd:text>
For purposes of the XML file I'm trying to parse, the "name" attribute is a unique identifier, while what GAS thinks is the "name" for purposes of the XmlService.Element.getChild(name) method ("text" for each node shown in this snippet) is a non-unique classifier for the type of node. I'd like to be able to write a function to retrieve a specific node from this XML file with only the name attribute. XMLPath notation in other languages has this capability using the [# notation. Is there a way to do it in GAS, or do I need to write a function that walks through the XML until it finds a node with the right name attribute, or store it in some different type of data structure for fast searching if the XML file is sufficiently large?
Here's the snippet I started writing: it's fine if there's no built-in function, I just wondered if there was a better/faster way to do this. My function isn't so efficient, and I wondered if the XmlService had a more efficient internal data structure it's using to speed up searching. My approach is just to loop through all of the element's children until there's a match.
function getComponentFromXML(xml,name) {
for (var i = 0; i < xml.length; i++) {
var x = xml[i];
var xname = x.getAttribute('name').getValue();
if (xname == name) {
return getComponentAttributes(x);
}
}
}
There is no built-in search, so the only way is to read the list of elements looking for the one with the desired value of attribute 'name'. If elements is an array of elements to search through, you can do
var searchResults = elements.filter(function (e) {
return e.getAttribute('name') && e.getAttribute('name').getValue() == searchString;
});
(Both checks are needed to avoid an error when there is no 'name' attribute at all.)
How to obtain such an array elements may depend on XML document. If, as in your example, the elements to search are the immediate children of the root element, then
var doc = XmlService.parse(xmlString);
var elements = doc.getRootElement().getChildren();
would be a quick and easy way to do this.
In general, to get all elements without recursion, the getDescendants method can be used. It returns an array of Content object, which can be filtered down to Element objects:
var elements = doc.getDescendants().filter(function (c) {
return c.getType() == XmlService.ContentTypes.ELEMENT;
}).map(function (c) {
return c.asElement();
});

How can I link paths so results are updated in dom-repeat

I am developing an appointment booking system. It basically consists of a set of Polymer custom elements arranged as follows (indented elements are in the template of the element rather than actually organised as shown)
<my-appointments>
<person-appointment booking="{{booking}}>
<booking-type type="{{booking.type}}">
<div>[[booking.type]]</div>
</booking-type>
</person-appointment>
<appointment-day booking="{{booking}}>
<template is="dom-repeat" items="{{appointments}} as="{{appointment}}">
<div>[[appointment.type]]</div>
</template>
</appointment-day>
</appointment>
inside the <appointment-day> element booking is defined as an "Object" property and appointments as an "Array". As a booking is made, the booking object is spliced into the appointments array at the correct place.
At the same time I use the linkPaths function to join path 'booking' to path 'appointments.n' (where n is 0, 1, 2 etc for where in the appointments array booking is situated)
This is the code that does this
if (foundAppointment) {
//found where to insert appointment, so do so
this.splice(path, j, 0, this.booking);
this.linkPaths('booking', path + '.' + j);
this.linkedBooking = true;
break;
}
Not shown is a mechanism inside <booking-type> to update the type property. SO when I update the type property using this mechanism, the visual representation changes inside the <person-appointment> element but it does not change inside the dom-repeat. I can check that the object located at appointents[n] IS updated, but the display is not updated.
I presume I haven't properly linked booking to the appropriate appointment entry. BUT how should I achieve this
Polymer does not observe the change of sub-properties of appointments.
Try to Force data system to pick up array the mutations with the code below:
// Force data system to pick up the **array** mutations
var array = this.appointments;
this.appointments= [];
this.appointments= array;
Try to add this to your code.
if (foundAppointment) {
//found where to insert appointment, so do so
this.splice(path, j, 0, this.booking);
this.linkPaths('booking', path + '.' + j);
this.linkedBooking = true;
// Force data system to pick up array mutations
var array = this.appointments;
this.appointments= [];
this.appointments= array;
break;
}
Or to Force data system to pick up the Object mutations with the code below:
// Force data system to pick up array mutations
var object = this.appointment;
this.appointment= [];
this.appointment= object;
I'm only guessing that the problem is in binding to array items.
Polymer has special rules for that.
Here is one example of array binding: Plunk
<template is="dom-repeat" items="[[first4People(people, people.*)]]">
<div>
Index: <span>[[index]]</span>
<br>
First as this:[[arrayItem(people.*, index, 'first')]]
<br>
First not as this: <span>[[item.first]]</span>
</div>
<br><br>
</template>
Simplified online example of the problem would help to understand the issue better.

Combining two items (recipe-like)

I'd like a user to be able to combine two items and if compatible will yield a new item. In this example, the item IDs will be saved as Strings.
I was wondering what the most efficient way to do this would be, while making sure that swapped order will always yield the same result, so the user could input the order:
item X + item Y = item Z
item Y + item X = item Z
I've tried using Dictionaries and Objects, but I just haven't been able to get anything to work. I've also tried some various libraries that include HashMap/HashSet but nothing is working. here's some pseduo-code:
itemRecipe1:HashSet = new HashSet();
itemRecipe1.add("2");//Add item with ID of 2
itemRecipe1.add("3");//Add item with ID of 3
inputRecipe:HashSet = new HashSet();
inputRecipe.add("3");//Add item with ID of 3 (swapped)
inputRecipe.add("2");//Add item with ID of 2 (swapped)
recipeList:HashMap = new HashMap();
receipeList.put(itemRecipe1, "11");//Recipe has been added, the result of the recipe should be item 11
//This should output as TRUE since the composition of itemRecipe1 and inputRecipe are the same, despite a different input order.
trace(receipeList.containsKey(inputRecipe));
If anyone has a solution for this issue, please elt me know as I am willing to implement any design I can get working. I just don't see how a Dictionary could work as the key order matters.
So you're trying to associate two or more objects with each other. The first thing you need is some primitive data you can use to represent each item uniquely, typically an ID. This should give you something like the following to begin with:
class Item {
public var _id:int;
public function Item(id:int) {
_id = id;
}
public function get id():int { return _id; }
}
Now you need some piece of data that establishes a relationship between multiple Items using this ID. That could be as simple as the following, with a little extra functionality thrown in to see if an input list of these IDs matches the relationship:
class ItemRelationship {
private var _items:Vector.<Item>;
public function ItemRelationship(items:Vector.<Item>) {
_items = items;
}
public function matches(ids:Vector.<int>):Boolean {
if (_items.length !== ids.length) {
return false;
}
for each (var item:Item in _items) {
var found:Boolean = false;
for each (var id:int in ids) {
if (item.id === id) {
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
public function get items():Vector.<Item> { return _items; }
}
This lets us do something like this, assuming we have a bunch of items (item1, item2, ...) with IDs.
var rel:ItemRelationship = new ItemRelationship(new <Item>[item1, item2]);
And then:
trace(rel.matches(new <int>[1,2])); // true
trace(rel.matches(new <int>[2,1])); // true
trace(rel.matches(new <int>[3,4])); // false
Now all we need is something that stores all of these relationships and lets us fetch one based on a list of input IDs:
class RelationshipCollection {
private var _relationships:Vector.<ItemRelationship>;
public function RelationshipCollection(relationships:Vector.<ItemRelationship>) {
_relationships = relationships;
}
public function find(ids:Vector.<int>):ItemRelationship {
for each(var relationship:ItemRelationship in _relationships) {
if (relationship.matches(ids)) return relationship;
}
return null;
}
}
Put a load of relationships in there:
var collection:RelationshipCollection = new RelationshipCollection(new <ItemRelationship>[
new ItemRelationship(new <Item>[item1, item4]),
new ItemRelationship(new <Item>[item2, item3])
]);
And give it a whirl:
trace(collection.find(new <int>[1, 3])); // null (no match)
trace(collection.find(new <int>[1, 4])); // works
trace(collection.find(new <int>[3, 2])); // works
trace(collection.find(new <int>[2, 3])); // works
Of course for the sake of readability you can rename each class to something more appropriate for its application e.g. Item => Potion, ItemRelationship => Recipe, RelationshipCollection => RecipeBook.
so the user could input the order
The first step is to limit the possible input. If you allow any type of input, you have to parse that input and things get complicated very quickly.
Create an input method that only allows the user to put two items together, say for example via drag and drop of the items to only 2 slots.
I just don't see how a Dictionary could work as the key order matters.
The important part is to design the keys well.
As #George Profenza pointed out in the comments, you could change your IDs to a different format. Instead of having 1, 2, 3, ... n you could use 1, 2, 4, ... 2^n. The advantage is that you can combine any two IDs uniquely via bitwise or operator (|). In the following example, two such IDs are combined (binary notation):
00001
| 10000
--------
10001
As you can see, each ID occupies a separate position in binary: the 1st position and the 5th. Combining both via or operator means that now both 1st and 5th position are 1. The order doesn't matter. If you use such IDs in the form of powers of 2 you can combine them regardless of the order to form pairs, which can then be used as keys to a dictionary.
Another solution is to simply sort the pair of IDs.
The combination 3-2 becomes 2-3 and the combination 2-3 stays 2-3. Both 2-3 and 3-2 lead to the same result.
You can then build your data structure accordingly, that is: the outer data structure is for the lower ID number and the nested, inner one is for the bigger ID number. Here's some pseudo code with generic objects:
var map:Object = {};
map["2"] = {"3":"combination 2-3"};
To access that, you'd do something like:
trace(map[Math.min(ID1, ID2)][Math.max(ID1, ID2)])
There's also the brute force way of doing it by storing both possible combinations in the data structure. The code for that could roughly look like that:
var map:Object = {};
map["2"] = {"3":"combination 2-3"};
map["3"] = {"2":"combination 2-3"};
Now both
trace(map[ID1][ID2]);
and
trace(map[ID2][ID1]);
Should yield the same result.

D3 reusable multi-line chart with JSON data

I'm trying to do some re-factoring on my charts to make them re-usable using this as a guide: http://bost.ocks.org/mike/chart/
I'm having problems drawing the lines in my multi-line graph though - specifically passing the data to the x and y values. If I hard code the element names it works, but if I try to use the xValue and yValue objects this does not work. I'm assuming that this is because I'm trying to call a function within the parameter of an other object, but I'm not sure how to get around this. In the exmaple Mike uses d[0] and d[1], but this won't work with JSON data (or I'm not sure how to make it work).
I've posted this JSFiddle so you can see the code. The problem lines are 125 to 131 which in turn is being called from line 165.
var main_line = d3.svg.line()
.interpolate("cardinal")
// Hard coding the elements works
//.x(function(d) { return main_x(d.date); })
//.y(function(d) { return main_y(d.buildFixTime); });
// Passing xValue and yValue does not work
.x(function(d) { return main_x(xValue); })
.y(function(d) { return main_y(yValue); });
http://jsfiddle.net/goodspeedj/fDyLY/
Thank you in advance.
You need to redefine your accessor method within .x() and .y(). The accessor method defines the way that a datum is pulled out of the data that is bound to the selection that you call the line generator on.
Suppose you have a relatively flat data structure such as the following.
data = [{x : 1, y : 2}, {x:1, y:3}, {x:4, y:5}];
You then bind the data to a selection with the following statement
d3.select("body").datum(data).append("path").attr("d",lineGenerator);
Quite a bit is going on underneath this statement. I'll give you a bit more of a walkthrough after showing you a commonly used example.
The important aspect to understand is that similarly to other calls in d3 such as
var exampleRectangles = d3.select("body")
.data(data).enter()
.append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",function(datum){return datum.x}) // pay attention to this line
.attr("y",0);
d3 is implicitly iterating over each element in your data. For each datum in your data array, in this case there is a total of three datum, you are going to add a rectangle to the dom.
In the line that I tell you to pay attention to you notice that you're defining an anonymous (unnamed) function. What is that datum parameter coming from? It's implicitly being passed to your anonymous function.
So each rectangle has it's own corresponding datum {x : 1, y : 2}, {x:1, y:3}, {x:4, y:5} respectively. Each rectangle's x coordinate is defined by the respective datum.x attribute. Under the sheets, d3 is implicitly looping over the data array that you've defined. A similar approach to the example d3 code could be written as above.
for (var i = 0; i < data.length; i++)
{
d3.select("body").append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",data[i].x)
.attr("y",0);
}
This follows from the notion of data driven documents (d3). For each item added (a rectangle in the above example a piece of data is tied to it. In the above example you see that there is something kind of similar to your .x() and .y() accessor functions :
.attr("x",function(datum){return datum.x})
This function is telling d3 how to filter over the total datum that's being passed to the .attr() accessor method.
So, you need to determine which data you need to get a hold of to make your .attr("d", lineGenerator)call make sense. The difference between your.datum(data)call and the typical.data(data)call is that instead of parceling the data that's being passed to.data(data)`, the whole array is given as a single piece of data to the line generator function (similar to main_line(data), wherein it will again implicitly loop over the points to construct your path.
So, what you need to do is determine what a single datum will be defined as for your function to operate on.
I'm not going to define that as I don't seem to know quite which information you are operating on, but I would hazard a guess at something like.
.x(xAccessor)
.y(yAccessor)
function xAccessor(datum)
{
return xScale(datum._id.month);
}
function yAccessor(datum)
{
return yScale(datum.buildFixTime);
}
The way you have it set up, xValue and yValue are functions; you have to actually execute them on something to get a value back.
.x(function(d) { return main_x( xValue(d) ); })
.y(function(d) { return main_y( yValue(d) ); });
If you weren't using a scale, you could use
.x(xValue)
.y(yValue);
but only because if you pass in a function d3 executes it for you with the data as a parameter. And that only works for d3 methods that expect functions as possible input -- the scale functions expect data values as input.
I wrote a long piece work for another user last week that you may find useful, explaining methods that accept functions as parameters.

Building a new Dictionary out of an old one? Help with Dictionary recursion

I'm working with a large set of hierarchical taxonomic terms, where each term ("203") has a matching "term203" movie clip on the stage, and am having trouble getting a recursive function to return all of a given term's descendants.
There is a main Dictionary() object with the following nested organization for each term:
{ [object Movie Clip] : { "tid":203, "parent":99, "name":"Culture", selected:false, "otherData":"etc" } }
...where the [object Movie Clip]'s instance name would be "term203". All of these object:subObjectArray items ("terms") are stored in a master taxonomy:Dictionary() object.
I've been trying to make a recursive function (which is in itself already a little above my head) that takes the click.target of a movie clip and returns a new Dictionary() object with all of the children and grandchildren and great grandchildren (etc) of that term, in the same, nested organization described above.
The code below traces the right number of recursive loops, but the returned Dictionary() object only contains the first run's terms (only the immediate children of the requested term).
var taxonomy:Dictionary = new Dictionary();
// ...Term info is loaded into taxonomy from a JSON-style text file)
// ...MOUSE_OVER event listeners are added to each
function revealChildren(hvr:MouseEvent):void {
trace("Spotlighting " + taxonomy[hvr.target].name + "'s children...");
for(var key:Object in getAllChildren(taxonomy[hvr.target].tid)) {
trace("Animating " + taxonomy[key].tid); // Traces only immediate children
var revealTween = new Tween(key, "alpha", Regular.easeInOut, key.alpha, 1, 1, true);
}
}
function getAllChildren(origin):Dictionary {
var children:Dictionary = new Dictionary();
for(var element:Object in taxonomy) {
if(taxonomy[element].parent == origin) {
var subSet = getAllChildren(taxonomy[element].tid);
children[element] = subSet; // *CAN'T ACCESS 'subSet' PROPERLY*
trace("Parent = " + origin);
trace("Matched! Adding " + taxonomy[element].tid + " as key and setting its value to " + subSet); // Traces correct amount of times, one for each descendent
}
else {
}
}
return children;
}
I certainly do not claim to be the most efficient AS3 programmer, so I am open to alternative configurations. However, after trying static and nested Arrays, I would prefer to continue using the Dictionary() object as my main pool.
As noted, only the immediate children end up animating in the revealChildren() function. It's mystifying to me then, that in the getAllChildren() function, all of the descendants trace sequentially (well in no particular order) in the output window.
Also I can't get any sort of name or property out of the subSet Object. That could be the problem.
I've only tested it as far as 'two generations,' but it seems that only the first round of calling the function successfully adds those terms to the new Dictionary() object and returns it intact to the animating function.
Too bad dict.filter(getDescendants) won't work. Please help!
To simplify things, I've added an output parameter called children. This is the Dictionary into which our function will store its results. It has a default value, so you don't need to specify one. In that case, it will create a new instance for itself.
function getAllChildren(origin:*, children:Dictionary = null):Dictionary {
if (children = null) children = new Dictionary();
for(var element:* in taxonomy) {
if(taxonomy[element].parent == origin) {
children[element] = taxonomy[element];
getAllChildren(taxonomy[element].tid, children);
}
}
return children;
}
When a child is discovered, it is copied over exactly: children[element] = taxonomy[element];
Next, the function calls itself recursively, supplying it the same output dictionary as it has been using.
Edit:
In response to your comment... Your code originally said this after finding a child named element:
children[element] = getAllChildren(taxonomy[element].tid);
You're making children[element] equal to a Dictionary object here. What you create is a tree structure, mapping MovieClip objects to Dictionary objects containing a similar mapping of its children. Using a for in loop on this structure will only give you the top-level children. It will not recursively traverse the entire tree.