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

Vuforia Studio and Chalk Tech Tips

Sort by:
The new 3D-Guided Service Instructions example use case walks you through creating a Vuforia Studio Experience that will allow a frontline worker to:  Find the physical location of a broken part on an object using a digital model  Search for parts on a 3D model of the object  Find and order replacement parts from a vendor using a persistent shopping cart   The 3D-Guided Service Instructions use case will walk you through the following sections: 3D-Guided Service Instructions 101: Use Attributes in Creo Illustrate   3D-Guided Service Instructions 201: Use JavaScript to Highlight Parts and Create Ionic Popups   3D-Guided Service Instructions 202: Use JavaScript to Find Parts   3D-Guided Service Instructions 301: Add Pricing Data and a Shopping Cart to a Model   3D-Guided Service Instructions 302: Add a Simple ThingWorx Service to Vuforia Studio   3D-Guided Service Instructions 303: Create a Persistent Shopping Cart Using   ThingWorx
View full tip
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
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
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"}}}      T he 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
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
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
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
1.) The first point  here is to clarify : is it possible to extract model data of 3d models in Vuforia Studio?  ( data could be extracted by Creo View Toolkit apps but here is considered only the Vuforia Studio environment) Supposing , we have a model widget for an assembly model without explicit modelitem  widget definitions. The question is: Can we extract data for the components and if yes,  then what data we can extract? In Vuforia Studio Environment Extracting of data is possible only in Preview mode, because we have in preview mode the method tml3dRenderer.GetObject() where we can access a model object (a component) example:   let comp_widget=tml3dRenderer.GetObject(selection).GetWidget()   where the selection is some thing like "<modelname>-<compPath>" e.g. : "model-1-/0/0/3/2"   Then from the widget we can extract data:   var loc=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() console.error("DEBUG getObj.GetWidget()") console.warn(tml3dRenderer.GetObject(selection).GetWidget())   When we   explore  the different methods in the crome debugging console,  we will find methods to  get or  to set  properties. To extract data, we can use the get... methods.   The methods of  tml3dRenderer.GetObject() seems currently not to work in Vuforia View on end devices (the tml3dRenderer object is a handle of the cordova vuforia plug in and it has a different implementation on the different end devices.In preview mode so far I know, the graphic is based on WebGL and Three.js)  Therefore we will be not able for example to get the data of a component selection on the end device. So means we need a way to extract data in Preview mode and make it available in the Vuforia view on the end device.  Here I did not find a methods to extract the original component name but I was able to create a list (json) with the position data ( I did not add color but this is possible to access it - e.g. tml3dRenderer.GetObject(selection).GetWidget().GetColor()) We can create a json  e.g. of  following data:   {"model-1-/0/0/0":{"valid":false,"orientation":{"x":0,"y":0,"z":0}, "size":{"x":1,"y":1,"z":1},"scale":{"x":1,"y":1,"z":1}, "position":{"x":9.999999998199587e-24,"y":9.999999998199587e-24,"z":9.999999998199587e-24}}, "model-1-/0/0":{"valid":false,"orientation":{"x":0,"y":0,"z":0},"size":{"x":1,"y":1,"z":1}, "scale":{"x":1,"y":1,"z":1},"position":{"x":0,"y":0,"z":0}}, "model-1-/0/0/2":{"valid":false,"orientation":{"x":0,"y":90,"z":0},"size":{"x":1,"y":1,"z":1}, "scale":{"x":1,"y":1,"z":1},"position":{"x":0,"y":0.029500000178813934,"z":-5.51091050576101e-18}}, ...}   we can  assign the json to a variable e.g. $scope.COMP_LOCs So later we can read the current position data on end device:   var selection_location=$scope.COMP_LOCs[l_currentSelection] //read the location data from json varible console.log("selection:"+l_currentSelection+"->X= "+ selection_location.position.x); //print it to console selection_location.position.x= round(parseFloat(selection_location.position.x) + 0.005,4) //add 0.005 shift and round to 4 dec   2.)In point 1.)  we checked how to  extract the data of an compoent (a selection) .But Actually  we have a couple of methods to extract the data but what we do not have is a valid  selection of an assembly  component . This is required to obtain a valid modelitem widget (temporar) via tml3dRenderer.GetObject(). For the selection generation we have the model widget name e.g. “model-1” but   we do not have the component ID paths. To be able to construct a selection handle we need to construct the ID path of a component and then we need to check if it exist. This is some kind of graph search where we have an assembly with a components tree.  There the edges are the ids of the components. e.g. /0/0/1/1 , /0/0/1/2, /0/0/1/4, … etc. One possible algorithm is the deep first search:     To implement this I used the following javaScript code:   ///////////////////////////// var max_asm_depth=6; //this is the max depth in Creo Parametric var max_numb_comp_asm=25; /////////////////////////// ->deep first function check_comp_deep_first_recursively(target,path,arr) { //console.warn("called check_comp_deep_first_recursively(target="+target+",path="+path+")"); var selection = target+'-'+path var path_array = path.split('/') var depth = parseInt(path_array.length) var num = parseInt(path_array[depth -1]) var prev_num = parseInt(path_array[depth -2]) var prev_path = '' for (var i=1;i < depth -1;i++) {prev_path= prev_path +'/' + path_array[i]} if( check_for_valid_selection(selection) == 1) { arr[selection]=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() if( (depth+1) < max_asm_depth) check_comp_deep_first_recursively(target, path + '/0', arr) else { if(num +1 < max_numb_comp_asm) check_comp_deep_first_recursively(target, prev_path + '/'+(num +1), arr)} } else { var right_num = num +1 if(right_num < max_numb_comp_asm) check_comp_deep_first_recursively(target, prev_path + '/'+right_num, arr) else if(!Number.isNaN(prev_num) ) {//console.log("--2") prev_path = '' for (var i=1;i < depth -2;i++) {prev_path = prev_path +'/' + path_array[i]} prev_path = prev_path +'/' + (prev_num +1) check_comp_deep_first_recursively(target, prev_path , arr) } } } ////////////////////////// ///call of the function: $scope.compJSON_loc_Data = new Object(); var target="model-1" check_comp_deep_first_recursively(target,'/0',$scope.compJSON_loc_Data) ...   The code above has the following weak spot - I need to give the maximum depth (max_asm_depth) and the maximum possible branches (max_numb_comp_asm)  The maximum depth currently in Creo assembly is 25 so that value which > 25 will not make a sense.  The value of  max_numb_comp_asm  in a flat assembly (only one level of depth) corresponds to the number of the components - the maximum number of branches on particular level of depth   The another possible algorithm is the breadth first search:     To implement this  I used the following JavaScript code:   ///////////////////////////// var max_asm_depth=6; //this is the max depth in Creo Parametric var max_numb_comp_asm=25; /////////////////function check_comp_at_level(target,num,depth,arr) // ->breadth first function check_comp_at_level(selection,num,depth,arr) { var position =''; // console.log("call check_comp_at_level =>"+selection); try{ // console.log("====== check here ==========="); //console.warn(tml3dRenderer.GetObject(selection).GetWidget().GetLocation()); var loc=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() if( (loc.scale.x == 0) || (loc.scale.y == 0) || (loc.scale.z == 0) ) return 0; // the scale could not be zero //position= tml3dRenderer.GetObject(selection).GetWidget().GetLocation().position //console.warn(position); //arr[selection]=position arr[selection]=loc return arr[selection]; } catch (e) {console.error("failsed with error="+e); return 0;} } /////////////////////////// function check_comp_at_level_recursively(selection,depth,arr) { //console.warn("called check_comp_at_level_recursively("+selection+",depth="+depth+")"); var num =0; if(depth >= max_asm_depth) { //console.log("maximum depth of max_asm_depth ="+max_asm_depth+" reached"); return 0;} for (num=0;num < max_numb_comp_asm; num++) { var currentSelection =selection+'/'+num if(depth <0) return 0; var pos = check_comp_at_level(currentSelection,num,depth,arr) if(pos ==0 ) { continue;} else {check_comp_at_level_recursively(currentSelection,(depth+1),arr) } } //end of for } ////////////////////////// //////////////////////////////// function check_for_valid_selection(selection) { //console.log(" check_for_valid_selection =>"+selection); try{ var loc=tml3dRenderer.GetObject(selection).GetWidget().GetLocation() if( (loc.scale.x == 0) || (loc.scale.y == 0) || (loc.scale.z == 0) ) return 0; return 1; } catch (e) {console.error("failsed with error="+e); return 0;} } /////////////////////////// ///call of the function: $scope.compJSON_loc_Data = new Object(); var target="model-1" check_comp_at_level_recursively(target,'/0',$scope.compJSON_loc_Data) ...     The code for the breadth first search uses also the parameters for maximum depth (max_asm_depth) and the maximum possible branches (max_numb_comp_asm)  - so means it have the mentioned  restriction. If we set a value which is large this will increase the time until the search is completed so therefore depending of the particular assembly we need to set the both parameter properly ( we need to be able to scan the whole assembly but to minimize the search time) For different assemblies the first deep or first breadth could lead to better results. For example, for flat assembly structures the better approach will be to use the first breadth algorithm  But actually the performance is not so important here, because the search will be called one time and  then the json list should be saved.  With the current functionality we can read a file (json file ) from the project  upload directory , but it seems that it is  not  possible to save the information to a e.g. json file there (upload folder). To read a json file form the upload folder we can use some code like this:     target='model-1' $http.get('app/resources/Uploaded/' + jsonFile).success(function(data, status, headers, config) { $scope.compJSON_mod=data; // in this case the data is the received json object angular.forEach(data , function(color, path_id){ $scope.compJSON_Data[path_id] =position; console.log("target="+target+" --> $scope.compJSON_Data["+path_id+"] = "+$scope.compJSON_Data[path_id]); });//end of the error function ////////// finish for each path_id }) .error(function(data, status, headers, config) {console.log("problem in the http will create a new ");   When we want to save data  (the generated json list) we need to use another workaround - we can use a thingworx repository. Following functions /events could be used to save and receive an json object to/from a twx repository:   // the methods SaveJSON and LoadJSON // for the repository object should have //run permision for es-public-access user ////////////////////////////////////////////////////////////// $scope.SaveJsonToTwxRepository = function(path, content) { $scope.$applyAsync(function() { $rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.SaveJSON', {"content": content, "path":path} );} ,500 ); }; ////////////////////////////////////////////////////////////// $scope.GetJsonFromTwxRepository = function(path) { $scope.$applyAsync(function() { $rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadJSON', {"path":path} );} ,500 ); $scope.app.speak("after call of GetJsonFromTwxRepository") //in the modelloaded listener register // LoadJSON-complete event -> to laod the data into session rootScope.$on('modelLoaded', function() { //// $scope.$root.$on('LoadJSON-complete', function(event, args) { console.log("LoadJSON-complete event"); $scope.COMP_LOCs=args.data console.log(JSON.stringify( $scope.COMP_LOCs)) }); /// });   In  the code above I use the 'modelloaded' listener to register LoadJSON-complete event . Because the service is called asyncronously- we need this event to load the data into session when it is received from thingworx. Here in this example the repository object is named "CAD-Files-Repository" The Thingworx services should have run permission and it is required to be added in the external data panel :     So when we start the project in PREVIEW mode we can call the search for the assembly structure and save it then  to thingworx. In Vuforia View mode   then we can receive the previously saved json object from thingworx. To check the current mode (if Preview or End Device)  we can use    if(twx.app.isPreview() == true) ...   it will  check if the current mode is preview mode or Vuforia View on the end device - here an example of the workflow:   if(twx.app.isPreview() == true) {// preview mode //calling breadth first - test check_comp_at_level_recursively(target+'-',0,$scope.compJSON_POS_Data) //console.warn($scope.compJSON_POS_Data) //calling deep first a second test and generating a data - locations check_comp_deep_first_recursively(target,'/0',$scope.compJSON_loc_Data) console.log("========================================") console.log("$scope.compJSON_POS_Data ->breadth first") console.log("========================================") console.log(JSON.stringify($scope.compJSON_POS_Data)) console.log("========================================") console.log("") console.log("") console.log("========================================") console.log("$scope.compJSON_loc_Data ->deep first") console.log("========================================") console.log(JSON.stringify($scope.compJSON_loc_Data)) $scope.SaveJsonToTwxRepository('/CADFiles/json_lists/compJSON_loc_Data.json',$scope.compJSON_loc_Data) $scope.GetJsonFromTwxRepository('/CADFiles/json_lists/compJSON_loc_Data.json') console.log("========================================") console.log("") } else { //here is the part on mobile device $scope.GetJsonFromTwxRepository('/CADFiles/json_lists/compJSON_loc_Data.json') }   I tested all points of  the described techniques above in a  test project which I want to provide here as zip file for the HoloLens (hideComponetsHoloLens .zip):     So to be able to test it you need to create in Thingworx a repository thing - means a thing which uses  the thing template "FileRepositroy" with the name "CAD-Files-Repository" and create a folder there "/CADFiles/json_lists/" (if you use another name and another folder (e.g. "/" no folder - the root repository folder) you have to adapt the javaScript code:   ... /CADFiles/json_lists/compJSON_loc_Data.json ... app.mdl.CAD-Files-Repository.svc.SaveJSON' ... app.mdl.CAD-Files-Repository.svc.LoadJSON'    
View full tip
Your company might have a css that represents the corporate identity - or you may have other sources of reusable css styles that you want to include with minimal effort. Here is what you have to do to use corporate css files to drive the look and feel of your experiences: Add the corporate css file (e.g. company.css) to your resources In Application styles add the following at the beginning (before any other css style entry: @import url(#{$resources}/Uploaded/company.css);     With the following content in company.css: And this label definition:   Produces this outcome (you see it in the editor as well as in the preview):   Gotcha!    
View full tip
This is the third JavaScript quark in the series: it can be used to change a widget color by cycling through a given array of colors. You can find the second quark here.   Here's the code to copy & paste  to your H ome.js :   $scope.cycleColors = function(widget, colors, time, interval) {   let w = (widget.color !== undefined ? widget : $scope.view.wdg[widget]);   if (!w || w.color === undefined) { throw "Cannot color-cycle this widget"; }   let originalColor = w.color;   w.color = colors[0];   w.visible = true;   w.opacity = 1;    let nSteps = Math.ceil(Math.floor(time/interval) / colors.length) * colors.length;   return $interval(iterationCount => w.color = iterationCount === nSteps ? originalColor : colors[iterationCount % colors.length], interval, nSteps); } This JavaScript quark will make the widget color cycle through the colors provided in the colors array. The effect will last time milliseconds, and each color change will happen every interval milliseconds.   Invoke the function like this:   cycleColors(widget, colors, time, interval);   where   widget   is either the id of the widget (e.g.   modelItem-1) or the widget itself (e.g. $scope.view.wdg['modelItem-1']), colors represents an array of colors (e.g. ["rgba(200,0,0,1)", "rgba(0,0,200,1)"]), time represents the total time in milliseconds it takes to execute this effect, and interval represents the amount of time in milliseconds between each color change.   Here's an example:   cycleColors("modelItem-1", ["rgba(200,0,0,1)", "rgba(0,200,0,1)", "rgba(0,0,200,1)"], 2000, 50); This example cycles the model item color through red, green and blue; the effect will last for 2 seconds with a color change every 50 ms.   Comments and suggestions are welcome.   -Alessio  
View full tip
This is the second Javascript quark in the series: it can be used to fade a widget out. You can find the first quark here.   Here's the code to copy & paste  to your H ome.js :   $scope.fadeOut = function(widget, time, interval) { let w = (widget.opacity !== undefined ? widget : $scope.view.wdg[widget]); if (time <= 0 || interval <= 0 || w.opacity === undefined) { throw "Cannot fade this widget"; } let steps = Math.floor(time / interval); let opDelta = w.opacity / steps; return $interval(() => w.opacity = (opDelta < w.opacity) ? (w.opacity - opDelta) : 0, interval, steps); } This quark will make the widget fade out from its current opacity to 0 in time milliseconds, uniformly decrementing opacity at every interval .   Invoke the function like this:   fadeOut(widget, time, interval);   where   widget   is either the id of the widget (e.g.   modelItem-1) or the widget itself (e.g. $scope.view.wdg['modelItem-1']), time represent the total time it takes to fade the widget out from its current opacity, and interval represents the amount of time between each opacity change.   Here's an example:   fadeOut('modelItem-1', 2000, 50); This example fades the model item out by bringing its current opacity to zero after 2 seconds with an opacity change every 50 ms.   Comments and suggestions are welcome.   -Alessio    
View full tip
Hi,   we can do many tricks with Javascript in Studio and most of the times it's just a matter of copying & pasting the right code.   I'd like all Studio users, not just coders, to benefit from this, and thought I could drop here a snippet to blink a widget.    I call this a quark - from the particle physics standard model - and not atom, because it's really a smaller building block than an atom    Blinking a widget can be useful, for example, if you are not using Creo Illustrate to create a sequence but still want to draw the user attention to some item in the scene.     Here's the Javascript code to copy & paste  to your H ome.js:  (to Javascript coders: I'm using modern Javascript syntax, don't be frightened by that )   $scope.blink = function(widget, times, interval) { let w = (widget.visible !== undefined ? widget : $scope.view.wdg[widget]); if (!w || w.visible === undefined) { throw "Cannot blink this widget"; } $interval(() => w.visible = !w.visible, interval, times); }    Invoke the function like this:   blink(widget, times, delay); where widget is either the id of the widget (e.g. modelItem-1) or the widget itself (e.g. $scope.view.wdg['modelItem-1']). The other two numbers are the number of times that you want visibility to change, and the amount of milliseconds between each visibility change.     Here follow some examples.   You want to blink the widget 4 times with a 300 ms interval (and leave the widget visible at the end): blink('modelItem-1', 2*4, 300);   You want to blink the widget 4 times with a 300 ms interval (and leave the widget not visible at the end): blink('modelItem-1', 2*4+1, 300);   You can comment and suggest additional quarks if you want.   Alessio  
View full tip
Announcements
Topics available:
AR/VR for Data Optimization AR/VR for Security and Control AR/VR for Inspection