A TypeScript/JavaScript API to use the 3dJuump Infinite technology.
This documentation describes the use of the TypeScript/JavaScript 3dJuump Infinite API.
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 :
Observer pattern. Many objects send signals when changes occur, the different events and their content is explained in this module.Metadata module includes all the different types of filter that may be used to create sets of part instances.part instances.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).
You will need the following :
https://my_directory:443/directory. The api documentation is available through https://my_directory:443/directory/webapi.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.authenticate.html). The 3dJuump JavaScript web API is shipped with a code example.
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.
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")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).The sample will be available from your url : http://your_server/infinite_api/samples/v3_3/index.html.
You will have to :
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).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 listS : 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.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.
/**
* 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();
/**
* 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;
}
}
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);
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 :
wheeltire with link wheel_tire-1rim with link wheel_rim-1hub with link rim_hub-1bolt with link hub_bolt-genericbolt with link hub_bolt-genericbolt with link hub_bolt-genericbolt with link hub_bolt-customEach 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 :
wheel - tire - rim - hub - bolt at position X1,Y1,Z1 has the instance name bolt_1.wheel - tire - rim - hub - bolt at position X2,Y2,Z2 has the instance name bolt_2.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 :
part metadata document (data shared by all instances of this element).part).As such, the bolts of the given DMU have the given documents
part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-1part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-2part_metadata_bolt, link_metadata_hub_bolt-generic, instance_metadata_bolt-3part_metadata_bolt, link_metadata_hub_bolt-customEach 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.
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.
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 :
has a geometric instance id.
Each part instance that :
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.
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 :
part instances of your DMU in a given 3D cube that has the field CompletionStatus equal to done.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.
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 thesearch attribute setis exactly the value of the attribute).
or setContainsValues : contains at least one of the values (full text search) of thesearch 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 isenumerable. If an attribute isenumerable, then the special valueN/A(not applicable) is available and selected as default (hasNaValueAvailable, setNaValueActivated, setNaValueChecked). The special valueN/Aselects all otherpart instancesthan the union of all possible values of such a string attribute. Due to this special handling and implementation details, using theN/Avalue is not compatible with using the setContainsValues function. Any FilterAttributeInterface on anenumerableattribute has setNaValueActivated set totrue, if you want to use the setContainsValues function, you will have to make asetNaValueActivated(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.
Warning : the maximum supported depth (i.e. nested FilterSetInterface) is 8.
joined attribute set, this filter works on
the metadata documents attached to the part instance individually (see Documents).part instance is selected. As the filtering works on metadata documents, calling setInverted on a filter inside a FilterCompoundInterface only inverts the filtering statement, and does not select all other part instances that do not match the given filtering statement.In the following examples, the metadata assembly is as follows (dates are expressed as day/month/year):
In all the examples, the FilterSolverInterface and the filtering context creation are not explained, as it is explained in the next section : Filtering Context.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
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 :
true and false, case insensitive." 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)".=,>,<,>=,<=,<>,== 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).field contains. :PartNumber = Freins searches for the word Freins in all the metadata documents that have the metadata Freins in the field PartNumber. 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*, ? and $ cannot be used. The query is sensitive to the case and the field value should be exactly equal: :PartNumber == "Freins Brembo".= 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.PartNumber is exactly equal to Freins Brembo: :PartNumber == "Freins Brembo", the metadata document is matched.PartNumber begins with Brembo: :PartNumber = $brembo, the metadata document is not matched.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).
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 :
part instances ids and geometric instance ids with a {@link DocumentIdConverterInterface}./**
* 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();
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.
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 :
application matrix)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);
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 :
Or the user may query these information with a single call :
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');
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.
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 ...
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();
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 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 :
under an area (a point or a rectangle). The picking result is handled by the {@link PickingAttachment} interface, which stores for each picked object :
geometric instance id/point id/line id/box id/annotation id)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):
/**
* 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);
This section points the developer to the relevant sections of the documentation.
You want to :
Data Retrievers module.Data Retrievers module.Metadata module..Metadata/Filters group.part instances (part instance ids) from a combination of metadata criteria (union/intersection) : {@link FilterSolverInterface} in the Metadata module.part instance ids to geometric instance ids : {@link PartInstanceConverterInterface} in the Converters module.geometric instance ids to part instance ids depending on a filtering context : {@link GeometricInstanceConverterInterface} in the Converters module.Data Retrievers and Converters module.geometric instance ids are rendered : {@link InfiniteEngineInterface.updateGeometricState}, {@link MaterialManagerInterface} in the 3D Rendering module.3D Primitives module. 3D Rendering module.Data Retrievers and 3D Rendering module.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.
https://my_server/myapplication) => https://my_server/myapplication/favicon.ico.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.
3.3.11
3.3.10 13/10/2022
Fly mode ({@link CameraControllerMode.CCM_FLY}) for camera user moves.https://my_directory/directory/webapi.3.3.9 23/05/2022
3.3.8 11/05/2022
3.3.7 25/04/2002
3.3.6 04/04/2022
3.3.5 15/03/2022
3.3.4 09/03/2022
"10.5" => 10.5 if a number is expected).3.3.3 22/02/2022
3.3.2 04/02/2022
3.3.1 12/01/2022
3.3.0 29/11/2021
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).
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}.
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}.
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.
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).
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 :
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 queries were not sent on browser quit or on page reload.
Automatic session close system is added upon closing the tab.
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.
A lot of asynchronous calls have been added to use the javascript promises.
All mathematical objects have an equals function to compare two instances.
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}.
The {@link DocumentIdConverterInterface.convert} function may also retrieve {@link PartInstanceInfoStatusFlag} along with the corresponding part instance ids
({@link DocumentIdResultInterface.getPartInstanceFlag}).
A new object, the {@link ChildrenIdCardGetterInterface} (very similar to the {@link IdCardGetterInterface}), can retrieve the
id cards of the children of a part instance.
Added highlight information telling the context in which the search query was matched.
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.
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}).
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: {} });
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.
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.
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.
The FilterSetInterface and FilterCompoundInterface now send a FilterDataChanged signal on add,remove, removeAll and move operations on their sub filters.
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).
None to date 😀.
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.