Viewer3D's getIsolatedNodes and getHiddenNodes not working for multiple models - autodesk-forge

I'm loading multiple IFC's with their SVF derivatives into the Forge viewer. This works fine, but I cannot seem to get the Viewer3D's getIsolatedNodes or getHiddenNodesto work.
I'm using the GuiViewer3D class like this:
// initializing:
const viewer = new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv);
...
// load model1
viewer.start(model1Url ....
...
// load model2
viewer.loadModel(model2Url, ...
...
// after having loaded the models:
console.log(viewer.getIsolatedNodes());
The last line here only prints node ids if model1 has isolated nodes. If isolating nodes from model2, the last line prints an empty array. Is there a way to make this work with multiple models?

You can get access to the viewer's visibility manager via viewer.impl.visibilityManager, and call its methods for a specific model ID, for example:
viewer.impl.visibilityManager.getIsolatedNodes(modelID);
viewer.impl.visibilityManager.getHiddenNodes(modelID);
viewer.impl.visibilityManager.getAggregateHiddenNodes(); // across all models

Related

Forge viewer: how to isolate custom objects?

We use custom objects to visualize ifc space data (mostly rooms). As a guidance, we used this very helpful blog. After drawing the objects, we would also like to select the custom objects from outside and isolate them in the viewer. As the tutorial suggests, we change the model builder's changeFragmentsDbId function to set DbIds that do not exist yet and therefor do not overlap with already existing DbIds. One approach is to use the negative space [-1, -2, -3...] for our custom objects DbIds like this:
const roomFragId = this.modelBuilder.addFragment(roomGeometryId, materialName, transform);
this.modelBuilder.changeFragmentsDbId(roomFragId, -roomFragId);
Another one is to find the maximum DbId (eg. 4905) and use numbers higher than this maximum DbId for our custom objects DbIds (eg. [4906, 4907, 4908...]):
const roomFragId = this.modelBuilder.addFragment(roomGeometryId, materialName, transform);
this.modelBuilder.changeFragmentsDbId(roomFragId, maxDbId + roomFragId);
However, when we try to isolate a custom drawn object (viewer.isolate(-1) or viewer.isolate(4906)), the viewer kind of refreshs itself, but no object gets isolated...
Thus, we would like to know how we can isolate custom objects?
The other way, when we select the object in the viewer works for the negative space approach => we get the DbId (eg. -1) in the aggregate selection event.
Thank you for any kind of help!
To isolate or select custom objects created by the SceneBuilder ext, you need to pass model object to Viewer3D#isolate / Viewer3D#select like the below. Otherwise, viewer will use viewer.model instead.
viewer.isolate( [4906, 4907, 4908...], this.modelBuilder.model )
viewer.select( [4906, 4907, 4908...], this.modelBuilder.model )

combine model on Autodesk Forge

I have couple of questions about combine model on forge viewer (load list urn to 1 viewer):
when i combine model. i only can get data from 1 main model in that combine. for instance, 
var instanceTree = GlobalViewer.model.getData().instanceTree;
var allDbIdsStr = Object.keys(instanceTree.nodeAccess.dbIdToIndex);
var list = allDbIdsStr.map(function (id) { return parseInt(id) });
list will return all dbid of main model, how can i access all data of all model when i combine?
what is the unique id for object in combine model. i do some function with dbid and i realize it can appear in others model too.
When i combine 3d model(revit) with 2d model(autocad). it has 2 case: if 3d model load first i can rotate like normal, if 2d model load first i cant rotate the model any more. how can i force it always can rotate?
Autocad unit seems different with model in viewer. it always scale down compare with the model. how can i fix that?
Appreciate any comments,
Regarding #1: viewer.model obviously only references one of the models (I believe it's the last one you loaded), but you can use viewer.getVisibleModels() or viewer.getHiddenModels() to get other loaded models as well.
Regarding #2: dbIDs are only unique within a single model; many of the viewer methods accept an additional parameter specifying the model on which to operate, for example, you could say viewer.select([123, 456], oneOfMyModels).
Regarding #3: that's a good question; loading a 2D model first puts the viewer into 2D viewing mode (where only zoom and pan is allowed); if you know you will be working with 3D models, I'd recommend always loading those first
Regarding #4: yes, each loaded model can have different units; when loading a model using the loadDocumentNode method you can specify additional options (for example, a placement transform for the loaded geometries), and one of them is an object called applyScaling, for example, like so:
viewer.loadDocumentNode(doc, viewable, {
applyScaling: { to: 'mm' }
});

Multiplayer game using pygame [duplicate]

We are working on a Top-Down-RPG-like Multiplayer game for learning purposes (and fun!) with some friends. We already have some Entities in the Game and Inputs are working, but the network implementation gives us headache :D
The Issues
When trying to convert with dict some values will still contain the pygame.Surface, which I dont want to transfer and it causes errors when trying to jsonfy them. Other objects I would like to transfer in a simplyfied way like Rectangle cannot be converted automatically.
Already functional
Client-Server connection
Transfering JSON objects in both directions
Async networking and synchronized putting into a Queue
Situation
A new player connects to the server and wants to get the current game state with all objects.
Data-Structure
We use a "Entity-Component" based architecture, so we separated the game logic very strictly into "systems", while the data is stored in the "components" of each Entity. The Entity is a very simple container and has nothing more than a ID and a list of components. Example Entity (shorten for better readability):
Entity
|-- Component (Moveable)
|-- Component (Graphic)
| |- complex datatypes like pygame.SURFACE
| `- (...)
`- Component (Inventory)
We tried different approaches, but all seems not to fit very well or feel "hacky".
pickle
Very Python near, so not easy to implement other clients in future. And I´ve read about some security risks when creating items from network in this dynamic way how pickle it offers. It does not even solve the Surface/Rectangle issue.
__dict__
Still contains the reference to the old objects, so a "cleanup" or "filter" for unwanted datatypes happens also in the origin. A deepcopy throws Exception.
...\Python\Python36\lib\copy.py", line 169, in deepcopy
rv = reductor(4)
TypeError: can't pickle pygame.Surface objects
Show some code
The method of the "EnitityManager" Class which should generate the Snapshot of all Entities, including their components. This Snapshot should be converted to JSON without any errors - and if possible without much configuration in this core-class.
class EnitityManager:
def generate_world_snapshot(self):
""" Returns a dictionary with all Entities and their components to send
this to the client. This function will probably generate a lot of data,
but, its to send the whole current game state when a new player
connects or when a complete refresh is required """
# It should be possible to add more objects to the snapshot, so we
# create our own Snapshot-Datastructure
result = {'entities': {}}
entities = self.get_all_entities()
for e in entities:
result['entities'][e.id] = deepcopy(e.__dict__)
# Components are Objects, but dictionary is required for transfer
cmp_obj_list = result['entities'][e.id]['components']
# Empty the current list of components, its going to be filled with
# dictionaries of each cmp which are cleaned for the dump, because
# of the errors directly coverting the whole datastructure to JSON
result['entities'][e.id]['components'] = {}
for cmp in cmp_obj_list:
cmp_copy = deepcopy(cmp)
cmp_dict = cmp_copy.__dict__
# Only list, dict, int, str, float and None will stay, while
# other Types are being simply deleted including their key
# Lists and directories will be cleaned ob recursive as well
cmp_dict = self.clean_complex_recursive(cmp_dict)
result['entities'][e.id]['components'][type(cmp_copy).__name__] \
= cmp_dict
logging.debug("EntityMgr: Entity#3: %s" % result['entities'][3])
return result
Expectation and actual results
We can find a way to manually override elements which we dont want. But as the list of components will increase we have to put all the filter logic into this core class, which should not contain any components specializations.
Do we really have to put all the logic into the EntityManager for filtering the right objects? This does not feel good, as I would like to have all convertion to JSON done without any hardcoded configuration.
How to convert all this complex data in a most generic approach?
Thanks for reading so far and thank you very much for your help in advance!
Interesting articles which we were already working threw and maybe helpful for others with similar issues
https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/
http://code.activestate.com/recipes/408859/
https://docs.python.org/3/library/pickle.html
UPDATE: Solution - thx 2 sloth
We used a combination of the following architecture, which works really great so far and is also good to maintain!
Entity Manager now calls the get_state() function of the entity.
class EntitiyManager:
def generate_world_snapshot(self):
""" Returns a dictionary with all Entities and their components to send
this to the client. This function will probably generate a lot of data,
but, its to send the whole current game state when a new player
connects or when a complete refresh is required """
# It should be possible to add more objects to the snapshot, so we
# create our own Snapshot-Datastructure
result = {'entities': {}}
entities = self.get_all_entities()
for e in entities:
result['entities'][e.id] = e.get_state()
return result
The Entity has only some basic attributes to add to the state and forwards the get_state() call to all the Components:
class Entity:
def get_state(self):
state = {'name': self.name, 'id': self.id, 'components': {}}
for cmp in self.components:
state['components'][type(cmp).__name__] = cmp.get_state()
return state
The components itself now inherit their get_state() method from their new superclass components, which simply cares about all simple datatypes:
class Component:
def __init__(self):
logging.debug('generic component created')
def get_state(self):
state = {}
for attr, value in self.__dict__.items():
if value is None or isinstance(value, (str, int, float, bool)):
state[attr] = value
elif isinstance(value, (list, dict)):
# logging.warn("Generating state: not supporting lists yet")
pass
return state
class GraphicComponent(Component):
# (...)
Now every developer has the opportunity to overlay this function to create a more detailed get_state() function for complex types directly in the Component Classes (like Graphic, Movement, Inventory, etc.) if it is required to safe the state in a more accurate way - which is a huge thing for maintaining the code in future, to have these code pieces in one Class.
Next step is to implement the static method for creating the items from the state in the same Class. This makes this working really smooth.
Thank you so much sloth for your help.
Do we really have to put all the logic into the EntityManager for filtering the right objects?
No, you should use polymorphism.
You need a way to represent your game state in a form that can be shared between different systems; so maybe give your components a method that will return all of their state, and a factory method that allows you create the component instances out of that very state.
(Python already has the __repr__ magic method, but you don't have to use it)
So instead of doing all the filtering in the entity manager, just let him call this new method on all components and let each component decide that the result will look like.
Something like this:
...
result = {'entities': {}}
entities = self.get_all_entities()
for e in entities:
result['entities'][e.id] = {'components': {}}
for cmp in e.components:
result['entities'][e.id]['components'][type(cmp).__name__] = cmp.get_state()
...
And a component could implement it like this:
class GraphicComponent:
def __init__(self, pos=...):
self.image = ...
self.rect = ...
self.whatever = ...
def get_state(self):
return { 'pos_x': self.rect.x, 'pos_y': self.rect.y, 'image': 'name_of_image.jpg' }
#staticmethod
def from_state(state):
return GraphicComponent(pos=(state.pos_x, state.pos_y), ...)
And a client's EntityManager that recieves the state from the server would iterate for the component list of each entity and call from_state to create the instances.

How to select by dbid in viewer when several models are loaded?

I load several models in my scene. I would like to:
Fit to view from dbId -> this can be done with viewer.fitToView(objectIds,model)
Select an element from dbid on a particular model. But viewer.select(dbids,selectionType) does not offer possibility to select the model.
How can we make a selection on a dbid, on specific model, when several models are loaded?
Thanks
Looking at the code of Forge Viewer v6, the select method is defined as
Viewer3D.prototype.select = function (dbids, model) { ... }
So the second parameter should actually be the model, not a selection type.

Global model in spine.js application

I have web application written with Spine.js. It has 2 language translations. I want to store current application's translation in Spine Model.
My model:
class Translation extends Spine.Model
#configure "Translation", "lang"
#getLang: ->
Translation.all()
module.exports = Translation
I have function which changes translation in application and i save new translation to the model in this function:
changeLang: (locale) ->
lang = Translation.create({lang: locale})
lang.save()
But when i try to fetch data from Translation model from another controller i get empty result:
Translation = require("models/translation")
...
alert(Translation.getLang())
I got empty alert. How can i make it correctly?
Thank you.
The Translation.all() returns copies of all instances of Translation class. It seems, from snippets you provided, that when you call Translation.getLang() there are no such instances. You should make sure that changeLang has been called (I suppose that it is the only place where new instances of Translation are created) before calling alert(Translation.getLang()).