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

Community Tip - New to the community? Learn how to post a question and get help from PTC and industry experts! X

Vuforia Studio and Chalk Tech Tips

Sort by:
How can we make a 3D Widget on HoloLens visible in front of me every time ?  The goal here is to get a some UI containing 3D Widgets (3dButtons , 3dImages , 3d Labels and 3d Models) in front of current HoloLens view. For example after saying "show UI" the UI elements should appear in front view e.g. distance of 1,5 meters. Example:   In the Tech Tip article "How to create a custom button pressable on HoloLens device?" is shown how to create a custom UI button. Now  based on the example of this project   the functionality will be extended by adding the possibility for dynamic display of the UI elements in front of the current view. To be able to move the elements in front of us we need to calculated the current view matrix and the inverse matrix  of if.  For this goal we need to prepare some mathemtics function like , matrix inverse calculation, marix multiplication , matrix with vector multiplicaiton, vector product und dot vector product ... etc. When we sear in Internet there a lot of sources where we can find the informormation how to implement such mathematical tools. The first steps is to calculate the view matrix - e.g. some code:   ... function get_mat4x4_camera(eyepos,eyedir,eyeup) { // printVector(eyepos, "eyepos") // printVector(eyedir, "eyedir") // printVector(eyeup, "eyeup") var mat4x4 =[[1.0,0.0,0.0,0.0],[0.0,1.0,0.0,0.0],[0.0,0.0,1.0,0.0],[0.0,0.0,0.0,1.0]]; var i=0; var eyeZaxis = this.vec_normilize(eyedir); var eyeYaxis = this.vec_normilize(eyeup); var eyeXaxis = this.vec_normilize(this.vec_product(eyeYaxis,eyeZaxis)) for (i=0;i<3;i++) mat4x4[i][0] = eyeXaxis[i]; for (i=0;i<3;i++) mat4x4[i][1] = eyeYaxis[i]; for (i=0;i<3;i++) mat4x4[i][2] = eyeZaxis[i]; for (i=0;i<3;i++) mat4x4[i][3] = eyepos[i]; //for (i=0;i<3;i++) mat4x4[i][3] = 0.0-eyepos[i]; return mat4x4; //return this.transpondMat(mat4x4); } ////////////////////// vector with length =1.0 function vec_normilize(ary1) { ret=[]; var len=this.vec_len(ary1); for (var i = 0; i < ary1.length; i++) ret[i]=ary1[i]/len; return ret; }; //vector product range =3 function vec_product(a, b) { var vecprod =[0.0,0.0,0.0]; vecprod[0]=a[1]*b[2]- a[2]*b[1]; vecprod[1]=a[2]*b[0]- a[0]*b[2]; vecprod[2]=a[0]*b[1]- a[1]*b[0]; return vecprod; };   When we find programming code for mathematical operation we need to verify it by some examples where we know the results, because often there are some math sources which does not implement the correct solution. I think a good resource are e.g.: https://www.learnopencv.com/rotation-matrix-to-euler-angles/  https://www.learnopencv.com/ https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web So that we do not need to re-invent the wheel, but we need to be careful to find the correct mathematical relations and to test and verify if they really work. Next step is to calculate the invert matrix of the view matrix.  A good implementation I found is this one:   function matrix_invert(M){ // I use Guassian Elimination to calculate the inverse: // (1) 'augment' the matrix (left) by the identity (on the right) // (2) Turn the matrix on the left into the identity by elemetry row ops // (3) The matrix on the right is the inverse (was the identity matrix) // There are 3 elemtary row ops: (I combine b and c in my code) // (a) Swap 2 rows // (b) Multiply a row by a scalar // (c) Add 2 rows //if the matrix isn't square: exit (error) if(M.length !== M[0].length){return;} //create the identity matrix (I), and a copy (C) of the original var i=0, ii=0, j=0, dim=M.length, e=0, t=0; var I = [], C = []; for(i=0; i<dim; i+=1){ // Create the row I[I.length]=[]; C[C.length]=[]; for(j=0; j<dim; j+=1){ //if we're on the diagonal, put a 1 (for identity) if(i==j){ I[i][j] = 1; } else{ I[i][j] = 0; } // Also, make the copy of the original C[i][j] = M[i][j]; } } // Perform elementary row operations for(i=0; i<dim; i+=1){ // get the element e on the diagonal e = C[i][i]; // if we have a 0 on the diagonal (we'll need to swap with a lower row) if(e==0){ //look through every row below the i'th row for(ii=i+1; ii<dim; ii+=1){ //if the ii'th row has a non-0 in the i'th col if(C[ii][i] != 0){ //it would make the diagonal have a non-0 so swap it for(j=0; j<dim; j++){ e = C[i][j]; //temp store i'th row C[i][j] = C[ii][j];//replace i'th row by ii'th C[ii][j] = e; //repace ii'th by temp e = I[i][j]; //temp store i'th row I[i][j] = I[ii][j];//replace i'th row by ii'th I[ii][j] = e; //repace ii'th by temp } //don't bother checking other rows since we've swapped break; } } //get the new diagonal e = C[i][i]; //if it's still 0, not invertable (error) if(e==0){return} } // Scale this row down by e (so we have a 1 on the diagonal) for(j=0; j<dim; j++){ C[i][j] = C[i][j]/e; //apply to original matrix I[i][j] = I[i][j]/e; //apply to identity } // Subtract this row (scaled appropriately for each row) from ALL of // the other rows so that there will be 0's in this column in the // rows above and below this one for(ii=0; ii<dim; ii++){ // Only apply to other rows (we want a 1 on the diagonal) if(ii==i){continue;} // We want to change this element to 0 e = C[ii][i]; // Subtract (the row above(or below) scaled by e) from (the // current row) but start at the i'th column and assume all the // stuff left of diagonal is 0 (which it should be if we made this // algorithm correctly) for(j=0; j<dim; j++){ C[ii][j] -= e*C[i][j]; //apply to original matrix I[ii][j] -= e*I[i][j]; //apply to identity } } } //we've done all operations, C should be the identity //matrix I should be the inverse: return I; }   Further we need to extract from the inverse matrix the Euler angels for rx , ry, and rz: //============================== function lcs2Euler( X1x, X1y, X1z, Y1x, Y1y, Y1z, Z1x, Z1y, Z1z) { var x=0; var y=0; var z=0; var sy = Math.sqrt(X1x * X1x + Y1x * Y1x); if (!sy < DBL_EPSILON) { x = Math.atan2( Z1y, Z1z); y = Math.atan2(-Z1x, sy); z = Math.atan2( Y1x, X1x); } else { x = Math.atan2(-Y1z, Y1y); y = Math.atan2(-Z1x, sy); z = 0; } //printVector ([pre,nut,rot],"lcs2Euler"); return [x,y,z]; } We already calucalted the rotation and now we need to calculate the x,y, z postion. This will be a point which is in front of use - along the gaze vector with particular distance and then x, and z corrinates of the view X and View Y vector: $scope.setWidgetProp( wdgName, 'rx', rad2deg(EulerAnglesInv[0]) ) $scope.setWidgetProp( wdgName, 'ry', rad2deg(EulerAnglesInv[1]) ) $scope.setWidgetProp( wdgName, 'rz', rad2deg(EulerAnglesInv[2]) ) var eye_dir_norm = vec_normilize($scope.eyedir) //projected to normal var eyeZaxis = eye_dir_norm; var eyeYaxis = vec_normilize($scope.eyeup); var eyeXaxis = neg_dir(vec_normilize(vec_product(eyeYaxis,eyeZaxis))) var newCalc = []; // here it will translate along the eye_dir and move in x and y axis for (var i=0; i<3; i++) newCalc[i]= $scope.eyepos[i] + disScale*eye_dir_norm[i] + Xcomp*eyeXaxis[i] + Ycomp*eyeYaxis[i]; //correction of the translation to the view plane in distnace of disScale $scope.setWidgetProp( wdgName, 'x', newCalc[0] ) $scope.setWidgetProp( wdgName, 'y', newCalc[1] ) $scope.setWidgetProp( wdgName, 'z', newCalc[2] )   So that now we can display the UI widgets with some calls like this: $scope.setWdgetPos('ShopingCard3DImage', 1.8, -0.25, 0.35) $scope.setWdgetPos('3DImage-1' , 1.6, 0.28, -0.1) $scope.setWdgetPos('3DImage-2' , 1.6, 0.28, 0.05) $scope.setWdgetPos('3DImage-3' , 1.6, 0.28, 0.3) $scope.setWdgetPos('3DButton-1' , 1.6, -0.25, -0.1) $scope.setWdgetPos('3DButton-2' , 1.6, -0.25, 0.0) $scope.setWdgetPos('3DButton-3' , 1.6, -0.25, 0.1) $scope.setWdgetPos('model-2' , 1.7, 0, 0.1 , 1) $scope.setWdgetPos('3DLabel-1' , 1.6, 0.25, 0.14)   In this article I provided a demo project which is attached to this article as zip file. I attached also the javascript math function which I used.    Where we have the widget name, distance along the gaze vector from the device and the X and Y position /X and Y view vectors /screen  To be able to get the device  gaze , up(y) and the x vector we can use the 'tracking' event:   //==================== $scope.eyeMat=[]; $scope.eyeInvMat=[]; //==================== $rootScope.$on('tracking', function( tracker,fargs ) { for(var i=0; i<fargs.position.length; i++) {$scope.eyepos[i] =fargs.position[i]; $scope.eyedir[i] =fargs.gaze[i]; $scope.eyeup [i] =fargs.up [i]; } $scope.eyeMat =get_mat4x4_camera(fargs.position,neg_dir(fargs.gaze),[0,1,0]); $scope.eyeInvMat = matrix_invert($scope.eyeMat) var EulerAngles =transfMat2Euler($scope.eyeMat); var EulerAnglesInv =transfMat2Euler($scope.eyeInvMat); $scope.$applyAsync(); //////////////////////// finished tml3dRenderer.setupTrackingEventsCommand })   The tracking callback function will save the current view vectors (gaze, up, postion) to a global variables which could be used later from the other functions. I created a demo project which is one possible example showing how we can implement such functionality. The sample project is for the HoloLens - I tested with on preview mode and on the  HoloLens 1 device and it was working fine. (- zipped project with password "PTC" - please, unzip it and import it to Studio) A note about the attached file: - the file project-"HoloLens3dFlexibleUI-article_ExampleProject.zip" should be unzipped to HoloLens3dFlexibleUI-article.zip - which is a project file for Vuforia Studio - demo project and could be imported in the Vuforia Studio UI. To unzip the project-example.zip you need a password simple: 'PTC ' - in capital characters -the file: myMathFunc.zip contains the used mathematical function definitions (zipped js file) . The javascript file is also contained by  the Studio project/ upload folder. When we test the project:     Where we see in front in the middle a cursor where we can tab or we can say show UI to display the UI:  
View full tip
The new 3D-Guided Service Instructions example use case walks you through creating a Vuforia Studio Experience that will allow a frontline worker to:  Find the physical location of a broken part on an object using a digital model  Search for parts on a 3D model of the object  Find and order replacement parts from a vendor using a persistent shopping cart   The 3D-Guided Service Instructions use case will walk you through the following sections: 3D-Guided Service Instructions 101: Use Attributes in Creo Illustrate  3D-Guided Service Instructions 201: Use JavaScript to Highlight Parts and Create Ionic Popups  3D-Guided Service Instructions 202: Use JavaScript to Find Parts  3D-Guided Service Instructions 301: Add Pricing Data and a Shopping Cart to a Model  3D-Guided Service Instructions 302: Add a Simple ThingWorx Service to Vuforia Studio  3D-Guided Service Instructions 303: Create a Persistent Shopping Cart Using ThingWorx
View full tip
With release 9.1.0, Vuforia Studio now supports Area Targets. Area Targets can be used when creating spatial instructions using the surroundings as interactive elements to be explored. For example, you can use an Area Target when you want to deliver augmentations to stationary objects in a scanned environment. You'll need to create an Area Target before you can import it into Vuforia Studio to leverage the Area Target widget. Below is an overview of the required steps to create and upload Area Targets for Vuforia Studio.   Step 1: Create a Vuforia Engine Developer Account   This account is separate from your regular PTC support account and is required to generate Area Targets.   Step 2: Download a Target Generator Once you've created a Developer account, you'll need to download a target generator. The target generator you download will depend on the device used to scan your environment.   Vuforia Studio currently supports scans created with: Matterport Pro2 Camera Requires the Area Target Generator desktop application. iOS devices with LiDAR sensors Requires the iOS app Vuforia Area Target Creator to scan and create Area Targets Step 3: Create an Area Target   Creating an Area Target using the Matterport Pro2 Camera:   Log into the Vuforia Area Target Generator with your Vuforia Developer Credentials Select New Area Target from Matterport Scan Enter your Matterport token information See Retrieving Models with the Matterport™ API Token for instructions on obtaining your token ID Enter the Scanned Space ID and Area Target Name The Scanned Space field is the Matterport™ SpaceID and a unique 11-digit ID that is associated with your individual scans. Find the unique ID of your scan by opening the scanned space from your Matterport™ account. Copy the entire URL, or just the SpaceID portion as illustrated and paste it into the Scanned Space  field. Area Target name must not contain any spaces Select Create Area Target to generate an Area Target of your scan. Full instructions for creating a scan and area target using the Matterport Pro2 Camera can be found here.   Creating an Area Target using the Vuforia Area Target Creator (requires internet connection): Login with your Vuforia Developer Credentials from step 1. Select the Plus Button to create a new scan and enter a scan name Start the scan Press the Stop Button to finish the scan Select the Legacy Dataset option, then select Generate to generate an Area Target database     Step 4: Prepare your files for Studio   NOTE: All filenames are case sensitive   Area Targets created with the Vuforia Area Target Generator: Navigate to the location you entered when creating your Area Target Location defaults to C:/Users/<username>/Documents/Vuforia Areas Create a zip file titled <target_name>.zip that contains ONLY: <target_name>.dat <target_name>.xml <target_name>_authoring.glb Area Targets created with the Vuforia Area Target Creator: Share your files to your Studio authoring machine Press the Container button and swipe one of the Area Target datasets listed to the left. A share icon will appear. Press the Share icon next to a listed dataset Select your transfer method It is possible to manually pull the files directly from your device. Using iTunes or a similar file explorer, navigate to the app's AreaTargetData folder and copy the desired Area Target directory.  Ensure that your <target_name>.zip file contains ONLY: <target_name>.dat <target_name>.xml <target_name>_authoring.glb Step 5: Upload your Area Target file to Vuforia Studio Add an Area Target widget your canvas Select the green plus button next to Data set  Browse to the location of your <target_name>.zip file and select open   See our Help Center for more information on Area Targets in Vuforia Studio.
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
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
How to create a custom button pressable on HoloLens device? We can create a customer button/button panel on HoloLens performing the following steps: Create a model with button and panel / it could be a panel with more buttons. In this example I used Creo Parametric for the 3d model creation – I estimated for this sample model I needed  30 – 60 Minutes .   The assembly above consist of 3 parts, panel and button. The panel is a part with a pattern of hole feature - in case that we need a circular button. If we need a button with other shape we could use a pattern of cut features     Here in this example I created a button which is circular and has a text which is a cut of the top surface. To have a different button’s I created a family table where a parameter for the text was added. I used the parameter in a relation to change the text in the cut.     Later I added the panel in a new component as default placement and added to each hole feature a button as repeat component. After all components are added to the assembly (the generic part)  we can replace each component (the generic part - the part which defines the family table) by the family table instance - so to have a buttons with different texts. Above we can see in the picture that in the instance "BUTTON3D_15" we have the value “BN15” for the parameter BNTTXT  which is displayed in the button text.     I attached the assembly (created in Creo Parametric 6.0). You can open the button part (button3d.prt) and edit the family table - the values of the parameter BNTTXT - to have other text values for the different buttons. Then you need to verify and to regenerate the part. Please, pay attention, that the text should be should be not so long , because the button top surface is small The last point here is to export /save as / the assembly to pvz format   2.) Create a Vuforia Studio project for HoloLens device. For the button panel we need to add a new modelWidget and for each button we can define a modelItem widget:     To make the project now to work we need to define a JavaScript code. Here the code is define in the Home.js define the callback function for the click on a button event/action: //============================================================ $scope.app.clickButton= function(target) //work of ModelItem widgets { var zDelta=0.035 var buttonReactionTime=1.2 if($scope.view.wdg[target.toString()].opacity==0.8) return; //ignorre the double click if(!target.toString().startsWith("bnt")) {console.log("not bnt button");return;} //extracts only the number of the string var btnNr= parseInt(target.toString().replace(/bnt/g,'')); console.log("btnNr="+btnNr) var currZvalue=$scope.view.wdg[target.toString()].z +zDelta console.log("1.).currZvalue="+currZvalue) $scope.setWidgetProp( target,"z",currZvalue) $scope.setWidgetProp( target, 'color', "rgba(255,0,0,1.0)") $scope.setWidgetProp( target, 'opacity', 0.8) $scope.$applyAsync(); $timeout(()=>{ currZvalue=$scope.view.wdg[target.toString()].z -zDelta console.log("2.).currZvalue="+currZvalue) $scope.setWidgetProp( target,"z",currZvalue) $scope.setWidgetProp( target, 'color', "rgba(3,255,22,1.0)") $scope.setWidgetProp( target, 'opacity',0.4 ) $scope.setWidgetProp( "3DLabel-1", 'text', $scope.view.wdg["3DLabel-1"].text+btnNr) $scope.$applyAsync(); },buttonReactionTime*1000) } //============================================================ In this function we have as input the target / this is the modelItem widget clicked - here the button/. The function will move the button in Z direction , will change it's color and will add the button text to a label /some kind as input/. After some delay it will move the button back and will set the color to the old value The next definition is the definition of the listener which will handle the button click and will call the button callback function for the clicked item: //============================================================ $scope.userpickDef= function() { // define the button action document.addEventListener('userpick', function (ev) { var widget = ev.target.closest('twx-widget'); if (widget) { console.log('*->the widget clicked is ', widget.getAttribute('widget-id')); $scope.app.clickButton(widget.getAttribute('widget-id').toString()) } else { var widget = twx.app.getScope(ev.target); if (widget) { console.log('**->the widget clicked is ', widget._widgetId, widget); $scope.app.clickButton( (widget._widgetId).toString()) } } }); }; //============================================================   finally we need to add a setup code which we could call e.g. on modelLoad event:   $rootScope.$on("modelLoaded", function() { $scope.view.wdg['btnPanel'].shader = "xray"; $scope.view.wdg['btnPanel'].occlude = 0.2; for(var i=1 ; i<=15; i++) { var wdgName = "bnt"+i.toString(); $scope.setWidgetProp( wdgName, 'color', "rgba(3,255,22,1.0)") $scope.setWidgetProp( wdgName, 'opacity', 0.4) } $scope.$applyAsync(); $scope.userpickDef(); }); Here we could set the opacity and the color of all buttons and define the button click listener. I added to this post the Creo Parametric Assembly model, the panel model as pvz and the demo project. 
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
Getting Orientation of Mobile Device via Javascript and detecting of device rotation on runtime. In some cases it could be a goal to get change the text of a widget based on the mobile devices orientation. This could be done  via Javascript and css. Follwong possible solutions:  In CSS. More details here https://developer.mozilla.org/en-US/docs/Web/CSS/@media/orientation In Javascript with various method: Here is some documentation : https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation   As we can read, it is not supported in Safari web browser for iOS.   In a new Project, in 2D canevas, I have added one Button and one Label named label-Result. In home.js, we can create this function very similar to the one provided in documentation above.   $scope.screenOrientation = function() { var orientation = screen.msOrientation || (screen.orientation || screen.mozOrientation || {}).type; if (orientation === "landscape-primary" || orientation === "landscape-secondary") { $scope.setWidgetProp("label-Result", "text", "landscape"); console.log("landscape"); } else if (orientation === "portrait-primary" || orientation === "portrait-secondary") { $scope.setWidgetProp("label-Result", "text", "portrait"); console.log("portrait"); } else if (orientation === undefined) { $scope.setWidgetProp("label-Result", "text", "The orientation API isn't supported in this browser"); console.log("The orientation API isn't supported in this browser :("); } } The following observation when we test it :   In Preview, in Chrome web browser, in my workstation in Windows when clicking the button, result is always landscape on Samsung S7 and S9+, in Vuforia View when clicking the button, result is always portrait. Vuforia View doesn't change screen position and rotating the mobile In iPad 6 generation, in Vuforia View when clicking the button, result is The orientation API isn't supported in this browser. Vuforia View screen is changing when rotating tablet So the JavaScript solution above has some problems which we can try to fix using another approach: As mentioned the previous solution could be used only on start- but it will not detect a dyncamic change - e.g. rotate the device.This solution  was tested and working in preview and on android. It seems that it does not work on IOS.  ON HoloLens we have  only landscape mode- so that this has no relvance. For Preview mode and Android devices the  following code is working fine: //============================================ angular.element(document).ready(function() { angular.element(window).on('resize', function(evt) { //console.log("resized window evnt :");console.warn(evt); var message = '' var win = evt.path[0]; if(win.innerWidth > win.innerHeight){ // LANDSCAPE -> do something here call your landscapeFunc() message = "current orientation is Landscape!" } else { // PORTRAIT -> do something here your PortraitFunc() message = "current orientation is Portrait!" } twx.app.fn.addSnackbarMessage(message,"twLogo"); }); }) //////////////////////  We need to pay attion here that we are in angular js environment and  the windows variable seems to  be static an is passed on system start - therefore it will not update dynamicaly. There is also an soluton for IOS which was verfied in a tests - e.g.  it works fine (IPad 6 generation).  Possibly this solution will work also on window -need to be testet. Following code: function readDeviceOrientation() { //only for IOS var orient="unknown" switch (window.orientation) { case 0: orient= "Portrait" break; case 180: orient= "Portrait Upside-down"// Portrait (Upside-down) break; case -90: orient= "Landscape (Clockwise)"// Landscape (Clockwise) break; case 90: orient= "Landscape (Counterclockwise)"// Landscape (Clockwise)// Landscape (Counterclockwise) break; } twx.app.fn.addSnackbarMessage("IOS Orientation: "+orient,"twLogo"); } And register the event on orientation change: angular.element(document).ready(function () { if($scope.app.isIOS()){ $scope.setWidgetProp('label-3', 'text', "called on IOS Device"); twx.app.fn.addSnackbarMessage("called on IOS Device","twLogo"); window.onorientationchange = readDeviceOrientation;} } }); Here is a function for testing of the current device -> please, check for more details this post( How to define functions to check the different mobile platforms ). So, to go deeper, we can see this thread in stackoverflow.com : https://stackoverflow.com/questions/4917664/detect-viewport-orientation-if-orientation-is-portrait-d... IOS Reference: http://www.williammalone.com/articles/html5-javascript-ios-orientation/#:~:text=The%20JavaScript%3A,%3D%20%22LANDSCAPE%20%22%20%2B%20window.
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
How to associate my image texture UV on my 3D Model Here the suggested   workflow is the following: 1.) we will create the CAD data in any CAD tools /   Creo Parametric or any AutoDesk , Catia, Solidwors and etc. / In generally when we prepare the model for the AR usage and there /in the CAD tool/ we will also assigned the texture to a model, component or particular surfaces. So means  the native CAD data will contain already the texture/'s before we will try to use it in Vuforia Studio 2.) So to use the data in Vuforia Studio we need to import it. The import tool is a part of studio and will convert the geometry . Internal Studio used the optimizer tool (here want to refer to the post: "Optimize PVZ before") So means that the native cad data is converted always to PTC light ware format pvz. According to rcp. setting it will  import also the textures or will not import them. So this is the most used way to use textures in Vuforia Studio.  Also UV setting are something what should be set in the CAD tool - e.g. in Creo Parametric:      3.) In case that we have a texture file  and want to assigned it to a model or modelItem  using some U V parameters in Vuforia Studio  this will make it more difficult. It is possible but you need to define an GLSL shader in the tmlText widget and assigned to the shader property. The glsl shader will used also the file refer in the texture property and could display it projected on the UV model geometry:     e.g. in Javascript;   $scope.app.shaderString="texture_test;scale f 2;moveX f 0;moveY f 0"; $rootScope.$on('modelLoaded', function() { $scope.view.wdg['modelItem-1']['texture'] = "app/resources/Uploaded/cvc.jpg?name=Texture0&edge=repeat"; $scope.setWidgetProp('modelItem-1', 'shader', $scope.app.shaderString); $scope.view.wdg['3DImage-1']['src'] = "app/resources/Uploaded/pinger_half.png?name=Texture0&edge=mirror"; $scope.setWidgetProp('3DImage-1', 'shader',$scope.app.shaderString); })     Here the pictures are created in project wher I used the following tmlText defintion. Please, pay attention that it is not a solution , but more only an example which display the general usage. If you need more to optimize the display you need to do some improvements - ,please, refer to some addtional links: https://wiki.delphigl.com/index.php/Tutorial_glsl2 https://viscircle.de/einsteigerguide-einfuehrung-in-glsl/ https://stackoverflow.com/questions/27646383/glsl-shader-for-texture-smoke-effect or lot of other links / key words glsl  fragmet vertex shader defintion, web.gl javascipt/   So in this example I used the following test shader code:     <script name="texture_test" type="x-shader/x-fragment"> precision mediump float; uniform sampler2D Texture0; uniform float scale; uniform float moveX; uniform float moveY; // determine varying parameters varying vec3 N; varying vec4 vertexCoord; // determine shader input parameters uniform vec4 surfaceColor; const vec3 lightPos = vec3(1.0, 2.2, 0.5); const vec4 ambientColor = vec4(0.3, 0.3, 0.3, 1.0); void main() { // calc the dot product and clamp based on light position // 0 -> 1 rather than -1 -> 1 // ensure everything is normalized vec3 lightDir = -(normalize(lightPos)); vec3 NN = normalize(N); // calculate the dot product of the light to the vertex normal float dProd = max(0.0, dot(NN, -lightDir)); { // calculate the color based on light-source and shadows on model vec2 TexCoord = vec2( vertexCoord )*scale; TexCoord.x=TexCoord.x+moveX; TexCoord.y=TexCoord.y+moveY; vec4 color = texture2D(Texture0, TexCoord) ; gl_FragColor = (ambientColor + vec4(dProd)) *color ;// surfaceColor; } } </script> <script name="texture_test" type="x-shader/x-vertex"> attribute vec3 vertexPosition; attribute vec3 vertexNormal; varying vec3 N; varying vec4 vertexCoord; uniform mat4 modelMatrix; uniform mat4 modelViewProjectionMatrix; void main() { vec4 vp = vec4(vertexPosition, 1.0); gl_Position = modelViewProjectionMatrix * vp; vertexCoord = modelMatrix*vp; // the surface normal vector N = vec3(normalize(modelMatrix* vec4(vertexNormal,0.0))); } </script>   The GLSL code should be added in the text area of the tmlText widget:     At the end I want to provide to this post  a sample project which demonstrate how to use the textures with shaders in Studio. Please, pay attention this is only an example which demonstrate the general principle. So far I now this way , works only on mobile devices. I did not get it work with the shader on the HoloLens. I tested the project in preview on Android and on IOS. On Android and in preview mode it work fine. On IOS it works fine for the ModelItem but not for the 3dImage- but I think there we need to downscale the value of the light intensity:   gl_FragColor = (ambientColor*lightscale + vec4(dProd)) *color ;// s project was attached : testShaderProperties-EXAMPLE.zip
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
How to define functions to check the different mobile platforms: Checking  if the Preview mode is used: $scope.app.isPreview=function() {// is in Preview if(window.twx.app.isPreview()) return true; return false; } // any other device type  Checking if the device called the Studio project is IOS device: //====================================== $scope.app.isIOS=function() { if(!window.twx.app.isPreview()) // is not in Preview if(ionic.Platform.isIOS()) //and is NOT called on IOS = means Android mode detected return true; return false; } //====================================== Checking if the Studio Project was called on Android device : //====================================== $scope.app.isAndroid=function() { if(!window.twx.app.isPreview()) // is not in Preview if(/android/i.test(navigator.userAgent || navigator.vendor || window.opera) ) // is Android return true; return false; } //====================================== Checking if the Studio Project was called on Windows Phone : //====================================== $scope.app.isWinPhone=function() { if(!window.twx.app.isPreview()) // is not in Preview if(/windows phone/i.test(navigator.userAgent || navigator.vendor || window.opera) ) // is Android return true; return false; } //======================================  or checking if device is a windows system (similiar) //====================================== $scope.app.isWindow=function() { if(!window.twx.app.isPreview()) // is not in Preview if( navigator.platform.indexOf("Win32") >- 1) // is this WindowsPhone return true; return false; } //========================================   The Vuforia Studio is based on cordova so that the most component works for all devices , but in some cases we can use some platfrom specific api. In this case it is important to check on which device the Vuforia app is started to select the platform specific API. Here an example: //======================================== angular.element(document).ready(function () { console.warn($scope) console.warn($rootScope) if($scope.app.isPreview()) { $scope.setWidgetProp('label-3', 'text', "PREVIEW MODE"); twx.app.fn.addSnackbarMessage("PREVIEW MODE","twLogo"); } else if($scope.app.isIOS()){ $scope.setWidgetProp('label-3', 'text', "called on IOS Device"); twx.app.fn.addSnackbarMessage("called on IOS Device","twLogo"); window.onorientationchange = readDeviceOrientation;} else if($scope.app.isAndroid()) { $scope.setWidgetProp('label-3', 'text', "called on Android Device"); twx.app.fn.addSnackbarMessage("called on Android Device","twLogo"); } else if($scope.app.isWindow()) { $scope.setWidgetProp('label-3', 'text', "called on Window Plattform"); alert("called on Windows Phone Device"); } //$scope.setWidgetProp('label-3', 'text', navigator.platform.toString()) if($scope.app.isPreview()) twx.app.fn.addSnackbarMessage("PREVIEW MODE","twLogo"); else { $timeout(twx.app.fn.addSnackbarMessage("navigator.platform="+navigator.platform,"twLogo"),3000); //after 3 sec $timeout(twx.app.fn.addSnackbarMessage("getMobileOperatingSystem()="+getMobileOperatingSystem(),"twLogo"),6000); //after 5 sec $timeout(twx.app.fn.addSnackbarMessage("getPlatform()="+getPlatform(),"twLogo"),9000); //after 5 sec } //=========================================================== angular.element(window).on('resize', function(evt) { console.log("resized window evnt : at Time ::"+$scope.getTime()); console.warn(evt); var message = '' var win = evt.path[0]; if(win.innerWidth > win.innerHeight){ // LANDSCAPE -> do something here call your landscapeFunc() message = "current orientation is Landscape!" $scope.$applyAsync(); } else { // LANDSCAPE -> do something here your PortraitFunc() message = "current orientation is Portrait!" $scope.app.params['myClass']='row3' $scope.$applyAsync(); } twx.app.fn.addSnackbarMessage(message,"twLogo"); });  
View full tip
Currently in Vuforia Studio is not possible to create a dynamically widgets- on the fly - at runtime to add or to remove widgets. Better to say it is not possible in a supported way. So, the most supported approach which users can use - is to to create everything - different layouts version and to blank and to display them depending on the current inputs. In some cases, it is possible to use a functionality of repeat region to create reports. So, we can use repeater widget (container ) to show a ThingWorx InfoTables. So, the repeater are mostly intended to be linked to services /added in the external data section / which result are InfoTables. The infoTables itself are handled as JSON object in the AngularJS environment in Vofria Studio. Therefore, we can try to provide directly a data as global variable in Studio or we can read it via the http service from a project directory (e.g. upload). So the follow example will read a json file “ItemList.json"  from the upload  folder and will set to the variable $scope.itemList:   //Global Variables var pjson ="ItemList.json"; //JSON File Name without extension $scope.itemList={}; //global $scope variable gotJSON=function(data){ { $scope.itemList=JSON.parse(data);} catch(ex){ console.warn(ex);} } doRead=function (jsonFile){ fetch(jsonFile) .then(response=>response.text()) .then(data=>gotJSON(data)) } $scope.Init=function() { doRead('app/resources/Uploaded/'+pjson); } ///// $scope.$on('$ionicView.afterEnter',function(){ $scope.Init();}) //event: when 2d View loaded //the code will read the complette JSON File and //assignee it to a jsonData global variable     Of course  we can also use a static list to set it to a variable e.g. :     var ItemList = [ {"display":"France","id_num":0,"checked":true,"value":"Paris" ,"src":"test.svg","someText":"some Text123"}, {"display":"Italy","id_num":1,"checked":false,"value":"Rome" ,"src":"test1.svg","someText":""}, {"display":"Spain","id_num":2,"checked":false,"value":"Madrid" ,"src":"test2.svg","someText":""}, {"display":"UK","id_num":3,"checked":false,"value":"London" ,"src":"test3.svg","someText":""}, {"display":"Germany","id_num":4,"checked":false,"value":"Berlin" ,"src":"test4.svg","someText":"-->"}, {"display":"Norway","id_num":5,"checked":false,"value":"Oslo" ,"src":"test.svg","someText":""}, {"display":"Switzerland","id_num":6,"checked":false,"value":"Bern" ,"src":"test1.svg","someText":""}, {"display":"Greece","id_num":7,"checked":false,"value":"Athens" ,"src":"test2.svg","someText":""}, {"display":"France","id_num":8,"checked":true,"value":"Paris" ,"src":"test3.svg","someText":"other Text"} ];//some list got from anywhere   When we have the data in Studio as value of the scope variable or as global variable we can try to set it to an repeat region and create something similar as simple report:     The clue here is to set the list to an app parameter and to link then the application parameter to the repeater data and also to the different repeater elements /widgets in the row/columns of the repeater. This is not trivial because it is not intent usage of the repeater but for such simple case it will work       To be able to link the elements to the correct repeater row we need a filter. Here an example of filter function. I need a window area because this is the only way to pass variable between 2 different calls of the filter. Here an example for filter definition for a label:     //filter for the checkbox element //console.warn("filterLabel1"); if(!window.my_filter3) window.my_filter3=1; else { if(window.my_filter3>= value.length) window.my_filter3=1; else {window.my_filter3++;} } return(value[window.my_filter3-1]['someText']);     and here an examle definition  for a image widget:   //filter for the Image Widget element // //console.warn("filterImage1"); if(!window.my_filter3) window.my_filter3=1; else { if(window.my_filter3>= value.length) window.my_filter3=1; else {window.my_filter3++;} } return('app/resources/Uploaded/'+value[window.my_filter3-1]['src']);     With little more work we can also implement a function which could change the value on click - e.g. a checkbox to set it to true or false and to update the list:     //================================================================== $scope.changeValueInJson= function (obj,id_num,field2change, new_value) { if(new_value==undefined) return; try{if( obj[0][field2change]==undefined) return;}catch(wrong){console.error("error::"+wrong);return;} for (var i = 0; i < obj.length; i++){ if (obj[i]['id_num'] == id_num){ obj[i][field2change]= new_value;}} $scope.app.params["ItemList"] = ""; $scope.app.params["ItemList"] = JSON.parse(JSON.stringify(obj));//check value $scope.$applyAsync(); } //================================================================== twx.app.fn.clickItemInRepeater = function(item,list,isMultiSelect) { console.warn("called clickItemInRepeater()"); $scope.changeValueInJson(list,item.id_num,'checked', item.checked?false:true) console.log("clickItemInRepeater::ROW Selected:: "+JSON.stringify(item)) $scope.setWidgetProp('textArea-2','text',JSON.stringify(item)) $scope.$applyAsync(); }; //==================================================================     The callback twx.app.fn.clickItemInRepeater = function(item,list,isMultiSelect) {...  will fire when an element is clicked. At the end we will have a list with labels where  their  content and values and number of rows is defined from a Json object- here the number of row is flexible but the number of column is fixed. Of course, we can define more columns in a row and then with filter (similar to the example) to drive their visibility. A sample project demonstrating the suggested techniques is attached to this  this post.
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
ThingWorx properties can be updated from a Vuforia Studio experience.    Below is a simple example: Create a test Thing and a test property for the Thing in ThingWorx :   Create a service for the Thing with text input parameter(Test) and add the below code to update the TestProperty value:   Click Done and Save the Thing In Vuforia Studio, Add a Text Input widget and a Button widget in 2D canvas Add the service of the Thing in the External data Panel Now, bind the Text property of the ‘Text Input’ widget to the Test Parameter of the service as shown below:   Bind the Click event of the Button widget to the Service to update the value of Thing property in ThingWorx   Test the experience by clicking Preview. Enter text in the Text Input widget and click the button. The Thing property should then be updated.      If you are creating a public experience, ensure that run time permissions for the es-public-access user have been assigned to the properties, events and services of the entity.  From ThingWorx composer, open the entity whose data must be accessed by a public experience Click the Permissions icon in the last column of the row containing the entity Click Run Time under Permissions Under All Properties, Events, Services, use the search box to find and add the es-public-access user Click green dot under the appropriate permissions columns   Click Save See the Vuforia Studio Help Center for more information on granting user permissions in ThingWorx for Vuforia Studio.  
View full tip
How to define Widget at runtime time and what is possible?  For example, the following problem: required is a button e.g. 'button-1' - widget to start a sequence. Now user want to create an 3D-Label that becomes visible after click the 'button-1' and  now for each sequence and step a the 3D-Label have to be visible as long as the sequence is played. The 3d label should display an information for the specific step and also should to have specific position. Unfortunately, it is not possible (using the supported functionality) to create widget on the fly - means create a new feature on run time where the widget was not defined in the design time. Theoretical with some more intensive work – it will be possible – we simple need to check what the UI is doing when you copy and paste widget  (but it is not easy ☹-  and it is not sure if this solution will be stable(if you will be able to implement 1:1 UI) and if it will work in later version -because PTC dev team could change some functionality – but will not change your code. For the most tasks it enough to use few 3d widgets (3Dlabels) the most are invisible and then switch the visibility on runtime and move them on the desired location. According request on address of this issue  , may be it is worth here to mention following statements of the PTC dev  :   .) Question: “Vuforia Studio 8.5.3.Is it possible to create 2D and 3D widgets from Java Script? Answer: If you mean to dynamically create/instatiate a widget and set its properties at runtime, no we have no such capability today. The simplest workaround is to pre-generate the widgets and manage their visibility at runtime. 2.) Question: Is it possible to create 2D widget dynamically by Java Script? Answer: Its fairly easy to have a couple of hidden widgets that are displayed and moved with a tap event. It might be possible to create some 2d widgets on the fly, but 3d widgets would be a bit harder as more of the unsupported api under the hood would be needed.   According to the statement of the PTC development team  - here the we will consider the possible workaround :. The following requirement: When the sequence 1 is played the labels 1, 2 and 3 have to be visible. When the sequence 2 is played the labels 4, 5 and 6 have to be visible. In all of these labels are only one word in it, so not a step description or something like this. Here is a sample table with coordinates for a better overview:   There is no a real reason to have 4,5,6 here. In this case we can use 1,2,3 again - also for step 2 but we could here change the position and will change the text content. In this case we need to have to define number of 3DLabel widgets which is equal to the maximum of used notes per step - and display and update only the necessary number of widget in particular step.   To demonstrate the workaround the following example was created and tested. This   example should show the suggestion above. It is not perfect but should demonstrate the general approach to achieve similar goal. The table with values is a json file- in the attached example is the file steps.json in the Project upload folder. Here example of values: {"1":{"3DLabel-1":{"visible":true,"text":"text Label1 Step 1", "x":0.0,"y":0.1,"z":0.0,"rx":0.0,"ry":0.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel1"}, "3DLabel-2":{"visible":true,"text":"text Label2 Step 1", "x":0.0,"y":0.2,"z":0.0,"rx":0.0,"ry":0.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel2"}, "3DLabel-3":{"visible":true,"text":"text Label3 Step 1", "x":0.0,"y":0.3,"z":0.0,"rx":0.0,"ry":0.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel3"}}, "2":{"3DLabel-1":{"visible":true,"text":"text Label1 Step 2", "x":0.0,"y":0.1,"z":0.1,"rx":0.0,"ry":10.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel2"}, "3DLabel-2":{"visible":false,"text":"text Label2 Step 2", "x":0.0,"y":0.2,"z":0.0,"rx":0.0,"ry":10.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel2"}, "3DLabel-3":{"visible":true,"text":"text Label3 Step 2", "x":0.0,"y":0.3,"z":0.1,"rx":0.0,"ry":10.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel2"}}, "3":{"3DLabel-1":{"visible":true,"text":"text Label1 Step 3", "x":0.0,"y":0.1,"z":0.0,"rx":0.0,"ry":20.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel3"}, .... "8":{"3DLabel-1":{"visible":true,"text":"text Label1 Step 8", "x":0.0,"y":0.1,"z":0.0,"rx":0.0,"ry":0.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel1"}, "3DLabel-2":{"visible":true,"text":"text Label2 Step 8", "x":0.0,"y":0.2,"z":0.0,"rx":0.0,"ry":0.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel2"}, "3DLabel-3":{"visible":true,"text":"text Label3 Step 8", "x":0.0,"y":0.3,"z":0.0,"rx":0.0,"ry":0.0,"rz":0.0,"scale":1.0,"class":"ptc-3DLabel3"}}}      The table contains the most possible values to set for 3DLabels. We can add other or omit values in the list. In generally we need only the values which should be changed but such list template which contains all values is better for editing. It should still work so far, the json syntax is correct First point is to load the list to a global variable from the project upload folder. Here a sample code:   $scope.jsonData_steps={}; //global $scope variable //================================== readSteps=function (jsonFile){ console.warn("**=>readSteps :: "+jsonFile); fetch(jsonFile) .then(response=>response.text()) .then(data=>{$scope.jsonData_steps=JSON.parse(data); console.warn( JSON.stringify($scope.jsonData_steps))}) .catch((wrong) => {console.log("problem in fetch: "); console.warn(wrong)}) } //================================== $scope.Init=function() { $timeout(readSteps('app/resources/Uploaded/'+stepsjson),200); } //================================================================================================= // $ionicView.afterEnter -> this event fires when 2d view was entered //================================================================================================= $scope.$on('$ionicView.afterEnter',function(){ console.log("$ionicView.afterEnter was called"); $scope.Init();}) //event: when 2d View loaded //=================================================================================================     further in the "stepstarted" event the code will set the values of the widget properties which are contained by the json object  with the same number as the started step number:     //================================================================================================= $scope.$on('stepstarted', function(evt, arg1, arg2, arg3) { var parsedArg3 = JSON.parse(arg3); console.log("stepstarted stepNumber="+parsedArg3.stepNumber + " nextStep="+parsedArg3.nextStep); $timeout(()=>{ $scope.setMutipleProps($scope.jsonData_steps[parsedArg3.stepNumber.toString()])},10) $scope.setWidgetProp('label-1', 'text',"STEP: "+ parsedArg3.stepNumber) $scope.$applyAsync(); }); //================================================================================================= //this function set multiply properties from a list //================================================================================================= $scope.setMutipleProps = function(obj){ Object.keys(obj).forEach(function (item) { for(var prop in obj[item]) { $scope.view.wdg[item][prop]=obj[item][prop]; //print the setting for debugging console.log("==>$scope.view.wdg["+item+"]["+prop+"]="+obj[item][prop] ) } }) $scope.$applyAsync(); }; ///=================================================================================================     Finally,  the project was  tested  and  it was working as expected:     The demo project is attached to this article.
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
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
Sometimes it is required and will be nice to use a picker functionality. For example, some data picker – so the question: How to achieve this. Yes it is possible in JavaScript that we can incorporate a data/calendar picker into the Vuforia Studio environment? In the Web there are some open source libraries and at least it works 1:1 in preview mode. But mostly they work also fine on mobile devices. In this article a data picker was tested and it was working fine in Preview mode but also was working fine on IOS and on Android devices  The example here is based on the follow link:  https://www.cssscript.com/tag/date-picker/ there are some data picker implemented. Here in this example the library (data-picker) is copied to  the Studio Project download folder and it javascript code will   load the lib and defined a function which is called from button First step is to define a function which enables to load javascript and css from a file / project folder:   // this code load a javascript or css file $scope.loadjscssfile= function(filename, filetype){ console.log("loading "+filename+":: type="+filetype) if (filetype=="js"){ //if filename is a external JavaScript file var fileref=document.createElement('script') fileref.setAttribute("type","text/javascript") fileref.setAttribute("src", filename) } else if (filetype=="css"){ //if filename is an external CSS file var fileref=document.createElement("link") fileref.setAttribute("rel", "stylesheet") fileref.setAttribute("type", "text/css") fileref.setAttribute("href", filename) } if (typeof fileref!="undefined") document.getElementsByTagName("head")[0].appendChild(fileref) } // this function will load the simplepicker javascript lib and the css lib $scope.testLoad= function() { $scope.loadjscssfile("app/resources/Uploaded/lib/simplepicker.css", "css") $scope.loadjscssfile("app/resources/Uploaded/dist/simplepicker.js", "js") }   In this example the $inoicView.afterEnter (after 2d/view  is loaded) event is used to load the library. The function for the picker call is defined -  $scope.app.TestPicker()- which is called from a button .   //============================= $scope.$on('$ionicView.afterEnter', function() { $scope.testLoad(); $timeout(() => { $scope.setWidgetProp("button-1","class", "simplepicker-btn"); $scope.$applyAsync(); },500) }) //https://www.cssscript.com/material-date-time-picker-simplepicker/ // function called from a button $scope.app.TestPicker = function() { let simplepicker = new SimplePicker({ zIndex: 10 }); simplepicker.open(); simplepicker.on('submit', (date, readableDate) => { console.warn( readableDate ); $scope.setWidgetProp('label-1','text', readableDate); $scope.$applyAsync(); }); simplepicker.on('close', (date) => { console.log('Picker Closed' ); }); }   When the picker is closed after date was selected - the javascript code will set the selected value to the text property of a label widget (label-2). The javascript and the style (css)  implementation was copied to the resource folder:       Now the   simplepicker could be tested and it has the following appearance in preview mode:     The test picker Studio project was attached to this case.  
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 Home.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