Infinite API v3.3.16

Infinite API

A TypeScript/JavaScript API to use the 3dJuump Infinite technology.

Table of content

Introduction

This documentation describes the use of the TypeScript/JavaScript 3dJuump Infinite API.

Getting started

In order to use the 3dJuump Infinite API, you just need a valid 3djuump infinite architecture. We assume the 3djuump infinite directory is requested from https://my_directory. You need to include a script markup that will include the infinite api from https://my_directory/directory/api/getwebapi?version=3.3 and begin the coding of your application.
If you upgrade the 3djuump infinite server to a new version, you do not need to change anything to your code, the implementation of the 3.3 api version on the new 3djuump infinite architecture will be available. But if the change concerns a minor version increment then keep in mind that you will not be able to take advantage of the new functionalities (see Migrating from 3.2/3.1 versions).

You may want to use TypeScript to code your application, then create a .npmrc file in your base folder and set :

 @3djuump.com:registry = https://3djuump.com/npm/

The required package is @3djuump.com/infiniteapi3.3. It is strongly advised to install the @3djuump.com/infiniteapi version of your infinite development server (including patch version), as new functionalities may be added during the development phase. Assuming your development server is of version 3.3.4, then make :

npm install "@3djuump.com/infiniteapi3.3.4"

If you are not sure which version your server is using, browse to https://my_directory/directory/api/getversion :

{
"comment": "directory http api",
"type": "directoryapi",
"version": {
"backend": "3.3.4.14860",
"clientfeature": "7.0",
"number": "3.0"
}
}

The version/backend field tells to use 3.3.4, then install the 3.3.4 version. If your server is updated to a new patch version, then install the new patch version, as new functionalities may be available (see the version log for more information).

If you do not comply with this rule and install the 3.3 version, you will get the last api definition, but your server may be in a lower revision, meaning that you may use functions not already available in you current server, which may resulting in using undefined functions.
(you may also get the api version by browsing the api documentation available in https://my_directory/directory/webapi).

Begin importing with :

import { Vector3 } from '@3djuump.com/infiniteapi';

If you are using a javascript bundle tool such as webpack, do not forget to add @3djuump.com/infiniteapi as external, that will be used with the infiniteapi global namespace.

externals: {
"@3djuump.com/infiniteapi": "infiniteapi",
},

The same holds if you are using a js loader such as require, etc ...

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">

<head>
<title>My Application</title>
</head>

<body>
<div id="mainContainer">
...
</div>
<script type="text/javascript" src="https://my_directory/directory/api/getwebapi?version=3.3"></script>
<script type="text/javascript" src="myscript.js"></script>
</body>
</html>

The 3dJuump Infinite API has one dependency to opentype.js (included in the js file), , luxon and bowser. The 3dJuump Infinite API only uses vanilla JavaScript, and does not redefine any public API. Thus, you may include any framework of your liking without fear of incompatibilities with the API.

The api documentation is available through https://my_directory/directory/webapi.

The 3dJuump Infinite API features the main following modules :

  • Main : this modules defines common interfaces used by all other modules.
  • Events : the 3dJuump API makes an intensive use of the Observer pattern. Many objects send signals when changes occur, the different events and their content is explained in this module.
  • Sessions : this module handles the authentication to the 3dJuump Infinite backend.
  • Factories: this module allows to create the main objects that are used to communicate with the 3dJuump Infinite architecture.
  • Maths : this module includes multiple classes that manipulate multi-dimensional vectors and matrices. This module contains the necessary classes to manipulate 3D data.
  • 3D Rendering : this module allows to display the 3D data of the DMU (geometries, cameras etc ...).
  • 3D Primitives : this module allows to display/add simple 3D primitives (boxes, lines, points) in the DMU.
  • Metadata : this module features the main objects to filter/gather a set of geometries.
  • Metadata/Filters : this sub-module of the Metadata module includes all the different types of filter that may be used to create sets of part instances.
  • Data Retrievers : this module includes multiple objects that may retrieve information about part instances.
  • Converters : this module allows converting different types of ID. The ID system is explained below.

A sample explaining the basic concepts is shipped with the given documentation. Do not hesitate to refer to the file sample.ts (TypeScript version) and sample_js.js (vanilla JavaScript version).

Requirements

You will need the following :

  • a working 3dJuump infrastructure (at least a 3dJuump Infinite directory and a 3dJuump Infinite server or proxy which has enabled web data generation, and one working web enabled 3dJuump Infinite build).
  • The 3dJuump Infinite directory contact address will be identified as https://my_directory:443/directory. The api documentation is available through https://my_directory:443/directory/webapi.
  • your clients (and the developers) must be valid users in the 3dJuump Infinite directory.
  • the authentication URL of the application you will develop must be registered onto the 3dJuump Infinite Directory (https://my_directory/directory/#/applications, and one of your registered application must have your URL registered : your application will be available on the your_server server and in the your_application folder. If you plan to use popup based authentication, you will need to create a page authenticate.html and add https://your_server/your_application/authenticate.html to the list of allowed authentication URLs. If you plan to use a same page authentication, you will need to add your main page (without url parameters) to the list of allowed authentication URLs.
  • Popup based authentication : you will need to write 2 scripts and 2 html files : one script and one html to handle your application logic, and one script and one html to handle authentication (in our case authenticate.html).
  • Same page based authentication : you will need to write 1 script and 1 html file : one script and one html to handle your application logic.

Testing / Installing / Configuring the sample

The 3dJuump JavaScript web API is shipped with a code example.

Installation of the sample

Unzip the infinite_api_<version>.zip to a folder of your liking in a web server (not necessarily your 3dJuump directory) and make the unzip-ed folder available from your server (making a folder available from a web server is beyond the scope of this documentation). We suppose the folder is available trough http://your_server/infinite_api.

The sample is available in the folder samples/v3_3.

Configuration of the sample

Edit the file samples/v3_3/config.js :

// Custom Configuration section : do as you wish
// directory to connect to
window["sDirectoryUrl"] = "https://"+window.location.hostname+":443/directory";
// should we test the TypeScript sample (require) or vanilla JavaScript ?
window["use_typescript"] = true;
// which authentication system to use ? popup or same page ?
//window["authentication"] = "popup";
window["authentication"] = "samepage";

// The picking test :
// "normal" => geometry are selected
// "box" => needs two picks : one for box center, the other for box extent and box creation
// "line"=> needs two picks : one for point 1, one for point 2 and line creation
// "point" => a point is created at the 3d position
window["pickingTest"] = "normal";

// some test values
window["test"] = {
// the sample will search on "roue", sorry we are with a french DMU :)
"searchTerm" : "roue",
// the filterAttribute will try to find `part instances` with attribute name "Name" that contains the values
// "elec" or "Train AV Link&Go" (it is actually inverted)
"attributeFilterContains" : {
"attributeName" : "Name",
"values" : ["elec", "Train AV Link&Go"]
},
// the filterAttribute will try to find `part instances` with attribute name "system" that have the exact value
// "4000-Inter"
"attributeFilterExact" : {
"attributeName" : "system",
"values" : ["4000-Inter"]
},
// the boxFilter will select `part instances` that are in the box centered on "center"
// and half extent "half_extent"
"boxFilter" : {
"center" : [1234, 0, 734],
"half_extent" : [1500, 1500, 1500],
},
// partInstanceList filter will select `part instances` that have the value "value"
"partInstanceListFilter" :
{
"value" : 786
}
};

The data shown here is only the relevant section of the configuration of the sample :

  • sDirectoryUrl controls the url to your 3dJuump Infinite directory, you may leave it unchanged if the sample is installed on the same web server as the 3dJuump Infinite directory.
  • use_typescript tells if you want to test the sample.ts file (TypeScript) or sample_js.js. Depending of your web language, i.e. vanilla JavaScript or TypeScript, you may want to use one or the other.
  • authentication tells the way authentication should be handled : popup based or same page.
  • pickingTest tells if the picking should be used to select geometries ("normal"), create 3D points ("point"), create 3D lines ("line") or create 3D boxes ("box")
  • the test property may be edited depending on the available DMU on your 3dJuump Infinite server. The sample tests 2 string metadata filters, a search request roue, a box request (you may change the values depending on your DMU) and a specific part instance (here part instance id : 786).

Testing of the sample

The sample will be available from your url : http://your_server/infinite_api/samples/v3_3/index.html. You will have to :

  • edit the application settings of your 3dJuump Infinite directory to allow the redirect urls: http://your_server/infinite_api/samples/v3_3/authentication.html (popup based authentication) and http://your_server/infinite_api/samples/v<version>/index.html (same page authentication).
  • Allow popups from http://your_server/infinite_api/samples/v3_3/index.html (popup based authentication).

Choose your DMU and you can see the content of your DMU.
Keyboard shortcuts :

  • space : re-centers the DMU and clears ghost and selection.
  • C : cycles through the available configurations of the DMU.
  • F : cycles through metadata filters test : string metadata, box, part instance ids list
  • S : triggers a search.
  • K : replaces the material of the last picked element by a red material, or restores the original material of the last changed element.

Authentication Samples

In order to get started, the following codes explains the basics of connecting to a 3dJuump Infinite build.

Tip : How to choose between same page authentication and popup based authentication ? The popup based authentication do the authentication process in another window/tab, therefore another window will be spawned. This authentication type preserves the application context from being erased during authentication process and rely on the LocalStorage API to transfer data session from the spawned window once authentication is finished to the application window. For the same page authentication, since the authentication process takes place inside the same page through redirections, it requires that the developer keep track of a previous application context if necessary.

Same page authentication

/** 
* Sample to illustrate the same page authentication mechanism.
*/
import {
InfiniteFactory, MetadataManagerFactory, InfiniteCacheFactory, DirectorySessionFactory,
InfiniteEngineInterface, MetadataManagerInterface, InfiniteCacheInterface, DirectorySessionInterface,
InfiniteEvent, ConnectionData,
DataSessionInterfaceSignal, DirectorySessionInterfaceSignal,
} from 'generated/documentation/appinfiniteapi';

// the current page (without url parameters) should be registered as authentication url in the directory
const sDirectoryUrl : string = 'https://my_directory:443/directory';

// Create a metadata manager interface
// the MetadataManagerInterface is the central point of your application
// It handles the creation of sets of geometries, search functions etc...
const lMetadataManager: MetadataManagerInterface = MetadataManagerFactory.CreateMetadataManager();
let lInfiniteEngine: InfiniteEngineInterface | undefined = undefined;
let lCache: InfiniteCacheInterface | undefined = undefined;
// bind the session to the MetadataManagerInterface
let lDirectorySession: DirectorySessionInterface | undefined = undefined;

// what to do to authenticate (authenticate function may be bound to a div `click` or called straight away)
let authenticate : () => void = undefined;

// Checks if the url has an hash in order to authenticate
let checkHash : () => void = undefined;

// Success callback on login
let onLoginSuccess : (pEvent: InfiniteEvent, _pCallbackData: Object | undefined) => void;

// Success callback when DataSession is ready
let onDMULoaded : (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) => void;

// *****************************************************************************
// ******************************* Authentication ******************************
// *****************************************************************************
// what to do to authenticate (authenticate function may be bound to a div `click` or called straight away)
authenticate = () : void =>
{
// if we have a hash, do not request a new authentication
if (window.location.hash.length > 0)
{
// perhaps some GUI code there
return;
}
// connect to directory with address sDirectoryAddress, in the folder "/directory" through https. The default port is unchanged (443)
// bind the session to the MetadataManagerInterface
lDirectorySession = DirectorySessionFactory.CreateDirectorySession(lMetadataManager, sDirectoryUrl);

// redirect url is the current url without query parameters
// the directory needs a full url (with query parameters if needed), so we recommend to
// register the url without parameters, use this url as authentication, store the url parameters if present
// and then redirect to the "final url" (see checkHash)
const sRedirectUrl : string = window.location.origin + window.location.pathname;
if (window.location.href.indexOf('?') >= 0)
{
// store the url with query parameters in session storage
// we will finally redirect to this url
window.sessionStorage.setItem('redirecturl', window.location.href.split('#')[0]);
}
const sURL = lDirectorySession.getSamePageAuthenticationUrl(sRedirectUrl);
if (typeof sURL === 'string')
{
// navigate to the given url for authentication
window.location.href = sURL;
}
else
{
console.log('Error not expected !!! ' + sURL);
window.sessionStorage.removeItem('redirecturl');
}
}

// Checks if the url has an hash in order to authenticate
checkHash = () : void =>
{
const lHash : string = window.location.hash;
if (lHash.length === 0)
{
// do not bother if no hash
return;
}
// we may here put some GUI code

// here we can check if the url has query parameters
const lSessionItem : string | null = window.sessionStorage.getItem('redirecturl');
if (lSessionItem)
{
// we have a redirect url
// remove it from storage in order to avoid infinite redirections
window.sessionStorage.removeItem('redirecturl');
// navigate to this url with hash
window.location.replace(lSessionItem + lHash);
}
else
{
// remove hash with some fancy methods
if (window.history.replaceState)
{
const uri : string = window.location.toString();
const cleanUri : string = uri.substring(0, uri.indexOf('#'));
window.history.replaceState({}, document.title, cleanUri);
}
else
{
window.location.hash = '';
}

// bind the session to the MetadataManagerInterface
lDirectorySession = DirectorySessionFactory.CreateDirectorySession(lMetadataManager, sDirectoryUrl);
// do something when we are connected !!!! => onLoginSuccess
lDirectorySession.addEventListener(DirectorySessionInterfaceSignal.LoginSuccess, onLoginSuccess);
// and end authentication
lDirectorySession.setTokenFromHash(lHash);
}
}

// Success callback on login
onLoginSuccess = (pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// Create a 3D engine to handle the rendering of the DMU. The engine is optional if you
// just want to search data in the DMU, but required to display 3D data.
// you need to provide a div inside your html file, or create one div programmatically
lInfiniteEngine = InfiniteFactory.CreateInfiniteEngine(lMetadataManager);
// bind the engine to the given div that will be used for rendering
lInfiniteEngine.setView(<HTMLElement>document.getElementById('rendering'));
// Create a cache to avoid requesting heavy data from the server if data has been retrieved already
lCache = InfiniteCacheFactory.CreateInfiniteCache();
// Get the Connection Data to get the build lists
const lConnectionData: ConnectionData = <ConnectionData>pEvent.attachments;

// Get the list of projects => do something as you like
const lProjectList = lConnectionData.projects;
for (const lProjectId in lProjectList)
{
// iterate but get the first project
const lBuildList = lProjectList[lProjectId].builds;
for (const lBuild in lBuildList)
{
// iterate but get the first build
// create a datasession that uses the cache with the first item
const lDataSession = (<DirectorySessionInterface>(pEvent.emitter)).createDataSession(lBuild, lCache);
if (lDataSession)
{
// be ready to do something when build is ready (i.e. all init data has been parsed and server is ready)
lDataSession.addEventListener(DataSessionInterfaceSignal.DMULoadingSuccess, onDMULoaded);
// and go !!! => open the data session
lDataSession.openDataSession();
}
return;
}
}
};

// Success callback when DataSession is ready
onDMULoaded = (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// do something !!!
// create filters
// change visibility
// etc ....
}

// we may launch an authentication procedure (or from a button)
authenticate();

// check hash if we should validate the authentication
checkHash();

Popup based authentication

/** 
* Sample to illustrate the popup based authentication mechanism.
*/
import {
InfiniteFactory, MetadataManagerFactory, InfiniteCacheFactory, DirectorySessionFactory,
InfiniteEngineInterface, MetadataManagerInterface, InfiniteCacheInterface, DirectorySessionInterface,
InfiniteEvent, ConnectionData,
DataSessionInterfaceSignal, DirectorySessionInterfaceSignal,
} from 'generated/documentation/appinfiniteapi';

// authentication url as defined in the directory
// authenticate.html must include your authentication script
const sAuthenticationUrl : string = 'https://your_server/your_application/authenticate.html';
const sDirectoryUrl : string = 'https://my_directory:443/directory';

// Create a metadata manager interface
// the MetadataManagerInterface is the central point of your application
// It handles the creation of sets of geometries, search functions etc...
const lMetadataManager: MetadataManagerInterface = MetadataManagerFactory.CreateMetadataManager();

// Create a 3D engine to handle the rendering of the DMU. The engine is optional if you
// just want to search data in the DMU, but required to display 3D data.
// you need to provide a div inside your html file, or create one div programmatically
const lInfiniteEngine: InfiniteEngineInterface = InfiniteFactory.CreateInfiniteEngine(lMetadataManager);
// bind the engine to the given div that will be used for rendering
lInfiniteEngine.setView(<HTMLElement>document.getElementById('rendering'));
// Create a cache to avoid requesting heavy data from the server if data has been retrieved already
const lCache: InfiniteCacheInterface = InfiniteCacheFactory.CreateInfiniteCache();

// what to do to authenticate (authenticate function may be bound to a div `click` or called straight away)
let authenticate : () => void = undefined;

// Success callback on login
let onLoginSuccess : (pEvent: InfiniteEvent, _pCallbackData: Object | undefined) => void = undefined;

// Success callback when DataSession is ready
let onDMULoaded : (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) => void = undefined;

// *****************************************************************************
// ******************************* Authentication ******************************
// *****************************************************************************
// what to do to authenticate (authenticate function may be bound to a div `click` or called straight away)
authenticate = () : void =>
{
// connect to directory with address sDirectoryAddress, in the folder "/directory" through https. The default port is unchanged (443)
// bind the session to the MetadataManagerInterface
const lDirectorySession: DirectorySessionInterface = DirectorySessionFactory.CreateDirectorySession(lMetadataManager, sDirectoryUrl);

// do something when we are connected !!!! => onLoginSuccess
lDirectorySession.addEventListener(DirectorySessionInterfaceSignal.LoginSuccess, onLoginSuccess);

// retrieve the url to contact to begin authentication with popup
// that also starts the authentication procedure
const sURL = lDirectorySession.getPopupBasedAuthenticationUrl(sAuthenticationUrl);
if (typeof sURL === 'string') {
if (sURL !== '' && sURL.indexOf('authenticate') >= 0)
{
// popup based authentication
window.open(sURL);
}
}
}

// Success callback on login
onLoginSuccess = (pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// Get the Connection Data to get the build lists
const lConnectionData: ConnectionData = <ConnectionData>pEvent.attachments;

// Get the list of projects => do something as you like
const lProjectList = lConnectionData.projects;
for (const lProjectId in lProjectList)
{
// iterate but get the first project
const lBuildList = lProjectList[lProjectId].builds;
for (const lBuild in lBuildList)
{
// iterate but get the first build
// create a datasession that uses the cache with the first item
const lDataSession = (<DirectorySessionInterface>(pEvent.emitter)).createDataSession(lBuild, lCache);
if (lDataSession)
{
// be ready to do something when build is ready (i.e. all init data has been parsed and server is ready)
lDataSession.addEventListener(DataSessionInterfaceSignal.DMULoadingSuccess, onDMULoaded);
// and go !!! => open the data session
lDataSession.openDataSession();
}
return;
}
}
};

// Success callback when DataSession is ready
onDMULoaded = (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// do something !!!
// create filters
// change visibility
// etc ....
}

authenticate();

As for authentication, the following code handles the authentication :

/** 
* Sample to illustrate the use of the authentication in the popup based authentication.
*/
import { DirectorySessionFactory } from 'generated/documentation/appinfiniteapi';

if (window.location.hash)
{
// try to decode the authentication hash.
if (!DirectorySessionFactory.DecodeAuthenticationHash(window.location.hash))
{
// output some debug error message.
console.log('Error: unable to decode hash content of authentication');
// display error
const lError: HTMLElement = <HTMLElement>document.getElementById('error');
lError.textContent = 'Unable to decode hash content of authentication';
}
else {
// everything is fine, the other tab is notified
// just need to close the tab
window.close();
}
}
else
{
// we have no hash => go back to main page
// get the url content
const lPath = window.location.pathname;
const lOffset : number = lPath.lastIndexOf('/');
// replace 'authentication.html' by main page 'index.html'
if (lOffset >= 0)
{
// create new location
const sLocation = window.location.origin + lPath.substring(0, lOffset) + '/index.html';
// ang go
window.location.href = sLocation;
}
}

Main concepts

Observer pattern

Many objects in the API trigger events, the developer just needs to connect to these events in order to be notified of key events and react accordingly. The Observer pattern is provided by the EventDispatcherInterface. The 3djuump Infinite API uses a system very similar to DOM events with the addEventListener and removeEventListener functions.

/** 
* Sample to illustrate some CameraManagerInterface signal system.
*/
import { InfiniteEngineInterface, CameraManagerInterfaceSignal, tListenerCallback } from 'generated/documentation/appinfiniteapi';

// engine has been created previously
let lEngine : InfiniteEngineInterface;
// callback called when the camera location is changed
let myCallback : tListenerCallback;

lEngine.getCameraManager().addEventListener(CameraManagerInterfaceSignal.CameraLocationChanged, myCallback);

Documents

The 3dJuump Infinite database is composed of numerous documents. There are several types of document that are contained in a DMU. The DMU is basically a collection of parts that reference themselves in a child/parent relationship. A part may have a geometric representation, but this is not required. A part may instantiate one or several children with a parental link.

Example : the DMU wheel is composed of the parts tire, rim, hub, bolt in the given instantiation chain :

  • wheel
    • tire with link wheel_tire-1
    • rim with link wheel_rim-1
      • hub with link rim_hub-1
        • bolt with link hub_bolt-generic
        • bolt with link hub_bolt-generic
        • bolt with link hub_bolt-generic
        • bolt with link hub_bolt-custom

Each part has common metadata shared by other instances of the same part, and each link has some metadata. The DMU provider has also attached the following instance metadata :

  • the instance referenced by wheel - tire - rim - hub - bolt at position X1,Y1,Z1 has the instance name bolt_1.
  • the instance referenced by wheel - tire - rim - hub - bolt at position X2,Y2,Z2 has the instance name bolt_2.
  • the instance referenced by wheel - tire - rim - hub - bolt at position X3,Y3,Z3 has the instance name bolt_3.

With this example we have introduced the three main metadata documents that compose a DMU :

  • the part metadata document (data shared by all instances of this element).
  • the link metadata document (the information included in each father/child relationship).
  • the instance metadata document (specific information bound to only specific instances and not all the instances of a given part).

As such, the bolts of the given DMU have the given documents

  • 1st bolt : part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-1
  • 2nd bolt : part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-2
  • 3rd bolt : part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-3
  • 4th bolt : part_metadata_bolt, link_metadata_hub_bolt-custom

Each document in the DMU has metadata (associations key : value) that enrich the instances that have it.

part_metadata_bolt document :

{ 
"id" : "part_metadata_bolt",
"Name" : "Wheel Bolt",
"Original Name" : "WHEEL-BOLT",
"PartNumber" : "WHEEL-BOLT",
"Diameter" : "10",
"Diameter Units" : "mm"
}

link_metadata_hub_bolt-generic document :

{ 
"id" : "link_metadata_hub_bolt-generic",
"Tightening Torque" : "80",
"Tightening Torque" : "N.m"
}

instance_metadata_bolt-1

{ 
"id" : "instance_metadata_bolt-1",
"Instance Name" : "bolt_1",
"S.N." : "70012341",
"Replacement Date" : "30/10/2022"
}

instance_metadata_bolt-2

{ 
"id": "instance_metadata_bolt-2",
"Instance Name" : "bolt_2",
"S.N." : "70034563",
"Replacement Date" : "30/01/2022"
}

instance_metadata_bolt-3

{ 
"id": "instance_metadata_bolt-3",
"Instance Name" : "bolt_3",
"S.N." : "70045675",
"Replacement Date" : "01/01/2021"
}

As such the bolt_1 has the metadata (i.e. the union of part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-1):

{ 
"Name" : "Wheel Bolt",
"Original Name" : "WHEEL-BOLT",
"PartNumber" : "WHEEL-BOLT",
"Diameter" : "10",
"Diameter Units" : "mm",
"Tightening Torque" : "80",
"Tightening Torque" : "N.m",
"Instance Name" : "bolt_1",
"S.N." : "70012341",
"Replacement Date" : "30/10/2022"
}

All metadata documents may be fetched by their id : an integer metadata document id that is recomputed on each build of the project. There may be attached documents (i.e. external documents) that are embedded or referenced (by using an URL) to part instances. All attached documents may be fetched by their id : a string attached document id.

Each document in the DMU has a string ID and an internal integer ID. Depending on the functions of the API, the API may expose integer or string based ids.

Configurations

Each instantiation link in the DMU may be configured, i.e. has effectivities attached to it. An effectivity is a list of values that are acceptable for a given metadata name.

In the example below, the given instantiation link will be available for the configuration with model GT or the configuration with model ST AND trim special.

{
"effectivity": [
{
"model": "GT"
},
{
"model": "ST",
"trim": "special"
}
]
}

A configuration is a list of applicable effectivities. Only instantiation links with the same effectivity (or no effectivity i.e. unconfigured) will take part in the given configuration. A configuration is therefore a subset of the DMU with only the part instances that come from a matching instantiation link (the root part instance is included in all the configurations). Your DMU provider may have included configuration information in your DMU (optional).

Please see the samples in Filtering Context.

Main ID Types

Each instance of the DMU is referred by a unique ID : the part instance id. This ID is not consistent from a build to the other, you MUST NOT rely on this number to be constant. Each new build of the same project involves a complete change of these ids.

Each geometry in the DMU has an ID : the geometric instance id.
That means that every single part instance that :

  • is a leaf
  • and has a geometrical representation

has a geometric instance id.

Each part instance that :

  • is not a leaf
  • has children (direct or not) with a graphical representation

is expressed with the list of geometric instance id of their children.

When a geometry is picked, the API gives the geometric instance id of the picked geometry.

There may be cases when 2 different part instances have the same geometric representation on the same location : in these cases, a single geometric instance id is the expression of two part instance ids.

The API in the Converters module allows to convert IDs depending on a filtering context (see below).

In the same way, converting the root part instance to get all the geometric instance ids that are represented by it leads to getting all the geometric instance ids of the DMU.

There may be cases when two different part instance nodes (not leaves) have the same exact geometric representation at the same location. Usually, this concerns a single object in the DMU that fulfills two distinct functional usages. In such cases, the part instance nodes are considered different but instead of gathering all their children, taking their geometric instance id, removing duplicates, ordering them and comparing the results, such part instance nodes have the same class id. Each part instance is assigned a class id, and thus two different part instance nodes that have the same geometric representation have the same class id. Class ids are retrieved by an IdCardGetterInterface.

Filtering Context

All the interfaces exposed by the DataSessionInterface (actually in the Metadata namespace and its sub-namespaces) are used to make lists of part instance ids, in all the hierarchy (not only leafs, but nodes too). These interfaces usually expose a way to get the corresponding geometric instance ids.
The FilterItemInterface is the base interface of objects that express a filter request (e.g. get part instances with name engine). Now, let us assume we make a FilterItemInterface in a FilterSolverInterface(see below) that provides a set with a single instance id : the root instance id. The corresponding geometric instance ids are therefore the geometric instance ids of all the DMU, since all leafs represents the root.

Creating a set of part instance ids is done with the use of a FilterSolverInterface. This interface allows to combine filters (intersection, union, difference - or exclusion - : FilterOperator), and get the result with the SolverReady signal. Depending on the filters in use, part instance leaves or nodes may be included in the resulting set. There is NO single treatment done on these part instance ids, it is up to the application, with the use of the optional InfiniteEngineInterface to make them ghosted/hidden ... (updateGeometricState and VisualStates) by getting their geometric instance ids.

Warning: the InfiniteEngineInterface only works with geometric instance ids.

The order of filters in the FilterSolverInterface is relevant and changing the order of the filters may (or not) change the final result. The FilterSolverInterface content is represented as a list of [geometric instance ids] and optionally a list of [part instance ids]. A FilterOperator is included in each FilterItemInterface : it represents the set operator to use when combined with the previous enabled filter in the list of the filters of the filter solver. As such, the FilterOperator of the first FilterItemInterface in the list is always discarded, but if it is different that FO_UNION, a warning is outputted in the console to help the user understand the final result.

The FilterSolverInterface must be provided with a valid VisibilityContextInterface which is a filtering context that narrows the filtering to a set of configurations and optionally other FilterSolverInterface.

A filtering context is a also set of part instances. This filtering context is the VisibilityContextInterface in the Metadata module that is the expression of the intersection of :

The representation of a list of configurations is the ConfContextInterface in the Metadata module. This object represents the union of configurations (or unconfigured if the list of configurations is empty, meaning all the part instances in the DMU belong to this object). A VisibilityContextInterface without a ConfContextInterface is invalid and thus will not work as expected.

/** 
* Sample to illustrate the use of an "unconfigured" configuration (all `part instances`) in the ConfContextInterface.
*/
import { ConfContextInterface, DataSessionInterface } from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;
// create a ConfContextInterface
const lConfContext : ConfContextInterface = lDataSession.createConfContext();
// we will take the first ConfigurationInterface for this context
// set the active confs
lConfContext.setActiveConfs([]);
// and ask to compute all changes requested since the last call to the DataSessionInterface
lDataSession.update();

You may need to display only a subset of data depending on some criteria, e.g. the part instances of your DMU in the given 3D cube that has the field CompletionStatus equal to done. Such a list of filters is grouped in a FilterSolverInterface in the Metadata module. The following code snippet may be used to get the corresponding subset :

/** 
* Sample to illustrate the use of an intersection of
* FilterAABBInterface and FilterAttributeInterface.
*/
import {
AABB, AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
ConfContextInterface, VisibilityContextInterface, FilterSolverInterface,
FilterAABBInterface, FilterAttributeInterface, FilterOperator,
} from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;

// MAKE SURE the attributes "CompletionStatus"
const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
const lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('CompletionStatus');
// make sure the attribute is a string one
console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

// create the resolution chain :
// create a filtering context "unconfigured"
// first create ConfContextInterface "unconfigured"
const lConfCtx : ConfContextInterface = lDataSession.createConfContext();
// and create the filtering context and bind it to the unconfigured ConfContextInterface
const lConfVisibilityCtx: VisibilityContextInterface = lDataSession.createVisibilityContext();
lConfVisibilityCtx.setConfContext(lConfCtx);

// the AABB to use
const lABB : AABB = new AABB();
lABB.mCenter.x = 3.0;
lABB.mCenter.y = 3.0;
lABB.mCenter.z = 3.0;

lABB.mHalfExtent.x = 10.0;
lABB.mHalfExtent.y = 10.0;
lABB.mHalfExtent.z = 10.0;

// create a Filter Solver
const lFilterSolver : FilterSolverInterface = lDataSession.createFilterSolver();

// create a box filter
const lFilterAABB : FilterAABBInterface = lDataSession.createFilterAABB();
// useless, FilterOperator.FO_UNION is the default operator when creating a new filter
// lFilterAABB.setFilterOperator(FilterOperator.FO_UNION);

lFilterAABB.setAABB(lABB);

// create an FilterAttributeInterface
const lFilterAttributes : FilterAttributeInterface = lDataSession.createFilterAttribute();
// completion status to done
lFilterAttributes.setAttributeName('CompletionStatus');
lFilterAttributes.setExactValues(['done']);
// no n/a
lFilterAttributes.setNaValueChecked(false);

// intersection is the way to go since intersection of box and instances that have the field "CompletionStatus"
// to "done"
// only change the operator of the filters except the first
lFilterAttributes.setFilterOperator(FilterOperator.FO_INTERSECTION);

// and add the filters
// push back (-1) the AABB filter
lFilterSolver.insertFilter(-1, lFilterAABB);
// push back (-1) the FilterAttributeInterface, as it is the second one, its operator is used and therefore
// intersection is used
lFilterSolver.insertFilter(-1, lFilterAttributes);

// set the conf context to use => unconfigured for this example
lFilterSolver.setVisibilityContext(lConfVisibilityCtx);
// and tell the DataSessionInterface to update the modified ConfContextInterface, VisibilityContextInterface and FilterSolverInterfaces
lDataSession.update();

Now that we have the configuration to use and the combining of filters, we can create a Filtering Context.

/** 
* Sample to explain how to create filtering contexts.
*/
import { ConfContextInterface, FilterSolverInterface, VisibilityContextInterface, DataSessionInterface } from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;
// lConfContext has been created previously
let lConfContext : ConfContextInterface;
// lFilterSolver has been created previously
let lFilterSolver : FilterSolverInterface
// create the filtering context
const lFilteringContext : VisibilityContextInterface = lDataSession.createVisibilityContext();
// sets the conf context
lFilteringContext.setConfContext(lConfContext);
lFilteringContext.insertFilterSolver(-1, lFilterSolver);
// and tell the DataSessionInterface to update the modified ConfContextInterface, VisibilityContextInterface and FilterSolverInterfaces
lDataSession.update();
// the filtering context may then be used to restrict a search, or a metadata retrieval to the given FilteringContext.

The VisibilityContextInterface does not provide a way to get the list of part instances inside it, it only provide a way to restrict a further API call to this list of part instances (e.g. a search in this list).

There are cases when the user wants to get the list of part instance of a given filtering request. We assume that we want to :

  • display the part instances of your DMU in a given 3D cube that has the field CompletionStatus equal to done.
  • colorize in yellow the part instances that have the field ToBeRedesigned equal to true.
/** 
* Sample to illustrate the use of multiple FilterSolverInterfaces to define a set of visible pieces, and
* colorize a subset.
* The visibility is composed of all the part instances included in a Box, that have the "CompletionStatus" attribute
* set to "done".
* The colorization set is based on the visibility, the part instances that have the attribute "ToBeRedesigned"
* true should be colored in yellow.
* This colors all part instance in yellow in the given box, that ave the attribute "ToBeRedesigned"
* true and "CompletionStatus" attribute set to "done".
* This sample uses multiple FilterSolverInterface, VisibilityContextInterface,
* It uses some FilterAABBInterface, FilterAttributeInterface, FilterBooleanInterface.
*/
import {
AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
InfiniteEngineInterface, ConfContextInterface, Vector3, AABB, FilterSolverInterface, VisibilityContextInterface, FilterAABBInterface, FilterAttributeInterface, FilterOperator, FilterSolverInterfaceSignal, VisualStates, FilterBooleanInterface,
} from 'generated/documentation/appinfiniteapi';

// lEngineInterface has been created previously
let lEngineInterface : InfiniteEngineInterface;
// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;

// MAKE SURE the attributes "CompletionStatus", "ToBeRedesigned" are relevant
const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
let lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('CompletionStatus');
// make sure the attribute is a string one
console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

lAttributeInfo = lAttributeDictionary.getAttributeInfo('ToBeRedesigned');
// make sure the attribute is a date one
console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_BOOLEAN));

// create a custom material yellow
const lMaterialId : number = lEngineInterface.getMaterialManager().createNewMaterial(new Vector3(1, 1, 0));
// create a conf context
const lConfContext : ConfContextInterface = lDataSession.createConfContext();
// no configuration for the moment (all `part instances`)
lConfContext.setActiveConfs([]);
// the AABB to use
const lABB : AABB = new AABB();
lABB.mCenter.x = 3.0;
lABB.mCenter.y = 3.0;
lABB.mCenter.z = 3.0;

lABB.mHalfExtent.x = 10.0;
lABB.mHalfExtent.y = 10.0;
lABB.mHalfExtent.z = 10.0;

// create the visibility Filter Solver
const lVisibilityFilterSolver : FilterSolverInterface = lDataSession.createFilterSolver();

// create the filtering context of the filter solver
const lInnerFilteringContext : VisibilityContextInterface = lDataSession.createVisibilityContext();
lInnerFilteringContext.setConfContext(lConfContext);
lVisibilityFilterSolver.setVisibilityContext(lInnerFilteringContext);

// create a box filter
const lFilterAABB : FilterAABBInterface = lDataSession.createFilterAABB();
// useless, FilterOperator.FO_UNION is the default operator when creating a new filter
// lFilterAABB.setFilterOperator(FilterOperator.FO_UNION);
lFilterAABB.setAABB(lABB);

// create a FilterAttributeInterface
const lFilterAttributes : FilterAttributeInterface = lDataSession.createFilterAttribute();
// completion status to done
lFilterAttributes.setAttributeName('CompletionStatus');
lFilterAttributes.setExactValues(['done']);
// no n/a
lFilterAttributes.setNaValueChecked(false);

// intersection is the way to go since intersection of box and instances that have the field "CompletionStatus"
// to "done"
// only change the operator of the filters except the first
lFilterAttributes.setFilterOperator(FilterOperator.FO_INTERSECTION);

// and add the filters
// push back (-1) the AABB filter
lVisibilityFilterSolver.insertFilter(-1, lFilterAABB);
// push back (-1) the FilterAttributeInterface, as it is the second one, its operator is used and therefore
// intersection is used
lVisibilityFilterSolver.insertFilter(-1, lFilterAttributes);

// create the filtering context
const lFilteringContext : VisibilityContextInterface = lDataSession.createVisibilityContext();
// sets the conf context
lFilteringContext.setConfContext(lConfContext);
// the colorize filter should be set a sub set of the visibility
lFilteringContext.insertFilterSolver(-1, lVisibilityFilterSolver);

lVisibilityFilterSolver.addEventListener(FilterSolverInterfaceSignal.SolverReady, () =>
{
const lGeometries : Uint32Array | undefined = lVisibilityFilterSolver.getGeometricInstanceIds();
if (lGeometries)
{
// set hidden and not ghosted for everyone
lEngineInterface.updateGeometricStateForAll(VisualStates.S_Ghost | VisualStates.S_Hidden, (~VisualStates.S_Ghost) | VisualStates.S_Hidden);
// and set not hidden for the given geometries
lEngineInterface.updateGeometricState(lGeometries, VisualStates.S_Hidden, ~VisualStates.S_Hidden);
}
});

// create the colorize Filter Solver
const lColorizeFilterSolver : FilterSolverInterface = lDataSession.createFilterSolver();
// create an attribute filter
const lColorizeFilterAttributes : FilterBooleanInterface = lDataSession.createBooleanFilter();
// search ToBeRedesigned attribute
lColorizeFilterAttributes.setAttributeName('ToBeRedesigned');
// value should be true
lColorizeFilterAttributes.setBooleanValue(true);

// when the FilterSolverInterface is ready, colorize the given `geometric instances`
lColorizeFilterSolver.addEventListener(FilterSolverInterfaceSignal.SolverReady, () =>
{
const lGeomIds : Uint32Array | undefined = lColorizeFilterSolver.getGeometricInstanceIds();
if (lGeomIds)
{
// change material for these elements
lEngineInterface.getMaterialManager().changeMaterialOfInstances(lGeomIds, lMaterialId);
}
});

// set the filtering context of the given `part instances` list
lColorizeFilterSolver.setVisibilityContext(lFilteringContext);
// add the colorize filter
lColorizeFilterSolver.insertFilter(-1, lColorizeFilterAttributes);
// and tell the DataSessionInterface to update the modified ConfContextInterface, VisibilityContextInterface and FilterSolverInterfaces
lDataSession.update();
// the filtering context may then be used to restrict a search, or a metadata retrieval to the given FilteringContext.

NB : Any change in a ConfContextInterface, VisibilityContextInterface, FilterSolverInterface and any Filter will be broadcasted and computed on the next call to update call.

Please refer to the documentation for each class for more detailed explanations.

Available Filters

All filter interfaces extend the FilterItemInterface base interface. They are used in a FilterSolverInterface. A filter is a way to elect part instances from specific criteria (depending on the type of the filter).
The same filter cannot be used in multiple FilterSolverInterface, FilterSetInterface or FilterCompoundInterface, each filter can only be contained in one and only one "container".
All filters have a unique id (UUID), a type (FilterType) depending on the filter, an enabled state, an inverted state and a filter operator (FilterOperator).
Any disabled filter will not take part in the computation of the result of the FilterSolverInterface.
If a filter elects a set of part instances, then the same inverted filter will elects all other part instances leaves minus the one in the non inverted filter.
Warning : in a FilterCompoundInterface, the meaning of the inverted state is different.
The filter operator of a filter tells the way the part instances elected from this filter combine with the preceding filter (union, intersection, minus). The filter operator of the first enabled filter is always ignored, but a warning is outputted in the console when the FilterOperator is different that FO_UNION. There are no operator precedence when combining filters, they are processed sequentially by their order in the container that holds them. There are cases when the user may want to combine filters in a certain order, the "parenthesis" system is provided by the FilterSetInterface interface.
Suppose we have a DMU with 3 part instances {a,b,c} and 3 filters A = { a, b, c }, B = { b, c } , C = { a, c }.

We want to calculate (C U A) - B = C U A - B = { a }:

/** 
* Sample to illustrate the operator precedence.
*/
import { FilterItemInterface, FilterSolverInterface, FilterOperator, DataSessionInterface } from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;
let lFilterSolver: FilterSolverInterface;
// A, B and C have been created previously
let A : FilterItemInterface;
let B : FilterItemInterface;
let C : FilterItemInterface;

// compute (C U A) - B = C U A - B

// it is better to modify the filters first and insert them after if possible (less signals sent)

// filter operator of C will be discarded, but a warning is outputted in the console if not FilterOperator.FO_UNION.
C.setFilterOperator(FilterOperator.FO_UNION);
// compute union and exclusion
A.setFilterOperator(FilterOperator.FO_UNION);
B.setFilterOperator(FilterOperator.FO_EXCLUSION);

// and insert filters
lFilterSolver.insertFilter(-1, C);
lFilterSolver.insertFilter(-1, A);
lFilterSolver.insertFilter(-1, B);

// and tell the DataSessionInterface to update the modified FilterSolverInterface
lDataSession.update();

We want to calculate C U (A - B) = { a, c }:

/** 
* Sample to illustrate the operator precedence.
*/
import { FilterItemInterface, FilterSolverInterface, FilterOperator, FilterSetInterface, DataSessionInterface } from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;

let lFilterSolver: FilterSolverInterface;
// A, B and C have been created previously
let A : FilterItemInterface;
let B : FilterItemInterface;
let C : FilterItemInterface;

// compute C U (A - B)
// we create the parenthesis with a FilterSetInterface
const lParenthesis : FilterSetInterface = lDataSession.createFilterSet();

// it is better to modify the filters first and insert them after if possible (less signals sent)

// filter operator of C will be discarded, but a warning is outputted in the console if not FilterOperator.FO_UNION.
C.setFilterOperator(FilterOperator.FO_UNION);
// filter operator of A will be discarded, but a warning is outputted in the console if not FilterOperator.FO_UNION.
A.setFilterOperator(FilterOperator.FO_UNION);

// compute union and exclusion
lParenthesis.setFilterOperator(FilterOperator.FO_UNION);
B.setFilterOperator(FilterOperator.FO_UNION);

// add filters in parenthesis
lParenthesis.insertFilter(-1, A);
lParenthesis.insertFilter(-1, B);

// and insert filters
lFilterSolver.insertFilter(-1, C);
lFilterSolver.insertFilter(-1, lParenthesis);

// and tell the DataSessionInterface to update the modified FilterSolverInterface
lDataSession.update();
  • FilterAllPartsInterface ("Everything" filter)
    The easiest filter. This filter selects all the part instances of the DMU. A FilterSolverInterface that contains only a FilterAllPartsInterface will contain all part instance ids between 1 and getMaximumPartInstanceId included (thus a length of getMaximumPartInstanceId) and all geometric instance ids between 1 and getMaximumGeometricId included (thus a length of getMaximumGeometricId).

  • FilterAABBInterface (Axis Aligned Bounding Box filter)
    This filter selects part instances intersecting an axis aligned box.
    The FilterAABBInterface defines an Axis Aligned bounding box (AABB).
    The inclusion algorithm may be strict, i.e. all triangles of a part instance must be strictly contained inside the box for the part instance to be elected, or loose, in this case, any part instance with a triangle inside the box or intersecting the box will be elected (setOverlapped).
    A FilterAABBInterface that contains only a FilterAABBInterface initialized with the DMU AABB (getDmuAABB) will contain all geometric instance ids between 1 and getMaximumGeometricId included (thus a length of getMaximumGeometricId) and the part instance ids that have a geometric representation (i.e. leaves with geometries and their parents).

  • FilterDiagonalInterface (Squared Diagonal length Filter)
    This filter selects part instances whose SQUARED diagonal length is inside a set of ranges. The FilterDiagonalInterface defines a set of numeric values : a set of FilterRangeItemInterface.

  • FilterAttributeInterface (String metadata Filter)
    This filter selects part instances based on their string attributes or their parent string attributes (as defined in Documents). Each part instance is considered to have the union between their own attributes and the attributes of its genealogy (parent, grand-parent, etc ...) : their joined attribute set.
    The FilterAttributeInterface defines a set of string values : the search attribute set.
    This filter selects part instances having a string attribute inside their joined attribute set whose value :

    setExactValues : is exactly inside the search attribute set (an item in the search attribute set is exactly the value of the attribute).
    or setContainsValues : contains at least one of the values (full text search) of the search attribute set. If the given attribute has only a limited number of possible values across the DMU (less than 1024), and its values are not too large, then such a string attribute is enumerable. If an attribute is enumerable, then the special value N/A (not applicable) is available and selected as default (hasNaValueAvailable, setNaValueActivated, setNaValueChecked). The special value N/A selects all other part instances than the union of all possible values of such a string attribute. Due to this special handling and implementation details, using the N/A value is not compatible with using the setContainsValues function. Any FilterAttributeInterface on an enumerable attribute has setNaValueActivated set to true, if you want to use the setContainsValues function, you will have to make a setNaValueActivated(false); call.

  • FilterRangeInterface (Numeric/date metadata Filter)
    This filter selects part instances based on their numeric/date attributes or their parent numeric/date attributes (as defined in Documents). Each part instance is considered to have the union between their own attributes and the attributes of its genealogy (parent, grand-parent, etc ...) : again the joined attribute set.
    The FilterRangeInterface defines a set of numeric values : a set of FilterRangeItemInterface.
    Date are expressed as a number : the number of milliseconds elapsed since January 1, 1970, 00:00:00 UTC.
    This filter selects part instances having a numeric/date attribute inside their joined attribute set whose value (or range):

    is inside or intersects a least one of the FilterRangeItemInterface defined in the filter.

  • FilterHasFieldInterface (Existing metadata Filter)
    This filter selects part instances based on their attributes or their parent attributes (as defined in Documents). Each part instance is considered to have the union between their own attributes and the attributes of its genealogy (parent, grand-parent, etc ...) : again the joined attribute set.
    The FilterRangeInterface defines an attribute name.
    This filter selects part instances having an attribute with the given name inside their joined attribute set.

  • FilterBooleanInterface (Boolean metadata Filter)
    This filter selects part instances based on their boolean attributes or their parent boolean attributes (as defined in Documents). Each part instance is considered to have the union between their own attributes and the attributes of its genealogy (parent, grand-parent, etc ...) : again the joined attribute set.
    The FilterBooleanInterface defines a truth (boolean) value.
    This filter selects part instances having a boolean attribute inside their joined attribute set whose value :

    is exactly the one defined in the filter.

  • FilterLiteralInterface (Advanced metadata query Filter) the individually
    This filter selects part instances if any metadata document attached to a part instance matches a string query. The FilterLiteralInterface defines a string query (literal) written in the 3dJuump Infinite Literal and Search query language. ). This filter allows to select part instances with a complex query that can mimic and extend the FilterBooleanInterface, FilterHasFieldInterface, FilterRangeInterface and FilterAttributeInterface altogether, but on the same document.
    This filter selects part instances having a metadata document whose set of attributes matches the query.

  • FilterPartInstanceListInterface (part instance id Filter)
    This filter selects part instances based on their part instance id.
    The FilterPartInstanceListInterface defines a list of part instance id.
    This filter selects part instances having an part instance id whose value :

    is inside the given set.

Warning : using such a filter is very risky since part instance id are recomputed at each new 3dJuump Infinite build process. That means that creating such a filter on a build will be invalidated if a new build is done on your project. It is highly recommended to create your filters based on geometric values and metadata rather than their part instance id. Use extreme caution when using this type of filter and be sure you know what you are doing. Moreover, such a filter may consume more memory that a combination of other filters if a large number of part instance id is inside the filter.

  • FilterSetInterface (Group Filter)
    The filter allows to gather filters together and to create an operator precedence with FilterOperator.
    It is a sort of "parenthesis" filter (see above).

Warning : the maximum supported depth (i.e. nested FilterSetInterface) is 8.

Filters examples

In the following examples, the metadata assembly is as follows (dates are expressed as day/month/year):

metadata assembly example

In all the examples, the FilterSolverInterface and the filtering context creation are not explained, as it is explained in the next section : Filtering Context.

  1. The user wants to select electrical `part instances` whose replacement date is within the next 10 days (we assume we are the 26th of jan 2021). The expected result is [part instance 3].
    1. Create the two required filters in a [FilterCompoundInterface](interfaces/FilterCompoundInterface.html) and put them in intersection :

      /**
      * Sample to illustrate :
      * Select electrical `part instances` whose replacement date is within the next 10 days (we assume we are the 26th of jan 2021).
      * Test : create two filters in a [[FilterCompoundInterface]] and put them in intersection.
      */
      import {
      AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
      FilterCompoundInterface, FilterRangeItemInterface, FilterSolverInterface, FilterAttributeInterface, FilterOperator, FilterRangeInterface,
      } from 'generated/documentation/appinfiniteapi';

      // the DataSessionInterface has been created previously and is connected
      let lDataSession : DataSessionInterface;

      // The Filter Solver has been created previously, its filtering context has been set
      let lFilterSolver : FilterSolverInterface;

      // MAKE SURE the attributes "type" and "replacement" are relevant */
      const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
      let lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('type');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('replacement');
      // make sure the attribute is a date one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_DATE));

      // create the electrical filter
      const lElectricalFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to electrical (exactly)
      lElectricalFilter.setAttributeName('type');
      lElectricalFilter.setExactValues(['electrical']);
      // the filter will be used inside a [[FilterCompoundInterface]], N/A cannot be used (limitation on [[FilterCompoundInterface]])
      // lElectricalFilter.setNaValueChecked(false);
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter
      // lElectricalFilter.setFilterOperator(FilterOperator.FO_UNION);

      const lReplacementFilter : FilterRangeInterface = lDataSession.createFilterRange();
      // work on "replacement" metadata
      lReplacementFilter.setAttributeName('replacement');
      // create a range
      {
      const lRepRange : FilterRangeItemInterface = lReplacementFilter.createFilterRangeItem();
      // we want to include the bounds in the query
      lRepRange.setIncludedLower(true);
      lRepRange.setIncludedUpper(true);
      const lCurrentTime : number = Date.now();
      const lCurrentTimePlus10Days : number = lCurrentTime + 10 * 24 * 3600 * 1000;
      // range between now and 10 days
      lRepRange.setLowerBound(lCurrentTime);
      lRepRange.setUpperBound(lCurrentTimePlus10Days);
      }
      // intersection is the way to go since intersection of type electrical and instances that should be replaced within the next 10 days
      lReplacementFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      const lCompoundFilter : FilterCompoundInterface = lDataSession.createCompoundFilter();
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter
      // lCompoundFilter.setFilterOperator(FilterOperator.FO_UNION);

      // add the filters inside the FilterCompoundInterface
      // push back (-1) the electrical filter
      lCompoundFilter.insertFilter(-1, lElectricalFilter);
      // push back (-1) the replacement filter, as it is the second one, its operator is used and therefore
      // intersection is used
      lCompoundFilter.insertFilter(-1, lReplacementFilter);

      // and add the compound filter
      lFilterSolver.insertFilter(-1, lCompoundFilter);

      // and tell the DataSessionInterface to update the modified FilterSolverInterfaces
      lDataSession.update();

      This results in [] inside the result of the getPartInstanceIds. This is not the expected result since there is no single metadata document inside the example that contains both a type and replacement metadata. The type metadata is inherited from the hierarchy.

    2. Create the two required filters in a [FilterSolverInterface](interfaces/FilterSolverInterface.html) and put them in intersection :

      /**
      * Sample to illustrate :
      * Select electrical `part instances` whose replacement date is within the next 10 days (we assume we are the 26th of jan 2021).
      * Test : create two filters directly in a [[FilterSolverInterface]] and put them in intersection.
      */
      import {
      AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
      FilterRangeItemInterface, FilterSolverInterface, FilterAttributeInterface, FilterOperator, FilterRangeInterface,
      } from 'generated/documentation/appinfiniteapi';

      // the DataSessionInterface has been created previously and is connected
      let lDataSession : DataSessionInterface;

      // The Filter Solver has been created previously, its filtering context has been set
      let lFilterSolver : FilterSolverInterface;

      // MAKE SURE the attributes "type" and "replacement" are relevant */
      const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
      let lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('type');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('replacement');
      // make sure the attribute is a date one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_DATE));

      // create the electrical filter
      const lElectricalFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to electrical (exactly)
      lElectricalFilter.setAttributeName('type');
      lElectricalFilter.setExactValues(['electrical']);
      // warning : n/a is set to true as default, we do not want to include other `part instances`
      lElectricalFilter.setNaValueChecked(false);
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter
      // lElectricalFilter.setFilterOperator(FilterOperator.FO_UNION);

      const lReplacementFilter : FilterRangeInterface = lDataSession.createFilterRange();
      // work on "replacement" metadata
      lReplacementFilter.setAttributeName('replacement');
      // create a range
      {
      const lRepRange : FilterRangeItemInterface = lReplacementFilter.createFilterRangeItem();
      // we want to include the bounds in the query
      lRepRange.setIncludedLower(true);
      lRepRange.setIncludedUpper(true);
      const lCurrentTime : number = Date.now();
      const lCurrentTimePlus10Days : number = lCurrentTime + 10 * 24 * 3600 * 1000;
      // range between now and 10 days
      lRepRange.setLowerBound(lCurrentTime);
      lRepRange.setUpperBound(lCurrentTimePlus10Days);
      }
      // intersection is the way to go since intersection of type electrical and instances that should be replaced within the next 10 days
      lReplacementFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      // and add the filters
      // push back (-1) the electrical filter
      lFilterSolver.insertFilter(-1, lElectricalFilter);
      // push back (-1) the replacement filter, as it is the second one, its operator is used and therefore
      // intersection is used
      lFilterSolver.insertFilter(-1, lReplacementFilter);

      // and tell the DataSessionInterface to update the modified FilterSolverInterfaces
      lDataSession.update();

      This results in [part instance 3] inside the result of the getPartInstanceIds. This is the expected result.

  2. The user wants to select electrical `part instances` which were plugged on the 01/01/2020. The expected result is [part instance 2].
    1. Create the three required filters in a [FilterCompoundInterface](interfaces/FilterCompoundInterface.html) and put them in intersection :

      /**
      * Sample to illustrate :
      * Select electrical `part instances` which were plugged on the 01/01/2020.
      * Test : create three filters in a [[FilterCompoundInterface]] and put them in intersection.
      */
      import {
      AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
      FilterCompoundInterface, FilterRangeItemInterface, FilterSolverInterface, FilterAttributeInterface, FilterOperator, FilterRangeInterface,
      } from 'generated/documentation/appinfiniteapi';

      // the DataSessionInterface has been created previously and is connected
      let lDataSession : DataSessionInterface;

      // The Filter Solver has been created previously, its filtering context has been set
      let lFilterSolver : FilterSolverInterface;

      // MAKE SURE the attributes "type", "history.date" and "history.state" are relevant
      const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
      let lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('type');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('history.date');
      // make sure the attribute is a date one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_DATE));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('history.state');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      // create the electrical filter
      const lElectricalFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to electrical (exactly)
      lElectricalFilter.setAttributeName('type');
      lElectricalFilter.setExactValues(['electrical']);
      // the filter will be used inside a [[FilterCompoundInterface]], N/A cannot be used (limitation on [[FilterCompoundInterface]])
      // lElectricalFilter.setNaValueChecked(false);
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter (and it will be the first one)
      // lElectricalFilter.setFilterOperator(FilterOperator.FO_UNION);

      const lDateFilter : FilterRangeInterface = lDataSession.createFilterRange();
      // work on "date" metadata
      lDateFilter.setAttributeName('history.date');
      // create a range
      {
      const lDateRange : FilterRangeItemInterface = lDateFilter.createFilterRangeItem();
      // we want to include the lower bound in the query, but exclude the upper bound (it is the next day !!!)
      lDateRange.setIncludedLower(true);
      lDateRange.setIncludedUpper(false);
      const lExpectedDate : number = Date.UTC(2020, 0, 1, 0, 0, 0, 0);
      const lExpiredDate : number = lExpectedDate + 24 * 3600 * 1000;

      // range between the 1st of january and the end of the first of january
      lDateRange.setLowerBound(lExpectedDate);
      lDateRange.setUpperBound(lExpiredDate);
      }
      // intersection is the way to go since intersection of type electrical and instances with the given state at the given time
      lDateFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      // create the state filter
      const lStateFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to plugged (exactly)
      lStateFilter.setAttributeName('history.state');
      lStateFilter.setExactValues(['plugged']);
      // the filter will be used inside a [[FilterCompoundInterface]], N/A cannot be used (limitation on [[FilterCompoundInterface]])
      // lStateFilter.setNaValueChecked(false);
      // intersection is the way to go since intersection of type electrical and instances with the given state at the given time
      lStateFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      const lCompoundFilter : FilterCompoundInterface = lDataSession.createCompoundFilter();
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter
      // lCompoundFilter.setFilterOperator(FilterOperator.FO_UNION);

      // add the filters inside the FilterCompoundInterface
      // push back (-1) the electrical filter
      lCompoundFilter.insertFilter(-1, lElectricalFilter);
      // push back (-1) the date filter, as it is the second one, its operator is used and therefore
      // intersection is used
      lCompoundFilter.insertFilter(-1, lDateFilter);
      // push back (-1) the state filter, as it is the third one, its operator is used and therefore
      // intersection is used
      lCompoundFilter.insertFilter(-1, lStateFilter);

      // and add the compound filter
      lFilterSolver.insertFilter(-1, lCompoundFilter);

      // and tell the DataSessionInterface to update the modified FilterSolverInterfaces
      lDataSession.update();

      This results in [] inside the result of the getPartInstanceIds. This is not the expected result since there is no single metadata document inside the example that contains both type, history.state and history.date metadata. The type metadata is inherited from the hierarchy.

    2. Create the three required filters in a [FilterSolverInterface](interfaces/FilterSolverInterface.html) and put them in intersection :

      /**
      * Sample to illustrate :
      * Select electrical `part instances` which were plugged on the 01/01/2020.
      * Test : create three filters directly in a [[FilterSolverInterface]] and put them in intersection.
      */
      import {
      AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
      FilterRangeItemInterface, FilterSolverInterface, FilterAttributeInterface, FilterOperator, FilterRangeInterface,
      } from 'generated/documentation/appinfiniteapi';

      // the DataSessionInterface has been created previously and is connected
      let lDataSession : DataSessionInterface;

      // The Filter Solver has been created previously, its filtering context has been set
      let lFilterSolver : FilterSolverInterface;

      // MAKE SURE the attributes "type", "history.date" and "history.state" are relevant
      const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
      let lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('type');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('history.date');
      // make sure the attribute is a date one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_DATE));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('history.state');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      // create the electrical filter
      const lElectricalFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to electrical (exactly)
      lElectricalFilter.setAttributeName('type');
      lElectricalFilter.setExactValues(['electrical']);
      // warning : n/a is set to true as default, we do not want to include other `part instances`
      lElectricalFilter.setNaValueChecked(false);
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter (and it will be the first one)
      // lElectricalFilter.setFilterOperator(FilterOperator.FO_UNION);

      const lDateFilter : FilterRangeInterface = lDataSession.createFilterRange();
      // work on "date" metadata
      lDateFilter.setAttributeName('history.date');
      // create a range
      {
      const lDateRange : FilterRangeItemInterface = lDateFilter.createFilterRangeItem();
      // we want to include the lower bound in the query, but exclude the upper bound (it is the next day !!!)
      lDateRange.setIncludedLower(true);
      lDateRange.setIncludedUpper(false);
      const lExpectedDate : number = Date.UTC(2020, 0, 1, 0, 0, 0, 0);
      const lExpiredDate : number = lExpectedDate + 24 * 3600 * 1000;

      // range between the 1st of january and the end of the first of january
      lDateRange.setLowerBound(lExpectedDate);
      lDateRange.setUpperBound(lExpiredDate);
      }
      // intersection is the way to go since intersection of type electrical and instances with the given state at the given time
      lDateFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      // create the state filter
      const lStateFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to plugged (exactly)
      lStateFilter.setAttributeName('history.state');
      lStateFilter.setExactValues(['plugged']);
      /// warning : n/a is set to true as default, we do not want to include other `part instances`
      lStateFilter.setNaValueChecked(false);
      // intersection is the way to go since intersection of type electrical and instances with the given state at the given time
      lStateFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      // add the filters inside the FilterSolverInterface
      // push back (-1) the electrical filter
      lFilterSolver.insertFilter(-1, lElectricalFilter);
      // push back (-1) the date filter, as it is the second one, its operator is used and therefore
      // intersection is used
      lFilterSolver.insertFilter(-1, lDateFilter);
      // push back (-1) the state filter, as it is the third one, its operator is used and therefore
      // intersection is used
      lFilterSolver.insertFilter(-1, lStateFilter);

      // and tell the DataSessionInterface to update the modified FilterSolverInterfaces
      lDataSession.update();

      This results in [part instance 2, part instance 3] inside the result of the getPartInstanceIds. This is not the expected result. Indeed, the part instance 3 is selected since instance document 1 has history.state : plugged and history.date : 01/01/2020. Even if the two metadata are not present in the same time in a metadata array, they can be both found on the metadata document. The FilterCompoundInterface can make sure (if the required values are contained inside an array) that the required values are found together in the same item in an array.

    3. Create the "history.date" and "history.state" filters in a [FilterCompoundInterface](interfaces/FilterCompoundInterface.html), add it to a [FilterSolverInterface](interfaces/FilterSolverInterface.html) with a "type" filter:

      /**
      * Sample to illustrate :
      * Select electrical `part instances` which were plugged on the 01/01/2020.
      * Test : Create "history.date" and "history.state" filters in a [[FilterCompoundInterface]],
      * add to a [[FilterSolverInterface]] with a "type" filter.
      */
      import {
      AttributesDictionaryInterface, AttributeInfoInterface, AttributeType, DataSessionInterface,
      FilterCompoundInterface, FilterRangeItemInterface, FilterSolverInterface, FilterAttributeInterface, FilterOperator, FilterRangeInterface,
      } from 'generated/documentation/appinfiniteapi';

      // the DataSessionInterface has been created previously and is connected
      let lDataSession : DataSessionInterface;

      // The Filter Solver has been created previously, its filtering context has been set
      let lFilterSolver : FilterSolverInterface;

      // MAKE SURE the attributes "type", "history.date" and "history.state" are relevant
      const lAttributeDictionary : AttributesDictionaryInterface = lDataSession.getAttributesDictionary();
      let lAttributeInfo : AttributeInfoInterface | undefined = lAttributeDictionary.getAttributeInfo('type');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('history.date');
      // make sure the attribute is a date one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_DATE));

      lAttributeInfo = lAttributeDictionary.getAttributeInfo('history.state');
      // make sure the attribute is a string one
      console.assert((lAttributeInfo !== undefined) && (lAttributeInfo.getAttributeType() === AttributeType.ATTR_STRING));

      // create the electrical filter
      const lElectricalFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to electrical (exactly)
      lElectricalFilter.setAttributeName('type');
      lElectricalFilter.setExactValues(['electrical']);
      // warning : n/a is set to true as default, we do not want to include other `part instances`
      lElectricalFilter.setNaValueChecked(false);
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter (and it will be the first one)
      // lElectricalFilter.setFilterOperator(FilterOperator.FO_UNION);

      const lDateFilter : FilterRangeInterface = lDataSession.createFilterRange();
      // work on "date" metadata
      lDateFilter.setAttributeName('history.date');
      // create a range
      {
      const lDateRange : FilterRangeItemInterface = lDateFilter.createFilterRangeItem();
      // we want to include the lower bound in the query, but exclude the upper bound (it is the next day !!!)
      lDateRange.setIncludedLower(true);
      lDateRange.setIncludedUpper(false);
      const lExpectedDate : number = Date.UTC(2020, 0, 1, 0, 0, 0, 0);
      const lExpiredDate : number = lExpectedDate + 24 * 3600 * 1000;

      // range between the 1st of january and the end of the first of january
      lDateRange.setLowerBound(lExpectedDate);
      lDateRange.setUpperBound(lExpiredDate);
      }
      // useless, FilterOperator.FO_UNION is the default operator when creating a new filter
      // lDateFilter.setFilterOperator(FilterOperator.FO_UNION);

      // create the state filter
      const lStateFilter : FilterAttributeInterface = lDataSession.createFilterAttribute();
      // type set to plugged (exactly)
      lStateFilter.setAttributeName('history.state');
      lStateFilter.setExactValues(['plugged']);
      // the filter will be used inside a [[FilterCompoundInterface]], N/A cannot be used (limitation on [[FilterCompoundInterface]])
      // lStateFilter.setNaValueChecked(false);
      // intersection is the way to go since intersection of type electrical and instances with the given state at the given time
      lStateFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      const lCompoundFilter : FilterCompoundInterface = lDataSession.createCompoundFilter();
      // intersection is the way to go since intersection of type electrical and instances with the given state at the given time
      lCompoundFilter.setFilterOperator(FilterOperator.FO_INTERSECTION);

      // add the filters inside the FilterCompoundInterface
      // push back (-1) the date filter,
      lCompoundFilter.insertFilter(-1, lDateFilter);
      // push back (-1) the state filter, as it is the second one, its operator is used and therefore
      // intersection is used
      lCompoundFilter.insertFilter(-1, lStateFilter);

      // and add the electrical and compound filter
      // push back (-1) the electrical filter
      lFilterSolver.insertFilter(-1, lElectricalFilter);
      lFilterSolver.insertFilter(-1, lCompoundFilter);

      // and tell the DataSessionInterface to update the modified FilterSolverInterfaces
      lDataSession.update();

      This results in [part instance 2] inside the result of the getPartInstanceIds. This is the expected result.

3dJuump Infinite Literal and Search query language

The query language follows the following BNF representation :

scopedClause     =	scopedClause boolean searchClause
                 |	searchClause
                 ;

searchClause     =	( scopedClause )
                 |	index comparator searchTerm
                 |	searchTerm
                 ;

comparator       =	comparatorSymbol
                 |	namedComparator
                 ;

searchTerm       =	term
                 ;

index            =	term
                 ;

term             =	identifier
                 ;

namedComparator  =	identifier
                 ;


identifier       =	[^\s/>=<()"]+
                 |	".*"
                 ;

comparatorSymbol =	=
                 |	>
                 |	<
                 |	>=
                 |	<=
                 |	<>
                 |	==
                 ;

boolean          =	and
                 |	or
                 |	not
                 ;

In the examples below, the test metadata document is as follows :

{
"metadata": {
"Issue": "A",
"PartNumber": "Freins Brembo",
"subsystem": "Sous caisse",
"Input Format and Emitter": "CATIA V5R19 SP0 HF0",
"Name": "Freins Brembo",
"filename": "Base Roulante/Sous caisse/Freins AV_brembo.CATPart",
"system": "Base Roulante",
"Last update": "31/03/2011",
"Design Status": "WIP already Released (Up Issue)",
"Design Name": "Freins AV-brembo",
"Original Name": "Freins Brembo",
"UUID": "0913b9260000032c504741b80000b3ae",
"OriginalUnits": {
"Original mass unit (kg)": 1,
"Original length unit (m)": 0.0010000000000000005,
"Original time unit (s)": 1
},
"mass" : 2
},
"type": "partmetadata",
"id": "xxx"
}

The query syntax follows the general rules below :

  • A word is a sequence of non blank characters. The separator is any blank character.
  • The search values are not sensitive to the case.
  • Date values are expressed as the number of milliseconds since January 1, 1970, 00:00:00 UTC.
  • Warning : a query that follows the grammar is not necessarily a valid query.
  • Truth values used for boolean type attributes are true and false, case insensitive.
  • There are special characters :
    • " is used to group words together and to include spaces in the clause (see identifier above in the grammar). "Freins Brembo" searches in all the metadata documents the words Freins and Brembo.
    • \ is the escape character in order to make a special character look normal (search explicitly " => \").
    • * character is the wildcard (see Wildcard character). The syntax is not the one of regular expression (i.e. .*) but the one used for globing in file search command line tools. The wildcard represents any number of characters.
    • ? character represents exactly one arbitrary character.
    • $ character represents the start or end of a string. For example, :PartNumber = $Freins means to look for the metadata documents that have the field PartNumber that begins by Freins.
    • ( and ) are used to group clauses together and introduce operator precedence, for example (Freins or chassis) and issued tries to find all metadata documents that contains Freins or chassis in their content. Then, this set of metadata documents is reduced to get only metadata documents that contain also issued.
    • and, or, not are boolean binary operators. Warning : not is not an unary operator but a binary one (its meaning is actually "and not"). These operators serve to group clauses together.
    • : at the beginning of a word indicates a field in the metadata document : :PartNumber = Freins search for the word Freins in all the metadata documents that have the word Freins in the field PartNumber. The field is sensitive to the case. In order to search for values of the field Original length unit (m) : ":OriginalUnits.Original length unit (m)".
    • The operator =,>,<,>=,<=,<>,== are used to compare field content with values.
      • = is legal with string, number or date, or boolean metadata (not legal with date range or number range).
        • with string metadata, it is a loose equal comparator, it means the field contains. :PartNumber = Freins searches for the word Freins in all the metadata documents that have the metadata Freins in the field PartNumber.
        • In case of numeric metadata (date or number), that means the field value should be exactly the one provided.
        • In case of boolean metadata, that means the field value should be exactly the one provided, true or false.
      • >,>=,<,<= is legal with numeric and range (date, number, date range, number range) metadata. :mass > 2 : search all the metadata documents that have the metadata mass whose value is a number superior to 2.
      • <> is the not equal operator. It is legal with numeric (date, number) and boolean metadata. :mass <> 2 : search all the metadata documents that have the metadata mass whose value is not 2, but the field mass must be present in the metadata document.
      • == is the strict equal operator
        • with string metadata, the *, ? and $ cannot be used. The query is sensitive to the case and the field value should be exactly equal: :PartNumber == "Freins Brembo".
        • with other metadata, it is equivalent to the = operator : rules for = apply.

Some examples : The user wants to find metadata documents that contains :

  • Freins in the metadata document : "freins", the metadata document is matched.
  • Freins and caisse in the metadata document (not necessarily in the same field): "freins caisse" or "freins and caisse", the metadata document is matched.
  • Freins followed by Brembo in the same field (any field): "freins*brembo", the metadata document is matched.
  • Brembo followed by Freins in the same field (any field): brembo*freins, the metadata document is not matched.
  • Freins followed by Brembo with a single character in between in the same field (any field): freins?brembo, the metadata document is matched.
  • The metadata document whose PartNumber is exactly equal to Freins Brembo: :PartNumber == "Freins Brembo", the metadata document is matched.
  • The metadata documents whose PartNumber begins with Brembo: :PartNumber = $brembo, the metadata document is not matched.
  • The metadata documents whose PartNumber ends with Brembo: :PartNumber = brembo$, the metadata document is matched.

The 3dJuump Infinite Literal and Search query system works on metadata documents and not on the joined attribute set, a metadata document is matched if all the search clauses complies with this metadata document (and not on its hierarchy).

Search

A Search request is done through the {@link SearchInterface} interface. The search system operates on metadata documents (see Documents). The search result is therefore a list of metadata documents, their content and the resulting geometric instance ids (of all the metadata documents involved in the search result). The search query MUST be limited to a finite maximum number of metadata documents in order to monitor the bandwidth use. Also, the metadata content of the documents may be filtered to limit again the bandwidth use. The search query follows the 3dJuump Infinite Literal and Search query language. If the match query would get 80 metadata documents, but the request is limited to 50, the result will return the geometric instance ids from the part instances involved in the 80 metadata documents (and not the first 50 that are returned). The search request must be provided with a Filtering Context, but the user may choose to limit the results to the visibility context or the whole DMU (in that case the {@link SearchDocumentResultInterface.isInVisibilityCtx} function will tell if the given metadata document is included in a part instance in the visibility context).

Be a source metadata document :

{
"metadata": {
"Issue": "A",
"PartNumber": "Freins Brembo",
"subsystem": "Sous caisse",
"Input Format and Emitter": "CATIA V5R19 SP0 HF0",
"Name": "Freins Brembo",
"filename": "Base Roulante/Sous caisse/Freins AV_brembo.CATPart",
"system": "Base Roulante",
"Last update": "31/03/2011",
"Design Status": "WIP already Released (Up Issue)",
"Design Name": "Freins AV-brembo",
"Original Name": "Freins Brembo",
"UUID": "0913b9260000032c504741b80000b3ae",
"OriginalUnits": {
"Original mass unit (kg)": 1,
"Original length unit (m)": 0.0010000000000000005,
"Original time unit (s)": 1
},
"mass" : 2
},
"type": "partmetadata",
"id": "xxx"
}

Calling

    search("\"freins brembo\"",lSearchVisibilityCtx, false, 50, []);

will return the full document content.

Calling

    search("\"freins brembo\"",lSearchVisibilityCtx, false, 50, ["PartNumber"]);

will return the following document:

{
"metadata": {
"PartNumber": "Freins Brembo"
},
"type": "partmetadata",
"id": "xxx"
}

The search mechanism is as follows :

  • create a {@link SearchInterface} from {@link DataSessionInterface.createSearch}
  • connect the {@link SearchInterfaceSignal.SearchReady} to a custom callback
  • In this custom callback, convert the metadata document result to a list of part instances ids and geometric instance ids with a {@link DocumentIdConverterInterface}.
  • The search result also provides an "highlight" i.e. the context where the search query has matched.
/** 
* Sample to explain how to make search requests.
*/
import {
InfiniteEvent, SearchInterface, DocumentIdConverterInterface,
VisibilityContextInterface, SearchDocumentResultInterface, DocumentIdResultInterface, DocumentIdConverterResultInterface,
SearchInterfaceSignal, DocumentIdConverterInterfaceSignal, DataSessionInterface
} from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;
// the filtering context for the whole DMU as been set previously
let lCurrentDMUVisibilityCtx : VisibilityContextInterface;

// the filtering context for the search has been set previously (can be lCurrentDMUVisibilityCtx)
let lSearchVisibilityCtx : VisibilityContextInterface;
// the string to search in documents
let lQueryString : string;
// the max number of results
const lMaxDocsToRetrieve : number = 50;

// create a search requester
const lSearch : SearchInterface = lDataSession.createSearch();

// to get actual results from a search request :
// the search requester gets result in the form of documents,
// in a second step, the user may enquire the server to know the
// relevant `part instance ids`
// the DocumentIdConverterInterface is there to make the conversion between
// a document id and the actual `part instances`
const lDocumentIdConverter : DocumentIdConverterInterface = lDataSession.createDocumentIdConverter();

// When the search is ready, we check for errors
// we may find all the `geometric instance ids` in the search result
// we get the first found document
// and find the relevant `part instance ids` and `geometric instance ids`
const onSearchReady = (pEvent: InfiniteEvent) : void =>
{
const lSearchInterface: SearchInterface = <SearchInterface>(pEvent.emitter);
// make sure the search in no more running
if (lSearchInterface.isRunning()) {
console.assert(false, 'this should not happen');
return;
}
// and make sure no error
if (lSearchInterface.getLastError().length > 0) {
return;
}

// get all the `geometric instance ids` of the search request
const lGeometricInstanceIds: Uint32Array | undefined = lSearchInterface.getGeometricInstanceIds();
if (lGeometricInstanceIds !== undefined)
{
console.log('all the geometric instances concerned by the search:' + lGeometricInstanceIds.length);
}

// and get the documents (content is filtered by the last parameter of SearchInterface.search)
const lSearchResult: Array<SearchDocumentResultInterface> | undefined = lSearchInterface.getSearchDocuments();
if (lSearchResult) {
console.log('Search finished: ' + lSearchResult.length + ' result(s)');
// do we have all documents, or are there more that were not included due to the search results capped to lMaxDocsToRetrieve ?
if (lSearchInterface.hasOtherResults())
{
// some GUI code there perhaps ?
console.log('Search was capped to ' + lMaxDocsToRetrieve);
}
if (lSearchResult.length > 0) {
// get the `part instance ids` of the first document
const lDocumentId: number = lSearchResult[0].getDocumentId();
const lIsInVisibility : boolean = lSearchResult[0].isInVisibilityCtx();
let lVisibilityCtxToUse : VisibilityContextInterface;
if (lIsInVisibility)
{
console.log('retrieve document in visibility : ' + lDocumentId);
// we will try here to find instances only in the search filtering context
// but depending on your needs, you may use lCurrentDMUVisibilityCtx to extend the
// result to the `part instances` of the whole DMU, it is up to you
// or make 2 requests, one in the search ctx, the other outside, or ....
lVisibilityCtxToUse = lSearchVisibilityCtx;
}
else {
// this case cannot happen if the third parameter to search is true
console.log('retrieve document outside visibility : ' + lDocumentId);
// in order to get results, we will make the conversion in the whole DMU filtering context
// since this concerns `part instances` outside the search filtering context
lVisibilityCtxToUse = lCurrentDMUVisibilityCtx;
}
// find the `part instance ids` and `geometric instance ids` of this document
lDocumentIdConverter.convert(lDocumentId, lVisibilityCtxToUse);
}
}
};

// When the document `part instances` are found
// we get the relevant `part instance ids` and `geometric instance ids` of the requested document
const onDocumentIdConverterReady = (pEvent: InfiniteEvent) : void =>
{
const lEventDocumentIdConverter: DocumentIdConverterInterface = <DocumentIdConverterInterface>(pEvent.emitter);
// make sure the search in no more running
if (lEventDocumentIdConverter.isRunning()) {
console.assert(false, 'this should not happen');
return;
}
if (lEventDocumentIdConverter.getLastError().length > 0) {
return;
}
const lResult: DocumentIdConverterResultInterface | undefined = lEventDocumentIdConverter.getConversionResult();
if (lResult)
{
const lDocumentIdConverterInstances: Array<DocumentIdResultInterface> = lResult.getConvertedInstances();
const lPartInstanceIds: Uint32Array = new Uint32Array(lDocumentIdConverterInstances.length);

for (let i = 0; i < lDocumentIdConverterInstances.length; ++i) {
lPartInstanceIds[i] = lDocumentIdConverterInstances[i].getPartInstanceId();
}
console.log('documents converted :');
console.log('Convert part instances to geometric instance ids: ' + JSON.stringify(lPartInstanceIds));
}
};

// what to do when we get the results ?
lDocumentIdConverter.addEventListener(DocumentIdConverterInterfaceSignal.DocumentIdConverterReady, onDocumentIdConverterReady);
lSearch.addEventListener(SearchInterfaceSignal.SearchReady, onSearchReady);

// make a search request, and only retrieve the "PartNumber" metadata of the result documents
// we will get all the documents of the DMU (third parameter : false), but will get a hint of the
// documents inside lSearchVisibilityCtx
// imit the results to lMaxDocsToRetrieve documents maximum
lSearch.search(lQueryString, lSearchVisibilityCtx, false, lMaxDocsToRetrieve, ['PartNumber']);

or asynchronously :

/** 
* Sample to explain how to make asynchronous search requests.
*/
import {
SearchInterface, DocumentIdConverterInterface,
VisibilityContextInterface, SearchDocumentResultInterface, DocumentIdResultInterface,
AsyncSearchResult, AsyncResultReason, AsyncSearchResultContent, AsyncDocumentIdConverterResultInterfaceResult,
DataSessionInterface,
} from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;
// the filtering context for the whole DMU as been set previously
let lCurrentDMUVisibilityCtx : VisibilityContextInterface;

// the filtering context for the search has been set previously (can be lCurrentDMUVisibilityCtx)
let lSearchVisibilityCtx : VisibilityContextInterface;
// the string to search in documents
let lQueryString : string;
// the max number of results
const lMaxDocsToRetrieve : number = 50;

// create a search requester
const lSearch : SearchInterface = lDataSession.createSearch();

// to get actual results from a search request :
// the search requester gets result in the form of documents,
// in a second step, the user may enquire the server to know the
// relevant `part instance ids`
// the DocumentIdConverterInterface is there to make the conversion between
// a document id and the actual `part instances`
const lDocumentIdConverter : DocumentIdConverterInterface = lDataSession.createDocumentIdConverter();

const triggerSearchRequest = async () : Promise<any> =>
{
// make a search request, and only retrieve the "PartNumber" metadata of the result documents
// we will get all the documents of the DMU (third parameter : false), but will get a hint of the
// documents inside lSearchVisibilityCtx
// limit the results to lMaxDocsToRetrieve documents maximum
const lResult : AsyncSearchResult = await lSearch.asyncSearch(lQueryString, lSearchVisibilityCtx, false, lMaxDocsToRetrieve, ['PartNumber']);

if (lResult.reason !== AsyncResultReason.ARR_Success || lResult.value === undefined)
{
console.assert(false, 'this should not happen');
return;
}
// When the search is ready, we check for errors
// we may find all the `geometric instance ids` in the search result
// we get the first found document
// and find the relevant `part instance ids` and `geometric instance ids`

const lSearchContent : AsyncSearchResultContent = lResult.value;
// get all the `geometric instance ids` of the search request
const lGeometricInstanceIds: Uint32Array | undefined = lSearchContent.geometricInstanceIds;
if (lGeometricInstanceIds !== undefined)
{
console.log('all the geometric instances concerned by the search:' + lGeometricInstanceIds.length);
}

// and get the documents (content is filtered by the last parameter of SearchInterface.search)
const lSearchResult: Array<SearchDocumentResultInterface> | undefined = lSearchContent.searchDocuments;
if (!lSearchResult) {
console.assert(false, 'this should not happen');
return;
}
console.log('Search finished: ' + lSearchResult.length + ' result(s)');
// do we have all documents, or are there more that were not included due to the search results capped to lMaxDocsToRetrieve ?
if (lSearchContent.hasOtherResults)
{
// some GUI code there perhaps ?
console.log('Search was capped to ' + lMaxDocsToRetrieve);
}
if (lSearchResult.length <= 0) {
console.log('no document found');
return;
}
// get the `part instance ids` of the first document
const lDocumentId: number = lSearchResult[0].getDocumentId();
const lIsInVisibility : boolean = lSearchResult[0].isInVisibilityCtx();
let lVisibilityCtxToUse : VisibilityContextInterface;
if (lIsInVisibility)
{
console.log('retrieve document in visibility : ' + lDocumentId);
// we will try here to find instances only in the search filtering context
// but depending on your needs, you may use lCurrentDMUVisibilityCtx to extend the
// result to the `part instances` of the whole DMU, it is up to you
// or make 2 requests, one in the search ctx, the other outside, or ....
lVisibilityCtxToUse = lSearchVisibilityCtx;
}
else {
// this case cannot happen if the third parameter to search is true
console.log('retrieve document outside visibility : ' + lDocumentId);
// in order to get results, we will make the conversion in the whole DMU filtering context
// since this concerns `part instances` outside the search filtering context
lVisibilityCtxToUse = lCurrentDMUVisibilityCtx;
}
// find the `part instance ids` and `geometric instance ids` of this document
const lConversion : AsyncDocumentIdConverterResultInterfaceResult = await lDocumentIdConverter.asyncConvert(lDocumentId, lVisibilityCtxToUse);

// When the document `part instances` are found
// we get the relevant `part instance ids` and `geometric instance ids` of the requested document
if (lConversion.reason !== AsyncResultReason.ARR_Success || lConversion.value === undefined)
{
console.assert(false, 'this should not happen');
return;
}
const lDocumentIdConverterInstances: Array<DocumentIdResultInterface> = lConversion.value.getConvertedInstances();
const lPartInstanceIds: Uint32Array = new Uint32Array(lDocumentIdConverterInstances.length);

for (let i = 0; i < lDocumentIdConverterInstances.length; ++i) {
lPartInstanceIds[i] = lDocumentIdConverterInstances[i].getPartInstanceId();
}
console.log('documents converted :');
console.log('Convert part instances to geometric instance ids: ' + JSON.stringify(lPartInstanceIds));
}

triggerSearchRequest();

Annotations

The 3Djuump architecture allows to download and display 2D data representing measures, drawings (Functional Tolerancing and Annotations, Product Manufacturing Information, Measures, etc ...). These data are basically a 3D plane containing information in form of texts, curves, and symbols in this plane. Any data of this type is referred to an Annotation.
Annotations are only available from the DMU if your maintainer has included such data, future versions of the web API will allow creating custom annotations.

Annotations must be first downloaded, then displayed. Once displayed, they are affected an Annotation Id that allows them to be accessed, modified and queried. Each annotation has a display flag {@link AnnotationInstanceState} with a priority (to show an annotation on top of others regardless of their depth), a visible status (visible/hidden), and some display properties (show on top of geometries, highlighted, transparency if below geometries).

Annotations in the same plane and of the same meaning are grouped together in an Annotation View. An Annotation View features a display plane (position, orientation) and a number of Annotations. Annotation Views are accessed by an AnnotationView id. Annotation Views can be rotated, translated resulting in the transformation applied to all annotations contained within. Such a transformation is referred as the application matrix of the annotation view.

Retrieving annotations

Annotation views are available in an {@link AnnotationViewInfoInterface} from a {@link PartInstanceInfoInterface} by the {@link PartInstanceInfoInterface.getAnnotationViews} function ({@link PartInstanceInfoInterface} are obtained by an idcard request with an {@link IdCardGetterInterface}). The {@link AnnotationViewInfoInterface} allows to access the name of the Annotation view, and its type as defined by the DMU maintainer. All available annotation types are available through {@link DataSessionInterface.getAnnotationTypes}. A part instance with an annotation has the {@link PartInstanceInfoStatusFlag.PI_Annotated} flag ({@link PartInstanceInfoInterface.getAncestorsStatusFlags}).

Once obtained, the Annotation view data is downloaded by an {@link AnnotationGetterInterface} that provides the result as an {@link AnnotationResultInterface} that contains :

  • The matrix to set to the annotations view (application matrix)
  • The {@link AnnotationViewInterface} that will be displayed.

Therefore, the {@link AnnotationGetterInterface} retrieves the content of the annotation view but also the matrix that places the instance to the correct location. This matrix also sets the offsets (application matrix) to set to accurately display the annotation view.

/** 
* Sample to illustrate the use of an AnnotationGetterInterface to retrieve multiple annotation views.
*/
import {
DataSessionInterface, IdCardGetterInterface, IdCardGetterInterfaceSignal,
InfiniteEvent, PartInstanceInfoInterface, AnnotationViewInfoInterface, AnnotationGetterInterface, AnnotationGetterInterfaceSignal, AnnotationResultInterface, VisibilityContextInterface,
} from 'generated/documentation/appinfiniteapi';

// the DataSessionInterface has been created previously and is connected
let lDataSession : DataSessionInterface;
// created previously
let lIdCardGetterInterface : IdCardGetterInterface;
// created previously, the visibility context to use for the id card query
let lVisibilityContext : VisibilityContextInterface;
// the part instance id to fetch
let lPartInstanceId : number;

// to retrieve the annotations
const lAnnotationViewGetter : AnnotationGetterInterface = lDataSession.createAnnotationGetter();

// what to do when we have retrieved id card information ?
let onIdCardReady : (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) => void;

// what to do when annotation data has been downloaded ?
let onAnnotationViewDownloaded : (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) => void;

// what to do when id card is ready ?
lIdCardGetterInterface.addEventListener(IdCardGetterInterfaceSignal.IdCardReady, onIdCardReady);
// what to do when the download of the annotation view is ready ?
lAnnotationViewGetter.addEventListener(AnnotationGetterInterfaceSignal.AnnotationFetchReady, onAnnotationViewDownloaded);

// onIdCardReady will be called when idcard data is available
onIdCardReady = (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
if (lIdCardGetterInterface.getLastError().length !== 0) {
// do nothing in case of error
// perhaps some GUI code ?
}
const lPartInstanceInfos : Array<PartInstanceInfoInterface> | undefined = lIdCardGetterInterface.getPartInstanceInfos();
if (!lPartInstanceInfos || lPartInstanceInfos.length !== 1)
{
// no data (isCancelled ?)
return;
}
// get the first the instantiation chain
const lCurrentChain : PartInstanceInfoInterface = lPartInstanceInfos[0];
// the annotation views that will be fetched
const lAnnotationsToFetch : Array<AnnotationViewInfoInterface> = [];
// iterate over the annotation views to retrieve all views at once
const lAllAnnotationViews : Array<Array<AnnotationViewInfoInterface>> = lCurrentChain.getAnnotationViews();
// loops
let i : number;
let j : number;
// number of annotation views for this instance
let lNbAnnotationViews : number;
// number of ancestors
const lNbAncestors : number = lAllAnnotationViews.length;
let lAnnotationViewsOfPartInstance :Array<AnnotationViewInfoInterface>;
let lCurAnnotationView : AnnotationViewInfoInterface;
for (i = 0; i < lNbAncestors; ++i)
{
lAnnotationViewsOfPartInstance = lAllAnnotationViews[i];
lNbAnnotationViews = lAnnotationViewsOfPartInstance.length;
for (j = 0; j < lNbAnnotationViews; ++j)
{
lCurAnnotationView = lAnnotationViewsOfPartInstance[j];
// some fancy log
console.log('Will fetch annotation view ' + lCurAnnotationView.getAnnotationViewName() + ' of type ' + lCurAnnotationView.getAnnotationViewTypeName()
+ ' with ' + lCurAnnotationView.getAnnotationViewAnnotationCount() + ' annotations');
// add the annotation view to be fetched
lAnnotationsToFetch.push(lCurAnnotationView);
}
}
// and download
lAnnotationViewGetter.fetchAnnotationViews(lAnnotationsToFetch);
}

// what to do when annotation data has been downloaded ?
onAnnotationViewDownloaded = (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// annotation are ready
const lAnnotationViewsContent : Array<AnnotationResultInterface> | undefined = lAnnotationViewGetter.getAnnotationViewsResult();
if ((!lAnnotationViewsContent) || (lAnnotationViewsContent.length === 0))
{
console.log('error while fetching annotation content');
return;
}
// now annotation content is there, just need to display them
// use only the first view
const lCurAnnotationViewResult : AnnotationResultInterface = lAnnotationViewsContent[0];
console.log('display annotation view ' + lCurAnnotationViewResult.getAnnotationViewName() + ' (of type ' + lCurAnnotationViewResult.getAnnotationViewTypeName() + ')');
}

lIdCardGetterInterface.retrieveIdCard(lPartInstanceId, lVisibilityContext);

Annotations positioning

Remember that each element in the DMU can be instantiated any number of times, thus Annotation views are usually designed in CAD software for a component in the local frame of the object. Each object is then instantiated at a particular location of the DMU. In order to be accurate, an annotation view for such a part must also be translated and oriented by the same amount. Such a matrix is referred as the application matrix of the annotation view.

The user may query the 3D plane orientation / position with the 4 vectors :

  • normal : {@link AnnotationRendererInterface.getViewWorldPlaneXAxis} / {@link AnnotationRendererInterface.getAnnotationWorldPlaneXAxis}
  • X Axis (right vector of the annotation view (text is aligned upon this vector)) : {@link AnnotationRendererInterface.getViewWorldPlaneXAxis} / {@link AnnotationRendererInterface.getAnnotationWorldPlaneXAxis}
  • Y Axis (up vector of the annotation view) : {@link AnnotationRendererInterface.getViewWorldPlaneYAxis} / {@link AnnotationRendererInterface.getAnnotationWorldPlaneYAxis}
  • position (the annotations are positioned from this position) : {@link AnnotationRendererInterface.getViewWorldPlaneOrigin} / {@link AnnotationRendererInterface.getAnnotationWorldPlaneOrigin}

Or the user may query these information with a single call :

  • {@link AnnotationRendererInterface.getViewWorldMatrix} : that gives the 4 vectors in column [ XAxis YAxis Normal Position] (pay attention to the memory layout of a {@link Matrix4}).

The Annotation View world matrix is the resulting matrix that moves the annotations to the expected location, but keep in mind that each annotation view has first a local frame (not alterable, part of the annotation view definition), and an application matrix. The Annotation View world matrix is the composition of the two matrices, the Annotation View local matrix multiplied by the Annotation View application matrix such that :

World = Application x Local

/** 
* Sample to illustrate the concept of annotation view matrices.
*/
import { tAnnotationViewId, Matrix4, InfiniteEngineInterface, AnnotationRendererInterface } from 'generated/documentation/appinfiniteapi';

// retrieved previously
let lAnnotationViewId : tAnnotationViewId;
// created previously
let lInfiniteEngine : InfiniteEngineInterface;
// retrieve the annotation renderer
const lAnnotationRenderer : AnnotationRendererInterface = lInfiniteEngine.getAnnotationRenderer();

// get the local matrix from the annotation view
const lLocalMatrix : Matrix4 = new Matrix4();
lAnnotationRenderer.getViewLocalMatrix(lAnnotationViewId, lLocalMatrix);

// get the application matrix from the annotation view
const lApplicationMatrix : Matrix4 = new Matrix4();
lAnnotationRenderer.getViewApplicationMatrix(lAnnotationViewId, lApplicationMatrix);

// get the world matrix from the annotation view
const lWorldMatrix : Matrix4 = new Matrix4();
lAnnotationRenderer.getViewWorldMatrix(lAnnotationViewId, lWorldMatrix);

// to store the computation of lApplicationMatrix * lLocalMatrix
const lResultingMatrix : Matrix4 = new Matrix4();

// compute res = lApplicationMatrix * lLocalMatrix
lLocalMatrix.multiplyMatrixLeft(lApplicationMatrix, lResultingMatrix);

// make sure the result is ok
const lExpectedResult : boolean = lWorldMatrix.equals(lResultingMatrix);
console.assert(lExpectedResult, 'the two matrices should be equal');

Annotations rendering state

The annotation rendering state is an "Ored" value of multiple flags.

First, each annotation is assigned a depth priority. There is a guarantee that an annotation with an higher priority will always be drawn on top of annotations with lower priority, regardless of their relative depth on screen. When two annotations have the same priority, then the one with the lower depth will be drawn on top of the other.

Then, each annotation has a rendering flag, visible ({@link AnnotationInstanceState.AIS_Visible}), overprint ({@link AnnotationInstanceState.AIS_Overprint}), highlighted ({@link AnnotationInstanceState.AIS_Highlight}), and appear through ({@link AnnotationInstanceState.AIS_AppearThroughGeometry}).

Any annotation that has not the visible flag is hidden.
Any annotation with the {@link AnnotationInstanceState.AIS_Overprint} flag is drawn on top of geometries, even if it should have been occluded behind.
Any annotation with the {@link AnnotationInstanceState.AIS_Highlight} flag is drawn with an orange pulse color.
Any annotation with the {@link AnnotationInstanceState.AIS_AppearThroughGeometry} flag is drawn on transparency if it is behind a geometry.

annotation rendering flags effect

All annotation flags are queried and set at once, with the use of a mask and a value. Any bit outside the mask will not be altered. Any bit inside the mask will take the bit value of the new value.

/** 
* Sample to illustrate the displaying of annotations.
*/
import { InfiniteEngineInterface, AnnotationRendererInterface, tAnnotationId, AnnotationInstanceState } from 'generated/documentation/appinfiniteapi';

// created previously
let lInfiniteEngine : InfiniteEngineInterface;
// retrieve the annotation renderer
const lAnnotationRenderer : AnnotationRendererInterface = lInfiniteEngine.getAnnotationRenderer();
// the annotation id to set
let lAnnotationId : tAnnotationId;

// the mask for the annotation
let lRenderingMask : number;
// the value for the annotation
let lRenderingState : number;

{
// we want to change all rendering flags
lRenderingMask = AnnotationInstanceState.AIS_AppearThroughGeometry | AnnotationInstanceState.AIS_Highlight | AnnotationInstanceState.AIS_Overprint | AnnotationInstanceState.AIS_Visible;
// and set the annotation only visible
lRenderingState = AnnotationInstanceState.AIS_Visible;
lAnnotationRenderer.setAnnotationRenderingState([lAnnotationId], lRenderingMask, lRenderingState);
}

{
// we want to change onl visibility
lRenderingMask = AnnotationInstanceState.AIS_Visible;
// and set the annotation only hidden
lRenderingState = 0;
lAnnotationRenderer.setAnnotationRenderingState([lAnnotationId], lRenderingMask, lRenderingState);
}
// etc ...

Displaying annotations

Once annotation views content are retrieved, the {@link AnnotationViewInterface} still need to be included in the 3D rendering to be parsed, assigned ids and set visible. The {@link AnnotationRendererInterface} is responsible for displaying annotation views.
Just call {@link AnnotationRendererInterface.addAnnotationView} with the retrieved {@link AnnotationViewInterface} ({@link AnnotationResultInterface.getAnnotationView}), the correct matrix ({@link AnnotationResultInterface.getAnnotationViewMatrix}) and the appropriate rendering flags ({@link AnnotationInstanceState}) (priority, visible status, show on top of geometries, highlighted, transparency if below geometries)).

The {@link AnnotationRendererInterface.addAnnotationView} function is asynchronous, it returns a request id. When the parsing of the annotation view is over, then an {@link AnnotationRendererInterfaceSignal.AnnotationViewParsed} signal is fired, with an {@link AnnotationViewParsingResultInterface} as attachment. Each valid annotation view is assigned an AnnotationView id by the {@link AnnotationRendererInterface}, {@link AnnotationViewParsingResultInterface.annotationViewId}. Each valid annotation inside the annotation view is assigned an annotation id by the {@link AnnotationRendererInterface}, from {@link AnnotationViewParsingResultInterface.annotationIdStart} to {@link AnnotationViewParsingResultInterface.annotationIdStart} + {@link AnnotationViewParsingResultInterface.nbAnnotations}.

The {@link AnnotationRendererInterface} only works with AnnotationView ids and annotation ids.

Calling these methods will have the same output :

/** 
* Sample to illustrate the concept of annotation view ids and annotation ids.
*/
import { AnnotationRendererInterface, AnnotationViewParsingResultInterface, InfiniteEngineInterface, tAnnotationViewId, Vector3 } from 'generated/documentation/appinfiniteapi';

// created previously
let lInfiniteEngine : InfiniteEngineInterface;

// obtained previously
let lParsingResult : AnnotationViewParsingResultInterface;
// lParsingResult :
// {
// "annotationViewId": 1,
// "annotationIdStart": 1,
// "nbAnnotations": 3,
// "requestId" : 1,
// "errorMessage" : ""
// }

const lAnnotationRenderer : AnnotationRendererInterface = lInfiniteEngine.getAnnotationRenderer();
let lExpectedResult : boolean;

// testing the world plane origin of the annotation view
{
const lPlaneOriginView : Vector3 = new Vector3();
const lPlaneOriginAnnotation : Vector3 = new Vector3();

// same result for world plane origin
// get world plane origin from view
lExpectedResult = lAnnotationRenderer.getViewWorldPlaneOrigin(lParsingResult.annotationViewId, lPlaneOriginView);
console.assert(lExpectedResult, 'this call should be valid');
// get world plane origin from the starting annotation of the view
lExpectedResult = lAnnotationRenderer.getAnnotationWorldPlaneOrigin(lParsingResult.annotationIdStart, lPlaneOriginAnnotation);
console.assert(lExpectedResult, 'this call should be valid');
// the two vectors should be equal
console.assert(lPlaneOriginView.equals(lPlaneOriginAnnotation), 'the two vectors should be equal');

// get world plane origin from another annotation of the view (remember there are 3 annotations in this view)
lExpectedResult = lAnnotationRenderer.getAnnotationWorldPlaneOrigin(lParsingResult.annotationIdStart + 1, lPlaneOriginAnnotation);
console.assert(lExpectedResult, 'this call should be valid');
console.assert(lPlaneOriginView.equals(lPlaneOriginAnnotation), 'the two vectors should be equal');

// get world plane origin from an invalid annotation (warning, if there are another annotation view lParsingResult.annotationIdStart+lParsingResult.nbAnnotations
// may be the starting annotation id of another annotation view)
lExpectedResult = lAnnotationRenderer.getAnnotationWorldPlaneOrigin(lParsingResult.annotationIdStart + lParsingResult.nbAnnotations, lPlaneOriginAnnotation);
const lViewId : tAnnotationViewId = lAnnotationRenderer.getAnnotationViewId(lParsingResult.annotationIdStart + lParsingResult.nbAnnotations);
console.assert((!lExpectedResult) || (lViewId !== lParsingResult.annotationViewId), 'this call should not be valid');
}

// we can do exactly the same tests with :
// getViewWorldPlaneNormal / getAnnotationWorldPlaneNormal
// getViewWorldPlaneXAxis / getAnnotationWorldPlaneXAxis
// getViewWorldPlaneYAxis / getAnnotationWorldPlaneYAxis
// getViewApplicationMatrix / getAnnotationApplicationMatrix
// getViewWorldMatrix / getAnnotationWorldMatrix

And the final rendering is obtained through :

/** 
* Sample to illustrate the displaying of annotations.
*/
import {
AnnotationGetterInterface, AnnotationGetterInterfaceSignal, InfiniteEvent,
AnnotationResultInterface, Matrix4, InfiniteEngineInterface, AnnotationRendererInterface, AnnotationRendererInterfaceSignal,
tAnnotationViewId, AnnotationInstanceState, AnnotationViewParsingResultInterface,
} from 'generated/documentation/appinfiniteapi';

// created previously
let lAnnotationViewGetter : AnnotationGetterInterface;
// created previously
let lInfiniteEngine : InfiniteEngineInterface;
// retrieve the annotation renderer
const lAnnotationRenderer : AnnotationRendererInterface = lInfiniteEngine.getAnnotationRenderer();

// the annotation view id that has just been parsed
let lAnnotationViewId : tAnnotationViewId = -1;

// what to do when an annotation has been downloaded ?
let onAnnotationRetrieved : (pEvent: InfiniteEvent, pCallbackData: Object | undefined) => void;

// what to do when an annotation is ready to be displayed ?
let onAnnotationReady : (pEvent: InfiniteEvent, pCallbackData: Object | undefined) => void;

// connect to signals
lAnnotationViewGetter.addEventListener(AnnotationGetterInterfaceSignal.AnnotationFetchReady, onAnnotationRetrieved);
lAnnotationRenderer.addEventListener(AnnotationRendererInterfaceSignal.AnnotationViewParsed, onAnnotationReady);

// what to do when an annotation has been downloaded ?
onAnnotationRetrieved = (_pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// the result of the download
const lAnnotationViewsContent : Array<AnnotationResultInterface> | undefined = lAnnotationViewGetter.getAnnotationViewsResult();
if (!lAnnotationViewsContent || (lAnnotationViewsContent.length === 0))
{
console.log('Weird, annotation is downloaded but no content');
return;
}
if (lAnnotationViewsContent.length > 1)
{
console.log('Only displaying one annotation view at the moment, other annotation views will be discarded');
return;
}
// take the first annotation result, discarding others
const lAnnotationResult : AnnotationResultInterface = lAnnotationViewsContent[0];

// and add the annotation view to be displayed with standard depth
// we want to set the depth and visible state of the annotation
// mask is AnnotationInstanceState.AIS_DepthPriorityMask | AnnotationInstanceState.AIS_Visible
// but we want to be depth std and invisible
// state is AnnotationInstanceState.AIS_DepthPriorityStd | 0 = AnnotationInstanceState.AIS_DepthPriorityStd

const lMask : number = AnnotationInstanceState.AIS_DepthPriorityMask | AnnotationInstanceState.AIS_Visible;
const lState : number = AnnotationInstanceState.AIS_DepthPriorityStd;
const lRequestId : number = lAnnotationRenderer.addAnnotationView(lAnnotationResult.getAnnotationView(), lAnnotationResult.getAnnotationViewMatrix(), lMask, lState);
if (lRequestId < 0)
{
// weird error
console.log('The parsing procedure could not be started');
}
}

// what to do when an annotation is ready to be displayed ?
onAnnotationReady = (pEvent: InfiniteEvent, _pCallbackData: Object | undefined) : void =>
{
// fancy log
console.log('annotation view has finished parsing');

// take the parsing result
const lParsingResult : AnnotationViewParsingResultInterface = pEvent.attachments;
if (lParsingResult.annotationViewId < 0)
{
// this is an error
console.log('annotation view parsing has failed ' + lParsingResult.errorMessage);
return;
}

// store the annotation view id
lAnnotationViewId = lParsingResult.annotationViewId;

// create the list of annotation ids from lParsingResult.annotationIdStart to lParsingResult.annotationIdStart + lParsingResult.nbAnnotations - 1
const lAnnotationIds : number [] = [];
lAnnotationIds.length = lParsingResult.nbAnnotations;
let i : number;
for (i = 0; i < lParsingResult.nbAnnotations; ++i)
{
lAnnotationIds[i] = lParsingResult.annotationIdStart + i;
}
// mask for all rendering flags (except priority)
const lMask : number = AnnotationInstanceState.AIS_AppearThroughGeometry | AnnotationInstanceState.AIS_Highlight
| AnnotationInstanceState.AIS_Overprint | AnnotationInstanceState.AIS_Visible;
// state is visible (but we may add AnnotationInstanceState.AIS_AppearThroughGeometry just for fun)
const lState : number = AnnotationInstanceState.AIS_Visible; // | AnnotationInstanceState.AIS_AppearThroughGeometry

lAnnotationRenderer.setAnnotationRenderingState(lAnnotationIds, lMask, lState);
// you should now see the annotation on the next rendering
}

or asynchronously :

/** 
* Sample to illustrate the asynchronous displaying of annotations.
*/
import {
AnnotationGetterInterface,
AnnotationResultInterface, AnnotationViewInfoInterface, InfiniteEngineInterface, AnnotationRendererInterface,
tAnnotationViewId, AnnotationInstanceState, AnnotationViewParsingResultInterface, AsyncAnnotationResult, AsyncResultReason
} from 'generated/documentation/appinfiniteapi';

// created previously
let lAnnotationViewGetter : AnnotationGetterInterface;
// created previously
let lInfiniteEngine : InfiniteEngineInterface;
// retrieve the annotation renderer
const lAnnotationRenderer : AnnotationRendererInterface = lInfiniteEngine.getAnnotationRenderer();
// the annotation to fetch
let lAnnotationToFetch : AnnotationViewInfoInterface;

// the annotation view id that has just been parsed
let lAnnotationViewId : tAnnotationViewId = -1;

const displayResult = async () : Promise<any> =>
{
const lAsyncAnnotationResult : AsyncAnnotationResult = await lAnnotationViewGetter.asyncFetchAnnotationViews(lAnnotationToFetch);

// the result of the download
const lAnnotationViewsContent : Array<AnnotationResultInterface> | undefined = lAsyncAnnotationResult.value;
if (!lAnnotationViewsContent || (lAnnotationViewsContent.length === 0))
{
console.log('Weird, annotation download error');
return;
}
if (lAnnotationViewsContent.length > 1)
{
console.log('Weird, annotation is downloaded but too many results');
return;
}
// take the first annotation result, discarding others
const lAnnotationResult : AnnotationResultInterface = lAnnotationViewsContent[0];

// and add the annotation view to be displayed with standard depth
// we want to set the depth and visible state of the annotation
// mask is AnnotationInstanceState.AIS_DepthPriorityMask | AnnotationInstanceState.AIS_Visible
// but we want to be depth std and invisible
// state is AnnotationInstanceState.AIS_DepthPriorityStd | 0 = AnnotationInstanceState.AIS_DepthPriorityStd
let lMask : number = AnnotationInstanceState.AIS_DepthPriorityMask | AnnotationInstanceState.AIS_Visible;
let lState : number = AnnotationInstanceState.AIS_DepthPriorityStd;
const lAnnotationParsingResult = await lAnnotationRenderer.asyncAddAnnotationView(lAnnotationResult.getAnnotationView(), lAnnotationResult.getAnnotationViewMatrix(), lMask, lState);

if (lAnnotationParsingResult.reason !== AsyncResultReason.ARR_Success
|| lAnnotationParsingResult.value === undefined)
{
// weird error
console.log('The parsing procedure could not be started');
return;
}
// fancy log
console.log('annotation view has finished parsing');

// take the parsing result
const lParsingResult : AnnotationViewParsingResultInterface = lAnnotationParsingResult.value;
if (lParsingResult.annotationViewId < 0)
{
// this is an error
console.log('annotation view parsing has failed ' + lParsingResult.errorMessage);
return;
}

// store the annotation view id
lAnnotationViewId = lParsingResult.annotationViewId;

// create the list of annotation ids from lParsingResult.annotationIdStart to lParsingResult.annotationIdStart + lParsingResult.nbAnnotations - 1
const lAnnotationIds : number [] = [];
lAnnotationIds.length = lParsingResult.nbAnnotations;
let i : number;
for (i = 0; i < lParsingResult.nbAnnotations; ++i)
{
lAnnotationIds[i] = lParsingResult.annotationIdStart + i;
}
// mask for all rendering flags (except priority)
lMask = AnnotationInstanceState.AIS_AppearThroughGeometry | AnnotationInstanceState.AIS_Highlight
| AnnotationInstanceState.AIS_Overprint | AnnotationInstanceState.AIS_Visible;
// state is visible (but we may add AnnotationInstanceState.AIS_AppearThroughGeometry just for fun)
lState = AnnotationInstanceState.AIS_Visible; // | AnnotationInstanceState.AIS_AppearThroughGeometry

lAnnotationRenderer.setAnnotationRenderingState(lAnnotationIds, lMask, lState);
// you should now see the annotations on the next rendering
}

displayResult();

Fonts

The 3djuump javascript api also bundles a font EuclidFlexB-Regular.ttf, which is used as a default font. Annotations usually use different fonts (symbol fonts, arial, bold fonts, etc ...) but the {@link AnnotationRendererInterface} needs to have access to font definitions (curves, sizes, etc ...) which are not available as default in plain javascript apis. The {@link AnnotationRendererInterface} uses internally a library (opentype.js) that needs to have access to true type font files (usually files with .ttf extension). Such files must be registered onto the annotation rendering mechanism with the {@link FontLoaderInterface} before using {@link AnnotationRendererInterface.addAnnotationView}. As fonts are system wide, the {@link FontLoaderInterface} is a singleton (which is evil in normal cases) and is accessed through the {@link InfiniteFactory}.

/** 
* Sample to illustrate the use of an FontLoaderInterface to register fonts.
*/
import { FontLoaderInterface, InfiniteFactory } from 'generated/documentation/appinfiniteapi';

// access the FontLoaderInterface singleton from the InfiniteFactory
const lFontLoader : FontLoaderInterface = InfiniteFactory.GetInfiniteApiController().getFontLoader();

// register arial regular which is on the same site of the main page
// name bold ? italic ? url
lFontLoader.registerFont('arial', false, false, './resources/fonts/arial.ttf');

// register arial bold which is on the same site of the main page
// name bold ? italic ? url
lFontLoader.registerFont('arial', true, false, './resources/fonts/arialbd.ttf');

Picking

Picking is the action to determine which object(s) is/are at specific 2D positions inside a rendering area. This feature is handled with the {@link InfiniteEngineInterface} with the use of the {@link InfiniteEngineInterface.pickAt} and {@link InfiniteEngineInterface.pickRect} functions. Indeed, it is possible to get the :

  • geometries
  • points
  • lines
  • boxes
  • annotations

under an area (a point or a rectangle). The picking result is handled by the {@link PickingAttachment} interface, which stores for each picked object :

  • its id (geometric instance id/point id/line id/box id/annotation id)
  • its closest 3D position (a virtual ray is casted on the scene for each picking position, the resulting point is the closest point of the set of the intersection of these rays and the object)
  • the number of times this object is intersected (minimum one)
  • the id of the picking request (see {@link InfiniteEngineInterface.getLastPickingRequestId}).

The picking procedure depends on the elements that are rendered at the time of the picking, if an element is not rendered because it is behind a closer object, or for performance reasons (memory limit reached, too many triangles in the scene), then such an object will not be present in the pick result. For developers accustomed to 3D rendering, such a picking is not a frustum picking but a rendering based picking (not software but hardware based).

The Picking coordinate system follows the given convention (x,y):

picking convention
/** 
* Sample to illustrate the handling of a mouse pick in an InfiniteEngineInterface.
*/
import { InfiniteEngineInterface, InfiniteFactory, MetadataManagerInterface, PickingAttachment, PickingAttachmentItem, Vector3, VisualStates, InfiniteEngineInterfaceSignal, Rectangle, InfiniteEvent, PrimitiveManagerInterface } from 'generated/documentation/appinfiniteapi';

// created previously
let lMetadataManager : MetadataManagerInterface;
// the div to render to
let lView3D: HTMLElement;

// create an 3D engine
const lInfiniteEngine: InfiniteEngineInterface = InfiniteFactory.CreateInfiniteEngine(lMetadataManager);

// What to do on pick ?
let onButtonClicked : (pEvent) => void = undefined;

// What to do on pick ?
let onPicking : (pEvent : InfiniteEvent, _pCallbackData) => void = undefined;

// We pick a rectangle if big enough, else only a point
let pickAtImpl : () => void = undefined;

// bind the 3D rendering to the div
lInfiniteEngine.setView(lView3D);

// ####################################################################################################################################
// ############################################ PICKING ###############################################################################
// ####################################################################################################################################
// Used to copy the relevant portion of a MouseEvent
// position, previous buttons, current button and type
// store also if we started showing the rubber band
interface EventPos
{
x : number;
y : number;
buttons : number;
button : number;
type : string;
// is the rubber band initialized, i.e. have we begun to draw a rectangle ?
rubberInit : boolean;
}
// store the last click to know the extent of the rectangle to pick, and after pick request
// to know the properties of the pick request
// we store a copy of the MouseEvent
const lLastClickEvent: EventPos = {
x: 0,
y: 0,
buttons: 0,
button: 0,
type: '',
rubberInit: false,
};

// What to do on pick ?
onPicking = (pEvent : InfiniteEvent, _pCallbackData) : void =>
{
const lAttachment: PickingAttachment = <PickingAttachment>pEvent.attachments;
// care only about 3d geometries (no line, point, box)
const l3dAttachment: PickingAttachmentItem[] | undefined = lAttachment.geometric;

let lFirstGeometricId: number = 0;
let l3dPosition: Vector3 | undefined = undefined;
if (l3dAttachment !== undefined && l3dAttachment.length > 0)
{
lFirstGeometricId = l3dAttachment[0].instanceId;
l3dPosition = l3dAttachment[0].position;
}

// Mid button click => set Center Of Interest (COI)
if (lLastClickEvent.button === 1) {
if (!l3dPosition) return;
// set the center coi
lInfiniteEngine.getCameraManager().lookAt(l3dPosition);
return;
}

// no left button click => do nothing
if (lLastClickEvent.button !== 0)
{
return;
}
// if double click
if (lLastClickEvent.type === 'dblclick')
{
// fit to geometry
lInfiniteEngine.getCameraManager().fitGeometry(lFirstGeometricId);
return;
}
// clear selected state on pick
let lWasSelected: boolean = false;
if (lFirstGeometricId !== 0)
{
const lGeomState = lInfiniteEngine.getGeometricState(lFirstGeometricId);
lWasSelected = ((lGeomState & VisualStates.S_Selected) !== 0);
}
// if multiple selections => do not change the selected state of a single geometry
// but select all the relevant 3D data instead
if (l3dAttachment && l3dAttachment.length > 1)
{
lWasSelected = false;
}
// unselect everyone
lInfiniteEngine.updateGeometricStateForAll(VisualStates.S_Selected, ~VisualStates.S_Selected);
// and begin
if (lFirstGeometricId !== 0 && !lWasSelected && l3dAttachment)
{
// update visual state of selected items
const lIds = new Uint32Array(l3dAttachment.length);
for (let i = 0; i < l3dAttachment.length; ++i) {
lIds[i] = l3dAttachment[i].instanceId;
}
lInfiniteEngine.updateGeometricState(lIds, VisualStates.S_Selected, VisualStates.S_Selected);
}
};

// and bind the callback on pick result
lInfiniteEngine.addEventListener(InfiniteEngineInterfaceSignal.Picked, onPicking);

// the rectangle to pick (if relevant)
const lRubberBand: Rectangle = new Rectangle();

// We pick a rectangle if big enough, else only a point
pickAtImpl = (): void =>
{
// single pick if small rectangle
if (((lRubberBand.width <= 3) && (lRubberBand.height <= 3)) || (lRubberBand.width === 0) || (lRubberBand.height === 0))
{
// pick the last point
lInfiniteEngine.pickAt(lLastClickEvent.x, lLastClickEvent.y);
}
else
{
// pick an area
lInfiniteEngine.pickRect(lRubberBand);
}
// and clear the rectangle in all the cases
lRubberBand.clear();
}

onButtonClicked = (event) : void =>
{
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
lLastClickEvent.rubberInit = false;

if (lLastClickEvent.buttons === 0)
{
// make a pick
pickAtImpl();
}
}
// on left click => pick
lView3D.addEventListener('click', onButtonClicked);

const onMouseDown = (event) : void =>
{
// store the pick start
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
// no rubber
lLastClickEvent.rubberInit = false;
}
// store the position to the first pick pos
lView3D.addEventListener('mousedown', onMouseDown);

// when we release the mouse button, make a pick only on middle mouse if close to start pos
// and hide the rubber band
const onMouseUp = (event) : void =>
{
// middle click
if (lLastClickEvent.button === 1 && event.button === 1)
{
if (!lLastClickEvent.rubberInit) {
// if no rectangle => set COI
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
lLastClickEvent.rubberInit = false;
pickAtImpl();
}
}
const lPrimitiveManager : PrimitiveManagerInterface = lInfiniteEngine.getPrimitiveManager();
lPrimitiveManager.getRubberBandManager().setRubberBandVisible(false);
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
lLastClickEvent.rubberInit = false;
}

lView3D.addEventListener('mouseup', onMouseUp);

// we we move the mouse, display the rubber band
const onMouseMove = (event) : void =>
{
if (lLastClickEvent.rubberInit || (Math.abs(lLastClickEvent.x - event.offsetX) > 3 || Math.abs(lLastClickEvent.y - event.offsetY) > 3))
{
lRubberBand.x = Math.min(lLastClickEvent.x, event.offsetX);
lRubberBand.y = Math.min(lLastClickEvent.y, event.offsetY);
lRubberBand.width = Math.abs(lLastClickEvent.x - event.offsetX);
lRubberBand.height = Math.abs(lLastClickEvent.y - event.offsetY);
lLastClickEvent.rubberInit = true;
}
if ((lLastClickEvent.buttons === 1) && (event.buttons === 1) && lLastClickEvent.rubberInit)
{
// show/update the rubber band
const lPrimitiveManager: PrimitiveManagerInterface = lInfiniteEngine.getPrimitiveManager();
lPrimitiveManager.getRubberBandManager().setRubberBandRectangle(lRubberBand);
lPrimitiveManager.getRubberBandManager().setRubberBandVisible(true);
}
}
lView3D.addEventListener('mousemove', onMouseMove);

or with async calls :

/** 
* Sample to illustrate the handling of an asynchronous mouse pick in an InfiniteEngineInterface.
*/
import {
InfiniteEngineInterface, InfiniteFactory, MetadataManagerInterface, PickingAttachment, PickingAttachmentItem, Vector3, VisualStates, AsyncPickingResult,
Rectangle, AsyncResultReason, PrimitiveManagerInterface,
} from 'generated/documentation/appinfiniteapi';

// created previously
let lMetadataManager : MetadataManagerInterface;
// the div to render to
let lView3D: HTMLElement;

// create an 3D engine
const lInfiniteEngine: InfiniteEngineInterface = InfiniteFactory.CreateInfiniteEngine(lMetadataManager);

// What to do on pick ?
let onButtonClicked : (pEvent) => void = undefined;

// We pick a rectangle if big enough, else only a point
let pickAtImpl : () => void = undefined;

// bind the 3D rendering to the div
lInfiniteEngine.setView(lView3D);

// ####################################################################################################################################
// ############################################ PICKING ###############################################################################
// ####################################################################################################################################
// Used to copy the relevant portion of a MouseEvent
// position, previous buttons, current button and type
// store also if we started showing the rubber band
interface EventPos
{
x : number;
y : number;
buttons : number;
button : number;
type : string;
// is the rubber band initialized, i.e. have we begun to draw a rectangle ?
rubberInit : boolean;
}
// store the last click to know the extent of the rectangle to pick, and after pick request
// to know the properties of the pick request
// we store a copy of the MouseEvent
const lLastClickEvent: EventPos = {
x: 0,
y: 0,
buttons: 0,
button: 0,
type: '',
rubberInit: false,
};

// What to do on pick ?
const onPicking = (pAttachment: PickingAttachment) : void =>
{
// care only about 3d geometries (no line, point, box)
const l3dAttachment: PickingAttachmentItem[] | undefined = pAttachment.geometric;

let lFirstGeometricId: number = 0;
let l3dPosition: Vector3 | undefined = undefined;
if (l3dAttachment !== undefined && l3dAttachment.length > 0)
{
lFirstGeometricId = l3dAttachment[0].instanceId;
l3dPosition = l3dAttachment[0].position;
}

// Mid button click => set Center Of Interest (COI)
if (lLastClickEvent.button === 1) {
if (!l3dPosition) return;
// set the center coi
lInfiniteEngine.getCameraManager().lookAt(l3dPosition);
return;
}

// no left button click => do nothing
if (lLastClickEvent.button !== 0)
{
return;
}
// if double click
if (lLastClickEvent.type === 'dblclick')
{
// fit to geometry
lInfiniteEngine.getCameraManager().fitGeometry(lFirstGeometricId);
return;
}
// clear selected state on pick
let lWasSelected: boolean = false;
if (lFirstGeometricId !== 0)
{
const lGeomState = lInfiniteEngine.getGeometricState(lFirstGeometricId);
lWasSelected = ((lGeomState & VisualStates.S_Selected) !== 0);
}
// if multiple selections => do not change the selected state of a single geometry
// but select all the relevant 3D data instead
if (l3dAttachment && l3dAttachment.length > 1)
{
lWasSelected = false;
}
// unselect everyone
lInfiniteEngine.updateGeometricStateForAll(VisualStates.S_Selected, ~VisualStates.S_Selected);
// and begin
if (lFirstGeometricId !== 0 && !lWasSelected && l3dAttachment)
{
// update visual state of selected items
const lIds = new Uint32Array(l3dAttachment.length);
for (let i = 0; i < l3dAttachment.length; ++i) {
lIds[i] = l3dAttachment[i].instanceId;
}
lInfiniteEngine.updateGeometricState(lIds, VisualStates.S_Selected, VisualStates.S_Selected);
}
};

// the rectangle to pick (if relevant)
const lRubberBand: Rectangle = new Rectangle();

// We pick a rectangle if big enough, else only a point
pickAtImpl = async () : Promise<any> =>
{
let lPickingResult : AsyncPickingResult;
// single pick if small rectangle
if (((lRubberBand.width <= 3) && (lRubberBand.height <= 3)) || (lRubberBand.width === 0) || (lRubberBand.height === 0))
{
// pick the last point
lPickingResult = await lInfiniteEngine.asyncPickAt(lLastClickEvent.x, lLastClickEvent.y);
}
else
{
// pick an area
lPickingResult = await lInfiniteEngine.asyncPickRect(lRubberBand);
}
// and clear the rectangle in all the cases
lRubberBand.clear();
if (lPickingResult.reason !== AsyncResultReason.ARR_Success || lPickingResult.value === undefined)
{
return;
}
onPicking(lPickingResult.value);
}

onButtonClicked = (event) : void =>
{
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
lLastClickEvent.rubberInit = false;

if (lLastClickEvent.buttons === 0)
{
// make a pick
pickAtImpl();
}
}
// on left click => pick
lView3D.addEventListener('click', onButtonClicked);

const onMouseDown = (event) : void =>
{
// store the pick start
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
// no rubber
lLastClickEvent.rubberInit = false;
}
// store the position to the first pick pos
lView3D.addEventListener('mousedown', onMouseDown);

// when we release the mouse button, make a pick only on middle mouse if close to start pos
// and hide the rubber band
const onMouseUp = (event) : void =>
{
// middle click
if (lLastClickEvent.button === 1 && event.button === 1)
{
if (!lLastClickEvent.rubberInit) {
// if no rectangle => set COI
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
lLastClickEvent.rubberInit = false;
pickAtImpl();
}
}
const lPrimitiveManager : PrimitiveManagerInterface = lInfiniteEngine.getPrimitiveManager();
lPrimitiveManager.getRubberBandManager().setRubberBandVisible(false);
lLastClickEvent.x = event.offsetX;
lLastClickEvent.y = event.offsetY;
lLastClickEvent.button = event.button;
lLastClickEvent.buttons = event.buttons;
lLastClickEvent.type = event.type;
lLastClickEvent.rubberInit = false;
}

lView3D.addEventListener('mouseup', onMouseUp);

// we we move the mouse, display the rubber band
const onMouseMove = (event) : void =>
{
if (lLastClickEvent.rubberInit || (Math.abs(lLastClickEvent.x - event.offsetX) > 3 || Math.abs(lLastClickEvent.y - event.offsetY) > 3))
{
lRubberBand.x = Math.min(lLastClickEvent.x, event.offsetX);
lRubberBand.y = Math.min(lLastClickEvent.y, event.offsetY);
lRubberBand.width = Math.abs(lLastClickEvent.x - event.offsetX);
lRubberBand.height = Math.abs(lLastClickEvent.y - event.offsetY);
lLastClickEvent.rubberInit = true;
}
if ((lLastClickEvent.buttons === 1) && (event.buttons === 1) && lLastClickEvent.rubberInit)
{
// show/update the rubber band
const lPrimitiveManager: PrimitiveManagerInterface = lInfiniteEngine.getPrimitiveManager();
lPrimitiveManager.getRubberBandManager().setRubberBandRectangle(lRubberBand);
lPrimitiveManager.getRubberBandManager().setRubberBandVisible(true);
}
}
lView3D.addEventListener('mousemove', onMouseMove);

Use cases

This section points the developer to the relevant sections of the documentation.

You want to :

  • Display metadata information about a geometry : {@link IdCardGetterInterface} in the Data Retrievers module.
  • Display metadata information about the children of a geometry : {@link ChildrenIdCardGetterInterface} in the Data Retrievers module.
  • List the available metadata in the current DMU : {@link AttributesDictionaryInterface} in the Metadata module..
  • Create metadata criteria : see the interfaces extending {@link FilterItemInterface} in the Metadata/Filters group.
  • Get a list of part instances (part instance ids) from a combination of metadata criteria (union/intersection) : {@link FilterSolverInterface} in the Metadata module.
  • Convert part instance ids to geometric instance ids : {@link PartInstanceConverterInterface} in the Converters module.
  • Convert geometric instance ids to part instance ids depending on a filtering context : {@link GeometricInstanceConverterInterface} in the Converters module.
  • Perform a search request : {@link SearchInterface}, {@link DocumentIdConverterInterface} in the Data Retrievers and Converters module.
  • Change the way geometric instance ids are rendered : {@link InfiniteEngineInterface.updateGeometricState}, {@link MaterialManagerInterface} in the 3D Rendering module.
  • Display lines, boxes, points in the DMU : {@link PrimitiveManagerInterface} in the 3D Primitives module.
  • Get the geometry under the mouse cursor: see Picking.
  • Create/edit cut planes to hide some triangles : {@link CutPlaneManagerInterface}, {@link CutPlaneManipulatorInterface} in the 3D Rendering module.
  • Display annotations : {@link AnnotationGetterInterface}, {@link AnnotationResultInterface}, {@link AnnotationRendererInterface} in the Data Retrievers and 3D Rendering module.

Registering / Enabling your application

As with the configuration of the sample, your application url (redirect url) must be trusted from the directory in order to allow the authentication mechanism. Lets say your application is available though https://my_server/myapplication/index.html with a same page authentication mechanism, your 3djuump infinite administrator must create a new application onto the directory front-end (https://<directory>/directory/frontend), and register your url https://my_server/myapplication/index.html in the trusted list.

registering application
  • If your redirect url, if called without a hash, lead to your main application page
  • and if your favicon is available through :
    • your redirect url is a folder (e.g. https://my_server/myapplication) => https://my_server/myapplication/favicon.ico.
    • your redirect url is a file (e.g. https://my_server/myapplication/index.html) => https://my_server/myapplication/favicon.ico.

Then your application shortcut (along with its icon) will be available from https://<directory>/directory or https://<directory>/directory/home.

applications list

What's new

Versions log

3.3.11

  • Added {@link ChildrenPartInstanceInfoInterface.getSubLeafMinimums}, telling the minimum part instance id of the leaves that represent the node/leaf part instance of all the children.
  • Added {@link ChildrenPartInstanceInfoInterface.getSubLeafMaximums}, telling the maximum part instance id of the leaves that represent the node/leaf part instance of all the children.
  • Added {@link PartInstanceInfoInterface.getSubLeafMinimums}, telling the minimum part instance id of the leaves that represent the node/leaf part instance of the full hierarchy.
  • Added {@link PartInstanceInfoInterface.getSubLeafMaximums}, telling the maxi part instance id of the leaves that represent the node/leaf part instance of the full hierarchy.
  • Added {@link ChildrenPartInstanceInfoInterface.areChildrenLeaves}, telling if the children of a part are leaves.
  • Filters, {@link FilterSolverInterface}, {@link VisibilityContextInterface} and {@link ConfContextInterface} can now be created with an optional applicative id. It is the responsibility of the caller to ensure the uniqueness of ids. All ids may be changed after creation.
  • Removed deprecated code from samples.
  • Fixed Promise result when object was disposed and a promise call is done (the previous version was returning an undefined value).
  • Added {@link CameraManagerInterface.setCustomSettings} and {@link CameraManagerInterface.getCustomSettings} to change some more specific settings to camera mode (translation / rotation speed, field of view etc.).
  • Bug fixes / new features (use of keys) in the {@link CameraControllerMode.CCM_FLY}.
  • Changed default font in annotations, allowing common special symbols to be rendered even if the adequate font is not loaded by the application. This font is also mono spaced, allowing tables to be correctly spaced.
  • The starting animation should be now more visible (slowly center to DMU).
  • The first frame should be less subject to random patterns.
  • Some performance enhancement in the rendering.
  • Fixed a bug that lead to discarding HD data and reloading them shortly thereafter.
  • Added {@link UserData.id} the subid of the user.
  • Fixed a problem with the low definition DMU rendering.
  • Increased performance of the low definition DMU rendering.
  • Lowered CPU consumption when selection is changed.
  • Fixed a flickering bug when DMU was loaded before setting the view.
  • Fixed an infinite recursion on {@link DataSessionInterface.getProjectDocument} and {@link MetadataManagerInterface.getProjectDocument}.

3.3.10 13/10/2022

  • Added new Fly mode ({@link CameraControllerMode.CCM_FLY}) for camera user moves.
  • Added {@link CameraManagerInterface.enableNavigationCube}, {@link CameraManagerInterface.isNavigationCubeEnabled} to make the navigation cube not responsive to user inputs.
  • Added a way to get extended information from the infinite data session bearer (subid, tags, teams information etc). The {@link DirectorySessionInterface.getPopupBasedAuthenticationUrl} and {@link DirectorySessionInterface.getSamePageAuthenticationUrl} include a boolean to get extended information on the bearer. The extended bearer is then retrieved by the new function {@link DataSessionInterface.getExtendedDataSessionBearer}.
  • Added a new event {@link CameraManagerInterfaceSignal.CameraComputationEnded} sent when the camera position / orientation has been computed.
  • Added {@link InfiniteEngineInterface.pickFromRay} to pick elements from a 3D ray.
  • More documentation consistency : no more undocumented function should be included in the documentation.
  • Fixed a display bug when using multiple builds on the same project, some objects did not have the correct color.
  • Less window flickering when resizing the 3d display.
  • Screenshot requests are now available.
  • The rendering can be set with a fixed resolution (InfiniteEngineInterface.setFixedResolution).
  • npm server is available.
  • the api should now be loaded from the infinite servers (https://my_directory/directory/api/getwebapi?version=3.3).
  • MetadataManagerInterface.getDefaultCustomizationScript is now deprecated and is no more referenced in the documentation.
  • The api documentation is available through https://my_directory/directory/webapi.
  • Added asynchronous calls from the api (Promises).
  • Added {@link InfiniteObjectInterface} as top interface for most interfaces.
  • A lot of dispose functions have been added, allowing infinite objects to be garbage collected (no more references are held in the infinite api upon calling dispose).
  • A dispose signal has been added when an object dispose function is called.
  • Added missing signal cancelled on the {@link FilterSolverInterface}.
  • added missing sent signal ready on {@link FilterSolverInterface} after a cancelled is performed (e.g when the {@link ConfContextInterface} has changed).
  • added {@link InfiniteApiControllerInterface} for logging, fonts and browser detection.
  • {@link BrowserDetector} is now deprecated, browser detection system is included with bowser https://www.npmjs.com/package/bowser in the {@link InfiniteApiControllerInterface}.
  • Automatic session close system is added upon closing the tab.
  • Fixed {@link DataSessionInterface.isConnected} (bad return value when {@link DataSessionInterface.openDataSession} was not called)
  • Major changes in the signals ordering with conf contexts / filters / solvers / visibility, the object states are now updated before the signals are called.
  • Some api call inputs were not validated, some functions that returned void previously now return a boolean value telling the input is correct.
  • The converter and data retriever interfaces ({@link DocumentContentGetterInterface}, {@link IdCardGetterInterface}, ...) had an undefined behavior when a request is running and a new one with bad input was issued. The first call is now cancelled, and a cancelled and ready signals are sent.
  • Fixed a bug with {@link ConfContextInterface.setActiveConfs} modifying the object even if the input was the same as {@link ConfContextInterface.getActiveConfs} (the call now returns false if the value is not updated).
  • Converters and data retrievers (e.g. {@link AnnotationGetterInterface}, {@link GeometricInstanceConverterInterface}, ...) that take a {@link VisibilityContextInterface} as input are now cancelled when they are running while the {@link VisibilityContextInterface} is modified.
  • {@link IdCardGetterInterfaceSignal.IdCardReady}, {@link GeometricInstanceConverterInterfaceSignal.GeometricInstanceConverterReady}, {@link AnnotationGetterInterfaceSignal.AnnotationFetchReady} have now a string attachment with the id of the last call to their relevant call ({@link IdCardGetterInterface.retrieveIdCard}, ...) please refer to the relevant getLastRequestId call (e.g. {@link IdCardGetterInterface.getLastRequestId}, ...).
  • The {@link PickingAttachment} has now new properties pickingRequestId and the picking request infos, telling the id of the picking request that triggered the signal, please refer to {@link InfiniteEngineInterface.getLastPickingRequestId} and origin calls.
  • Multiple pick requests are now allowed concurrently, the last picking request do not cancel former unfinished pick requests any more.
  • Fixed a bug with primitives when previously removed primitives were not correctly cleaned up, leading to allowing removing them multiple times, thus leading to undefined behavior.
  • WebXR handled (please refer to {@link https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API}), with {@link InfiniteEngineInterface.setView}.

3.3.9 23/05/2022

  • No change.

3.3.8 11/05/2022

  • No change.

3.3.7 25/04/2002

  • No change.

3.3.6 04/04/2022

  • {@link DirectorySessionInterface.getApiUrl} was not returning the expected result (fixed).
  • Fixed {@link CameraManagerInterface.fitBox} when going to current camera position (camera position was set too far away).
  • Fixed NaN values set for number range attributes in the {@link AttributeInfoInterface} ({@link AttributeInfoInterface.getAttributeMin}, {@link AttributeInfoInterface.getAttributeMax}).
  • {@link InfiniteEngineInterface.setBackground} updated to allow setting the grid background without a color.
  • Added missing {@link InfiniteEngineInterface.getBackgroundColor}.
  • Fixed a bug when {@link DataSessionInterface.closeDataSession} is called during the loading phase.
  • Fixed a bug not closing session when the idle time was reached.

3.3.5 15/03/2022

  • fixed potential browser crashes when reloading pages running the infinite api.
  • some proxy choices were wrong when connecting a {@link DataSessionInterface}, leading to unexpected errors.
  • Less resources are now used if some parts are selected and the data has finished loading.

3.3.4 09/03/2022

  • added highlight information in the search result ({@link SearchDocumentResultInterface}).
  • fixed a bug when detailed data would not be displayed just after moving the camera.
  • fixed a bug telling resources were loaded (even if it is not the case) when {@link InfiniteEngineInterface.getResourceLoadingState} was called outside the main loop (i.e. outside of a callback registered with {@link MetadataManagerInterface.addCallback}).
  • some changes in the dispose/cleanup functions to make sure all references are removed, and the object can be garbage collected.
  • some changes in the dispose/cleanup functions to make sure no signal is sent during the cleanup phase.
  • Added {@link ChildrenIdCardGetterInterface} to get only the children of a part instance (now, you can retrieve either ancestors or children).
  • Fixed idcard validation scheme to trans-type data from string to the correct expected type ("10.5" => 10.5 if a number is expected).
  • Added ordered build (sorted from most recent to oldest) in {@link ConnectionData}.

3.3.3 22/02/2022

  • Increased annotations parsing performance.
  • Updated annotation api.
  • Annotations bug fixes.
  • Removed some useless queries being sent to backend.
  • Avoid browser authentication popup messages when data session is no longer valid.
  • Added {@link ObjectMetadataInterface} fields : {@link ObjectMetadataInterface.hasannotations} that tells if a given part instance has annotation, {@link ObjectMetadataInterface.nonindexedpartmd}, {@link ObjectMetadataInterface.nonindexedlinkmd} and {@link ObjectMetadataInterface.nonindexedinstancemd} to tell if some fields of the ObjectMetadataInterface cannot be indexed.

3.3.2 04/02/2022

  • Multiple annotation bug fixes (fixed gpu memory leaks, infinite loops, etc ...).
  • Fixed picking implementation not reflecting the documentation and api.
  • Fixed literal query bug with configured metadata.
  • Fixed {@link InfiniteEngineInterface.pickRay} bug.

3.3.1 12/01/2022

  • Fixed close requests not being sent to backend on browser close with new {@link DataSessionInterface.closeDataSession} api.
  • Fixed display bug when too many 3D primitives objects were created.
  • Fixed cut plane rendering trouble when too many cut planes were created.
  • Fixed some cut plan manipulator bugs.
  • Added 3D primitive visibility api.
  • Fixed some culling problems with camera for objects too close to the camera.
  • Added edge detection rendering.
  • Added math objects equality test api.
  • Multiple Annotations bug fixes.
  • The navigation cube now rotates around itself.
  • It is now possible to roll the camera.
  • Added idcard data validation and parsing to make sure date are provided as numbers if possible.
  • Fixed {@link InfiniteEngineInterface.projectToScreen} bug.

3.3.0 29/11/2021

  • First release

Bundle/Compilation changes

Please see Getting started.
The 3djuump javascript api file should be loaded from https://my_directory/directory/api/getwebapi?version=3.3. A npm server https://3djuump.com/npm/ is available to retrieve the api definition (.d.ts file).

InfiniteApiControllerInterface

The singleton {@link InfiniteApiControllerInterface} has been added for logging, fonts and browser detection (deprecating {@link BrowserDetector}).
The {@link InfiniteApiControllerInterface} is available through {@link InfiniteFactory.GetInfiniteApiController}.

Authentication

The authentication to a 3djuump infinite directory ({@link DirectorySessionFactory.CreateDirectorySession}) now uses a full URL string, instead of splitting the URL into hostname / port / path. The default URL for a directory is now https://mydirectory:443/directory. The former URL was https://mydirectory:443/directoryapi, split onto mydirectory, 443, directoryapi.

The 3djuump infinite architecture now allows an authentication inside an IFrame.

Ordered builds (from most recent to oldest) are now available in {@link ConnectionData}.

Rendering

The 3D graphics engine has been modified to allow better lighting, with optional edge detection ({@link InfiniteEngineInterface.enableEdgeDetect} {@link InfiniteEngineInterface.isEdgeDetectEnabled}). The back-face culling is now fully handled by the DMU maintainer, without any way for the user/developer to override the settings in the DMU. {@link InfiniteEngineInterface.setBackfaceCullingEnabled} is now a no-op and has been deprecated.

The rendering window is now dpi aware, handles better the zoom set by the browser in order to have a finer and sharper image (high dpi handling). Warning, using the 3D engine on such high screen monitors may hinder performance, you can still limit the rendering size with {@link InfiniteEngineInterface.setMaximumEngineResolution}.

You may also change the material of all instances with {@link MaterialManagerInterface.changeMaterialOfAllInstances} and restore them with {@link MaterialManagerInterface.restoreOriginalMaterialOfAllInstances} (missing in previous versions).

Asynchronous screenshots are available with {@link InfiniteEngineInterface.screenshot}.

The rendering size (independent from the size of the view of the 3D display) can be set with {@link InfiniteEngineInterface.setFixedResolution}, in this case the aspect ratio of the fixed resolution is preserved, all rendering is done with the included resolution, and the final image is scaled to fit in the area of the view set {@link InfiniteEngineInterface.setView}. The 3D display is centered in the area of the view.

Annotations

The 3Djuump architecture now allows to download and display 2D data representing measures, drawings (Functional Tolerancing and Annotations, Product Manufacturing Information, Measures, etc ...). These data are basically a 3D plane containing information in form of texts, curves, and symbols in this plane. Any data of this type is referred to an Annotation.

The 3Djuump web api can now query if a part instance contains (an) annotation(s), download such data, and asks the renderer to display such annotation(s). User created Annotations will be available in future versions of the web api.

Fonts files (True Type Fonts) can/must now be registered onto the {@link FontLoaderInterface} in order to display accurate text on annotations. If the required font is missing, then a default font is used (Euclid FlexB regular).

Class IDs

The 3Djuump web api wan now query part instances class ids. Two part instances share the same class ids if they have the same graphical representation (same geometries at the same location). This is particularly true for 2 part instances leaves that have the same geometric instance id, but also for part instances nodes. The class ids allow to create hierarchy branch viewers.

See below :

class ids example

Primitive change visibility

Each 3D Primitive element (Line, Box, Point) can now be set visible/hidden ({@link PrimitiveManagerBoxInterface.setBoxVisible},{@link PrimitiveManagerLineInterface.setLineVisible}, {@link PrimitiveManagerPointInterface.setPointVisible}).

Data session close

Data session close queries were not sent on browser quit or on page reload.
Automatic session close system is added upon closing the tab.

Signals

Any {@link EventDispatcherInterface} can now remove all listeners with {@link EventDispatcherInterface.removeAllEventListeners}. The {@link FilterSetInterface} and {@link FilterCompoundInterface} now send a {@link FilterItemInterfaceSignal.FilterDataChanged} signal on add,remove, removeAll and move operations on their sub filters.
A single signal is sent during the cleanup phase (dispose/remove functions) with {@link InfiniteObjectDispatcherInterfaceSignal.ObjectDisposed}, with the exceptions of {@link DirectorySessionInterface} and {@link DataSessionInterface} that still send their former signals.

Asynchronous calls

A lot of asynchronous calls have been added to use the javascript promises.

Mathematical objects

All mathematical objects have an equals function to compare two instances.

Camera

The current position can now be saved/restored with {@link CameraManagerInterface.fromJson} / {@link CameraManagerInterface.toJson}.

Rolling was forbidden in previous versions, you may deactivate this constraint with {@link CameraManagerInterface.setConstraintUpAxis}.

You may now zoom to a 2D region with {@link CameraManagerInterface.zoomToScreenRegion}.

DocumentIdConverterInterface

The {@link DocumentIdConverterInterface.convert} function may also retrieve {@link PartInstanceInfoStatusFlag} along with the corresponding part instance ids ({@link DocumentIdResultInterface.getPartInstanceFlag}).

Id Card

A new object, the {@link ChildrenIdCardGetterInterface} (very similar to the {@link IdCardGetterInterface}), can retrieve the id cards of the children of a part instance.

Search

Added highlight information telling the context in which the search query was matched.

FilterSolverInterface

When a {@link FilterSolverInterface} is cancelled due to a change, a {@link FilterSolverInterfaceSignal.SolverCancelled} signal is now sent.
A {@link FilterSolverInterfaceSignal.SolverReady} signal on {@link FilterSolverInterface} is triggered after a cancelled is performed (e.g when the {@link ConfContextInterface} has changed) with invalid data.

Bug fixes

Some useless requests were sent to the 3djuump Infinite architecture, this has been fixed.

After a large idle period (period st by the setting of the 3dJuump Infinite directory), the data sessions are closed, but some requests were triggering an authentication dialog from the browser. This is no longer the case.

Detailed data would not be displayed just after moving the camera, this has been fixed.

Fixed a bug telling resources were loaded (even if it is not the case) with {@link InfiniteEngineInterface.getResourceLoadingState}).

WebXR (experimental)

Partial support for WebXR is provided.

Please refer to {@link https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API}.

/** 
* Sample to illustrate using a webxr session.
*/
import { InfiniteEngineInterface, InfiniteEngineInterfaceSignal, InfiniteEvent, InfiniteXRSession, CameraManagerInterfaceSignal, InfiniteXRReferenceSpace } from 'generated/documentation/appinfiniteapi';

// We try to make a screenshot when all data is loaded

// created previously the 3D engine
let lInfiniteEngine: InfiniteEngineInterface;
// the webXR session
let lCurSession : InfiniteXRSession;

// store the XRSession when ready
lInfiniteEngine.addEventListener(
InfiniteEngineInterfaceSignal.XRSessionReady,
(pEvent: InfiniteEvent) : void =>
{
lCurSession = pEvent.attachments;
}
);

// make some fancy things on camera changes
// we may have used InfiniteEngineInterfaceSignal.DisplayDone
// to be called each frame
lInfiniteEngine.getCameraManager().addEventListener(
CameraManagerInterfaceSignal.CameraLocationChanged,
(_pEvent: InfiniteEvent) : void =>
{
if (!lCurSession)
{
return;
}
const lXRRefSpace : InfiniteXRReferenceSpace | undefined = lInfiniteEngine.getCameraManager().getXRReferenceFrame();
if (!lXRRefSpace)
{
return;
}
// make some computations with XRSession inputs
console.log(lCurSession.inputSources.length);
}
);

// bind the 3D rendering to the div (should have been done earlier :) )
lInfiniteEngine.setView(undefined, { type: 'webxr', options: {} });

License

The 3djuump javascript api now uses three libraries (bundled in the infiniteapi3.3.js) : opentype.js, luxon and bowser. Their respective license can be found on top of the header of the packaged files and at the bottom of this document. The 3djuump javascript api also bundles a font EuclidFlexB-Regular.ttf, which is used as a default font for rendering texts in annotations.

Migrating from 3.2/3.1 versions

This section lists the main changes to allow the migration to the current version.

Please keep in mind that each version of the api comes with the retro-compatibility code that allows previous code to still work on the updated 3dJuump infrastructure (infiniteapi3.3-compatibility3.1.js, infiniteapi3.3-compatibility3.2.js).

It is strongly advised to switch to the new javascript api loading scheme by requesting the 3djuump api from https://my_directory/directory/api/getwebapi?version=3.2 if you are using api version 3.2, or https://my_directory/directory/api/getwebapi?version=3.1 if you are using api version 3.1.

References to 3djuump infinite api calls should be set as external calls (in order not to bundle the 3djuump api in your final javascript api file), and reference those calls to the use of the infiniteapi (Please see Getting started).

Old code should still work but without any possibility to use the new features, just replace the call

    DirectorySessionFactory.CreateDirectorySession(lMetadataManager, sDirectoryHost, 'directoryapi',443);

with

    let lUrl : string = "https://"+sDirectoryHost+":443/directory";
DirectorySessionFactory.CreateDirectorySession(lMetadataManager, lUrl);

It is highly recommended to update your code base to benefit from the new features.

Migrating to the new web api should take less than an hour.

Connection

Former version used the given URL : https://my_directory:443/directoryapi split into my_directory, 443 and directoryapi. The new web api takes a full URL : https://my_directory:443/directory, and /directory is now the default location to bind the web api (replacing directoryapi).

From :

/** 
* Sample to illustrate the changes from 3.2 to 3.3 to the main objects creation.
*/
import { MetadataManagerFactory, DirectorySessionFactory, DirectorySessionInterface, MetadataManagerInterface } from 'generated/documentation/appinfiniteapi';

const sDirectoryHost : string = 'mydirectory';
const lMetadataManager: MetadataManagerInterface = MetadataManagerFactory.CreateMetadataManager();
const lDirectorySession: DirectorySessionInterface = DirectorySessionFactory.CreateDirectorySession(lMetadataManager, sDirectoryHost, 'directoryapi', 443);

To :

/** 
* Sample to illustrate the changes from 3.2 to 3.3 to the main objects creation.
*/
import { MetadataManagerFactory, DirectorySessionFactory, DirectorySessionInterface, MetadataManagerInterface } from 'generated/documentation/appinfiniteapi';

const sDirectoryUrl : string = 'https://mydirectory:443/directory';
const lMetadataManager: MetadataManagerInterface = MetadataManagerFactory.CreateMetadataManager();
const lDirectorySession: DirectorySessionInterface = DirectorySessionFactory.CreateDirectorySession(lMetadataManager, sDirectoryUrl);

Upon successful connection, the ConnectionData object has still the tags property, but it has been deprecated and is now always empty.

Signals

The FilterSetInterface and FilterCompoundInterface now send a FilterDataChanged signal on add,remove, removeAll and move operations on their sub filters.

ObjectMetadataInterface

When retrieving an id card, attributes of type Date were present as a string (with no way to know how to parse them). Now every attribute of type ATTR_DATE or ATTR_DATE_RANGE is now expressed as a number (number of milliseconds since January 1, 1970, 00:00:00 UTC). All metadata in the ObjectMetadataInterface will now be trans-typed to their expected type (and left as is if the trans-typing failed).

Known bugs

None to date 😀.

License

This program belongs to AKKODIS INGENIERIE PRODUIT SAS.
It is considered a trade secret, and is not to be divulged or used
by parties who have not received written authorization from the owner.


This package includes the opentype.js library, licensed under the MIT license terms. This package includes the bowser library, licensed under the MIT license terms. This package includes the ajv library (https://ajv.js.org/), licensed under the MIT license terms.

The MIT License (MIT)
Copyright (c) 2017 Frederik De Bleser
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


This package includes the luxon library, licensed under the JS Foundation license.

Copyright 2019 JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.