cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Vuforia Studio and Chalk Tech Tips

Sort by:
To ensure your Chalk experience is the best, make sure to familiarize yourself with the below best practices.   Initialization Keep movement fluid and slow Forward and backward smooth motion is best to allow device to create mapping Small circles in front of the object are also good Note: Do not rotate your device - keep the device's orientation fixed, moving it parallel to the object of interest and keeping the latter in view during initialization movement Environment It is important for the environment to have a lot of saliency, interesting features, & textures e.g. Stickers, buttons, cables, images/designs, shapes with corners, etc Stationary objects are best for Chalking Reflective, plain colored, or blank surfaces are not good for using Chalk Marks Well-lit areas are best for Chalk performance If an environment is too dark the device's camera will not be able to detect objects External light may be needed if the environment is too dark Either user can toggle the flash on Network/Bandwidth Low bandwidth will result in poor video quality Ensure that you have good bandwidth Chalk Marks Use simple drawings to communicate instructions Circles, lines, & arrows work best Delete Chalk Marks that are no longer needed to reduce clutter Use the pause button to draw on a steady image  
View full tip
  This example briefly describes how you can use the Step names that you used in Creo Illustrate sequence definitions to drive a corresponding step instruction/description in your experience. This is an unsupported, preliminary solution - R&D is working on a better, final solution. But as long as this is not available, you can use this one for PoC and demo purposes. To setup the scene: Here is what I meant with Step names that you used in Creo Illustrate: Now in Thingworx Studio you want to have the following result: The text is rendered with a simple Label widget. You'll have to remember the ID of this widget for the following javascript tweak. Add the following text to the Javascript section of your View: var labelId = "label-1"; // ID of the Label widget that displays the Step progress and description  text // this $on event handler switches the label based on the the sequence definition // the arg variable is of the following form: (<step #>/<total step #) <step name> $scope.$on('newStep', function(evt, arg) {   $scope.setWidgetProp( labelId, "text", arg); // get the currentStep from the arg }); Now you only need to provide the correct initial value in the Label widget text property and add control widgets (Buttons, Playback) to drive your animation and you're done. Easy!    
View full tip
Mechanism  Concept in Vuforia Studio- How to make rotation more easy   When we try to rotate a model or 3d modelitem about a particular space axis it seldom will rotate about the correct axis as we want.  So, in this case we can try to solve this  when we use  some mathematical calculations. For example in the example(picture below)  -   door assembly we want to rotate  the door subassembly via the door hinges:     but  when we  try to rotate the door model about 60degree it rotates undesired on the wrong axis.      The question is:  Which is this axis and how to change it? The answer here is : When we have a PVZ model we cannot really change it!  We can use some mathematical relations to get the correct behavior . In this particular sample case the correct javaScript relation should be something like :   ... $scope.simple_door_slider_change = function (angle, door_length) {  var angle1=angle; var l_door=door_length;  var angle1_rad=angle1*Math.PI/180.0; $scope.view.wdg['modelItem-door-asm']['rx'] = angle1 ; $scope.view.wdg['modelItem-door-asm']['y']  = 0.0 - l_door*Math.sin(angle1_rad); $scope.view.wdg['modelItem-door-asm']['z']  = 0.0 - l_door*(1.0-Math.cos(angle1_rad)); $scope.app.params['door_angle']=  angle1; }; ...   So  calling $scope.simple_door_slider_change(70,0.950); will rotate  this particular door assembly on the correct place:       But what we can do to solve the problem  for more complex assemblies. For example when we want to rotate the door handle. Of course such calculation is no problem but this calculation will be more complex (containing movements and rotations ) and we need to invest significantly more  time for the creation of the mathematical concept of it. The main problem is that mostly we do not know what is the correct coordinate axis for each component.   Unfortunately, the only option, what we have here is to make some consideration already in the Creo Parametric design (or in another Cad tools) . So for example the following part have a default coordinate system. Here on the example picture is the name  PRT_CSYS_DEF.     When we later rotate about the x  in Vuforia Studio  then it will rotate about the X axis of the default csys here “PRT_CSYS_DEF”  So, this means when we have some component which should be later rotated in Vuforia, in this case  we need to pay attention already   in the design and try to  assemble the component were  the default csys is on the correct location.    The default coordinate system in a Creo Parametric model is created with the model and it is not possible to change it later (there is a workaround where we can use an auxiliary  assembly where we can insert the model. In this case we can move the model inside  the auxiliary assembly. The auxiliary  assembly will rotate about the default coordinate axis).   So, the next step is to consider, how to design a more complex mechanism assembly. Lets consider the following assembly:       When we create a project and then try to rotate  different components (arms) via slider then we will have e.g. the following situation:       So that the one (blue) component is rotated as desired but when we rotate the blue component the green component does not follow it. Let's create another version of the mechanism were we have the correct behavior:     What is there different?   The answer is that we used a different structure. Here we nested the moved component in further sub assemblies.       It is important that in this case for the modelItems widget defintion in vuforia studio we are not using only a parts  but also  assemblies. So here the subassembly arm 2 was used  for the definition of modelitem which contains the arm1  (part) which is an addtional modelitem.       So in this case we could change the rotation value of the axis and they rotates as desired.     
View full tip
In Vuforia studio the best way to interact with 3d model components is to define explicit 3d modelitems (widget modelItem). So this will be an easy way to access the componets and to change their properties e.g. setting of the color  e.g.: $scope.setWidgetProp("modelItem-1", "color",  "rgba(128,0,0,1)");   This will  change the modelItem-1 property color to brown – and will display the component which is specified by this modelItem with a  brown color. Another way to do this in javaScript is something like :   $scope.view.wdg['modelItem-1']['color'] = "rgba(128,0,0, 1);";//brown $scope.view.wdg['modelItem-1']['opacity'] = 0.5;//set transparency to 0.5 //or for the same $scope.setWidgetProp("modelItem-1", "color", "rgba(128,0,0,1);"); //brown $scope.setWidgetProp("modelItem-1", "opacity", 0.5); //set transparency to 0.5   But in some cases during the project development it  could  be helpful when we are  able to manipulate the components or request information about them without defining of explicit modelItem widgets. For example if we want to select a component to see some information about the component and change the color of it:   var PICK_COLOR = "rgba(255,0,0,1)"; ... $timeout( //timeout block 1 function() { //timeout function 1 angular.forEach( //==== for each 3d model block // this will call the function below for each 3d model $element.find('twx-dt-model'), function(value, key) { //for each 3d model block function //===================================================================================== angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { // start the $on() listener 'userpick' + function definition //================================================================================= var pathid = JSON.parse(edata).occurrence; $scope.currentSelection = target + "-" + pathid; // create a component selection e.g. "model-1-/0/0/3/2" console.log("twx.app.isPreview() ="+twx.app.isPreview() ); //print an info if is called in preview mode and could be checked if required try{tml3dRenderer.setColor($scope.currentSelection, PICK_COLOR);} catch (ex) {console.warn("Exception 1 in tml3dRenderer.setColor()=>"+ex);} //will set the color of the selected component } //end of mobile device modelItemsList.push($scope.currentSelection); } //end is in array //================================================================================= }); // finish the $on() listener 'userpick' + function definition } //finish for each 3d model block function ); // finish for each 3d model block //================================================================================= } ,50); // finishtimeout block 1 and function   If  we use  PICK_COLOR  = "rgba(255,0,0,0)"; It means that this color (red) is set for a selected component. Here the one additional detail is the last argument - which have a value of 0. Means alpha channel =0 - or full transparence. On the most mobile devices it will hide the selected component, but this is not supported techniques and we have to use always color with alpha channel >0. / transparent but still visible/   Calling of the tml3dRenderer.setColor(…, undefined); will set the component color back to default - example:   tml3dRenderer.setColor(‘model-1-/0/0/3/2’, undefined);    Another important point is that when we know the model name and know the component ids, in this case we can also set the color or hide components without explicit definition of model items. For example for a particular model we have prepared  a json file (*):     { "/0/0/2" :"rgba(255,0,0,1);", "/0/0/0" :"rgba(128,0,0,1);", "/0/0/5" :"rgba(128,0,128,1);", "/0/0/3/0":"rgba(0,255,0,1);", "/0/0/6" :"rgba(255,200,0,1);", "/0/0/3/1":"rgba(0,0,0,0.2);", "/0/0/7/0":"rgba(0,0,0,0.2);", "/0/0/7/1":"rgba(0,0,0,0.2);" }   The model to which this json file was created is placed in Vuforia Studio as model widget with name=model-1  We can then read this json file (from prject->src\phone\resource\Uploaded folder) with some javaScript construct like (below) and set the color property of the components (also change the transparence - for the components with alpha channel =0.2)  Here an example (*):   //======================================================== // reading a json file with component setting for the components //======================================================== $scope.setCompProps=function() { var FILES_MODEL_COMP = { 'model-1':'comp_info.json' }; $scope.compJSON_Data = new Array(); angular.forEach(FILES_MODEL_COMP, function(jsonFile, target) { console.log("angular.forEach jsonFile = "+jsonFile + ", target="+target); $http.get('app/resources/Uploaded/' + jsonFile).success(function(data, status, headers, config) { $scope.compJSON_Data[target]=data; // in this case is $scope.compJSON_Data['model-1']= of the json structure file here the content'comp_info.json'; angular.forEach(data , function(color, path_id){ console.log("target="+target+" --> color = "+color + ",path_id="+path_id); tml3dRenderer.setColor(target+'-'+path_id, color); });//end for each function }) .error(function(data, status, headers, config) {console.log("calling in foreach 1 failed"); }); }); };     So when we start for this particular model the test code it will change the display of the model according to the setting of the comp_info.json  file:     The code above is more than intended for setting colors and transparency  . According a recommendation from development for hiding of components is better to use  the hidden property:   tml3dRenderer.setProperties($scope.currentSelection, { hidden:true } );   The sample  code below  ( more simplified) is  for the case that we want to blank a component by click on it:   angular.forEach($element.find('twx-dt-model'), function(value, key) { // search all twx-td-model's -> means all model widgets angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { //for each model widget will set a userpick listener try{ console.log('edata');console.warn(edata); console.log("JSON.parse(edata)");console.warn(JSON.parse(edata)); var pathid = JSON.parse(edata).occurrence; $scope.currentSelection = target + "-" + pathid; console.log("=>>"+$scope.currentSelection); } catch (ea) {console.error("not twx-model is clicked but still fired")} try{ // here below the change recommended from R&D tml3dRenderer.setProperties($scope.currentSelection, { hidden:true } ); } catch (e1234) {console.error( "e="+e1234); }   Here tested the code on the HoloLens 1.0 device:     When we have a color definiton  with  opacity -> the alpha channel set here e.g. to 0.1 /  and the defined color should be changed later :   var PICK_COLOR_OPACITY1 = "rgba(,,,0.1)";   to change the rgba expression by setting another value of transparency you can use some construct like this:   var PICK_COLOR_OPACITY1 = "rgba(,,,0.1)"; var OPACITY_VAL=0.3; var PICK_COLOR_OPACITY2= PICK_COLOR_OPACITY1.replace( "0.1)",OPACITY_VAL+")");   The JavaScript code above  will set transperancy value of  0.3 (replacing the 0.1 by 0.3) But for the case that we have in a json file a defintion of color with alpha chanel =0  :     ... "/0/0/3/1":"rgba(0,0,0,0.0);", ...   In this case we can  modify (recommended)  the code to check the value of the alpha channel and if it is ==0 to set  the "hidden" property  - example (*) :   ... //======================================================== // reading a json file with component setting for the components //======================================================== $scope.setCompProps=function() { var FILES_MODEL_COMP = { 'model-1':'comp_info.json' }; $scope.compJSON_Data = new Array(); angular.forEach(FILES_MODEL_COMP, function(jsonFile, target) { console.log("angular.forEach jsonFile = "+jsonFile + ", target="+target); $http.get('app/resources/Uploaded/' + jsonFile).success(function(data, status, headers, config) { $scope.compJSON_Data[target]=data; // in this case is $scope.compJSON_Data['model-1']= of the json structure file here the content'comp_info.json'; //because R&D statement to use hidde property need to check of alpha chanel ==0 angular.forEach(data , function(color, path_id){ console.log("target="+target+" --> color = "+color + ",path_id="+path_id); var start_alpha = color.lastIndexOf(","); var end_alpha = color.lastIndexOf(")"); var alpha_str = color.substring(start_alpha+1, end_alpha); var num = Number(alpha_str); if ( (isNaN(num )) || (num <= 0.0) ) {//set color properly to alpha channel 1.0 var new_color= color.substring(0,start_alpha+1)+"1.0"+ color.substring(end_alpha,color.length) tml3dRenderer.setColor(target+'-'+path_id, new_color); tml3dRenderer.setProperties(target+'-'+path_id, { hidden:true } ); } else { // color unchanged tml3dRenderer.setColor(target+'-'+path_id, color); } });//end for each function }) .error(function(data, status, headers, config) {console.log("calling in foreach 1 failed"); }); }); }; ///////////// ...   The example above will set to the component the correct values of the color but with alpha channel 1.0 and will interpret the original alpha value from the file as setting of the hidden property to true.  Does this make sense? Yes if we later set the hidden property to false then the color setting will be applied according to the definition from  the json file
View full tip
In this particular cases we have some sensors/devices which could be accessed via WLAN/ Web  and also  we need to scan /request the values of these sensors via rest API calls. For example from javascript code for simple REST API request the code   should looks like (used a test web page which provides demo response) :   //this code will work fetch('https://jsonplaceholder.typicode.com/todos/6') .then(response => response.json()) .then(json => {console.log(json); }) .catch(error =>{ console.error(error);}) };   ... but the same code will not work for http url   fetch('http://ip.jsontest.com/') .then(response => response.json()) .then(json => {console.log(json); }) .catch(error =>{ console.error(error);}) };   When I tested it - my observation was that https and http requests will work in Studio in preview mode.  But only the https request will work on both Android and IOS devices. The http fetch request will not work ...   This means trying to design a solution which will call javaScript on the Vuforia view where we will try to read data will not work / or at least  will not work  stable. Therefore,  a better  way  is  /also it is the supported way /- to get (to bind)  the sensors data via the External DATA panel:     To achieve this goal , we need: we need first to create a Thing with properties which could be displayed in the experience project. The next step is to read the sensors and update the properties. In case that we can see the sensors URLs from the thingworks instance / in this case we can use a thingworks service called by  a timer. The time  will call the service  in  particular interval  , so that the  service will  read then the  data from the sensors.     In the picuture above we need to define a service which will call a rest API to read the sensors. Here in the example to simulate the call we will read a timestamp from a postman-echo service. As the name say's it will return exact the same values what  was  send to it (but with different format - as JSON object) . So for example when we call in a web browser the following link:   http://postman-echo.com/time/object?timestamp=2018-6-9:8:8:4   this will return the following json object:   {"years":2018,"months":5,"date":1,"hours":9,"minutes":8,"seconds":8,"milliseconds":4}   In this  example we will create a service "testGetValue() which will call the echo service and will return the json respose as an InfoTable as output )   //URL_STRING="http://postman-echo.com/time/object?timestamp=2018-6-9:8:8:4" var year= 2010 +Math.floor((Math.random() * 10) + 1);//2011...2020 var month= Math.floor((Math.random() * 8) + 1);//1-9 var day= Math.floor((Math.random() * 18) + 10);//10-28 var hour= Math.floor((Math.random() * 24) );//1-24 var minute= Math.floor((Math.random() * 60) );//0-59 var second= Math.floor((Math.random() * 60) );//0-59 var msecond= Math.floor((Math.random() * 1000) );//0-999 //these values are only here specific to the web side not to have an error //calling the rest API var URL_STRING="http://postman-echo.com/time/object?timestamp="+year+"-0"+ month+"-"+day+":"+hour+":"+minute+":"+second+":"+msecond; var params = { proxyScheme: undefined /* STRING */, headers: undefined /* JSON */, ignoreSSLErrors: undefined /* BOOLEAN */, useNTLM: undefined /* BOOLEAN */, workstation: undefined /* STRING */, useProxy: undefined /* BOOLEAN */, withCookies: undefined /* BOOLEAN */, proxyHost: undefined /* STRING */, url: undefined /* STRING */, timeout: undefined /* NUMBER */, proxyPort: undefined /* INTEGER */, password: undefined /* STRING */, domain: "postman-echo.com" /* STRING */, username: undefined /* STRING */ }; params.url=URL_STRING; // result: JSON var json = Resources["ContentLoaderFunctions"].GetJSON(params); //var json_string= JSON.stringify(json); //var new_json = JSON.parse(json_string); var params1 = { infoTableName: "InfoTable", dataShapeName : "InoTableDataShape_Time1" }; var infotabletest = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); infotabletest.AddRow({years:json.years, months:json.months, date:json.date, hours:json.hours, minutes:json.minutes, seconds:json.seconds, milliseconds:json.milliseconds}); var result = infotabletest; //set now the value to the properties me.years=parseInt((infotabletest).getFirstRow().getValue('years')).toString(); me.months=parseInt((infotabletest).getFirstRow().getValue('months')).toString(); me.date=parseInt((infotabletest).getFirstRow().getValue('date')).toString(); me.hours=parseInt((infotabletest).getFirstRow().getValue('hours')).toString(); me.minutes=parseInt((infotabletest).getFirstRow().getValue('minutes')).toString(); me.seconds=parseInt((infotabletest).getFirstRow().getValue('seconds')).toString(); me.milliseconds=parseInt((infotabletest).getFirstRow().getValue('milliseconds')).toString(); //=================================================================   To be able to convert the json object to an infotable we need to define a datashape with the same fields -> corresponding to the json elements (here e.g  InoTableDataShape_Time1):         So every time when we call this service it will call the postman-echo web.side with some random data and will set the values of properties based on the received data  from the request. In this case the request returns the sent data (makes no really sense) but here is only important to demonstrate the principle how to call it. This should demonstrate how to request values from some edge devices (measurments) via REST API calls - supposing that the edge device supports  REST API call. (For example we can setup some Arduinos, Raspberry ,  ESP8266, etc...  as Web Service supporting REST API calls for reading of measurment values) Now we need to create a timer object  which will call call the service  for  an particular interval (here 1 sec /1000msec)  -> the used  service is here testGetValues() according the definition above.     this will update the values of the property and we can see the updated property  in Vuforia Studio.       But often the sensors URLs are not visible for the thingworx instance. In this case we can try to read the values of the sensors in the local network (some kind of intermediate service)  and then send the values to the thing properties using one of the methods described in the PTC guide “Choose a Connectivity Method ->Guidelines for selecting the optimal method for connecting to ThingWorx.” https://developer.thingworx.com/en/resources/guides/choosing-connectivity-method An  example for one alternative way you can fine in "Node.js Rest API example  how to display data from the local network in Vuforia Studio project?"  
View full tip
In this article  we have the same start point/state as described in “How to read sensors via Rest API call in and display it Vuforia Studio experience project?”… but with one significant difference ->the sensors URLs are not visible for the Thingworx service. The problem is that the sensors values should be requested via Rest API calls in a local intranet. This means that the end devices are connected to a local router and have IP valid only in the local WLAN. Othersides the router   have also internet access. The end devices could connect to  the Experience Server and could download e.g.  the experience. The sensor URL and rest API call should be some thing like:   var url="http://172.16.40.43.5900/api/v0/dev_id=6&size_id=123";   So, it means the IP address of the device, where the value should be requested via Rest API calls is not visible from outside of the local WLAN and the Rest API call could done only inside the local network. So here we can use a node.js program (service)  which will request the sensors and will send the values to Thingworx. So the main loop is an interval callback function “requestFunction” which is called - here in example  every 5 seconds. It will read the sensors data via Rest API fetch call . In this example the data is called from  a local test web server (it simulates an edge device) . For the test I used 2 server URLs  wich require parametrs 1.) http://127.0.0.1:8081/userId=8 here the the user_id is random value 1...10 and  the resonse returns a json object  with some properties 2.)http://127.0.0.1:8081/api/todos?id=122 here the the id is random value 1...200 and the response  returns also a  json object  with some properties   var http = require('http') var https = require('https') const fetch = require('node-fetch') var request = require("request") var userId = 1 //could be from 1 to 10 var todosId = 1 //could be 1 -200 function requestFunction() { userId = Math.floor((Math.random() * 10) + 1) todosId = Math.floor((Math.random() * 200) + 1) fetch('http://127.0.0.1:8081/userId/' + userId) .then(response => response.json()) .then(json => { console.log(JSON.stringify(json)) setPropValue("profession", json["profession"]) setPropValue("userName", json["name"]) setPropValue("userId", json["id"]) setPropValue("userPassword", json["password"]) }) fetch('http://127.0.0.1:8081/api/todos?id=' + todosId) .then(response => response.json()) .then(json => { console.log(JSON.stringify(json)) setPropValue("message", json["title"]) }) } // ============================================== setInterval(requestFunction, 5000) //every 5 sec   If we need information about what  the  syntax of the Rest API is  to   set/ change the value of the thing property - for this  we can  check  the  REST API Reference Guide: https://developer.thingworx.com/en/resources/guides/rest-api-how-guide Property values access: https://developer.thingworx.com/en/resources/guides/rest-api-how-guide/property-values-rest-api-how    When we review the code above we can see that there is function “setPropValue” which should set a value to a particular property. Here the twx server:port is mxxxxx7o.studio-trial.thingworx.io:8443. The Thingname is  “REST_API_EDGE”   function setPropValue(propName, propValue) { var options = { method: 'PUT', url: 'https://mxxxxx7o.studio-trial.thingworx.io:8443/Thingworx/Things/REST_API_EDGE/Properties/PROPNAME', headers: { // use here the user appKey who created the Thing /here REST_API_EDGE appKey: 'fxxx7x4a-19x4-4xx3-bxxxa-9978a8xxxx17x', //appkey for the user 'Content-Type': 'application/json' }, body: { PROPNAME: 'XXXXXXX' }, json: true }; //this will make a string from the option json and will replace the // place holder “PROPNAME” by function argument propName var str_temp = JSON.stringify(options).replace(/PROPNAME/g, propName) //this will replace place placeholder XXXXXXX by function argument propVaule // and will convert the string back to json options = JSON.parse(str_temp.replace(/XXXXXXX/g, propValue)) console.log("option in setPropValue:") console.warn(options) request(options, function(error, response, body) { //print the return code – success is 200 console.log("response.statusCode=" + response.statusCode) if (error) { console.log("error in request"); throw new Error(error); } console.log("response") }); } // =================================================   The code was generated with the REST API client POSTMAN. We can use this tool to test some Rest API calls in the POSTMAN GUI , where we could use some more confortable functionality for testing and debugging . When the call is working in the POSTMAN UI we can export it to different programming formats (javaScript, nodeJs etc. - means it  will  generate here a  javaScript code for postprocessing. When we start the script (above) we can verify that the property values will change every 5 seconds.     The best way now to bind the data in Vuforia studio is via the External DATA panel     Afterwards  we can test in the Preview and later on the end device:    
View full tip
This post should provide more detailed steps additionally  to the posts ("How to extract the components with properties from a pvz file"[1.] and "How to use ThingView Widget from Navigate to display CAD Model/Viewables in custom mashup- Concept"[2])  This post should consider more detailed the steps for the extracting of viewables and also how to extract the sequence steps information from a .pvz / Creo Illustrate model for further usage in a Thingworx service. Following steps: 1.) Extracting the data from the Creo View Model ( Created from Creo Illustrate via publish to pvz functionality) As described in [1.] we required for the extraction of information a Creo View Toolkit.  A good choice will be the usage the Creo View  WebGL toolkit module.   A web toolkit program is called inside a html document ,where the javaScript Creo View WebGl Api is embedded. So, the most important logic could be called on the windows load function. The code below will initialize  the thingview library and  will open the pvz model file (value of variable CUR_MODELPATH in the code below  is set the complete model path):   ... window.onload = function() { ThingView.init("js/ptc/thingview", function() { // ThingView should only be initialised once per frame //---------------------------------- //send the modelname to server var xhr = new XMLHttpRequest(); xhr.open("POST", "RAY_LOG_FILE:", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { var json = JSON.parse(xhr.responseText); } }; //set the json modelname object var JSON_BOM_MODEL_OBJ = new Object; MODELNAME = CUR_MODELPATH.substring(CUR_MODELPATH.lastIndexOf("/") + 1) JSON_BOM_MODEL_OBJ.name = "BOMMODELNAME"; JSON_BOM_MODEL_OBJ.value = MODELNAME; xhr.send(JSON.stringify(JSON_BOM_MODEL_OBJ)) console.warn("sent BOMMODELNAME=" + MODELNAME) //finish the sending of the model_name //---------------------------------- console.log("Creo View WebGL Viewer is now initialised"); session = ThingView.CreateSession("CreoViewWebGLDiv"); //refers to the CreoViewWebGLDiv -> a div area in the html fileSource // which contains the code var xhttp = new XMLHttpRequest(); MODELNAME = CUR_MODELPATH.substring(CUR_MODELPATH.lastIndexOf("/") + 1) xhttp.open("POST", "RAY_JSON_VIEWABLE:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var js_obj = new Object; js_obj.name = "VIEWABLE"; js_obj.value = MODELNAME; var data = JSON.stringify(js_obj); xhttp.send(data); model = session.MakeModel(); ////==================LoadFromURL Callback model.LoadFromURLWithCallback(CUR_MODELPATH, true, true, false, function(success, isStructure, errorStack) { var illustrations = model.GetIllustrations(); for (var i = 0; i < illustrations.size(); i++) { console.log("Illistration name: " + illustrations.get(i).name); // seems illustrations.get(i).name == pviFile model.LoadIllustrationWithCallback(illustrations.get(i).name, function(success, pviFile, stepInfoVec) { if (success === true) { var hasAnimation = model.HasAnimation() var hasSequence = model.HasSequence() xhttp.open("POST", "RAY_JSON_VIEWABLE:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var js_obj = new Object; js_obj.name = pviFile; js_obj.value = pviFile; js_obj.type = "viewable"; js_obj.hasSequence = hasSequence; js_obj.hasAnimation = hasAnimation; var data = JSON.stringify(js_obj); xhttp.send(data); for (var ii = 0; ii < stepInfoVec.size(); ++ii) { xhttp.open("POST", "RAY_JSON_VIEWABLE_STEP:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var step_obj = new Object; console.log("step nr=" + ii); step_obj.viewablename = pviFile; step_obj.nr = ii; step_obj.name = stepInfoVec.get(ii).name; step_obj.description = stepInfoVec.get(ii).description; var data = JSON.stringify(step_obj); xhttp.send(data); //============================================= } } }) } ////============= setTimeout(function() { { xhttp.open("POST", "RAY_JSON_VIEWABLE:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var js_obj = new Object; js_obj.name = "FINISHVIEWABLES"; var data = JSON.stringify(js_obj); xhttp.send(data); } }, 10000); }); ///model load from URL funciton ///////////// }); // ThingView.init( ) }; //window onload function   The program will generate 2 different json files and will send them to the http server. When the Creo View WegGl program is started (load  the html file from the http server)  - on the server side - in the node.js console  we can see the printing of the received data - example on the picture below:     The Creo View WebGl program will create on the server side  2 json files (this requires also handling of the received  data on the server side as allready mention in the post [1.]  ) For the data extraction I used in this example the PTC demo file (provided with the installation of Creo View Toolkit) : worldcar-brake-multi-figure.pvz,  worldcar-brake-multi-figure.pvz-viewableSteplist.json   [{"viewablename":"Sequence","nr":0,"name":"Sequence","description":""}, {"viewablename":"Sequence","nr":1,"name":"Step 1","description":"Remove spring clips"}, {"viewablename":"Sequence","nr":2,"name":"Step 2","description":"Release 4 bolts"}, {"viewablename":"Sequence","nr":3,"name":"Step 3","description":"Pull apart the calipers"}] worldcar-brake-multi-figure.pvz-viewablelist.json [{"name":"Figure 1","value":"Figure 1","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Sequence","value":"Sequence","type":"viewable","hasSequence":true,"hasAnimation":false}, {"name":"Parts List","value":"Parts List","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Sectioning","value":"Sectioning","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Translation","value":"Translation","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Animation","value":"Animation","type":"viewable","hasSequence":false,"hasAnimation":true}]   2.) Definition of a service in Thingworx for the returning of the Sequence Step List (see also " Service for creating of Bom - and Viewable Lists from json files "  [3]  )    Here the first Step is to define a general service which will will convert the JSON file from a file repository to an InfoTable using particular dataShape.  The json file will be taken from a  repository:   var params = { path: the_json_path /* STRING */ }; var Content = Things[the_Repository_Name].LoadJSON(params); var params1 = { infoTableName: undefined /* STRING */, dataShapeName: the_dataShape_name /* DATASHAPENAME */ }; // result: INFOTABLE var jsonTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); var result = DataShapes.ThingviewBomData.CreateValues(); for(i in Content.array){ jsonTable.AddRow(Content.array[i]); } result = jsonTable; //returns the InfoTable     The service has 3 Input parameters : the_json_path, the_dataShape_name and the_Repository_Name (all are String type)       This service could be called for any filerepository, containing any json files(it is not file specific) . Via the dataShape name we will specify the filed definitions.  We need to do this  for each specific json file. In this case we have to  create first manually a DataShape which is compatible to the Json Object.  For example when we start the service RayJsonToInfoTable:     And the dataShape what we need to define for  this particular example:       For the achieving  of the final goal -> the creation of the  Sequence Step List. We need to create a  another service where we can specify the arguments for DataShape and  repository name. Example:     So the input argumets are:  I.) the path to the json file and II.) the name of the sequence for which we want to see the steps.     I am not sure if we can omit the step , where we create a  dataShape for specific json - so some kind of  dynamic dataShape generation - because in such case  we need only to specify the json file without manual editing opreration.      
View full tip
In this article  I want to consider the question how to   define a  TWX service, which could create   a  Bom and Viewable Lists from  json files. This will be very helpful option when we want to display some CreoView data in  Thingview.   How to extract BOM and viewable data form a Creo View /Illustrate *.pvz file is shown in the post "How to extract the components with properties from a pvz file".   In this example the json files are saved already  in a thingworx repository (means a thing form template FileRepostiroy) e.g.  "CAD-Files-Repository" :   1.)Create a service for the BOM List /InfoTable                 edit the CAD-Files-Repository thing and create a new service named  "GetBomStruct_arg_path" set  the BaseType : INFOTABLE . Set the DataShape property of the service - > here to BomListStuct -> this datashape need to be created  first . It should have the following Fields / property (all String type):                               create a service input parameter "the_json_path". Using this parameter we can past the repository path to the service edit the java script . We can past the following script (see the comments in the script area) var params = { path: the_json_path /* STRING */ // path it set to the input argument // example for such setting value // --> "/json_lists/Machine-complete-CV.PVZ-bomlist.json" var Content = Things["CAD-Files-Repository"].LoadJSON(params); //This will call the method LoadJSON(params) of the thing CAD-File-Reposistory var params1 = { infoTableName: undefined /* STRING */, dataShapeName: "BomListStruct" /* DATASHAPENAME */ }; // result: INFOTABLE var jsonTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); var result = DataShapes.ThingviewBomData.CreateValues(); // working fine for(i in Content.array){ jsonTable.AddRow({Part:Content.array[i].part, Description:Content.array[i].description, sBOMDepth:Content.array[i].sBOMDepth, sBOMID:Content.array[i].sBOMID, sBOMIDPath:Content.array[i].sBOMIDPath, sBOMName:Content.array[i].sBOMName, sBOMPath:Content.array[i].sBOMPath, path:Content.array[i].sBOMIDPath, parentPath:Content.array[i].sBOMIDPath.substring(0,Content.array[i].sBOMIDPath.lastIndexOf('/')) }); } result = jsonTable;   The parentPath  infoTable field is required for Tree Widget  to display the BOM list as tree The following code:   for(i in Content.array) { jsonTable.AddRow(Content.array[i]);}   the  code above will transfer  1 to 1  all  fields defined  in the json file with data into  infoTable fields. In this case  is not used because we have different mapping for "parentPath" -> it is new in the output InfoTable and is not contained by the json file. Test the  service:     2.)Create a service for the BOM List /InfoTable (steps are similar to 1.) edit the CAD-Files-Repository thing and create a new service named  " getViewableList_arg_path " set  the BaseType : INFOTABLE . Set the DataShape property of the service - > here to viewablelist_new -> this datashape need to be created  first . It should have the following Fields / property (all String type)     Create a service input parameter "the_json_path". Using this parameter we can past the repository path to the service Edit the java script . We can past the following script (see the comments in the script area):   var params = { //path: "/json_lists/Machine-complete-CV.PVZ-viewablelist.json" /* STRING */ path: the_json_path /* STRING */ }; var Content = Things["CAD-Files-Repository"].LoadJSON(params); var params1 = { infoTableName: undefined /* STRING */, dataShapeName: "viewablelist_new" /* DATASHAPENAME */ }; // result: INFOTABLE var jsonTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); var result = DataShapes.ThingviewBomData.CreateValues(); for(i in Content.array){ jsonTable.AddRow({name:Content.array[i].name, //type:Content.array[i].description, type:"viewable", value:Content.array[i].value, hasAnimation:Content.array[i].hasAnimation, hasSequence:Content.array[i].hasSequence }); } result = jsonTable;   Test the  service:      
View full tip
In the post “How to select model components in a 3d model without explicit definition of model Items”  there is one point where we  require a list of component items. Often we have the  case where we get a Creo View .pvz file which was published by Creo Illustrate.  In this case Creo Illustrate will  do additional changes for  the occurrence Id path  so that if we have a bom list coming from Creo Parametric then this data  will be not usable. So the question here is :  Is it possible to extract a bom list for any *.pvz files? The answer is Yes.  We can do this  using the Creo View Toolkit API ( this is one possible option). Creo View API Toolkit consist of many different moduls : Java Toolkit , Web Toolkit (only Internet Explorer related) Office Toolkit and  the new one introduced since 5.1 release module WebGL Toolkit.  We can extract BOM list  with Java Toolkit but it requires more complex programming environment. Therefore I think it is the most easily way to use Creo View WebGL toolkit   The Toolkit WebGl is based on the Thingview.js library. When we install Creo View Toolkit we will find the following directory structure:     In the web-application-examples  directory we can find some sample files which could be a good introduction how to use this API. The documentation is the WebGLToolkitDevGuide_en.pdf  and also in html(doxygen) in the documentation sub directory:     The CreoWebGL Toolkit requires a node.js environment. So  when we want to start it we have first to start the Toolkit server: >>>node http_server.js [port]   The port parameter is optional and if we do not use it then it takes by default 8080 :     The next step is to open the localhost:port/ ExtractBomPVZ.html which implements  here our program For example a simple version which will print the components with some properties  to the chrome console  could  looks like:   <!DOCTYPE html> <html style="height: 100%"> <head> <meta charset="utf-8" /> <meta name="author" content="PTC"> <title>Creo View WebGL Viewer</title> </head> <script src="js/ptc/thingview/thingview.js"></script> <body style="height: 100%; margin: 0px"> <div id="TITLE" style="width: 100%;height: 20%;border:2; float: left"></div> <div id="CreoViewWebGLDiv" style="width: 100%;height: 80%;border:0; float: left"></div> </body> <script type="text/javascript"> var thingview; var session; var model; var model1; var model2; var MODELPATH = "sample-data/thingview_test/Machine-complete-CV.PVZ"; var MODELNAME = ""; var global_number_generated = false; var BOM_LIST = []; var USE_LOG = false; var CUR_MODELPATH = getAllUrlParams().modpath ? decodeURIComponent(getAllUrlParams().modpath) : MODELPATH; document.getElementById('TITLE').innerHTML = "<h3>Extract Bom List </h3><hr><h4>"+CUR_MODELPATH+"</h4><hr>"; window.onload = function() { //return; ThingView.init("js/ptc/thingview", function() { // ThingView should only be initialised once per frame BOM_LIST = new Array(); console.log("Creo View WebGL Viewer is now initialised"); global_number_generated = false; GLOBAL_COUNT = 0; session = ThingView.CreateSession("CreoViewWebGLDiv"); //---------------------------------- model = session.MakeModel(); ////================= Selecton Call back function definition model.SetSelectionCallback(function(type, si, idPath, selected, selType) { var JSON_OBJ = new Object; var shapeInst = session.GetShapeInstanceFromIdPath(idPath) var color = shapeInst.GetColor() var instId = shapeInst.GetIdPath(); //the shape instance instId now contains a prefix "SI_" which is bug //or at least not wanted - here remove it var instId_corrected = instId.replace(/SI_/g, ''); var instIdPath = shapeInst.GetInstanceIdPath(); console.log("idPath=" + idPath) //contains the id path e.g /1/23/3 var description = model.GetPropertyValue(idPath, "PROE Parameters", "DESCRIPTION") if (!(description == null)) console.log("description=" + description); else desciption = "---"; var sBOMPath = instId; var sBOMPath = instId_corrected; //replaced with the corrected string var sBOMIDPath = instIdPath; var sBOMID = sBOMIDPath.substring(sBOMIDPath.lastIndexOf("/") + 1) var sBOMName = sBOMPath.substring(sBOMPath.lastIndexOf("/") + 1) var sBOMDepth = instIdPath.split("/").length - 1; { console.log("NAME=part&VALUE=" + sBOMName); console.log("NAME=sBOMDepth&VALUE=" + sBOMDepth); console.log("NAME=sBOMID &VALUE=" + sBOMID); console.log("NAME=sBOMIDPath&VALUE=" + sBOMIDPath); console.log("NAME=PARTPATH&VALUE=" + sBOMName); console.log("NAME=DESCRIPTION&VALUE=" + description); console.warn("color (a=" + color.a + " b=" + color.b + " g=" + color.g + " r=" + color.r + ")"); } }); ////==================LoadFromURL with Callback model.LoadFromURLWithCallback(CUR_MODELPATH, true, true, false, function(success, isStructure, errorStack) { console.log("Model(2) LoadFromURLWithCallback - success: " + success + ", isStructure: " + isStructure); if (success) { ////============= session.SelectAllInstances() var num = session.GetSelectionCount() console.log("Number of selections =" + num) } }); ///model load from URL funciton end ///////////// }); // ThingView.init( ) end }; //window onload function end //// URL parameter handling found in the WWW Overflow and works quite good function getAllUrlParams(url) { // get query string from url (optional) or window var queryString = url ? url.split('?')[1] : window.location.search.slice(1); // we'll store the parameters here var obj = {}; // if query string exists if (queryString) { // stuff after # is not part of query string, so get rid of it queryString = queryString.split('#')[0]; // split our query string into its component parts var arr = queryString.split('&'); for (var i = 0; i < arr.length; i++) { // separate the keys and the values var a = arr[i].split('='); // set parameter name and value (use 'true' if empty) var paramName = a[0]; var paramValue = typeof(a[1]) === 'undefined' ? true : a[1]; // (optional) keep case consistent paramName = paramName.toLowerCase(); if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase(); // if the paramName ends with square brackets, e.g. colors[] or colors[2] if (paramName.match(/\[(\d+)?\]$/)) { // create key if it doesn't exist var key = paramName.replace(/\[(\d+)?\]/, ''); if (!obj[key]) obj[key] = []; // if it's an indexed array e.g. colors[2] if (paramName.match(/\[\d+\]$/)) { // get the index value and add the entry at the appropriate position var index = /\[(\d+)\]/.exec(paramName)[1]; obj[key][index] = paramValue; } else { // otherwise add the value to the end of the array obj[key].push(paramValue); } } else { // we're dealing with a string if (!obj[paramName]) { // if it doesn't exist, create property obj[paramName] = paramValue; } else if (obj[paramName] && typeof obj[paramName] === 'string') { // if property does exist and it's a string, convert it to an array obj[paramName] = [obj[paramName]]; obj[paramName].push(paramValue); } else { // otherwise add the property obj[paramName].push(paramValue); } } } } return obj; } </script> </html>   When we start the  ExtractBomPVZ.html and open the chrome debugging console (Ctrl-Shift-I)     We can call the Creo View WebGl Toolkit program above in chrome with a parameter which will specify a path of the .pvz model –> example:   http://localhost:8080/ExtractBomPVZ.html?modpath=sample-data/Brake/worldcar-brake-multi-figure.pvz   The program will selects all visible components and will print to the console the idPah, partname , color etc… One problem we have here is that we could not print it to a local file because of the security restriction of the browser. A possible solution is to send the data back to the server e.g. as Json object and save the data to the server root directory. Later we can download these file if required e.g. calling the json:   http://localhost:8080/worldcar-brake-multi-figure.pvz-bomlist.json   To implement the creation of the json file we need  to change the CreoWeb.Toolkit html file. So our program  should send data to the server.  Additionaly we  also need to modify/extend the server - http.createServer() callback function to handle also the received data.     Also as next step we will extend  the Creo View Toolkit  program file by adding a XMLHttpRequest() . This request  will send the modelname and the generated json object to the http server     The call of the Toolkit html file with a start parameter was here directly in chrome via the URL but we can also call  it from a javascript or other html file:   <!DOCTYPE html> <html style="height: 100%"> <head> <meta charset="utf-8" /> <meta name="author" content="PTC"> <title>Creo View WebGL Viewer</title> </head> <script> function callBomPVZ(path) { //window.location.href = "http://localhost:8080/ExtractBomPVZ.html?modpath="+path; var myWindow = window.open("http://localhost:8080/ExtractBomPVZ.html?modpath="+path, "_blank", "height=600,width=500",false); //setTimeout(function(){ browser.refresh(); }, 1500); } </script> <body> <hr><p> <button type="button" onclick="callBomPVZ('sample-data/thingview_test/Machine-complete-CV.PVZ')">callBomPVZ:: Machine-complete-CV.PVZ</button> <hr><p><hr> <button type="button" onclick="callBomPVZ('sample-data/Brake/worldcar-brake-multi-figure.pvz')">callBomPVZ:: worldcar-brake-multi-figure.pvz</button> </body>  
View full tip
At the frist sight the  issue , may be , seems to be more related to thingworx or navigate, but this is often a question which is related to models  used in Studio and customers want to have some tools to view such models outside Vuforia Stuido and Vuforia View . Another important point is that Vuforia Studio Preview,  Navigate Thingview  and Creo View WebGL Toolkit use /are based on  the same thingview library.   The official statement of PTC here is that thingview widget, which is part from the Navigate extension is not supported for customized mashups and correct work is guaranteed  only as part of the navigate functionality. Therefore, there is no documentation provided for the customization of this widget and no technical support cases will be handled on address of related issues. Also the geometry file formats  supported by thingview are mainly the files supported by the Creo View application. All other file formats (which are not availalbe in the open dialog box of Creo View) are not supported -> e.g. glTF format. Of course, this can change in a future versions ->  therefore, please, check for future releases always the "What's new" docs. Actually the thingview allows  opening of some simple glTF files. But this is not working for every glTF file. Mostly there is a problem when we have geometry with high complexity.  … Additional I want to pont that not supported does not mean is not allowed. So therefore, I want to provide here some Info in case that customers want to customize the thingview widgets at their own risk. (no bug fixing and no compatibility guaranteed for future Releases)   In this post I will only introduce  the concept of such mashup's/view's and then in further posts will consider the different details for the different  suppoints in the picture below.      In the picture above, we have an example for customized mashup where we have a different areas for the different functionality. The mashup displayed in design /edit mode should looks like:     The mashup contains the following main areas(marked in the picture with a callouts numbers ):   1.) Selector for the models. This is a list element widget which provides the possibility for the  selection of a particular model. The list  here is based on an Infotable returned by a service. Here is an example how to implement such service:     For simplification reason the service contains here only 2 rows. Here is an example how to implement such service (javaScript code) :     var data = [ { "FileName": "Machine-complete-CV.PVZ", "repository_link": "https://mrerot7o.studio-trial.thingworx.io:8443/Thingworx/FileRepositories/CAD-Files-Repository/CADFiles/Machine-complete-CV.PVZ", "json_bom": "/CADFiles/json_lists/Machine-complete-CV.PVZ-bomlist.json", "json_viewable_list": "/CADFiles/json_lists/Machine-complete-CV.PVZ-viewablelist.json", "json_viewable_stepList": "/CADFiles/json_lists/Machine-complete-CV.PVZ-viewableSteplist.json" }, { "FileName": "worldcar-brake-multi-figure.pvz", "repository_link": "https://mrerot7o.studio-trial.thingworx.io:8443/Thingworx/FileRepositories/CAD-Files-Repository/CADFiles/worldcar-brake-multi-figure.pvz", "json_bom": "/CADFiles/json_lists/worldcar-brake-multi-figure.pvz-bomlist.json", "json_viewable_list": "/CADFiles/json_lists/worldcar-brake-multi-figure.pvz-viewablelist.json", "json_viewable_stepList": "/CADFiles/json_lists/worldcar-brake-multi-figure.pvz-viewableSteplist.json" } ]; var result = DataShapes.files_repository_source_shape.CreateValues(); for(var i =0;i<data.length;i++) { result.AddRow(data[i]); }   The return type of the service is an InfoTable . For the defintion of the InfoTable we need a DataShape for the used fields definitions:     The list Widget ( callout 1.)  in the picture) has a binding to the thingview data.  When we select in the list widget a model -> this will call  the services: { (getViewableSteplist_arg_path(), getViewableSteplist_arg_path_viewablename()  and GetBomStruct_arg_path() }   of the   CAD-Files-Repository (Repository Thing - means  a thing which uses the template FileRepository)  -> and will pass the arguments for the json files paths for bom , viewables and StepList .     The effect is that- when we select a model in the listWidget -> this will update the mashup and will display only data related to this model.  All models and Json Files in this example  are saved in the Repository " CAD-Files-Repository "     To upload data to the repository we can use the File Upload widget. To display (for testing)  the data we can use the GetDirectory() and GetFileListingWithLinks() services of the repositroy thing. Below is an example for tool mashup which could be used as a tool to upload and display the data in the repository -as shown in the  picture above:     2.) Model tree area (callout 2.). This will display the model tree of the assembly components. It requires a service returning an InfoTable.  - here an example the service {Things["CAD-Files-Repository"]. GetBomStruct_arg_path(the_json_path) }  is called and returns the folloiwng   output:       For the creation of the infoTable I used  here a json file. In this example the the information about the viewalbes and steps  was extracted from  PTC sample assemble worldcar-brake-multi-figure.pvz   to the file  "worldcar-brake-multi-figure.pvz-viewableSteplist.json" (attached to this post). How to generate such file with Creo View WebGL toolkit is described in the community post ( How to extract the components with properties from a pvz file )   The step how to create an InfoTable  from a  Json will is handled in an addtional post ( Service for creating of Bom - and Viewable Lists from json files )   For the  displays of the data as an assembly  tree the tree widget has the following settings:     3.) List area - the functionality in this area uses  the same services as in point 2.)  but it  displays the components bom in a Thingworx Grid widget.  This will list the Bom as list of all components  (uses also the service {Things["CAD-Files-Repository"]. GetBomStruct_arg_path(the_json_path) } )     In the mashup preview it will looks like:     4.) Area for the selection of a viewable /  viewable = Creo Illustrate Figures ( callout 3  in the  the overview picture)     To  display the viewables correctly in thingview we require to have a service/data which is based on InfoTable with specific field definition (specific field name + type  is important that it works)       We can bind the All Data of the service  (here : {Things["CAD-Files-Repository"]. getViewableList_arg_path(the_json_path) } ) to the Views property of the thingview widget. Depending on the values of the fileds HasAnimation and HasSequnece the buttons for playing of sequences and animation are shown or blanked (set the Visible property to true or false)      How to define the service used for this point (returns InfoTable with list of the viewables) is described in the post  ( Service for creating of Bom - and Viewable Lists from json files )   5.) Area for the display of the  sequence steps > data grid widget-> it displays data  only if the current viewable is a sequence.  Here  below an example for the results of the service execution (getviewableStepList )   for a Json files related to  models which have 1 (worldcar-brake-multi-figure.pvz)  respectively  2 (Machine-complete-CV.pvz) sequences. In the attached sample file  you can find  the  json file:  (worldcar-brake-multi-figure.pvz-viewableSteplist.json)       Here is an example of the mashup dipslay where we can see a Sequnece containing viewables     We can see the same mashup in design/edit mode (below). We can bind the All Data of the service  (here : {Things["CAD-Files-Repository"]. getViewableSteplist__arg_path_viewablename(the_json_path, the_viewablename) } ) to the Data property of the grid  widget. How to extract  a json file from the pvz model (e.g. worldcar-brake-multi-figure.pvz-viewableSteplist.json) and how to define the service for lnfoTable generation  e.g. getViewableSteplist_arg_path_viewablename  -> this is shown in the post ( Extracting the viewables and the seqnece steps information from a .pvz file for the usage in TWX ).      The display of the Step List  is here provided only as info. Because there  was not possible to set the current /selected dataset in the grid widget. Therefore I used  here an additional List widget (callOut 6 ) which helps to manage this problem   6.) The area of the List widget is an area  which should be made invisible. It is used only to allow the setting of the selection in the grid element. We can bind the All Data of the service  (here : {Things["CAD-Files-Repository"].getViewableSteplist__arg_path_viewablename(the_json_path, the_viewablename) } ) to the Data property of the List  widget (callOut 6). The list element ( 6.) could be blanked because it is only used to set the selection of the  gird table (default  functionality of the Data selection )   How to extract  a json file from the pvz model (e.g. worldcar-brake-multi-figure.pvz-viewableSteplist.json) and to define the service for it  e.g. getViewableSteplist_arg_path_viewablename  - is shown in the post( Extracting the viewables and the seqnece steps information from a .pvz file for the usage in TWX ).   7.) This is the area with the panel of the Thingview (Thingview is a thingworx  extension). The thingview widget is part of the windchill navigate module. If the extension is installed , we can check this in the Extension manager:     In the package details we can see  that the thingview widget is installed on this thingworx instance  
View full tip
1.) The first point  here is to clarify : is it possible to extract model data of 3d models in Vuforia Studio?  ( data could be extracted by Creo View Toolkit apps but here is considered only the Vuforia Studio environment) Supposing , we have a model widget for an assembly model without explicit modelitem  widget definitions. The question is: Can we extract data for the components and if yes,  then what data we can extract? In Vuforia Studio Environment Extracting of data is possible only in Preview mode, because we have in preview mode the method tml3dRenderer.GetObject() where we can access a model object (a component) example:   let comp_widget=tml3dRenderer.GetObject(selection).GetWidget()   where the selection is some thing like "<modelname>-<compPath>" e.g. : "model-1-/0/0/3/2"   Then from the widget we can extract data:   var loc=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() console.error("DEBUG getObj.GetWidget()") console.warn(tml3dRenderer.GetObject(selection).GetWidget())   When we   explore  the different methods in the crome debugging console,  we will find methods to  get or  to set  properties. To extract data, we can use the get... methods.   The methods of  tml3dRenderer.GetObject() seems currently not to work in Vuforia View on end devices (the tml3dRenderer object is a handle of the cordova vuforia plug in and it has a different implementation on the different end devices.In preview mode so far I know, the graphic is based on WebGL and Three.js)  Therefore we will be not able for example to get the data of a component selection on the end device. So means we need a way to extract data in Preview mode and make it available in the Vuforia view on the end device.  Here I did not find a methods to extract the original component name but I was able to create a list (json) with the position data ( I did not add color but this is possible to access it - e.g. tml3dRenderer.GetObject(selection).GetWidget().GetColor()) We can create a json  e.g. of  following data:   {"model-1-/0/0/0":{"valid":false,"orientation":{"x":0,"y":0,"z":0}, "size":{"x":1,"y":1,"z":1},"scale":{"x":1,"y":1,"z":1}, "position":{"x":9.999999998199587e-24,"y":9.999999998199587e-24,"z":9.999999998199587e-24}}, "model-1-/0/0":{"valid":false,"orientation":{"x":0,"y":0,"z":0},"size":{"x":1,"y":1,"z":1}, "scale":{"x":1,"y":1,"z":1},"position":{"x":0,"y":0,"z":0}}, "model-1-/0/0/2":{"valid":false,"orientation":{"x":0,"y":90,"z":0},"size":{"x":1,"y":1,"z":1}, "scale":{"x":1,"y":1,"z":1},"position":{"x":0,"y":0.029500000178813934,"z":-5.51091050576101e-18}}, ...}   we can  assign the json to a variable e.g. $scope.COMP_LOCs So later we can read the current position data on end device:   var selection_location=$scope.COMP_LOCs[l_currentSelection] //read the location data from json varible console.log("selection:"+l_currentSelection+"->X= "+ selection_location.position.x); //print it to console selection_location.position.x= round(parseFloat(selection_location.position.x) + 0.005,4) //add 0.005 shift and round to 4 dec   2.)In point 1.)  we checked how to  extract the data of an compoent (a selection) .But Actually  we have a couple of methods to extract the data but what we do not have is a valid  selection of an assembly  component . This is required to obtain a valid modelitem widget (temporar) via tml3dRenderer.GetObject(). For the selection generation we have the model widget name e.g. “model-1” but   we do not have the component ID paths. To be able to construct a selection handle we need to construct the ID path of a component and then we need to check if it exist. This is some kind of graph search where we have an assembly with a components tree.  There the edges are the ids of the components. e.g. /0/0/1/1 , /0/0/1/2, /0/0/1/4, … etc. One possible algorithm is the deep first search:     To implement this I used the following javaScript code:   ///////////////////////////// var max_asm_depth=6; //this is the max depth in Creo Parametric var max_numb_comp_asm=25; /////////////////////////// ->deep first function check_comp_deep_first_recursively(target,path,arr) { //console.warn("called check_comp_deep_first_recursively(target="+target+",path="+path+")"); var selection = target+'-'+path var path_array = path.split('/') var depth = parseInt(path_array.length) var num = parseInt(path_array[depth -1]) var prev_num = parseInt(path_array[depth -2]) var prev_path = '' for (var i=1;i < depth -1;i++) {prev_path= prev_path +'/' + path_array[i]} if( check_for_valid_selection(selection) == 1) { arr[selection]=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() if( (depth+1) < max_asm_depth) check_comp_deep_first_recursively(target, path + '/0', arr) else { if(num +1 < max_numb_comp_asm) check_comp_deep_first_recursively(target, prev_path + '/'+(num +1), arr)} } else { var right_num = num +1 if(right_num < max_numb_comp_asm) check_comp_deep_first_recursively(target, prev_path + '/'+right_num, arr) else if(!Number.isNaN(prev_num) ) {//console.log("--2") prev_path = '' for (var i=1;i < depth -2;i++) {prev_path = prev_path +'/' + path_array[i]} prev_path = prev_path +'/' + (prev_num +1) check_comp_deep_first_recursively(target, prev_path , arr) } } } ////////////////////////// ///call of the function: $scope.compJSON_loc_Data = new Object(); var target="model-1" check_comp_deep_first_recursively(target,'/0',$scope.compJSON_loc_Data) ...   The code above has the following weak spot - I need to give the maximum depth (max_asm_depth) and the maximum possible branches (max_numb_comp_asm)  The maximum depth currently in Creo assembly is 25 so that value which > 25 will not make a sense.  The value of  max_numb_comp_asm  in a flat assembly (only one level of depth) corresponds to the number of the components - the maximum number of branches on particular level of depth   The another possible algorithm is the breadth first search:     To implement this  I used the following JavaScript code:   ///////////////////////////// var max_asm_depth=6; //this is the max depth in Creo Parametric var max_numb_comp_asm=25; /////////////////function check_comp_at_level(target,num,depth,arr) // ->breadth first function check_comp_at_level(selection,num,depth,arr) { var position =''; // console.log("call check_comp_at_level =>"+selection); try{ // console.log("====== check here ==========="); //console.warn(tml3dRenderer.GetObject(selection).GetWidget().GetLocation()); var loc=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() if( (loc.scale.x == 0) || (loc.scale.y == 0) || (loc.scale.z == 0) ) return 0; // the scale could not be zero //position= tml3dRenderer.GetObject(selection).GetWidget().GetLocation().position //console.warn(position); //arr[selection]=position arr[selection]=loc return arr[selection]; } catch (e) {console.error("failsed with error="+e); return 0;} } /////////////////////////// function check_comp_at_level_recursively(selection,depth,arr) { //console.warn("called check_comp_at_level_recursively("+selection+",depth="+depth+")"); var num =0; if(depth >= max_asm_depth) { //console.log("maximum depth of max_asm_depth ="+max_asm_depth+" reached"); return 0;} for (num=0;num < max_numb_comp_asm; num++) { var currentSelection =selection+'/'+num if(depth <0) return 0; var pos = check_comp_at_level(currentSelection,num,depth,arr) if(pos ==0 ) { continue;} else {check_comp_at_level_recursively(currentSelection,(depth+1),arr) } } //end of for } ////////////////////////// //////////////////////////////// function check_for_valid_selection(selection) { //console.log(" check_for_valid_selection =>"+selection); try{ var loc=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() if( (loc.scale.x == 0) || (loc.scale.y == 0) || (loc.scale.z == 0) ) return 0; return 1; } catch (e) {console.error("failsed with error="+e); return 0;} } /////////////////////////// ///call of the function: $scope.compJSON_loc_Data = new Object(); var target="model-1" check_comp_at_level_recursively(target,'/0',$scope.compJSON_loc_Data) ...     The code for the breadth first search uses also the parameters for maximum depth (max_asm_depth) and the maximum possible branches (max_numb_comp_asm)  - so means it have the mentioned  restriction. If we set a value which is large this will increase the time until the search is completed so therefore depending of the particular assembly we need to set the both parameter properly ( we need to be able to scan the whole assembly but to minimize the search time) For different assemblies the first deep or first breadth could lead to better results. For example, for flat assembly structures the better approach will be to use the first breadth algorithm  But actually the performance is not so important here, because the search will be called one time and  then the json list should be saved.  With the current functionality we can read a file (json file ) from the project  upload directory , but it seems that it is  not  possible to save the information to a e.g. json file there (upload folder). To read a json file form the upload folder we can use some code like this:     target='model-1' $http.get('app/resources/Uploaded/' + jsonFile).success(function(data, status, headers, config) { $scope.compJSON_mod=data; // in this case the data is the received json object angular.forEach(data , function(color, path_id){ $scope.compJSON_Data[path_id] =position; console.log("target="+target+" --> $scope.compJSON_Data["+path_id+"] = "+$scope.compJSON_Data[path_id]); });//end of the error function ////////// finish for each path_id }) .error(function(data, status, headers, config) {console.log("problem in the http will create a new ");   When we want to save data  (the generated json list) we need to use another workaround - we can use a thingworx repository. Following functions /events could be used to save and receive an json object to/from a twx repository:   // the methods SaveJSON and LoadJSON // for the repository object should have //run permision for es-public-access user ////////////////////////////////////////////////////////////// $scope.SaveJsonToTwxRepository = function(path, content) { $scope.$applyAsync(function() { $rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.SaveJSON', {"content": content, "path":path} );} ,500 ); }; ////////////////////////////////////////////////////////////// $scope.GetJsonFromTwxRepository = function(path) { $scope.$applyAsync(function() { $rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadJSON', {"path":path} );} ,500 ); $scope.app.speak("after call of GetJsonFromTwxRepository") //in the modelloaded listener register // LoadJSON-complete event -> to laod the data into session rootScope.$on('modelLoaded', function() { //// $scope.$root.$on('LoadJSON-complete', function(event, args) { console.log("LoadJSON-complete event"); $scope.COMP_LOCs=args.data console.log(JSON.stringify( $scope.COMP_LOCs)) }); /// });   In  the code above I use the 'modelloaded' listener to register LoadJSON-complete event . Because the service is called asyncronously- we need this event to load the data into session when it is received from thingworx. Here in this example the repository object is named "CAD-Files-Repository" The Thingworx services should have run permission and it is required to be added in the external data panel :     So when we start the project in PREVIEW mode we can call the search for the assembly structure and save it then  to thingworx. In Vuforia View mode   then we can receive the previously saved json object from thingworx. To check the current mode (if Preview or End Device)  we can use    if(twx.app.isPreview() == true) ...   it will  check if the current mode is preview mode or Vuforia View on the end device - here an example of the workflow:   if(twx.app.isPreview() == true) {// preview mode //calling breadth first - test check_comp_at_level_recursively(target+'-',0,$scope.compJSON_POS_Data) //console.warn($scope.compJSON_POS_Data) //calling deep first a second test and generating a data - locations check_comp_deep_first_recursively(target,'/0',$scope.compJSON_loc_Data) console.log("========================================") console.log("$scope.compJSON_POS_Data ->breadth first") console.log("========================================") console.log(JSON.stringify($scope.compJSON_POS_Data)) console.log("========================================") console.log("") console.log("") console.log("========================================") console.log("$scope.compJSON_loc_Data ->deep first") console.log("========================================") console.log(JSON.stringify($scope.compJSON_loc_Data)) $scope.SaveJsonToTwxRepository('/CADFiles/json_lists/compJSON_loc_Data.json',$scope.compJSON_loc_Data) $scope.GetJsonFromTwxRepository('/CADFiles/json_lists/compJSON_loc_Data.json') console.log("========================================") console.log("") } else { //here is the part on mobile device $scope.GetJsonFromTwxRepository('/CADFiles/json_lists/compJSON_loc_Data.json') }   I tested all points of  the described techniques above in a  test project which I want to provide here as zip file for the HoloLens (hideComponetsHoloLens .zip):     So to be able to test it you need to create in Thingworx a repository thing - means a thing which uses  the thing template "FileRepositroy" with the name "CAD-Files-Repository" and create a folder there "/CADFiles/json_lists/" (if you use another name and another folder (e.g. "/" no folder - the root repository folder) you have to adapt the javaScript code:   ... /CADFiles/json_lists/compJSON_loc_Data.json ... app.mdl.CAD-Files-Repository.svc.SaveJSON' ... app.mdl.CAD-Files-Repository.svc.LoadJSON'    
View full tip
Unfortunately, in the Vuforia Studio Documentation there is no complete List with the possible events which could be handled JS. Therefore for the first time this article tries to provide additional Information about known events :   1.) modelLoaded - is not required any more because the UI allow directly to specify this event. ... $rootScope.$on('modelLoaded', function() { //do some code here } ) .... 2.) Step completed example: scope.$on('stepcompleted', function(evt, arg1, arg2, arg3) { var parsedArg3 = JSON.parse(arg3); console.log("stepcompleted stepNumber="+parsedArg3.stepNumber + " nextStep="+parsedArg3.nextStep); $scope.app.stepNumber=parseInt(parsedArg3.stepNumber); $scope.app.nextStep=parseInt(parsedArg3.nextStep); $scope.app.duration=parseFloat(parsedArg3.duration); }); 3.) Event - stepstarted: ... $scope.$on('stepstarted', function(evt, arg1, arg2, arg3) { var parsedArg3 = JSON.parse(arg3); console.warn(arg3); console.log("stepstarted stepNumber="+parsedArg3.stepNumber); $scope.app.stepNumber=parseInt(parsedArg3.stepNumber); $scope.app.nextStep=parseInt(parsedArg3.nextStep); $scope.app.duration=parseFloat(parsedArg3.duration); }); ... Please, pay attention that on some platforms will not provide complete information  in stepstarted. So, In this case the complete info is available in 'stepcompleted' – the best is to test it.   4.) after entering in  a view in studio (e.g. Home ...):   ... $scope.$on('$ionicView.afterEnter', function() {$scope.populateModelList(); }); ... 5.) click/tap event on the current panel: ... $rootScope.$on('click', function() { tapCount++;console.log("click event called");} ); ... or  with coordinates ... document.addEventListener('click', function(event) {console.log("click() 1 called"); $scope.lastClick = { x: event.pageX, y: event.pageY}; }); ... you can also see this   topic.   6.) New step  -example: ... $scope.$on('newStep', function(evt,arg) { var getStepRegex = /\((\d*)\//; console.log(arg); console.log( getStepRegex.exec(arg)[1]); //check what it prints to the console - the step number }); ...    7.) Here is also  a more advance construct- it defines a userpick event e.g. for all models widgets: angular.forEach($element.find('twx-dt-model'), function(value, key) { // search all twx-td-model's -> means all model widgets angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { //for each model widget will set a userpick listener console.log('edata');console.warn(edata); console.log("JSON.parse(edata)");console.warn(JSON.parse(edata)); var pathid = JSON.parse(edata).occurrence; $scope.currentSelection = target + "-" + pathid; // this is the current selection - the selected component occurence // you can use it for example as shown below // try{ //tml3dRenderer.GetObject($scope.currentSelection).GetWidget().ApplyOccludeOpacity(OCLUDE_VAL,OPACITY_VAL); //} catch (e1234) {$scope.view.wdg['3DLabel-4']['text']= "e 1234exception in GetObject.GetWidget..."; } // } ) //end of the userpick defintion } ) //end of for each funciton  8.) tracking event:   ... $scope.$on('trackingacquired', function (evt,arg) { // alert('didStartTracking'); // this is not really needed $scope.message = parseInt($scope.app.params["currentStep"]); $scope.$apply(); }); $scope.$on('trackinglost', function (evt,arg) { // alert('didFinishTracking'); $scope.message = "Scan the ThingCode with your camera."; $scope.$apply(); }); ....   9.) popover event:     // var my_tmp = '<ion-popover-view><ion-header-bar> <h1 class="title">My Popover Title</h1> </ion-header-bar> <ion-content> My message here! </ion-content></ion-popoverview>'; $scope.popover= $ionicPopover.fromTemplate(my_tmp, { scope: $scope }); $ionicPopover.fromTemplateUrl('my-popover.html', { scope: $scope }).then(function(popover) { $scope.popover= popover; }); $scope.openPopover= function($event) { $scope.popover.show($event); }; $scope.closePopover= function() { $scope.popover.hide(); } //////////////destroy popover $scope.$on('$destroy', function() { $scope.popover.remove(); }); /////// hide popover $scope.$on('popover.hidden', function() { // your hide action.. }); // on remove popover $scope.$on('popover.removed', function() { // your remove action }); }); 10) watch event -watches are created using the $scope.$watch() function. When you register a watch you pass two functions as parameters to the $watch() function: 1)A value function 2)A listener function    When the value returned by function 1.) changes - this lead to execution of the funciton 2.) Example:   ... $scope.$watch(function(scope) { return $scope.view.wdg['label-1']['text'] }, // watches if change for the the text of label-1 //when changes then play a step for model-1 function() { console.log($scope.view.wdg["model-1"]); $scope.view.wdg["model-1"].svc.play; } ); ...   11.) Camera tracking - make sense only on mobile device- no sense for preview mode!   //// define tracingEvent only on end device tml3dRenderer.setupTrackingEventsCommand (function(target,eyepos,eyedir,eyeup) { // $scope.view.wdg['3DLabel-1']['text']="eyepos=("+eyepos[0].toFixed(2)+","+eyepos[1].toFixed(2)+","+eyepos[2].toFixed(2)+")"; $scope.app.params['target']=target; $scope.app.params['eyepos']="eyepos=("+eyepos[0].toFixed(2)+","+eyepos[1].toFixed(2)+","+eyepos[2].toFixed(2)+")"; $scope.app.params['eyedir']="eyedir=("+eyedir[0].toFixed(2)+","+eyedir[1].toFixed(2)+","+eyedir[2].toFixed(2)+")"; $scope.app.params['eyeup'] ="eyeup =("+ eyeup[0].toFixed(2)+","+ eyeup[1].toFixed(2)+","+ eyeup[2].toFixed(2)+")"; ///////////////////// },undefined); //// define tracingEvent only on end device } //end device   12.) There is also a sequenceloaded event, which is useful if you have a model with multiple sequences defined, and you are switching sequences dynamically in the experience.   $scope.$on("sequenceloaded", function (evt, arg) { console.log("sequence loaded, starting play"); $scope.setWidgetProp("loading","visible",false); $scope.app.fn.triggerWidgetService("model-1","playAll"); }); In this point is  here a good feedback comming from advance user (expert) : If you grep for "$emit" through a project folder, you can turn up the following event names: valueacquired (bar code scanner) usercanceled (bar code scanner) tracking modelloadfailed sequenceloaded newStep playstarted sequenceacknowledge playstopped sequencereset onReset If you grep for "$on(", you can find some additional ones: trackingacquired trackinglost modelLoaded click app-fn-navigate app-fn-show-modal app-fn-hide-modal $ionicView.afterEnter $stateChangeStart loaded3DObj loadedSeqErr loadedSeq $destroy select3DObj move3DObj loadError3DObj readyForZoom3DObj serviceinvoke stepstarted stepcompleted twx-entity twx-service-input twx-service-name This is a  good point and it seems that this list contains the most of the possible events.   Events Handling Feedbacks from EXTERNAL DATA  services  Such event is    the   twx-service complete   event. This event is called when we call a service registered in the external data and the service is completed. Here an example (also mention in the post): Here in the example in the external data the service LoadJSON was added.       ////////////////////////////////////////////////////////////// $scope.GetJsonFromTwxRepository = function(path) { $scope.$applyAsync(function() { $rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadJSON', {"path":path} );} ,500 ); $scope.app.speak("after call of GetJsonFromTwxRepository") //in the modelloaded listener register // LoadJSON-complete event -> to laod the data into session rootScope.$on('modelLoaded', function() { //// $scope.$root.$on('LoadJSON-complete', function(event, args) { console.log("LoadJSON-complete event"); $scope.COMP_LOCs=args.data console.log(JSON.stringify( $scope.COMP_LOCs)) }); /// });   So the code above shows how to call from JavaScript the service added to the external data. This service should return the called JsonObject. The call is asynchronously so that when  thingworx  will come back the listener 'LoadJSON-complete' will be called and here will print the content of the JsonObject to the console. Here the listener is registered inside the modelload event (this is event is coming late – so to be on the save side that everything is already initialized) This is generally that you for any Thingworx services added to the External data   <your_twx_service_name>-complete  the arg.data contains then the data which should be returned by the method.
View full tip
With various Augmented Reality applications in PTC's product portfolio the technical aspects and use cases could leave you with some questions. Did you know that we do not only have a full blown Augmented Reality SDK but offer also the possibility for a easy to use integration with live sensor data coming in via ThingWorx?   This blog post hopefully clarifies some of the questions around what can be done with Vuforia SDK and Vuforia Studio.   Welcome to the real world   In the real world, or the "real reality" (sounds weird, but it's basically what you can see with your own eyes - no augmentation involved) there are various objects. These might look the same - or not. Just take the following example... that's what we perceive when looking at things around us:     These objects are recognized​ via shapes, contrasts (black & white) and whatever defines the actual form. ​Vuforia SDK ​is able to recognize those objects via it's built-in object recognition capabilities. However, there might be limits - depending on the use cases...   While buildings could be distinguished by their form, playing cards could be distinguished via their suits and nominations. The machines however, they all look the same, they probably all ​are​ the same.   Combining the real world with a virtual world   "Augmented Reality" will allow to enhance this physical object with virtual properties, e.g. overlay its CAD-Model or overlay some animations for a better gaming experience. Check out this video for the Genesis Augmented Reality Trading Card Game example.   Object Recognition allows to put actual names to what the (digital) eye can see:     Once the object is recognized and identified all kinds of virtual attributes can be added. Vuforia SDK allows to do this with e.g. Unity.   As all of the machines are basically the same... they look the same, come from the same manufacturer and behave the same, ​identification​ can only be done via a manual effort, e.g. selecting the actual machine manually within an app (via a menu etc.). This manual selection process will then map a generic form and shape of the machine to the actual physical machine you can see and touch just in front of you.   In an app this might be necessary if you can recognize the generic form of a playing card but forgot to implement the suit and nominations. In that case, either extend the recognition part, or choose a drop-down list when the card is identified to choose the actual​ card in front of you.   How do ThingMarks fit in?   Using the functionality of Vuforia SDK, Vuforia Studio combines the power of Vuforia (AR) with the power of ThingWorx (live sensor data / object information). In an industry environment I could select the correct machine I'm looking at. However, what's the identifier? It is probably written somewhere on the back of the machine with lots of other information, so I don't really know what to look for. Therefore I could be looking at any machine, but without the identifier I can not retrieve information for ​my​ machine.   Vuforia Studio uses ThingMarks​. They work similar to a QR-Code and allow for direct identification of individual machines. So instead of choosing manually in the app, the ThingMark automatically chooses the correct object and relates that ID to a Thing Entity in ThingWorx.     In above image, the ThingMark allows to a) identify we're looking at a machine and b) are looking at the specific machine A03 It's basic point and shoot. Scan the ThingMark with your mobile device and you're directly taken to this particular experience for this particular machine.   In this case, it's not the machine that defines our object's properties and shapes and contrasts and sizes etc. In this case, it's the ThingMark that's the object being recognized. That's quite a difference.   So now, in an additional step, we're using the power of Vuforia to identify individual machines by a ThingMark. Recognition is driven by the ThingMark's shape which includes an encoded object ID (the QR-code looking pattern).   How does ThingWorx fit in?   After recognizing the machine, ThingWorx studio provides the link between this specific object (or its instance) and the ThingWorx Thing Entity we've defined in Vuforia Studio.   This allows to retrieve individual properties, services, events, alerts etc. directly via ThingWorx. Those values are unique per object, not per shape!   So this allows to directly look at temperature, level and failure-indicator for the actual machine in front of us:     Bridging the gap   Vuforia Studio​ is used to bridge the gap between ​Vuforia ​and its Augmented Reality capabilites as well as ThingWorx ​and its Internet of Things (IoT) capabilities. Vuforia Studio uses parts of both applications, adds own functionality and defines its own product category: Connected Augmented Reality​     There are quite some components involved in this:     This can be split into two processes: developing and experiencing   Development   Create a new experience in VuforiaStudio, map the experience to the ThingMark ID, map the experience to a Thing Entity in ThingWorx. Publish the experience to the Experience Server. Done.   Experience   Scan the ThingMark with the Vuforia View app. Vuforia View will utilize Vuforia to recognize the ThingMark Vuforia View will load the data and the model(s) for this ThingMark from the Experience Server Vuforia View will automatically receive and update the experience you're viewing with live data from the ThingWorx platform Enjoy.   Resources   There are quite some videos, tutorial, best practices etc. available on how to develop and experience the world of Vuforia Studio. Check out ThingWorx Studio Resources: Getting Started Guides, Tutorials, Troubleshooting for the Article Hub and quite a lot of good stuff!   More information   To get more information visit the product pages at https://www.vuforia.com https://trial.studio.vuforia.com/   If you're looking for help, these might be of interest:   https://developer.vuforia.com/support for Vuforia SDK https://community.ptc.com/t5/Studio/bd-p/studio for Vuforia Studio https://community.ptc.com/t5/ThingWorx-Developers/bd-p/twxdevs for ThingWorx https://support.ptc.com/     What's next?   Get involved, create your own experience. It's fun, it's quite easy and well... it looks good, too!  
View full tip
Using Connection Filters for state-based rendering and value formatting   State-based rendering of properties that come from user inputs or from connected ThingWorx data is a common requirement. Think about the following scenario: from ThingWorx you get the state of a as one of these values: [ OK | At-Risk | Issue ]. You would like to visualize it with a 3D image or a changed 3DGauge icon.   You can achieve that easily with a Vuforia Studio functionality that is often overseen: The Connection Filters. With connection filters you can achieve a lot of very nice a common features, mainly: number and date formatting value pre- and postfixing (specifically units for numbers but also anything else) state-based formatting, e.g. changing the image or style based on a number change   Here is what I mean in practice:   Step 1:   Step 2:   Here is the stateBasedStyling Connection Filter content:   return value < 3000 ?    "fill:rgba(255, 255, 255, 1);textbaseline:middle;textalign:center" :    "fill:rgba(255,   0,   0, 1);textbaseline:middle;textalign:center" ;   value is a key word and represents the value on the connection before applying the filter function. The returned value is the one that is used in the bound-to attribute. I found that the expression has to be a one-liner (technically), i.e. you have to start your expression with 'return' and can't do something like:   if(value < 3000) return "fill:rgba(255, 255, 255, 1);textbaseline:middle;textalign:center"; else return "fill:rgba(255,   0,   0, 1);textbaseline:middle;textalign:center";     Resulting Filter in ThingWorx Studio: You can add multiple filters but I didn't check yet in which sequence they'll be evaluated (top-to-bottom or bottom-to-top).     Examples of Usage   1. Show icons for Enum values: I used it often to display enumerated-type values (i.e. the input values are from a discrete value set e.g.named colors) with images/icons:   return "app/resources/Uploaded/colorImg_" + value + ".png" ;   in combination with the corresponding uploaded images, in this case named colorImg_red.png, colorImg_blue.png, etc.   2. Format Numbers and postfix with unit The following formats the input number 12.769 to 12.8 mph.   return $filter ( 'number' )(value, 1) + " mph" ;     See the AngularJS documentation for the available filter types. Relevant are: currency number date   I haven't tested yet if these filters can also be applied to input arrays of objects that come from a service (e.g. for a repeater widget). If this is supported the array-based filters may be applicable/usable as well.  
View full tip
If the experience project exists in Vuforia Studio Unpublish the project by hovering over the project and clicking the unpublish project Experiences icon . This action removes Experiences from the Experience Service. If the experience project does not exist in Vuforia Studio Using CURL Command Curl -u <username>:<password> -H "Content-Type: Application/JSON" -X "DELETE" https://<your-domain-name>/ExperienceService/content/projects/<projectname> username: Experience Service username password: Experience Service password your-domain-name: Experience Service domain projectname: Experience project name to be deleted Using REST call from Postman Select query method as 'DELETE' Enter the URL as https://​<your-domain-name>/ExperienceService/content/projects/<project-name> your-domain-name: Experience Service domain projectname: Experience project name to be deleted In Authorization menu Choose Authorization type as 'Basic Auth'. Add the user credentials and update request.  
View full tip
Your company might have a css that represents the corporate identity - or you may have other sources of reusable css styles that you want to include with minimal effort. Here is what you have to do to use corporate css files to drive the look and feel of your experiences: Add the corporate css file (e.g. company.css) to your resources In Application styles add the following at the beginning (before any other css style entry: @import url(#{$resources}/Uploaded/company.css);     With the following content in company.css: And this label definition:   Produces this outcome (you see it in the editor as well as in the preview):   Gotcha!    
View full tip
Just helped someone who was seeing a difference between the Preview mode and what Vuforia View was showing. The experience was being developed for public access, so what was happening is that Preview mode worked because they were using their Studio credentials to run the Thingworx service. When they used Vuforia View, they were using es-public-access to run the Thingworx service. And es-public-access did not have the runtime permissions to execute the service.   The error indication was found in the Application Log complaining about the Entity and Service not being able to be run.   Don't forget to set your permissions for es-public-access so it can run your services!  
View full tip
With release 1.9.1, pilot and free trial participants can auto-configure Vuforia Studio to make it easier to get up and running quickly.  The auto-configure process does the following:​ Configures the sample projects included with your Vuforia Studio installation so that when you publish those projects they are published to your experience service and can be viewed in Vuforia View using one of your ThingMarks Retrieves the Experience Service (ES) URL - can find at Project -> Configuration -> Info section. We are no longer sending the ES url through welcome email. Downloads your ThingMarks and makes them available on the My ThingMarks page inside Vuforia Studio so that you can view your ThingMarks and print them out In order to complete the auto-configuration process, users are first required to authenticate using their PTC Account credentials.  For participants in the Vuforia Studio Free Trial , this does not introduce any confusion since they use their PTC Account credentials for everything: accessing the Studio Portal, publishing experiences from Vuforia Studio, downloading experiences to Vuforia View and working in ThingWorx Composer.   However, for participants in the Vuforia Studio Pilot Program , this may introduce some confusion.  Unlike free trial participants, pilot participants have two sets of credentials: PTC Account credentials used to access the Studio Portal and Auto-Configure Vuforia Studio Experience Service credentials provided in their Pilot Program Welcome Email that are used to publish experiences from Vuforia Studio, view experiences in Vuforia View and access ThingWorx Composer The auto-configuration process requires users to authenticate using their PTC Account credentials.  Since the auto-configuration process occurs inside Vuforia Studio and pilot participants do not normally use their PTC Account credentials inside Vuforia Studio, this may cause some confusion.   Note   The users that received access to an experience service instance before February 17, 2017 is a participant in the pilot program Any user that received access to an experience service on or after February 17, 2017 is a participant in the free trial.  
View full tip
This is the third JavaScript quark in the series: it can be used to change a widget color by cycling through a given array of colors. You can find the second quark here.   Here's the code to copy & paste  to your H ome.js :   $scope.cycleColors = function(widget, colors, time, interval) {   let w = (widget.color !== undefined ? widget : $scope.view.wdg[widget]);   if (!w || w.color === undefined) { throw "Cannot color-cycle this widget"; }   let originalColor = w.color;   w.color = colors[0];   w.visible = true;   w.opacity = 1;    let nSteps = Math.ceil(Math.floor(time/interval) / colors.length) * colors.length;   return $interval(iterationCount => w.color = iterationCount === nSteps ? originalColor : colors[iterationCount % colors.length], interval, nSteps); } This JavaScript quark will make the widget color cycle through the colors provided in the colors array. The effect will last time milliseconds, and each color change will happen every interval milliseconds.   Invoke the function like this:   cycleColors(widget, colors, time, interval);   where   widget   is either the id of the widget (e.g.   modelItem-1) or the widget itself (e.g. $scope.view.wdg['modelItem-1']), colors represents an array of colors (e.g. ["rgba(200,0,0,1)", "rgba(0,0,200,1)"]), time represents the total time in milliseconds it takes to execute this effect, and interval represents the amount of time in milliseconds between each color change.   Here's an example:   cycleColors("modelItem-1", ["rgba(200,0,0,1)", "rgba(0,200,0,1)", "rgba(0,0,200,1)"], 2000, 50); This example cycles the model item color through red, green and blue; the effect will last for 2 seconds with a color change every 50 ms.   Comments and suggestions are welcome.   -Alessio  
View full tip
This is the second Javascript quark in the series: it can be used to fade a widget out. You can find the first quark here.   Here's the code to copy & paste  to your H ome.js :   $scope.fadeOut = function(widget, time, interval) { let w = (widget.opacity !== undefined ? widget : $scope.view.wdg[widget]); if (time <= 0 || interval <= 0 || w.opacity === undefined) { throw "Cannot fade this widget"; } let steps = Math.floor(time / interval); let opDelta = w.opacity / steps; return $interval(() => w.opacity = (opDelta < w.opacity) ? (w.opacity - opDelta) : 0, interval, steps); } This quark will make the widget fade out from its current opacity to 0 in time milliseconds, uniformly decrementing opacity at every interval .   Invoke the function like this:   fadeOut(widget, time, interval);   where   widget   is either the id of the widget (e.g.   modelItem-1) or the widget itself (e.g. $scope.view.wdg['modelItem-1']), time represent the total time it takes to fade the widget out from its current opacity, and interval represents the amount of time between each opacity change.   Here's an example:   fadeOut('modelItem-1', 2000, 50); This example fades the model item out by bringing its current opacity to zero after 2 seconds with an opacity change every 50 ms.   Comments and suggestions are welcome.   -Alessio    
View full tip