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

Community email notifications are disrupted. While we are working to resolve, please check on your favorite boards regularly to keep up with your conversations and new topics.

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
Often, we need to display some sections, or we required to have a view of a x-section of the model. This is in generally no part of the current functionality but there are some approaches which could be helpful. In this article I want to mention 3 different approaches, which could be used but no one of them is really perfect:   1.) uploading different models -  so we can use an additional models for each cut and then we can change the model if you want to display a cut, it means you can make the one model not visible and the display the second model or vice versa.  I tested with cuts created in Creo Illustrate 5.0 and Creo View 5.0 but it seems that Vuforia Studio could not display them /neither as part of sequence or as static to the current figure:     The only possible  way in this example is to create in Creo an assembly cut /with option cut also on part level  and  then create from there a new .pvz model.  In this case this seems to work fine: :     2.) the second approach is to remove components  step  by step,  so to see the inner components when the outer components are blanked: All different components, which should be displayed or blanked, need to be defined as modelItem where we can set the visible property to true or false Is also possible to blank or display  a list of components where the list is defined in json file. This could be done with JavaScript code. In this case we do not need to define a modelItem widgets.  For more information you can check the post    3.)  The last most powerful option is to use a shader for the model Widget. So for example we can use some kind of clipping functionality of the model where we can set the x min and x max value or ymin and ymax or zmin and zmax value what should be displayed. This will create a clipping effect. So only the geometry which satisfy this criteria will be displayed.     How to achieve a clipping functionality using a shader. 3.1) define a shader - this requires creating a tmlText widget where we can define the shader Code. The code should be inserted into the text property of the tml widget For the clipping along x axis with planes which are parallel to the  YZ plain we need to define the following javascript with GLSL:   <script name="slice_world_based_x" 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> <script name="slice_world_based_x" type="x-shader/x-fragment"> // this setting is needed for a certain use of properties precision mediump float; // determine varying parameters varying vec3 N; varying vec4 vertexCoord; // determine shader input parameters uniform vec4 surfaceColor; uniform float slicex; uniform float slicewidth; 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 current color of vertex, unless it is being clipped... // only geometry with coordinates which satisfy the condition below // will be displayed if ( vertexCoord.x > (slicex + slicewidth/2.0) || vertexCoord.x < (slicex - slicewidth/2.0) ) { discard; } else { // calculate the color based on light-source and shadows on model gl_FragColor = (ambientColor + vec4(dProd)) * surfaceColor; } } </script> Save the tml Text widget .     3.2) You can also define shaders for clipping along the Y and the Z axis. Here you can use the same shader javascript definition but you need to change the shader name and to modify the if  check respectively to the Y or Z coordinate:   <script name="slice_world_based_y" type="x-shader/x-vertex"> ... // calculate current color of vertex, unless it is being clipped... if ( vertexCoord.y > (slicex + slicewidth/2.0) || vertexCoord.y < (slicex - slicewidth/2.0) ) { discard; } else ...   To set the clipping values for different axes and to control it by   sliders we can use some code like this:   $scope.DIR="y"; $scope.slice = function() { var slicexCur = ($scope.view.wdg['slider-1']['value']/100.0)-0.5; var slicewidthCur = ($scope.view.wdg['slider-2']['value']/100.0); $scope.view.wdg['modelx']['shader'] = "slice_world_based_"+$scope.DIR+";slicex f "+ slicexCur + ";slicewidth f " + slicewidthCur; } //////////////////////////////////////////// $scope.$on('$ionicView.afterEnter', function(){ // Anything you can think of $scope.clickY() }); /////////////////////////////////////// $scope.clickX=function() { $scope.view.wdg['toggle-X']['value'] =true $scope.view.wdg['toggle-Y']['value'] =false $scope.view.wdg['toggle-Z']['value'] =false $scope.DIR="x" } $scope.clickY=function() { $scope.view.wdg['toggle-X']['value'] =false $scope.view.wdg['toggle-Y']['value'] =true $scope.view.wdg['toggle-Z']['value'] =false $scope.DIR="y" } $scope.clickZ=function() { $scope.view.wdg['toggle-X']['value'] =false $scope.view.wdg['toggle-Y']['value'] =false $scope.view.wdg['toggle-Z']['value'] =true $scope.DIR="z" } ///////////////////////////////////////   Here the slice() function is called in the slider change event (for the both slider widgets). For better understanding of the functionality you can review the attached Vuforia Studio project Here attached the improved version for HL. It is also approved by dev team.  slice_example.zip https://community.ptc.com/sejnu66972/attachments/sejnu66972/tkb_vuforiatechtips/48/4/slice_example.z... Another version (also for HL) attached here is the example   slice_example_using_ABCD  project – where the dev team demonstrates an efficient way showing how we can define a cutting plane via plane ABCD parameters (refer to  plane geometry equations : geometry https://en.wikipedia.org/wiki/Plane_(geometry)   To cover this functionality the dev team developed some studio extensions that  PTC customers can use instead.   That extensions widgets will handle reflections etc, and I it is recommend to use  those extensions .: https://github.com/steveghee/OCTO_Studio_extensions https://github.com/steveghee/OCTO_effects_extensions  
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
Vuforia Studio Support for sequences created with Creo Illustrate 8.0 Support for occlusion on Area Targets Improvements to 3D Panel widget: Ability to configure offset and snap distance Bug fixes and minor improvements Vuforia View Support for Windows Surface Go 2 Android: Support for Android 6 and 7 will be discontinued in July 2021 RealWear: Support for RealWear 11 will be discontinued in July 2021 Bug fixes and minor improvements Experience Service Support for ThingWorx 9.2 Support for RHEL 8.4 Support for Ubuntu 20.04 Support for PostgreSQL 13.3 Bug fixes and minor improvements
View full tip
In Vuforia Studio version 8.5.13 a new metadata is intruduced. This will make the techniques described e.g. in the posts "How to extract the components with properties from a pvz file","Extracting the viewables and the seqnece steps information from a .pvz file for the usage in TWX" and "How to extract model data of 3d models in Vuforia Studio (without external Tools)?"  for the most cases not neccessarly any more. as In Vuforia Studio when a model is imported (add Resource) there is a new check button “Allow the Experience access to CAD metadata” :   The selection of this checkbox lead that when the model is loaded to the “app/resources/Uploaded” folder but also an a json object with the name <model name>.metadata.json Example when we load the model “Coffee Maker Model_High.pvz” then we have also a json file named “Coffee Maker Model_High.metadata.json”. This JSON file contains the metadata to the coffee maker model   The metadata json file contains some different sections for each component / For each component / CompPath Id  we have a sub object – comp obj {"/":{"":{"Adapter_name":"proepview",… "/11":{"":{"Feature_Id":"11","Source_file_name": … "/14":{"":{"Feature_Id":"14","Source_file_name": … Where “/” means the root asm , “/11” and “/14” are components paths The component object contains the following sections : general(empty string as key of the json obj) “”, "PROE Parameters" and "__PV_SystemProperties" (see the attached example “Coffee Maker Model_High.metadata.json”) The one possible approach could be , to use only this json object directly  but we can use also the PTC API which is provided starting with the Vuforia Studio 8.5.13 Metadata Access: Read the json object into memory (javascript):  In this case we can use the json object which was already created when the model data is omported in Studio.         var metaDataArray=[]; //////////////////////////////////////////////////////// //========================== When Model Loaded Event $rootScope.$on("modelLoaded", function() { if (arguments.length >0){ //================== if args >1 ====================== var modelWidgetId=arguments[1]; metaDataArray[modelWidgetId]={}; console.warn($scope.view.wdg); let wdg= $scope.view.wdg[modelWidgetId]; let mdlsrc=$scope.getWidgetProp(modelWidgetId,'src'); console.log( "mdlsrc="+mdlSrc); //==== extracts the model file name with extension let mdlNameExt= mdlSrc.replace(/^.*[\\\/]/, ''); console.log( "mdlNameExt="+mdlNameExt); //==== extracts the model file name without extension var mdlName=mdlNameExt.replace(/\.[^/.]+$/, ""); console.log( "mdlName="+mdlName); // ---adds the modelname to the array element for the widget metaDataArray[modelWidgetId]['mdlName']=mdlName; metaDataArray[modelWidgetId]['CompIdList']=[]; // $https call of the JSON file form the UPLOAD folder $http.get('app/resources/Uploaded/' + metaDataArray[modelWidgetId]['mdlName']+'.metadata.json') .success(function(data, status, headers, config) {//-------- success fnc metaDataArray[modelWidgetId]['data']=data; angular.forEach(data , function(value ,key){ //-------- ForEach json loop metaDataArray[modelWidgetId]['CompIdList'].push(key); });//--------end of ForEach json loop // print the data to the console console.log("metaDataArray[]"); console.warn(metaDataArray); })//-------- end success fnic .error(function(data, status, headers, config) {console.log("problem in the http will create a new ");}); //////////////////////////////////////////////////////// })          The code listed above will load the json file to the modelWidget/s (it will works also if we have many model widngets there - and each model widget ponts to model which is imported with metadata) When we  test this code we can check the object in memory (console.warn() ) :   Now we can access the metadata using the normal JSON functionality - so we can   access the component data via the Comp Path Id / id paths - corresponds to the modelItem widget property occurrence:         $scope.app.testFunction= function() { // visiting array with string index Object.keys(metaDataArray).forEach(function(key){ console.log(" model Widget = "+ key); let compList = metaDataArray[key]['CompIdList']; //select randomly from the coponent list let randomNum= parseInt(Math.random()*metaDataArray[key]['CompIdList'].length) let randomComp= metaDataArray[key]['CompIdList'][randomNum]; console.log("random component selected = " +randomComp); // let DispName = metadata.get(randomComp, 'Display Name') /*** you can use here one of the following fields "Child Count","Component Name","Display Name","Model Extents (mm)","OL File Name","Part Depth""Part ID","Part ID Path","Part Name","Part Path" ****/ let DispName = metaDataArray[key]['data'][randomComp]['__PV_SystemProperties']['Display Name'] console.log("DispName= "+ DispName) let model_extend_mm = metaDataArray[key]['data'][randomComp]['__PV_SystemProperties']['Model Extents (mm)'] console.log("model_extend_mm= "+ model_extend_mm) let creaDate = metaDataArray[key]['data'][randomComp]['PROE Parameters']['CREATION_DATE'] console.log("creaDate= "+ creaDate) console.log("model_extend_mm= "+ model_extend_mm) let designState = metaDataArray[key]['data'][randomComp]['PROE Parameters']['DESIGN_STATE'] console.log(" designState= "+ designState) // make a selection string for this component let mdl_selection= key+"-"+randomComp; console.log ("mdl_selection="+mdl_selection) // generate some random rgbá color let r =parseInt(Math.random()*255); let g =parseInt(Math.random()*255); let b =parseInt(Math.random()*255); let a =parseInt(Math.random()*0.8)+0.2; //apply blink funciton for this component selection with random color $scope.blinkSelection(mdl_selection,'rgba('+r+','+g+','+b+','+a+')',200,22); }); }         When we test the listed code above we will have in the chrome console window: Using furhter the json  functionality we can also implement e.g. some user picks where you can associate it with the metadata :         angular.forEach($element.find('twx-dt-model'), function(value, key) { // find all model widget feature and perform funciton() // for each model widget - key == modelWidgetId //define the userpick function for each modelWidgetId angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { if (edata) { if ($scope.currentSelection) { // selection is not null make it undefined tml3dRenderer.setColor($scope.currentSelection, undefined); } //create the selection string <modelWidgetId>-<occurance Path Id> $scope.currentSelection = target + '-' + JSON.parse(edata).occurrence; //generate a random rgba color let r =parseInt(Math.random()*255); let g =parseInt(Math.random()*255); let b =parseInt(Math.random()*255); let a =parseInt(Math.random()*0.8)+0.2; //call blinkSelection function for the selected component $scope.blinkSelection($scope.currentSelection,'rgba('+r+','+g+','+b+','+a+')',200,10); //write the data SystemProperty of this compoonent in to a textArea Widget $scope.setWidgetProp('textArea-1','text',JSON.stringify( metaDataArray[target]['data'][JSON.parse(edata).occurrence]['__PV_SystemProperties'])); } }) })           This code will write the meta data to the selected component form the __PV_SystemPoperties section to a text area widget. Also the component will blink fwith random rgba color: I attached an small example (modelMetaDataMobilTest.zip) which should demonstrated the described techniques   Vuforia Studio metadata API The first step is to call the metadata for a model widget. For this we can use the following construct e.g. WidgetId is ‘model-1’ :       PTC.Metadata.fromId('model-1').then( (metadata) => { // <HERE CALL YOUR CODE with metadata > });       So for example we can use get the “Display Name of the component with the path Id =’/0/6’:       PTC.Metadata.fromId('model-1').then( (metadata) => { Let disp_name= metadata.get('/0/6', 'Display Name'); console.log(“Display Name=”+disp_name); });       The most of the  methods which could be applied to the metadata object and also some examples are described in the PTC Help (Incorporate CAD Metadata Into an Experience) This functionality is available first with Vuforia Studio 8.5.13. To this article is also attached a PDF copy of the metioned link above. There we can use 2 different approaches: To get a selected object or list of objects: let metaDATA=PTC.Metadata.fromId('model-1') metaDATA.then(function(meta) { console.log("success func of metaData"); //a test with a fix model widget id var designer = meta.get('/11','DESIGNER','PROE Parameters'); console.log("Designer= " + designer); var ptc_mat = meta.get('/11','PTC_MATERIAL_NAME','PROE Parameters'); console.log("PTC_MATERIAL_NAME= " + ptc_mat); var dispName = meta.get('/11','Display Name','__PV_SystemProperties'); console.log("dispName= " + dispName); var DV_System_Categ= meta.get('/11'). getCategory ('__PV_SystemProperties') console.log(JSON.stringify(DV_System_Categ)) let myQuery=meta.find('Display Name').like('PRT').find('Part Depth').in(0,3); console.log("myQuery Name:"+myQuery._friendlyName); console.log("myQuery selected Paths :"+JSON.stringify(myQuery._selectedPaths)); }) .catch(function(err) {console.log("problem with the read of metadata ");console.warn(err);}); ​ To call for the selected object a callback function where the Path id was passed as function argument:       $scope.app.testCustomSel= function(){ let pathDepth=4 $scope.app.testCustomSelection('model-2', $scope.app.whereFunc,$scope.app.selectFunc,pathDepth) $scope.app.testFind1_modelId='model-1' PTC.Metadata.fromId( $scope.app.testFind1_modelId) .then(function(meta) {meta.find('Part Depth').lessThan(3).find('Display Name') .like('PRT',$scope.app.selectFunc);}) } //========================== app.testCustomSelection $scope.app.testCustomSelection= function(modelId,whereFunc,selectFunc,pathDepth) { BPRN("app.testCustomSelection()"); $scope.app.testCustomSelection.modelId=modelId; $scope.app.testCustomSelection.pathDepth=pathDepth var metaDATA= PTC.Metadata.fromId(modelId) .then(function(meta) {meta.findCustom(whereFunc,selectFunc);}) } //------------------------------------------------- $scope.app.whereFunc = function(idpath) { // scope var `this` is the metadata instance const depth = this.get(idpath, 'Part Depth') const name = this.get(idpath, 'Display Name') return parseFloat(depth) > $scope.app.testCustomSelection.pathDepth || (name && name.search('PRT') >= 0) } //------------------------------------------------- $scope.app.selectFunc = function(idpath) { const name = this.get(idpath, 'Display Name') console.log("app.selectFunc["+$scope.app.testCustomSelection.modelId+"] idpath=" +idpath+ " >>>Name: "+name); return this.get(idpath, 'Display Name');}         In the example above the query is done by the  where function and for all selected models the callback function select Func  is called where the pathId was passed to the function   I attached an small example (modelMetaDataMobilAPItest.zip) which should demonstrated the described techniques
View full tip
In Vuforia studio the best way to interact with 3d model components is to define explicit 3d modelitems (widget modelItem). So this will be an easy way to access the componets and to change their properties e.g. setting of the color  e.g.: $scope.setWidgetProp("modelItem-1", "color",  "rgba(128,0,0,1)");   This will  change the modelItem-1 property color to brown – and will display the component which is specified by this modelItem with a  brown color. Another way to do this in javaScript is something like :   $scope.view.wdg['modelItem-1']['color'] = "rgba(128,0,0, 1);";//brown $scope.view.wdg['modelItem-1']['opacity'] = 0.5;//set transparency to 0.5 //or for the same $scope.setWidgetProp("modelItem-1", "color", "rgba(128,0,0,1);"); //brown $scope.setWidgetProp("modelItem-1", "opacity", 0.5); //set transparency to 0.5   But in some cases during the project development it  could  be helpful when we are  able to manipulate the components or request information about them without defining of explicit modelItem widgets. For example if we want to select a component to see some information about the component and change the color of it:   var PICK_COLOR = "rgba(255,0,0,1)"; ... $timeout( //timeout block 1 function() { //timeout function 1 angular.forEach( //==== for each 3d model block // this will call the function below for each 3d model $element.find('twx-dt-model'), function(value, key) { //for each 3d model block function //===================================================================================== angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { // start the $on() listener 'userpick' + function definition //================================================================================= var pathid = JSON.parse(edata).occurrence; $scope.currentSelection = target + "-" + pathid; // create a component selection e.g. "model-1-/0/0/3/2" console.log("twx.app.isPreview() ="+twx.app.isPreview() ); //print an info if is called in preview mode and could be checked if required try{tml3dRenderer.setColor($scope.currentSelection, PICK_COLOR);} catch (ex) {console.warn("Exception 1 in tml3dRenderer.setColor()=>"+ex);} //will set the color of the selected component } //end of mobile device modelItemsList.push($scope.currentSelection); } //end is in array //================================================================================= }); // finish the $on() listener 'userpick' + function definition } //finish for each 3d model block function ); // finish for each 3d model block //================================================================================= } ,50); // finishtimeout block 1 and function   If  we use  PICK_COLOR  = "rgba(255,0,0,0)"; It means that this color (red) is set for a selected component. Here the one additional detail is the last argument - which have a value of 0. Means alpha channel =0 - or full transparence. On the most mobile devices it will hide the selected component, but this is not supported techniques and we have to use always color with alpha channel >0. / transparent but still visible/   Calling of the tml3dRenderer.setColor(…, undefined); will set the component color back to default - example:   tml3dRenderer.setColor(‘model-1-/0/0/3/2’, undefined);    Another important point is that when we know the model name and know the component ids, in this case we can also set the color or hide components without explicit definition of model items. For example for a particular model we have prepared  a json file (*):     { "/0/0/2" :"rgba(255,0,0,1);", "/0/0/0" :"rgba(128,0,0,1);", "/0/0/5" :"rgba(128,0,128,1);", "/0/0/3/0":"rgba(0,255,0,1);", "/0/0/6" :"rgba(255,200,0,1);", "/0/0/3/1":"rgba(0,0,0,0.2);", "/0/0/7/0":"rgba(0,0,0,0.2);", "/0/0/7/1":"rgba(0,0,0,0.2);" }   The model to which this json file was created is placed in Vuforia Studio as model widget with name=model-1  We can then read this json file (from prject->src\phone\resource\Uploaded folder) with some javaScript construct like (below) and set the color property of the components (also change the transparence - for the components with alpha channel =0.2)  Here an example (*):   //======================================================== // reading a json file with component setting for the components //======================================================== $scope.setCompProps=function() { var FILES_MODEL_COMP = { 'model-1':'comp_info.json' }; $scope.compJSON_Data = new Array(); angular.forEach(FILES_MODEL_COMP, function(jsonFile, target) { console.log("angular.forEach jsonFile = "+jsonFile + ", target="+target); $http.get('app/resources/Uploaded/' + jsonFile).success(function(data, status, headers, config) { $scope.compJSON_Data[target]=data; // in this case is $scope.compJSON_Data['model-1']= of the json structure file here the content'comp_info.json'; angular.forEach(data , function(color, path_id){ console.log("target="+target+" --> color = "+color + ",path_id="+path_id); tml3dRenderer.setColor(target+'-'+path_id, color); });//end for each function }) .error(function(data, status, headers, config) {console.log("calling in foreach 1 failed"); }); }); };     So when we start for this particular model the test code it will change the display of the model according to the setting of the comp_info.json  file:     The code above is more than intended for setting colors and transparency  . According a recommendation from development for hiding of components is better to use  the hidden property:   tml3dRenderer.setProperties($scope.currentSelection, { hidden:true } );   The sample  code below  ( more simplified) is  for the case that we want to blank a component by click on it:   angular.forEach($element.find('twx-dt-model'), function(value, key) { // search all twx-td-model's -> means all model widgets angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { //for each model widget will set a userpick listener try{ console.log('edata');console.warn(edata); console.log("JSON.parse(edata)");console.warn(JSON.parse(edata)); var pathid = JSON.parse(edata).occurrence; $scope.currentSelection = target + "-" + pathid; console.log("=>>"+$scope.currentSelection); } catch (ea) {console.error("not twx-model is clicked but still fired")} try{ // here below the change recommended from R&D tml3dRenderer.setProperties($scope.currentSelection, { hidden:true } ); } catch (e1234) {console.error( "e="+e1234); }   Here tested the code on the HoloLens 1.0 device:     When we have a color definiton  with  opacity -> the alpha channel set here e.g. to 0.1 /  and the defined color should be changed later :   var PICK_COLOR_OPACITY1 = "rgba(,,,0.1)";   to change the rgba expression by setting another value of transparency you can use some construct like this:   var PICK_COLOR_OPACITY1 = "rgba(,,,0.1)"; var OPACITY_VAL=0.3; var PICK_COLOR_OPACITY2= PICK_COLOR_OPACITY1.replace( "0.1)",OPACITY_VAL+")");   The JavaScript code above  will set transperancy value of  0.3 (replacing the 0.1 by 0.3) But for the case that we have in a json file a defintion of color with alpha chanel =0  :     ... "/0/0/3/1":"rgba(0,0,0,0.0);", ...   In this case we can  modify (recommended)  the code to check the value of the alpha channel and if it is ==0 to set  the "hidden" property  - example (*) :   ... //======================================================== // reading a json file with component setting for the components //======================================================== $scope.setCompProps=function() { var FILES_MODEL_COMP = { 'model-1':'comp_info.json' }; $scope.compJSON_Data = new Array(); angular.forEach(FILES_MODEL_COMP, function(jsonFile, target) { console.log("angular.forEach jsonFile = "+jsonFile + ", target="+target); $http.get('app/resources/Uploaded/' + jsonFile).success(function(data, status, headers, config) { $scope.compJSON_Data[target]=data; // in this case is $scope.compJSON_Data['model-1']= of the json structure file here the content'comp_info.json'; //because R&D statement to use hidde property need to check of alpha chanel ==0 angular.forEach(data , function(color, path_id){ console.log("target="+target+" --> color = "+color + ",path_id="+path_id); var start_alpha = color.lastIndexOf(","); var end_alpha = color.lastIndexOf(")"); var alpha_str = color.substring(start_alpha+1, end_alpha); var num = Number(alpha_str); if ( (isNaN(num )) || (num <= 0.0) ) {//set color properly to alpha channel 1.0 var new_color= color.substring(0,start_alpha+1)+"1.0"+ color.substring(end_alpha,color.length) tml3dRenderer.setColor(target+'-'+path_id, new_color); tml3dRenderer.setProperties(target+'-'+path_id, { hidden:true } ); } else { // color unchanged tml3dRenderer.setColor(target+'-'+path_id, color); } });//end for each function }) .error(function(data, status, headers, config) {console.log("calling in foreach 1 failed"); }); }); }; ///////////// ...   The example above will set to the component the correct values of the color but with alpha channel 1.0 and will interpret the original alpha value from the file as setting of the hidden property to true.  Does this make sense? Yes if we later set the hidden property to false then the color setting will be applied according to the definition from  the json file
View full tip
Vuforia Studio Ability to Install Vuforia Studio for offline use (Windows) New Scaling Digital Twin Experiences use case focuses on: Digital twins Identity Resolution Service (IRS) Connecting an AR digital twin to ThingWorx Object configurations ThingWorx content storage New Static Object setting when configuring the detection position for a Model Target Bug fixes and minor improvements Vuforia View iOS/Android/Windows: Added support for Model Targets with a "static" motion hint When used with Car Mode, this combination provides enhanced tracking for larger objects with reflective surfaces iOS Support for iOS 15 HoloLens: Resolved issue with component placement in certain Creo Illustrate sequences Bug fixes and minor improvements Experience Service Bug fixes and minor improvements
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
Vuforia Studio Area Targets are now supported! Area Targets bring Vuforia-powered environment tracking into your Experiences enabling you to track and augment areas and spaces. Area Targets allow users to easily navigate to the appropriate content using spatial clues, ultimately improving operational efficiency and work safety. Bug fixes and minor improvements Vuforia View Italian and Spanish languages now supported for HoloLens 2 voice commands RealWear: Fixes to provide better support and performance for RealWear OS 12; in order to leverage these enhancements, projects will need to be republished. There are a few known issues that have been reported to RealWear. For more information, see Article - CS345904 Bug fixes and minor improvements Experience Service Bug fixes and minor improvements
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
How can ThingWorx (external) data be used to update an experience in real-time? For example, if the data does not fall within a specified range, warning messages will be shown automatically.     In ThingWorx, in Thing, create a Service to check the range and to determine if a warning should be displayed or not. In Vuforia Studio, in the Project, in DATA panel, under External Data section, add the Service. Under Configuration section, check all checkboxes related to refresh to call this Service. Use the ServiceInvokeComplete Event to check the value reported by the Service        
View full tip
Vuforia Studio New Duplicate action available for views in an experience Usability and UI improvements to the New Project window Improvements to Image Target widget source validation to help users upload images that will result in reliable targets Support for ThingWorx 9.4 Vuforia View Bounding box and location information can now be generated for any model at runtime (iOS and Android) As of March 2024, you will no longer be able to install Vuforia View on devices running iOS 14. Experience Service New Project Access screen allows administrators to disable publishing projects with public access in Vuforia Studio Note: If your Experience Service is hosted by PTC Cloud Services, open a Technical Support case requesting to update the configuration Bug Fixes See Bug Fixes for additional information on bugs resolved in this release
View full tip
Vuforia Studio Changes to Check for Updates functionality—You will no longer be automatically prompted with the Update Available window when a new version of Vuforia Studio is available. Beginning in version 9.11.0, you must manually check for updates by navigating to the Vuforia Studio menu in the upper-right corner, and selecting Check for Updates. Once the Check for Updates window appears, compare Your version with the version that is displayed above the list of new features. For more information, see Update Vuforia Studio to the Latest Version. Windows OS: Vuforia Studio is now available for download from the Microsoft Store (The legacy installer is no longer available on the PTC Software Downloads page). A new app package (appxbundle) installer is now available on the PTC Software Downloads page for installing in a closed network environment. (The legacy restricted installer is no longer available on the PTC Software Downloads page). Mac: The Check for Updates action is now available from the Vuforia Studio menu in the upper-right corner. For more information, see Update Vuforia Studio to the Latest Version. Vuforia View ios: Quickly take sharper pictures—Users can now turn advanced camera controls on and off to using the new Turn on Tap to Focus action available from the experience menu. When Tap to Focus is turned off, the focus of interest is automatically selected by the device’s automatic focus areas and exposure setting. When Tap to Focus is turned on, the Vuforia View user can manually select the focus of interest by tapping on a specific area of the screen. Note: By default, Tap to focus is turned off. As of September 2023, Vuforia View will no longer be supported on the following devices. For more detailed information, see the What's New in the Help Center. Surface Pro 7 Surface Pro 6 Surface Pro 5th Gen (2017) Surface Pro 4 Surface Book Surface Go Surface Go2 Vuzix M400 Smart Glasses Experience Service: There are no new features or updates for the 9.11.0 version of the Experience Service. Bug fixes See Bug Fixes for additional information on bugs resolved in this release
View full tip
Currently, we do not recommend using Vuforia Studio with Chrome 106.0.5249.91 or later and Microsoft Edge 106.0.1370.37 due to a major issue with Chromium; Google has been made aware of the issue. Until it is resolved, we recommend using older versions of Chrome or Microsoft Edge.   Vuforia Studio The Wayfinder widget is now out of beta and full access is available. Improvements to the Wayfinder widget include: A pop-up with information about the Wayfinder widget appears when the widget is added to an experience A new Looping property is available that returns a user to the first Waypoint in the array once the last Waypoint is reached Improved ribbon density Improved property names Default gaze vector is now 90 degrees from the surface that the Waypoint is mated to Improved text label design Vuforia View Target acquisition effect is available on all mobile platforms NOTE: The model recognition for Advanced Model Targets will be visualized by a snapping effect in the experience.  HoloLens 2: Wayfinder and Waypoints are now available for 3D eyewear experiences Experience Service There are no new features or updates for the 9.7.1 version of the Experience Service
View full tip
Vuforia Studio Support for ThingWorx 9.3 Support for sequences created with Creo Illustrate 8.1 Bug fixes and minor improvements Vuforia View Bug fixes and minor improvements Experience Service Bug fixes and minor improvements
View full tip
Vuforia Studio New Tagalong property available for 3D Video and 3D Audio widgets Improvements to multi-select for 3D widgets Ability to delete multiple widgets Ability to assign common widget properties a value Audio widget is now available in 2D Eyewear projects Enable Tracking Events property now available on 3D Container for 3D Eyewear projects Bug fixes and minor improvements Vuforia View HoloLens: 3D audio and video widgets will now stay in a user's line of view if the Tagalong property has been enabled in Vuforia Studio Bug fixes and minor improvements Experience Service Support for PingFederate 9.3.3 Patch 5 or later (SSO only) Bug fixes and minor improvements
View full tip
Vuforia Studio New 3D Audio Widget Improvements to the 3D Video Widget for 3D Eyewear projects Ability to change the color of the buttons and panel Vuforia View iOS 12 is no longer supported Bug fixes and minor improvements   Experience Service Bug fixes and minor improvements 
View full tip
Vuforia Studio New 3D Video widget for 3D Eyewear projects Bug fixes and improvements Vuforia View Camera widget for mobile now supported on Windows devices iOS 12 will no longer be supported as of December Support for Vuzix M400 Bug fixes and minor improvements Experience Service Support for PostgreSQL 9.6 and 10.14 Bug fixes and minor improvements
View full tip
Vuforia Studio New Camera widget for mobile and 2D eyewear projects NOTE: This widget is not supported on Windows devices Ability to access and incorporate CAD metadata into an Experience using JavaScript See the Studio Help Center for more information on utilizing this feature New Car Mode checkbox when setting detection configuration properties for a Model Target Bug fixes and minor improvements Vuforia View Support for Car Mode capability for Model Targets Bug fixes and minor improvements Experience Service Support for Windows Server 2019 Support for Red Hat Enterprise Linux 8.0 (RHEL 8.0) Bug fixes and minor improvements
View full tip
Vuforia Studio Bug fixes and minor improvements Vuforia View Bug fixes and minor improvements Experience Service Compatibility with ThingWorx 9.0 Bug fixes and minor improvements
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