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

Community Tip - Help us improve the PTC Community by taking this short Community Survey! X

Vuforia Studio and Chalk Tech Tips

Sort by:
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
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
Vuforia Studio Bug fixes and minor improvements Vuforia View Improved detection and tracking for Model Targets and Spatial Targets (all platforms) HoloLens 2: Improved QR code scanning performance Bug fixes and minor improvements Experience Service Bug fix for the Trust Proxy setting Other bug fixes and minor improvements
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
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
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
Vuforia Studio New Camera widget for 3D Eyewear projects Bug fixes and minor improvements Vuforia View Improved detection and tracking for Model Targets and Spatial Targets Bug fixes and minor improvements Experience Service Bug fixes and minor improvements
View full tip
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
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
Vuforia Studio Bug fixes and minor improvements Vuforia View Bug fixes and minor improvements Experience Service (On-premises) Support for single sign-on (SSO) authentication for on-premises installations NOTE: Requires Vuforia Studio and Vuforia View 8.5.5 Bug fixes and minor improvements   For more information on configuring Vuforia Experience Service with single sign-on (SSO), see the Experience Service Installation and Deployment Guide. 
View full tip
Vuforia Studio Support for Microsoft Edge browser (version 79 or greater) Bug fixes and minor improvements Vuforia View Bug fixes and minor improvements Experience Service Experience Service 8.5.6 address a critical security issue (CVE) in Node.js Bug fixes and minor improvements
View full tip
Vuforia Studio Scan widget is now available in a 3D eyewear project Bug fixes and minor improvements Vuforia View Support for bar code scanning within HoloLens Experiences Improved detection and tracking for Model Targets and Spatial Targets iOS: iOS 11 is no longer supported Android: Android 5.0 will no longer be supported as of February 2020 Bug fixes and minor improvements Experience Service Bug fixes and minor improvements
View full tip
Vuforia Studio Bug fixes and minor improvements Vuforia View RealWear 6.0 is no longer supported Bug fixes and minor improvements Experience Service An 8.5.3 version of the Experience Service was not released
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
To reference a function on a voice command or gesture use  viewCtrl.myFunction() where "myFunction" is the name of your function.     Check out the Vuforia Studio Help Center for detailed instructions on creating a HoloLens experience using gestures.  
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
Vuforia Studio Bug fixes and minor improvements Vuforia View Bug fixes and minor improvements To download the latest version of Vuforia View on HoloLens, the app store must be updated to version 11911.1001.9.0 or higher Experience Service An 8.5.2 version of the Experience Service was not released
View full tip
PDFs can be linked to experiences using a few methods. Below is an example of using the toggle widget or a toggle button to open and close a PDF within your experience.          Example of JavaScript code to add to Home.js file: $scope.toggleButton = function() { //if the toggle is pressed if ( $scope.view.wdg['toggleButton-1']['pressed']==true) { window.location='app/resources/Uploaded/%5BBD-Logbuch%5D20190208-20190310.pdf' console.log($scope.view.wdg['file-1']['url']) } //unpress the toggle button after 1,5 sec $timeout(function () { $scope.view.wdg['toggleButton-1']['pressed']=false;}, 1500); }      
View full tip
The Portal Login/Auto-Configure option within Vuforia Studio is designed to help Trial users  configure their Studio instance with their assigned Experience Service and ThingMarks.    Corporate networks may block communication to the Vuforia portal or the assigned Experience Service.  If you receive any of the below errors, follow our the resolution steps in our Knowledge Base article Vuforia Studio failed connection issues Auto Configuration failed and get message to Check log for errors Error: connect ETIMEDOUT <ip address> Error: read ECONNRESET If a proxy is in use on the network, it must also be configured within Vuforia Studio as Studio does not  inherit browser proxy settings. A protocol must be used when defining the proxy URL within Studio. Proxy configuration can be done on the Settings menu option.      
View full tip
Vuforia Studio Improved Image Target and Model Target generation Bug fixes and minor improvements Vuforia View Improved detection and tracking for Model Targets and Spatial Targets Bug fixes and minor improvements Experience Service Improved Image Target and Model Target generation Bug fixes and minor improvements
View full tip
Vuforia Studio New 3D Button widget that supports HoloLens 2 articulated hand tracking (3D Eyewear projects only) Bug fixes and minor improvements Vuforia View Support for Microsoft HoloLens 2 Bug fixes and minor improvements Experience Service An 8.5.0 version of Experience Service was not released. However, Experience Service 8.4.6 will support the upcoming ThingWorx 8.5 release.  
View full tip