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

Community Tip - Did you get called away in the middle of writing a post? Don't worry you can find your unfinished post later in the Drafts section of your profile page. X

Vuforia Studio and Chalk Tech Tips

Sort by:
Often, we need to display some sections, or we required to have a view of a x-section of the model. This is in generally no part of the current functionality but there are some approaches which could be helpful. In this article I want to mention 3 different approaches, which could be used but no one of them is really perfect:   1.) uploading different models -  so we can use an additional models for each cut and then we can change the model if you want to display a cut, it means you can make the one model not visible and the display the second model or vice versa.  I tested with cuts created in Creo Illustrate 5.0 and Creo View 5.0 but it seems that Vuforia Studio could not display them /neither as part of sequence or as static to the current figure:     The only possible  way in this example is to create in Creo an assembly cut /with option cut also on part level  and  then create from there a new .pvz model.  In this case this seems to work fine: :     2.) the second approach is to remove components  step  by step,  so to see the inner components when the outer components are blanked: All different components, which should be displayed or blanked, need to be defined as modelItem where we can set the visible property to true or false Is also possible to blank or display  a list of components where the list is defined in json file. This could be done with JavaScript code. In this case we do not need to define a modelItem widgets.  For more information you can check the post    3.)  The last most powerful option is to use a shader for the model Widget. So for example we can use some kind of clipping functionality of the model where we can set the x min and x max value or ymin and ymax or zmin and zmax value what should be displayed. This will create a clipping effect. So only the geometry which satisfy this criteria will be displayed.     How to achieve a clipping functionality using a shader. 3.1) define a shader - this requires creating a tmlText widget where we can define the shader Code. The code should be inserted into the text property of the tml widget For the clipping along x axis with planes which are parallel to the  YZ plain we need to define the following javascript with GLSL:   <script name="slice_world_based_x" type="x-shader/x-vertex"> attribute vec3 vertexPosition; attribute vec3 vertexNormal; varying vec3 N; varying vec4 vertexCoord; uniform mat4 modelMatrix; uniform mat4 modelViewProjectionMatrix; void main() { vec4 vp = vec4(vertexPosition, 1.0); gl_Position = modelViewProjectionMatrix * vp; vertexCoord = modelMatrix*vp; // the surface normal vector N = vec3(normalize(modelMatrix* vec4(vertexNormal,0.0))); } </script> <script name="slice_world_based_x" type="x-shader/x-fragment"> // this setting is needed for a certain use of properties precision mediump float; // determine varying parameters varying vec3 N; varying vec4 vertexCoord; // determine shader input parameters uniform vec4 surfaceColor; uniform float slicex; uniform float slicewidth; const vec3 lightPos = vec3(1.0, 2.2, 0.5); const vec4 ambientColor = vec4(0.3, 0.3, 0.3, 1.0); void main() { // calc the dot product and clamp based on light position // 0 -> 1 rather than -1 -> 1 // ensure everything is normalized vec3 lightDir = -(normalize(lightPos)); vec3 NN = normalize(N); // calculate the dot product of the light to the vertex normal float dProd = max(0.0, dot(NN, -lightDir)); // calculate current color of vertex, unless it is being clipped... // only geometry with coordinates which satisfy the condition below // will be displayed if ( vertexCoord.x > (slicex + slicewidth/2.0) || vertexCoord.x < (slicex - slicewidth/2.0) ) { discard; } else { // calculate the color based on light-source and shadows on model gl_FragColor = (ambientColor + vec4(dProd)) * surfaceColor; } } </script> Save the tml Text widget .     3.2) You can also define shaders for clipping along the Y and the Z axis. Here you can use the same shader javascript definition but you need to change the shader name and to modify the if  check respectively to the Y or Z coordinate:   <script name="slice_world_based_y" type="x-shader/x-vertex"> ... // calculate current color of vertex, unless it is being clipped... if ( vertexCoord.y > (slicex + slicewidth/2.0) || vertexCoord.y < (slicex - slicewidth/2.0) ) { discard; } else ...   To set the clipping values for different axes and to control it by   sliders we can use some code like this:   $scope.DIR="y"; $scope.slice = function() { var slicexCur = ($scope.view.wdg['slider-1']['value']/100.0)-0.5; var slicewidthCur = ($scope.view.wdg['slider-2']['value']/100.0); $scope.view.wdg['modelx']['shader'] = "slice_world_based_"+$scope.DIR+";slicex f "+ slicexCur + ";slicewidth f " + slicewidthCur; } //////////////////////////////////////////// $scope.$on('$ionicView.afterEnter', function(){ // Anything you can think of $scope.clickY() }); /////////////////////////////////////// $scope.clickX=function() { $scope.view.wdg['toggle-X']['value'] =true $scope.view.wdg['toggle-Y']['value'] =false $scope.view.wdg['toggle-Z']['value'] =false $scope.DIR="x" } $scope.clickY=function() { $scope.view.wdg['toggle-X']['value'] =false $scope.view.wdg['toggle-Y']['value'] =true $scope.view.wdg['toggle-Z']['value'] =false $scope.DIR="y" } $scope.clickZ=function() { $scope.view.wdg['toggle-X']['value'] =false $scope.view.wdg['toggle-Y']['value'] =false $scope.view.wdg['toggle-Z']['value'] =true $scope.DIR="z" } ///////////////////////////////////////   Here the slice() function is called in the slider change event (for the both slider widgets). For better understanding of the functionality you can review the attached Vuforia Studio project Here attached the improved version for HL. It is also approved by dev team.  slice_example.zip https://community.ptc.com/sejnu66972/attachments/sejnu66972/tkb_vuforiatechtips/48/4/slice_example.z... Another version (also for HL) attached here is the example   slice_example_using_ABCD  project – where the dev team demonstrates an efficient way showing how we can define a cutting plane via plane ABCD parameters (refer to  plane geometry equations : geometry https://en.wikipedia.org/wiki/Plane_(geometry)   To cover this functionality the dev team developed some studio extensions that  PTC customers can use instead.   That extensions widgets will handle reflections etc, and I it is recommend to use  those extensions .: https://github.com/steveghee/OCTO_Studio_extensions https://github.com/steveghee/OCTO_effects_extensions  
View full tip
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
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
To ensure your Chalk experience is the best, make sure to familiarize yourself with the below best practices.   Initialization Keep movement fluid and slow Forward and backward smooth motion is best to allow device to create mapping Small circles in front of the object are also good Note: Do not rotate your device - keep the device's orientation fixed, moving it parallel to the object of interest and keeping the latter in view during initialization movement Environment It is important for the environment to have a lot of saliency, interesting features, & textures e.g. Stickers, buttons, cables, images/designs, shapes with corners, etc Stationary objects are best for Chalking Reflective, plain colored, or blank surfaces are not good for using Chalk Marks Well-lit areas are best for Chalk performance If an environment is too dark the device's camera will not be able to detect objects External light may be needed if the environment is too dark Either user can toggle the flash on Network/Bandwidth Low bandwidth will result in poor video quality Ensure that you have good bandwidth Chalk Marks Use simple drawings to communicate instructions Circles, lines, & arrows work best Delete Chalk Marks that are no longer needed to reduce clutter Use the pause button to draw on a steady image  
View full tip
Mechanism  Concept in Vuforia Studio- How to make rotation more easy   When we try to rotate a model or 3d modelitem about a particular space axis it seldom will rotate about the correct axis as we want.  So, in this case we can try to solve this  when we use  some mathematical calculations. For example in the example(picture below)  -   door assembly we want to rotate  the door subassembly via the door hinges:     but  when we  try to rotate the door model about 60degree it rotates undesired on the wrong axis.      The question is:  Which is this axis and how to change it? The answer here is : When we have a PVZ model we cannot really change it!  We can use some mathematical relations to get the correct behavior . In this particular sample case the correct javaScript relation should be something like :   ... $scope.simple_door_slider_change = function (angle, door_length) {  var angle1=angle; var l_door=door_length;  var angle1_rad=angle1*Math.PI/180.0; $scope.view.wdg['modelItem-door-asm']['rx'] = angle1 ; $scope.view.wdg['modelItem-door-asm']['y']  = 0.0 - l_door*Math.sin(angle1_rad); $scope.view.wdg['modelItem-door-asm']['z']  = 0.0 - l_door*(1.0-Math.cos(angle1_rad)); $scope.app.params['door_angle']=  angle1; }; ...   So  calling $scope.simple_door_slider_change(70,0.950); will rotate  this particular door assembly on the correct place:       But what we can do to solve the problem  for more complex assemblies. For example when we want to rotate the door handle. Of course such calculation is no problem but this calculation will be more complex (containing movements and rotations ) and we need to invest significantly more  time for the creation of the mathematical concept of it. The main problem is that mostly we do not know what is the correct coordinate axis for each component.   Unfortunately, the only option, what we have here is to make some consideration already in the Creo Parametric design (or in another Cad tools) . So for example the following part have a default coordinate system. Here on the example picture is the name  PRT_CSYS_DEF.     When we later rotate about the x  in Vuforia Studio  then it will rotate about the X axis of the default csys here “PRT_CSYS_DEF”  So, this means when we have some component which should be later rotated in Vuforia, in this case  we need to pay attention already   in the design and try to  assemble the component were  the default csys is on the correct location.    The default coordinate system in a Creo Parametric model is created with the model and it is not possible to change it later (there is a workaround where we can use an auxiliary  assembly where we can insert the model. In this case we can move the model inside  the auxiliary assembly. The auxiliary  assembly will rotate about the default coordinate axis).   So, the next step is to consider, how to design a more complex mechanism assembly. Lets consider the following assembly:       When we create a project and then try to rotate  different components (arms) via slider then we will have e.g. the following situation:       So that the one (blue) component is rotated as desired but when we rotate the blue component the green component does not follow it. Let's create another version of the mechanism were we have the correct behavior:     What is there different?   The answer is that we used a different structure. Here we nested the moved component in further sub assemblies.       It is important that in this case for the modelItems widget defintion in vuforia studio we are not using only a parts  but also  assemblies. So here the subassembly arm 2 was used  for the definition of modelitem which contains the arm1  (part) which is an addtional modelitem.       So in this case we could change the rotation value of the axis and they rotates as desired.     
View full tip
This post should provide more detailed steps additionally  to the posts ("How to extract the components with properties from a pvz file"[1.] and "How to use ThingView Widget from Navigate to display CAD Model/Viewables in custom mashup- Concept"[2])  This post should consider more detailed the steps for the extracting of viewables and also how to extract the sequence steps information from a .pvz / Creo Illustrate model for further usage in a Thingworx service. Following steps: 1.) Extracting the data from the Creo View Model ( Created from Creo Illustrate via publish to pvz functionality) As described in [1.] we required for the extraction of information a Creo View Toolkit.  A good choice will be the usage the Creo View  WebGL toolkit module.   A web toolkit program is called inside a html document ,where the javaScript Creo View WebGl Api is embedded. So, the most important logic could be called on the windows load function. The code below will initialize  the thingview library and  will open the pvz model file (value of variable CUR_MODELPATH in the code below  is set the complete model path):   ... window.onload = function() { ThingView.init("js/ptc/thingview", function() { // ThingView should only be initialised once per frame //---------------------------------- //send the modelname to server var xhr = new XMLHttpRequest(); xhr.open("POST", "RAY_LOG_FILE:", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { var json = JSON.parse(xhr.responseText); } }; //set the json modelname object var JSON_BOM_MODEL_OBJ = new Object; MODELNAME = CUR_MODELPATH.substring(CUR_MODELPATH.lastIndexOf("/") + 1) JSON_BOM_MODEL_OBJ.name = "BOMMODELNAME"; JSON_BOM_MODEL_OBJ.value = MODELNAME; xhr.send(JSON.stringify(JSON_BOM_MODEL_OBJ)) console.warn("sent BOMMODELNAME=" + MODELNAME) //finish the sending of the model_name //---------------------------------- console.log("Creo View WebGL Viewer is now initialised"); session = ThingView.CreateSession("CreoViewWebGLDiv"); //refers to the CreoViewWebGLDiv -> a div area in the html fileSource // which contains the code var xhttp = new XMLHttpRequest(); MODELNAME = CUR_MODELPATH.substring(CUR_MODELPATH.lastIndexOf("/") + 1) xhttp.open("POST", "RAY_JSON_VIEWABLE:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var js_obj = new Object; js_obj.name = "VIEWABLE"; js_obj.value = MODELNAME; var data = JSON.stringify(js_obj); xhttp.send(data); model = session.MakeModel(); ////==================LoadFromURL Callback model.LoadFromURLWithCallback(CUR_MODELPATH, true, true, false, function(success, isStructure, errorStack) { var illustrations = model.GetIllustrations(); for (var i = 0; i < illustrations.size(); i++) { console.log("Illistration name: " + illustrations.get(i).name); // seems illustrations.get(i).name == pviFile model.LoadIllustrationWithCallback(illustrations.get(i).name, function(success, pviFile, stepInfoVec) { if (success === true) { var hasAnimation = model.HasAnimation() var hasSequence = model.HasSequence() xhttp.open("POST", "RAY_JSON_VIEWABLE:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var js_obj = new Object; js_obj.name = pviFile; js_obj.value = pviFile; js_obj.type = "viewable"; js_obj.hasSequence = hasSequence; js_obj.hasAnimation = hasAnimation; var data = JSON.stringify(js_obj); xhttp.send(data); for (var ii = 0; ii < stepInfoVec.size(); ++ii) { xhttp.open("POST", "RAY_JSON_VIEWABLE_STEP:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var step_obj = new Object; console.log("step nr=" + ii); step_obj.viewablename = pviFile; step_obj.nr = ii; step_obj.name = stepInfoVec.get(ii).name; step_obj.description = stepInfoVec.get(ii).description; var data = JSON.stringify(step_obj); xhttp.send(data); //============================================= } } }) } ////============= setTimeout(function() { { xhttp.open("POST", "RAY_JSON_VIEWABLE:", true); xhttp.setRequestHeader("Content-Type", "application/json"); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4 && xhttp.status === 200) { var json = JSON.parse(xhttp.responseText); } }; var js_obj = new Object; js_obj.name = "FINISHVIEWABLES"; var data = JSON.stringify(js_obj); xhttp.send(data); } }, 10000); }); ///model load from URL funciton ///////////// }); // ThingView.init( ) }; //window onload function   The program will generate 2 different json files and will send them to the http server. When the Creo View WegGl program is started (load  the html file from the http server)  - on the server side - in the node.js console  we can see the printing of the received data - example on the picture below:     The Creo View WebGl program will create on the server side  2 json files (this requires also handling of the received  data on the server side as allready mention in the post [1.]  ) For the data extraction I used in this example the PTC demo file (provided with the installation of Creo View Toolkit) : worldcar-brake-multi-figure.pvz,  worldcar-brake-multi-figure.pvz-viewableSteplist.json   [{"viewablename":"Sequence","nr":0,"name":"Sequence","description":""}, {"viewablename":"Sequence","nr":1,"name":"Step 1","description":"Remove spring clips"}, {"viewablename":"Sequence","nr":2,"name":"Step 2","description":"Release 4 bolts"}, {"viewablename":"Sequence","nr":3,"name":"Step 3","description":"Pull apart the calipers"}] worldcar-brake-multi-figure.pvz-viewablelist.json [{"name":"Figure 1","value":"Figure 1","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Sequence","value":"Sequence","type":"viewable","hasSequence":true,"hasAnimation":false}, {"name":"Parts List","value":"Parts List","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Sectioning","value":"Sectioning","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Translation","value":"Translation","type":"viewable","hasSequence":false,"hasAnimation":false}, {"name":"Animation","value":"Animation","type":"viewable","hasSequence":false,"hasAnimation":true}]   2.) Definition of a service in Thingworx for the returning of the Sequence Step List (see also "Service for creating of Bom - and Viewable Lists from json files"  [3]  )    Here the first Step is to define a general service which will will convert the JSON file from a file repository to an InfoTable using particular dataShape.  The json file will be taken from a  repository:   var params = { path: the_json_path /* STRING */ }; var Content = Things[the_Repository_Name].LoadJSON(params); var params1 = { infoTableName: undefined /* STRING */, dataShapeName: the_dataShape_name /* DATASHAPENAME */ }; // result: INFOTABLE var jsonTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); var result = DataShapes.ThingviewBomData.CreateValues(); for(i in Content.array){ jsonTable.AddRow(Content.array[i]); } result = jsonTable; //returns the InfoTable     The service has 3 Input parameters : the_json_path, the_dataShape_name and the_Repository_Name (all are String type)       This service could be called for any filerepository, containing any json files(it is not file specific) . Via the dataShape name we will specify the filed definitions.  We need to do this  for each specific json file. In this case we have to  create first manually a DataShape which is compatible to the Json Object.  For example when we start the service RayJsonToInfoTable:     And the dataShape what we need to define for  this particular example:       For the achieving  of the final goal -> the creation of the  Sequence Step List. We need to create a  another service where we can specify the arguments for DataShape and  repository name. Example:     So the input argumets are:  I.) the path to the json file and II.) the name of the sequence for which we want to see the steps.     I am not sure if we can omit the step , where we create a  dataShape for specific json - so some kind of  dynamic dataShape generation - because in such case  we need only to specify the json file without manual editing opreration.      
View full tip
In this article  I want to consider the question how to   define a  TWX service, which could create   a  Bom and Viewable Lists from  json files. This will be very helpful option when we want to display some CreoView data in  Thingview.   How to extract BOM and viewable data form a Creo View /Illustrate *.pvz file is shown in the post "How to extract the components with properties from a pvz file".   In this example the json files are saved already  in a thingworx repository (means a thing form template FileRepostiroy) e.g.  "CAD-Files-Repository" :   1.)Create a service for the BOM List /InfoTable                 edit the CAD-Files-Repository thing and create a new service named  "GetBomStruct_arg_path" set  the BaseType : INFOTABLE . Set the DataShape property of the service - > here to BomListStuct -> this datashape need to be created  first . It should have the following Fields / property (all String type):                               create a service input parameter "the_json_path". Using this parameter we can past the repository path to the service edit the java script . We can past the following script (see the comments in the script area) var params = { path: the_json_path /* STRING */ // path it set to the input argument // example for such setting value // --> "/json_lists/Machine-complete-CV.PVZ-bomlist.json" var Content = Things["CAD-Files-Repository"].LoadJSON(params); //This will call the method LoadJSON(params) of the thing CAD-File-Reposistory var params1 = { infoTableName: undefined /* STRING */, dataShapeName: "BomListStruct" /* DATASHAPENAME */ }; // result: INFOTABLE var jsonTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); var result = DataShapes.ThingviewBomData.CreateValues(); // working fine for(i in Content.array){ jsonTable.AddRow({Part:Content.array[i].part, Description:Content.array[i].description, sBOMDepth:Content.array[i].sBOMDepth, sBOMID:Content.array[i].sBOMID, sBOMIDPath:Content.array[i].sBOMIDPath, sBOMName:Content.array[i].sBOMName, sBOMPath:Content.array[i].sBOMPath, path:Content.array[i].sBOMIDPath, parentPath:Content.array[i].sBOMIDPath.substring(0,Content.array[i].sBOMIDPath.lastIndexOf('/')) }); } result = jsonTable;   The parentPath  infoTable field is required for Tree Widget  to display the BOM list as tree The following code:   for(i in Content.array) { jsonTable.AddRow(Content.array[i]);}   the  code above will transfer  1 to 1  all  fields defined  in the json file with data into  infoTable fields. In this case  is not used because we have different mapping for "parentPath" -> it is new in the output InfoTable and is not contained by the json file. Test the  service:     2.)Create a service for the BOM List /InfoTable (steps are similar to 1.) edit the CAD-Files-Repository thing and create a new service named  "getViewableList_arg_path" set  the BaseType : INFOTABLE . Set the DataShape property of the service - > here to viewablelist_new -> this datashape need to be created  first . It should have the following Fields / property (all String type)     Create a service input parameter "the_json_path". Using this parameter we can past the repository path to the service Edit the java script . We can past the following script (see the comments in the script area):   var params = { //path: "/json_lists/Machine-complete-CV.PVZ-viewablelist.json" /* STRING */ path: the_json_path /* STRING */ }; var Content = Things["CAD-Files-Repository"].LoadJSON(params); var params1 = { infoTableName: undefined /* STRING */, dataShapeName: "viewablelist_new" /* DATASHAPENAME */ }; // result: INFOTABLE var jsonTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params1); var result = DataShapes.ThingviewBomData.CreateValues(); for(i in Content.array){ jsonTable.AddRow({name:Content.array[i].name, //type:Content.array[i].description, type:"viewable", value:Content.array[i].value, hasAnimation:Content.array[i].hasAnimation, hasSequence:Content.array[i].hasSequence }); } result = jsonTable;   Test the  service:      
View full tip
In the post “How to select model components in a 3d model without explicit definition of model Items”  there is one point where we  require a list of component items. Often we have the  case where we get a Creo View .pvz file which was published by Creo Illustrate.  In this case Creo Illustrate will  do additional changes for  the occurrence Id path  so that if we have a bom list coming from Creo Parametric then this data  will be not usable. So the question here is :  Is it possible to extract a bom list for any *.pvz files? The answer is Yes.  We can do this  using the Creo View Toolkit API ( this is one possible option). Creo View API Toolkit consist of many different moduls : Java Toolkit , Web Toolkit (only Internet Explorer related) Office Toolkit and  the new one introduced since 5.1 release module WebGL Toolkit.  We can extract BOM list  with Java Toolkit but it requires more complex programming environment. Therefore I think it is the most easily way to use Creo View WebGL toolkit   The Toolkit WebGl is based on the Thingview.js library. When we install Creo View Toolkit we will find the following directory structure:     In the web-application-examples  directory we can find some sample files which could be a good introduction how to use this API. The documentation is the WebGLToolkitDevGuide_en.pdf  and also in html(doxygen) in the documentation sub directory:     The CreoWebGL Toolkit requires a node.js environment. So  when we want to start it we have first to start the Toolkit server: >>>node http_server.js [port]   The port parameter is optional and if we do not use it then it takes by default 8080 :     The next step is to open the localhost:port/ ExtractBomPVZ.html which implements  here our program For example a simple version which will print the components with some properties  to the chrome console  could  looks like:   <!DOCTYPE html> <html style="height: 100%"> <head> <meta charset="utf-8" /> <meta name="author" content="PTC"> <title>Creo View WebGL Viewer</title> </head> <script src="js/ptc/thingview/thingview.js"></script> <body style="height: 100%; margin: 0px"> <div id="TITLE" style="width: 100%;height: 20%;border:2; float: left"></div> <div id="CreoViewWebGLDiv" style="width: 100%;height: 80%;border:0; float: left"></div> </body> <script type="text/javascript"> var thingview; var session; var model; var model1; var model2; var MODELPATH = "sample-data/thingview_test/Machine-complete-CV.PVZ"; var MODELNAME = ""; var global_number_generated = false; var BOM_LIST = []; var USE_LOG = false; var CUR_MODELPATH = getAllUrlParams().modpath ? decodeURIComponent(getAllUrlParams().modpath) : MODELPATH; document.getElementById('TITLE').innerHTML = "<h3>Extract Bom List </h3><hr><h4>"+CUR_MODELPATH+"</h4><hr>"; window.onload = function() { //return; ThingView.init("js/ptc/thingview", function() { // ThingView should only be initialised once per frame BOM_LIST = new Array(); console.log("Creo View WebGL Viewer is now initialised"); global_number_generated = false; GLOBAL_COUNT = 0; session = ThingView.CreateSession("CreoViewWebGLDiv"); //---------------------------------- model = session.MakeModel(); ////================= Selecton Call back function definition model.SetSelectionCallback(function(type, si, idPath, selected, selType) { var JSON_OBJ = new Object; var shapeInst = session.GetShapeInstanceFromIdPath(idPath) var color = shapeInst.GetColor() var instId = shapeInst.GetIdPath(); //the shape instance instId now contains a prefix "SI_" which is bug //or at least not wanted - here remove it var instId_corrected = instId.replace(/SI_/g, ''); var instIdPath = shapeInst.GetInstanceIdPath(); console.log("idPath=" + idPath) //contains the id path e.g /1/23/3 var description = model.GetPropertyValue(idPath, "PROE Parameters", "DESCRIPTION") if (!(description == null)) console.log("description=" + description); else desciption = "---"; var sBOMPath = instId; var sBOMPath = instId_corrected; //replaced with the corrected string var sBOMIDPath = instIdPath; var sBOMID = sBOMIDPath.substring(sBOMIDPath.lastIndexOf("/") + 1) var sBOMName = sBOMPath.substring(sBOMPath.lastIndexOf("/") + 1) var sBOMDepth = instIdPath.split("/").length - 1; { console.log("NAME=part&VALUE=" + sBOMName); console.log("NAME=sBOMDepth&VALUE=" + sBOMDepth); console.log("NAME=sBOMID &VALUE=" + sBOMID); console.log("NAME=sBOMIDPath&VALUE=" + sBOMIDPath); console.log("NAME=PARTPATH&VALUE=" + sBOMName); console.log("NAME=DESCRIPTION&VALUE=" + description); console.warn("color (a=" + color.a + " b=" + color.b + " g=" + color.g + " r=" + color.r + ")"); } }); ////==================LoadFromURL with Callback model.LoadFromURLWithCallback(CUR_MODELPATH, true, true, false, function(success, isStructure, errorStack) { console.log("Model(2) LoadFromURLWithCallback - success: " + success + ", isStructure: " + isStructure); if (success) { ////============= session.SelectAllInstances() var num = session.GetSelectionCount() console.log("Number of selections =" + num) } }); ///model load from URL funciton end ///////////// }); // ThingView.init( ) end }; //window onload function end //// URL parameter handling found in the WWW Overflow and works quite good function getAllUrlParams(url) { // get query string from url (optional) or window var queryString = url ? url.split('?')[1] : window.location.search.slice(1); // we'll store the parameters here var obj = {}; // if query string exists if (queryString) { // stuff after # is not part of query string, so get rid of it queryString = queryString.split('#')[0]; // split our query string into its component parts var arr = queryString.split('&'); for (var i = 0; i < arr.length; i++) { // separate the keys and the values var a = arr[i].split('='); // set parameter name and value (use 'true' if empty) var paramName = a[0]; var paramValue = typeof(a[1]) === 'undefined' ? true : a[1]; // (optional) keep case consistent paramName = paramName.toLowerCase(); if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase(); // if the paramName ends with square brackets, e.g. colors[] or colors[2] if (paramName.match(/\[(\d+)?\]$/)) { // create key if it doesn't exist var key = paramName.replace(/\[(\d+)?\]/, ''); if (!obj[key]) obj[key] = []; // if it's an indexed array e.g. colors[2] if (paramName.match(/\[\d+\]$/)) { // get the index value and add the entry at the appropriate position var index = /\[(\d+)\]/.exec(paramName)[1]; obj[key][index] = paramValue; } else { // otherwise add the value to the end of the array obj[key].push(paramValue); } } else { // we're dealing with a string if (!obj[paramName]) { // if it doesn't exist, create property obj[paramName] = paramValue; } else if (obj[paramName] && typeof obj[paramName] === 'string') { // if property does exist and it's a string, convert it to an array obj[paramName] = [obj[paramName]]; obj[paramName].push(paramValue); } else { // otherwise add the property obj[paramName].push(paramValue); } } } } return obj; } </script> </html>   When we start the  ExtractBomPVZ.html and open the chrome debugging console (Ctrl-Shift-I)     We can call the Creo View WebGl Toolkit program above in chrome with a parameter which will specify a path of the .pvz model –> example:   http://localhost:8080/ExtractBomPVZ.html?modpath=sample-data/Brake/worldcar-brake-multi-figure.pvz   The program will selects all visible components and will print to the console the idPah, partname , color etc… One problem we have here is that we could not print it to a local file because of the security restriction of the browser. A possible solution is to send the data back to the server e.g. as Json object and save the data to the server root directory. Later we can download these file if required e.g. calling the json:   http://localhost:8080/worldcar-brake-multi-figure.pvz-bomlist.json   To implement the creation of the json file we need  to change the CreoWeb.Toolkit html file. So our program  should send data to the server.  Additionaly we  also need to modify/extend the server - http.createServer() callback function to handle also the received data.     Also as next step we will extend  the Creo View Toolkit  program file by adding a XMLHttpRequest() . This request  will send the modelname and the generated json object to the http server     The call of the Toolkit html file with a start parameter was here directly in chrome via the URL but we can also call  it from a javascript or other html file:   <!DOCTYPE html> <html style="height: 100%"> <head> <meta charset="utf-8" /> <meta name="author" content="PTC"> <title>Creo View WebGL Viewer</title> </head> <script> function callBomPVZ(path) { //window.location.href = "http://localhost:8080/ExtractBomPVZ.html?modpath="+path; var myWindow = window.open("http://localhost:8080/ExtractBomPVZ.html?modpath="+path, "_blank", "height=600,width=500",false); //setTimeout(function(){ browser.refresh(); }, 1500); } </script> <body> <hr><p> <button type="button" onclick="callBomPVZ('sample-data/thingview_test/Machine-complete-CV.PVZ')">callBomPVZ:: Machine-complete-CV.PVZ</button> <hr><p><hr> <button type="button" onclick="callBomPVZ('sample-data/Brake/worldcar-brake-multi-figure.pvz')">callBomPVZ:: worldcar-brake-multi-figure.pvz</button> </body>  
View full tip
At the frist sight the  issue , may be , seems to be more related to thingworx or navigate, but this is often a question which is related to models  used in Studio and customers want to have some tools to view such models outside Vuforia Stuido and Vuforia View . Another important point is that Vuforia Studio Preview,  Navigate Thingview  and Creo View WebGL Toolkit use /are based on  the same thingview library.   The official statement of PTC here is that thingview widget, which is part from the Navigate extension is not supported for customized mashups and correct work is guaranteed  only as part of the navigate functionality. Therefore, there is no documentation provided for the customization of this widget and no technical support cases will be handled on address of related issues. Also the geometry file formats  supported by thingview are mainly the files supported by the Creo View application. All other file formats (which are not availalbe in the open dialog box of Creo View) are not supported -> e.g. glTF format. Of course, this can change in a future versions ->  therefore, please, check for future releases always the "What's new" docs. Actually the thingview allows  opening of some simple glTF files. But this is not working for every glTF file. Mostly there is a problem when we have geometry with high complexity.  … Additional I want to pont that not supported does not mean is not allowed. So therefore, I want to provide here some Info in case that customers want to customize the thingview widgets at their own risk. (no bug fixing and no compatibility guaranteed for future Releases)   In this post I will only introduce  the concept of such mashup's/view's and then in further posts will consider the different details for the different  suppoints in the picture below.      In the picture above, we have an example for customized mashup where we have a different areas for the different functionality. The mashup displayed in design /edit mode should looks like:     The mashup contains the following main areas(marked in the picture with a callouts numbers 😞   1.) Selector for the models. This is a list element widget which provides the possibility for the  selection of a particular model. The list  here is based on an Infotable returned by a service. Here is an example how to implement such service:     For simplification reason the service contains here only 2 rows. Here is an example how to implement such service (javaScript code) :     var data = [ { "FileName": "Machine-complete-CV.PVZ", "repository_link": "https://mrerot7o.studio-trial.thingworx.io:8443/Thingworx/FileRepositories/CAD-Files-Repository/CADFiles/Machine-complete-CV.PVZ", "json_bom": "/CADFiles/json_lists/Machine-complete-CV.PVZ-bomlist.json", "json_viewable_list": "/CADFiles/json_lists/Machine-complete-CV.PVZ-viewablelist.json", "json_viewable_stepList": "/CADFiles/json_lists/Machine-complete-CV.PVZ-viewableSteplist.json" }, { "FileName": "worldcar-brake-multi-figure.pvz", "repository_link": "https://mrerot7o.studio-trial.thingworx.io:8443/Thingworx/FileRepositories/CAD-Files-Repository/CADFiles/worldcar-brake-multi-figure.pvz", "json_bom": "/CADFiles/json_lists/worldcar-brake-multi-figure.pvz-bomlist.json", "json_viewable_list": "/CADFiles/json_lists/worldcar-brake-multi-figure.pvz-viewablelist.json", "json_viewable_stepList": "/CADFiles/json_lists/worldcar-brake-multi-figure.pvz-viewableSteplist.json" } ]; var result = DataShapes.files_repository_source_shape.CreateValues(); for(var i =0;i<data.length;i++) { result.AddRow(data[i]); }   The return type of the service is an InfoTable . For the defintion of the InfoTable we need a DataShape for the used fields definitions:     The list Widget ( callout 1.)  in the picture) has a binding to the thingview data.  When we select in the list widget a model -> this will call  the services: {(getViewableSteplist_arg_path(), getViewableSteplist_arg_path_viewablename()  and GetBomStruct_arg_path() }  of the  CAD-Files-Repository (Repository Thing - means  a thing which uses the template FileRepository)  -> and will pass the arguments for the json files paths for bom , viewables and StepList .     The effect is that- when we select a model in the listWidget -> this will update the mashup and will display only data related to this model.  All models and Json Files in this example  are saved in the Repository "CAD-Files-Repository"     To upload data to the repository we can use the File Upload widget. To display (for testing)  the data we can use the GetDirectory() and GetFileListingWithLinks() services of the repositroy thing. Below is an example for tool mashup which could be used as a tool to upload and display the data in the repository -as shown in the  picture above:     2.) Model tree area (callout 2.). This will display the model tree of the assembly components. It requires a service returning an InfoTable.  - here an example the service {Things["CAD-Files-Repository"]. GetBomStruct_arg_path(the_json_path) }  is called and returns the folloiwng   output:       For the creation of the infoTable I used  here a json file. In this example the the information about the viewalbes and steps  was extracted from  PTC sample assemble worldcar-brake-multi-figure.pvz   to the file  "worldcar-brake-multi-figure.pvz-viewableSteplist.json" (attached to this post). How to generate such file with Creo View WebGL toolkit is described in the community post (How to extract the components with properties from a pvz file)   The step how to create an InfoTable  from a  Json will is handled in an addtional post (Service for creating of Bom - and Viewable Lists from json files)   For the  displays of the data as an assembly  tree the tree widget has the following settings:     3.) List area - the functionality in this area uses  the same services as in point 2.)  but it  displays the components bom in a Thingworx Grid widget.  This will list the Bom as list of all components  (uses also the service {Things["CAD-Files-Repository"]. GetBomStruct_arg_path(the_json_path) } )     In the mashup preview it will looks like:     4.) Area for the selection of a viewable /  viewable = Creo Illustrate Figures ( callout 3  in the  the overview picture)     To  display the viewables correctly in thingview we require to have a service/data which is based on InfoTable with specific field definition (specific field name + type  is important that it works)       We can bind the All Data of the service  (here : {Things["CAD-Files-Repository"]. getViewableList_arg_path(the_json_path) } ) to the Views property of the thingview widget. Depending on the values of the fileds HasAnimation and HasSequnece the buttons for playing of sequences and animation are shown or blanked (set the Visible property to true or false)      How to define the service used for this point (returns InfoTable with list of the viewables) is described in the post  (Service for creating of Bom - and Viewable Lists from json files)   5.) Area for the display of the  sequence steps > data grid widget-> it displays data  only if the current viewable is a sequence.  Here  below an example for the results of the service execution (getviewableStepList )   for a Json files related to  models which have 1 (worldcar-brake-multi-figure.pvz)  respectively  2 (Machine-complete-CV.pvz) sequences. In the attached sample file  you can find  the  json file:  (worldcar-brake-multi-figure.pvz-viewableSteplist.json)       Here is an example of the mashup dipslay where we can see a Sequnece containing viewables     We can see the same mashup in design/edit mode (below). We can bind the All Data of the service  (here : {Things["CAD-Files-Repository"]. getViewableSteplist__arg_path_viewablename(the_json_path, the_viewablename) } ) to the Data property of the grid  widget. How to extract  a json file from the pvz model (e.g. worldcar-brake-multi-figure.pvz-viewableSteplist.json) and how to define the service for lnfoTable generation  e.g. getViewableSteplist_arg_path_viewablename -> this is shown in the post (Extracting the viewables and the seqnece steps information from a .pvz file for the usage in TWX).      The display of the Step List  is here provided only as info. Because there  was not possible to set the current /selected dataset in the grid widget. Therefore I used  here an additional List widget (callOut 6 ) which helps to manage this problem   6.) The area of the List widget is an area  which should be made invisible. It is used only to allow the setting of the selection in the grid element. We can bind the All Data of the service  (here : {Things["CAD-Files-Repository"].getViewableSteplist__arg_path_viewablename(the_json_path, the_viewablename) } ) to the Data property of the List  widget (callOut 6). The list element ( 6.) could be blanked because it is only used to set the selection of the  gird table (default  functionality of the Data selection )   How to extract  a json file from the pvz model (e.g. worldcar-brake-multi-figure.pvz-viewableSteplist.json) and to define the service for it  e.g. getViewableSteplist_arg_path_viewablename - is shown in the post(Extracting the viewables and the seqnece steps information from a .pvz file for the usage in TWX).   7.) This is the area with the panel of the Thingview (Thingview is a thingworx  extension). The thingview widget is part of the windchill navigate module. If the extension is installed , we can check this in the Extension manager:     In the package details we can see  that the thingview widget is installed on this thingworx instance  
View full tip
With various Augmented Reality applications in PTC's product portfolio the technical aspects and use cases could leave you with some questions. Did you know that we do not only have a full blown Augmented Reality SDK but offer also the possibility for a easy to use integration with live sensor data coming in via ThingWorx?   This blog post hopefully clarifies some of the questions around what can be done with Vuforia SDK and Vuforia Studio.   Welcome to the real world   In the real world, or the "real reality" (sounds weird, but it's basically what you can see with your own eyes - no augmentation involved) there are various objects. These might look the same - or not. Just take the following example... that's what we perceive when looking at things around us:     These objects are recognized​ via shapes, contrasts (black & white) and whatever defines the actual form. ​Vuforia SDK ​is able to recognize those objects via it's built-in object recognition capabilities. However, there might be limits - depending on the use cases...   While buildings could be distinguished by their form, playing cards could be distinguished via their suits and nominations. The machines however, they all look the same, they probably all ​are​ the same.   Combining the real world with a virtual world   "Augmented Reality" will allow to enhance this physical object with virtual properties, e.g. overlay its CAD-Model or overlay some animations for a better gaming experience. Check out this video for the Genesis Augmented Reality Trading Card Game example.   Object Recognition allows to put actual names to what the (digital) eye can see:     Once the object is recognized and identified all kinds of virtual attributes can be added. Vuforia SDK allows to do this with e.g. Unity.   As all of the machines are basically the same... they look the same, come from the same manufacturer and behave the same, ​identification​ can only be done via a manual effort, e.g. selecting the actual machine manually within an app (via a menu etc.). This manual selection process will then map a generic form and shape of the machine to the actual physical machine you can see and touch just in front of you.   In an app this might be necessary if you can recognize the generic form of a playing card but forgot to implement the suit and nominations. In that case, either extend the recognition part, or choose a drop-down list when the card is identified to choose the actual​ card in front of you.   How do ThingMarks fit in?   Using the functionality of Vuforia SDK, Vuforia Studio combines the power of Vuforia (AR) with the power of ThingWorx (live sensor data / object information). In an industry environment I could select the correct machine I'm looking at. However, what's the identifier? It is probably written somewhere on the back of the machine with lots of other information, so I don't really know what to look for. Therefore I could be looking at any machine, but without the identifier I can not retrieve information for ​my​ machine.   Vuforia Studio uses ThingMarks​. They work similar to a QR-Code and allow for direct identification of individual machines. So instead of choosing manually in the app, the ThingMark automatically chooses the correct object and relates that ID to a Thing Entity in ThingWorx.     In above image, the ThingMark allows to a) identify we're looking at a machine and b) are looking at the specific machine A03 It's basic point and shoot. Scan the ThingMark with your mobile device and you're directly taken to this particular experience for this particular machine.   In this case, it's not the machine that defines our object's properties and shapes and contrasts and sizes etc. In this case, it's the ThingMark that's the object being recognized. That's quite a difference.   So now, in an additional step, we're using the power of Vuforia to identify individual machines by a ThingMark. Recognition is driven by the ThingMark's shape which includes an encoded object ID (the QR-code looking pattern).   How does ThingWorx fit in?   After recognizing the machine, ThingWorx studio provides the link between this specific object (or its instance) and the ThingWorx Thing Entity we've defined in Vuforia Studio.   This allows to retrieve individual properties, services, events, alerts etc. directly via ThingWorx. Those values are unique per object, not per shape!   So this allows to directly look at temperature, level and failure-indicator for the actual machine in front of us:     Bridging the gap   Vuforia Studio​ is used to bridge the gap between ​Vuforia ​and its Augmented Reality capabilites as well as ThingWorx ​and its Internet of Things (IoT) capabilities. Vuforia Studio uses parts of both applications, adds own functionality and defines its own product category: Connected Augmented Reality​     There are quite some components involved in this:     This can be split into two processes: developing and experiencing   Development   Create a new experience in VuforiaStudio, map the experience to the ThingMark ID, map the experience to a Thing Entity in ThingWorx. Publish the experience to the Experience Server. Done.   Experience   Scan the ThingMark with the Vuforia View app. Vuforia View will utilize Vuforia to recognize the ThingMark Vuforia View will load the data and the model(s) for this ThingMark from the Experience Server Vuforia View will automatically receive and update the experience you're viewing with live data from the ThingWorx platform Enjoy.   Resources   There are quite some videos, tutorial, best practices etc. available on how to develop and experience the world of Vuforia Studio. Check out ThingWorx Studio Resources: Getting Started Guides, Tutorials, Troubleshooting for the Article Hub and quite a lot of good stuff!   More information   To get more information visit the product pages at https://www.vuforia.com https://trial.studio.vuforia.com/   If you're looking for help, these might be of interest:   https://developer.vuforia.com/support for Vuforia SDK https://community.ptc.com/t5/Studio/bd-p/studio for Vuforia Studio https://community.ptc.com/t5/ThingWorx-Developers/bd-p/twxdevs for ThingWorx https://support.ptc.com/    What's next?   Get involved, create your own experience. It's fun, it's quite easy and well... it looks good, too!  
View full tip
Unfortunately, in the Vuforia Studio Documentation there is no complete List with the possible events which could be handled JS. Therefore for the first time this article tries to provide additional Information about known events :   1.) modelLoaded - is not required any more because the UI allow directly to specify this event. ... $rootScope.$on('modelLoaded', function() { //do some code here } ) .... 2.) Step completed example: scope.$on('stepcompleted', function(evt, arg1, arg2, arg3) { var parsedArg3 = JSON.parse(arg3); console.log("stepcompleted stepNumber="+parsedArg3.stepNumber + " nextStep="+parsedArg3.nextStep); $scope.app.stepNumber=parseInt(parsedArg3.stepNumber); $scope.app.nextStep=parseInt(parsedArg3.nextStep); $scope.app.duration=parseFloat(parsedArg3.duration); }); 3.) Event - stepstarted: ... $scope.$on('stepstarted', function(evt, arg1, arg2, arg3) { var parsedArg3 = JSON.parse(arg3); console.warn(arg3); console.log("stepstarted stepNumber="+parsedArg3.stepNumber); $scope.app.stepNumber=parseInt(parsedArg3.stepNumber); $scope.app.nextStep=parseInt(parsedArg3.nextStep); $scope.app.duration=parseFloat(parsedArg3.duration); }); ... Please, pay attention that on some platforms will not provide complete information  in stepstarted. So, In this case the complete info is available in 'stepcompleted' – the best is to test it.   4.) after entering in  a view in studio (e.g. Home ...):   ... $scope.$on('$ionicView.afterEnter', function() {$scope.populateModelList(); }); ... 5.) click/tap event on the current panel: ... $rootScope.$on('click', function() { tapCount++;console.log("click event called");} ); ... or  with coordinates ... document.addEventListener('click', function(event) {console.log("click() 1 called"); $scope.lastClick = { x: event.pageX, y: event.pageY}; }); ... you can also see this topic.   6.) New step  -example: ... $scope.$on('newStep', function(evt,arg) { var getStepRegex = /\((\d*)\//; console.log(arg); console.log( getStepRegex.exec(arg)[1]); //check what it prints to the console - the step number }); ...    7.) Here is also  a more advance construct- it defines a userpick event e.g. for all models widgets: angular.forEach($element.find('twx-dt-model'), function(value, key) { // search all twx-td-model's -> means all model widgets angular.element(value).scope().$on('userpick',function(event,target,parent,edata) { //for each model widget will set a userpick listener console.log('edata');console.warn(edata); console.log("JSON.parse(edata)");console.warn(JSON.parse(edata)); var pathid = JSON.parse(edata).occurrence; $scope.currentSelection = target + "-" + pathid; // this is the current selection - the selected component occurence // you can use it for example as shown below // try{ //tml3dRenderer.GetObject($scope.currentSelection).GetWidget().ApplyOccludeOpacity(OCLUDE_VAL,OPACITY_VAL); //} catch (e1234) {$scope.view.wdg['3DLabel-4']['text']= "e 1234exception in GetObject.GetWidget..."; } // } ) //end of the userpick defintion } ) //end of for each funciton  8.) tracking event:   ... $scope.$on('trackingacquired', function (evt,arg) { // alert('didStartTracking'); // this is not really needed $scope.message = parseInt($scope.app.params["currentStep"]); $scope.$apply(); }); $scope.$on('trackinglost', function (evt,arg) { // alert('didFinishTracking'); $scope.message = "Scan the ThingCode with your camera."; $scope.$apply(); }); ....   9.) popover event:     // var my_tmp = '<ion-popover-view><ion-header-bar> <h1 class="title">My Popover Title</h1> </ion-header-bar> <ion-content> My message here! </ion-content></ion-popoverview>'; $scope.popover= $ionicPopover.fromTemplate(my_tmp, { scope: $scope }); $ionicPopover.fromTemplateUrl('my-popover.html', { scope: $scope }).then(function(popover) { $scope.popover= popover; }); $scope.openPopover= function($event) { $scope.popover.show($event); }; $scope.closePopover= function() { $scope.popover.hide(); } //////////////destroy popover $scope.$on('$destroy', function() { $scope.popover.remove(); }); /////// hide popover $scope.$on('popover.hidden', function() { // your hide action.. }); // on remove popover $scope.$on('popover.removed', function() { // your remove action }); }); 10) watch event -watches are created using the $scope.$watch() function. When you register a watch you pass two functions as parameters to the $watch() function: 1)A value function 2)A listener function    When the value returned by function 1.) changes - this lead to execution of the funciton 2.) Example:   ... $scope.$watch(function(scope) { return $scope.view.wdg['label-1']['text'] }, // watches if change for the the text of label-1 //when changes then play a step for model-1 function() { console.log($scope.view.wdg["model-1"]); $scope.view.wdg["model-1"].svc.play; } ); ...   11.) Camera tracking - make sense only on mobile device- no sense for preview mode!   //// define tracingEvent only on end device tml3dRenderer.setupTrackingEventsCommand (function(target,eyepos,eyedir,eyeup) { // $scope.view.wdg['3DLabel-1']['text']="eyepos=("+eyepos[0].toFixed(2)+","+eyepos[1].toFixed(2)+","+eyepos[2].toFixed(2)+")"; $scope.app.params['target']=target; $scope.app.params['eyepos']="eyepos=("+eyepos[0].toFixed(2)+","+eyepos[1].toFixed(2)+","+eyepos[2].toFixed(2)+")"; $scope.app.params['eyedir']="eyedir=("+eyedir[0].toFixed(2)+","+eyedir[1].toFixed(2)+","+eyedir[2].toFixed(2)+")"; $scope.app.params['eyeup'] ="eyeup =("+ eyeup[0].toFixed(2)+","+ eyeup[1].toFixed(2)+","+ eyeup[2].toFixed(2)+")"; ///////////////////// },undefined); //// define tracingEvent only on end device } //end device   12.) There is also a sequenceloaded event, which is useful if you have a model with multiple sequences defined, and you are switching sequences dynamically in the experience.   $scope.$on("sequenceloaded", function (evt, arg) { console.log("sequence loaded, starting play"); $scope.setWidgetProp("loading","visible",false); $scope.app.fn.triggerWidgetService("model-1","playAll"); }); In this point is  here a good feedback comming from advance user (expert) : If you grep for "$emit" through a project folder, you can turn up the following event names: valueacquired (bar code scanner) usercanceled (bar code scanner) tracking modelloadfailed sequenceloaded newStep playstarted sequenceacknowledge playstopped sequencereset onReset If you grep for "$on(", you can find some additional ones: trackingacquired trackinglost modelLoaded click app-fn-navigate app-fn-show-modal app-fn-hide-modal $ionicView.afterEnter $stateChangeStart loaded3DObj loadedSeqErr loadedSeq $destroy select3DObj move3DObj loadError3DObj readyForZoom3DObj serviceinvoke stepstarted stepcompleted twx-entity twx-service-input twx-service-name This is a  good point and it seems that this list contains the most of the possible events.   Events Handling Feedbacks from EXTERNAL DATA  services  Such event is    the twx-service complete event. This event is called when we call a service registered in the external data and the service is completed. Here an example (also mention in the post😞 Here in the example in the external data the service LoadJSON was added.       ////////////////////////////////////////////////////////////// $scope.GetJsonFromTwxRepository = function(path) { $scope.$applyAsync(function() { $rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadJSON', {"path":path} );} ,500 ); $scope.app.speak("after call of GetJsonFromTwxRepository") //in the modelloaded listener register // LoadJSON-complete event -> to laod the data into session rootScope.$on('modelLoaded', function() { //// $scope.$root.$on('LoadJSON-complete', function(event, args) { console.log("LoadJSON-complete event"); $scope.COMP_LOCs=args.data console.log(JSON.stringify( $scope.COMP_LOCs)) }); /// });   So the code above shows how to call from JavaScript the service added to the external data. This service should return the called JsonObject. The call is asynchronously so that when  thingworx  will come back the listener 'LoadJSON-complete' will be called and here will print the content of the JsonObject to the console. Here the listener is registered inside the modelload event (this is event is coming late – so to be on the save side that everything is already initialized) This is generally that you for any Thingworx services added to the External data <your_twx_service_name>-complete  the arg.data contains then the data which should be returned by the method.
View full tip