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

Community Tip - Did you know you can set a signature that will be added to all your posts? Set it here! X

Experience works on vuforia studio(PC) but not properly on view App

MA8731174
14-Alexandrite

Experience works on vuforia studio(PC) but not properly on view App

Hello Community,

    I am having a very strange issue. My experience works well on my pc means vuforia studio but not in view app. I mean it starts and then when i do same thing means run same features which i have built.. works well on pc but not in view app. It just could not finish the code execution and it breaks then its just stay in the same state and not doing anything. There is no error in my console.log even or anything which may impact on view app. Still it does not work and surprisingly sometimes it works but sometimes its not. 

 

 

Anyone has an idea why it is like that? I have also posted a photo below from log file of view app. i get this FPS many times and afterwards then nothing happens actually.

 

Jamal8548_0-1716378557223.png

 

10 REPLIES 10

Hi @MA8731174 

so far I now FPS - is for frame per seconds coming when you will try some animation or change the orientations etc where the rendering is called ... so it depends on that what you try to do there.

So some points:

  • Did you have some  customs renders defined - e.g. in tmlText widget? For example render could have different apperance when you call them on different mobilde devices IOS, Andorid . HoloLens do not use GLSL as Perview or on Android but HLSL so that in this case you need alternatvie render for the Holoens device
  • Is this issue releated to javascript code called there. what is the relevat code abd the workflow called there
  • Or is without Javascript.?
  • -is that on different Mobile devices reproducible let say IOS and Android?
  • is based on specfic model - so occurs only with specific model etc.
  • another option is to  try again with simpliefed project , recreate the proejct -  and when you suspect the model is the problem with the same model and try the workflow in simpliefied way when there is javascript involved. So , possible you can check if there is issue on you concept or it is  issue of  somekind of project corruption etc.
  • So check if there is related issues documented - so one possible related is that :

TS article CS344588 https://www.ptc.com/en/support/article/CS344588

where issue is based on some Javascript loop calling takeScreenshot addting  large values to array which cause kinde of overflow... etc. The memory amout on device side is lower then on Windows chrome so that possible the difference what I belive will cause the problem .

 

 

 

Thank you @RolandRaytchev for these insights. Actually On button click I am taking signature from user and then save the data in json after reading the values from widgets and then generate the pdf of 6 popups. I have shared my code also below which shows the sequence of the execution on the single button click. Its a bit lengthy operations i would say ...to first take the signature from user, save the data in json from all the widgets (Textfields and checkboxes) ..I have nearly 380 widgets (Textfields and checkboxes)  and i save the values of these widgets in json. The function which generates this json is called generateJson which i also added in the code below then i generate a pdf and save it into thingworx. Can you please just see the code may be and give me few more insights what can be the reason of this problem? 

 

HINT: When i see logs while creation of pdf it creates pdf for eg for page 1 then page 2 then page 3  or sometimes page 1 page 2 or page 1 page 2 page 3 page 4 and then just FPS logs will show and nothing happens. the spinner will keeip moving and nothing happens then ....so definitely problem comes in creation of pdf.

 

DEVICE: IPAD PRO 5th generation with 17.4.1 iOS

MODEL: NO 3D/2D MODEL IN MY EXPERIENCE

ANIMATION: NO

 

EXECUTION FLOW:-

 

1) take signature from user

2) generate the json with for loop and save the data on thingworx (generate json have loops with many IF conditions also)

3) create PDF and save it in thingworx

4) save signature images on thingworx

 

 

 

 

//This function executes on button click and this then let run other functions afterwards the execution

$scope.submitBtnPressed = function () {
  const status = $scope.app.params.Status; // take this instead $scope.projectInfo.status;
  
  if (status === PHASES.SCW2Phase1 || status === PHASES.SCW2Phase2 || status === PHASES.SCW2Phase3 ) {
    
    
    const SignatureHeadinglabelMap = {
      
    [PHASES.SCW2Phase1]: "Verantwortlicher Aufbau EDTCD1: ",
    [PHASES.SCW2Phase2]: "Verantwortlicher Aufbau EDTCD2: ",
    [PHASES.SCW2Phase3]: "ANLV bzw. delegierter Abnehmer: "
};

$scope.view.wdg['zfSignature'].label = SignatureHeadinglabelMap[status] || "Default Label: ";


    $scope.view.wdg['zfSignature'].text = $scope.app.params.UserID;
        
    twx.app.fn.triggerWidgetService('zfSignature', 'takeSignature');
    
  } else if (status === PHASES.SCW2Phase4) {
    $ionicPopup.prompt({
      title: '<h3>Inbetriebnehmer</h3>',
      subTitle: '<h3>Bitte den Namen des Inbetriebnehmer eintragen:</h3>',
      template: `
                <form>
                    Vorname:<br>
                    <input type="text" ng-model="data.firstname" name="firstname"><br>
                    Nachname:<br>
                    <input type="text" ng-model="data.lastname" name="lastname">
                </form>`,
      inputType: 'text',
      scope: $scope,
      buttons: [
        {
          text: '<b>OK</b>',
          type: 'button-positive',
          onTap: function () {
            const name = $scope.data.firstname + " " + $scope.data.lastname;
            $scope.view.wdg['zfSignature'].label = "Inbetriebnehmer des Versuchs: ";
            $scope.view.wdg['zfSignature'].text = name;   
            $scope.view.wdg['0_4_name'].text = name;
            console.log("Es wurde folgender Name eingetragen: " + name);
            $scope.customToastmessage("Es wurde folgender Name eingetragen: " + name, 3000);
            twx.app.fn.triggerWidgetService('zfSignature', 'takeSignature');
            return true;
          }
        },
        {
          text: 'Abbrechen',
          type: 'button-assertive',
          onTap: function () {
            console.log("Eingabe abgebrochen");
            return false;
          }
        }
      ]
    }).then(() => {
      $scope.data.firstname = "";
      $scope.data.lastname = "";
    });
  } 
};

// This runs after the execution of the above function 

$scope.$on("signatureTaken", function () {
  console.log("Signature Taken!");
  triggerPDF = true;  
  $scope.saveDataBtnPressed();
});


//Then this will run after the execution of the above function 

$scope.saveDataBtnPressed = function() {
  console.log("save pressed");
  
  twxDS("TestBench_Mobile.Controller", "saveReport", {
    "entryTimeStamp" 	: $scope.view.wdg["0_1"].text,
    "Position" 			: $scope.view.wdg["0_3"].text,
    "Location" 			: $scope.app.params.Location,
    "metaData"			: JSON.stringify($scope.generateJSON()),
    "UserID"			: $scope.app.params.UserID,
    "changeDate"		: + $scope.projectInfo.changeDate
  });   
}
// this below is the generateJSON function which loops over all the widgets and gets the data from them (textfields and checkboxes)
$scope.generateJSON = function() {
  
  let jsonData = {
    
    "mainValues": [],
    "subValues" : [],
    "signatures": [],
    "checkListVersion": $scope.app.params.checkListVersion 
  };

  // Loop for mainValues
  for (let i = 1; ; i++) {

    let mainValueQID = "0_" + i;

  
    if (!$scope.view.wdg[mainValueQID]) {
      break; 
    }

    let mainValue = {
      "QID": mainValueQID,
      "label": $scope.view.wdg[mainValueQID].label,
      "input": (i === 1) ? $scope.view.wdg["showDate"].text : $scope.view.wdg[mainValueQID].text,  
    };
    
    if (i === 1) {
      mainValue.value = $scope.view.wdg[mainValueQID].text  
    }
    
    jsonData.mainValues.push(mainValue);
  }

  // Loop for subValues
  for (let i = 1; ; i++) {
    var subValueExist = false;  

    for (let j = 1; ; j++) {
      var subValueQID = i + "_" + j;
      var widgetID = i + "_" + j + "_1";
      console.log('Checking widgetID:', widgetID); 

      // Check if the widget exists
      if (!$scope.view.wdg[widgetID]) {
         console.log('No widget found for ID:', widgetID);
        break;  // Exit the inner loop if the widget doesn't exist
      }
  console.log('Widget found:', widgetID);
      subValueExist = true;

      var subValue = {
        "QID": subValueQID,
        "title": "Name of a title", 
        "inputs": []  
      };


      
    // Check the status of subValue
    if ($scope.view.wdg[subValueQID + "_io"] && $scope.view.wdg[subValueQID + "_io"].value) {
      subValue.status = "io";
    }
    if ($scope.view.wdg[subValueQID + "_nio"] && $scope.view.wdg[subValueQID + "_nio"].value) {
      subValue.status = "nio";
    }
    if ($scope.view.wdg[subValueQID + "_nv"] && $scope.view.wdg[subValueQID + "_nv"].value) {
      subValue.status = "nv";
    }

    // Check the manager status of subValue
    if ($scope.view.wdg[subValueQID + "_Manager_io"] && $scope.view.wdg[subValueQID + "_Manager_io"].value) {
      subValue.managerStatus = "Manager_io";
    }
    if ($scope.view.wdg[subValueQID + "_Manager_nio"] && $scope.view.wdg[subValueQID + "_Manager_nio"].value) {
      subValue.managerStatus = "Manager_nio";
    }
    if ($scope.view.wdg[subValueQID + "_Manager_nv"] && $scope.view.wdg[subValueQID + "_Manager_nv"].value) {
      subValue.managerStatus = "Manager_nv";
    }



    
      for (let k = 1; ; k++) {
        var inputQID = i + "_" + j + "_" + k;

        // Check if the widget exists
        if (!$scope.view.wdg[inputQID]) {
          break;  
        }

        var input = {
          "label": $scope.view.wdg[inputQID].label, 
          "value": $scope.view.wdg[inputQID].text,
          "unit": ""  // Set the appropriate unit
        };
        subValue.inputs.push(input);
      }

      jsonData.subValues.push(subValue);
    }

    // Exit the outer loop if no subValues were found
    if (!subValueExist) {
      break;
    }
  }
  

  
  for (let i = 1; i < 5; i++) {
              
    if(!$scope.view.wdg['0_' + i + '_sign'].imgsrc){
      break;
    }
    
    jsonData.signatures.push({
      "QID": "0_" + i,
      "date": $scope.view.wdg['0_' + i + '_date'].text,
      "name": $scope.view.wdg['0_' + i + '_name'].text
    });
    
  } 
  
  
  return jsonData;
  
  
}

//Then this function will run after the execution of the above function 

let signaturePictureName;
let signaturePicture;
let triggerPDF = false;
$scope.$on("saveReport.serviceInvokeComplete", function () {
  console.log("Daten wurden gespeicher!");
  $scope.customToastmessage("Daten wurden gespeichert!",3000);
  
  if(triggerPDF){
    
  
    
	const currentDate = new Date();  
    const currentDateString = `${currentDate.getDate()}.${(currentDate.getMonth() + 1).toString().padStart(2, '0')}.${currentDate.getFullYear()} ${currentDate.getHours()}:${currentDate.getMinutes().toString().padStart(2, '0')}`;
    
    //$scope.app.params.Status.match(/\d+/) ==> Before it was like that when the status was phase1
    
    $scope.view.wdg['0_' + parseInt($scope.app.params.Status.match(/(?<=Phase)\d+/)) + '_date'].text = currentDateString;
    $scope.view.wdg['0_' + parseInt($scope.app.params.Status.match(/(?<=Phase)\d+/)) + '_sign'].imgsrc=$scope.view.wdg['zfSignature'].signatureUrl;
    
    if ($scope.app.params.Status !== PHASES.SCW2Phase4) {
      $scope.view.wdg['0_' + parseInt($scope.app.params.Status.match(/(?<=Phase)\d+/)) + '_name'].text = $scope.app.params.UserID;  
    }
  
    signaturePicture = $scope.view.wdg['0_' + parseInt($scope.app.params.Status.match(/(?<=Phase)\d+/)) + '_sign'].imgsrc; 
    
    if ($scope.app.params.Status == PHASES.SCW2Phase1) {
      signaturePictureName = "Signature_mechanic_EDTCD1.jpg";    
    }
     if ($scope.app.params.Status == PHASES.SCW2Phase2) {
      signaturePictureName = "Signature_mechanic_EDTCD2.jpg";    
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase3) {
      signaturePictureName = "Signature_manager.jpg";
    } 
    if ($scope.app.params.Status == PHASES.SCW2Phase4) {
      signaturePictureName = "Signature_customer.jpg";
    } 
    
    if($scope.app.params.Status == PHASES.SCW2Phase1 ||$scope.app.params.Status == PHASES.SCW2Phase2 || $scope.app.params.Status == PHASES.SCW2Phase4) {
      $scope.printPDF_MESSAGE();

    }
    
    if($scope.app.params.Status == PHASES.SCW2Phase3) {
 
      $ionicPopup.show({
        cssClass: 'custom-popup',
        title: 'Fertig?',
        subTitle: 'Ist der Prüfaufbau abgeschlossen oder steht noch die Unterschrift des Inbetriebnehmer aus? Bitte schließen Sie die Anwendung nicht, bis Sie die Meldung "Upload war erfolgreich" erhalten haben. Nach Erhalt dieser Meldung werden Sie automatisch ausgeloggt.',
        buttons: [{
          text: 'Inbetriebnehmer nötig',
          type: 'button-energized',
          onTap: function() {
            this.hide(); // Popup manuell schließen 
            $scope.printPDF_MESSAGE();
            return 'cancel';
          }
        }, {
          text: 'Fertig',
          type: 'button-balanced',
          onTap: function() {
            this.hide(); // Popup manuell schließen
            // Extract the number at the end of the string, increment it by 2, and replace the old number in the string with the new one.
            $scope.view.wdg['0_4_date'].text = "——";
            $scope.view.wdg['0_4_name'].text = "Kein Inbetriebnehmer vorhanden";
            $scope.app.params.Status = $scope.app.params.Status.replace(/\d+$/, match => parseInt(match) + 1);
            customerPresent = false;
            $scope.printPDF_MESSAGE();
            return 'ok';
          }
        }, ]
      })
    }
  }
});


//This will run after the execution of the above function 

$scope.printPDF_MESSAGE = function () {
     
  $ionicPopup.alert({
    title: "WICHTIG!",
    template: `<div>
                  Bitte schließen Sie die Applikation nicht, während die PDF erstellt wird! Das korrekte Format ist nur gewährleistet, wenn Sie das Tablet richtig ausrichten.
                  Dies erfolgt <span style="font-size: 24px; color: red; font-weight: bold;">nur</span> im 
              </div>
              <br>
              <div style="font-size: 52px; color: red; font-weight: bold;">Querformat(!)</div></br>
                  `,
    scope: $scope,
    buttons: [{
      text: `<b>PDF erstellen</b>`,
      type: 'button-positive',
      onTap: function() {
        $scope.view.wdg["loading-screen"].visible = true;       
        $timeout(function () {
          $scope.printPDF();
        }, 500);
      }
    }]
  });
};



//Afterwards this code will execute which generates the PDF for the popups which are called pages

let myscript = document.createElement('script');
myscript.src="app/resources/Uploaded/jspdf.js";
document.head.appendChild(myscript);
myscript.onload = async function(){
// code to be executed when the script has finished loading
    
 //...
console.log("stop here");

}

let html2canvas = document.createElement('script');
html2canvas.src="app/resources/Uploaded/html2canvas.js";
document.head.appendChild(html2canvas);
html2canvas.onload = function(){
// code to be executed when the script has finished loading

 //...
}

let PDFLib = document.createElement('script');
PDFLib.src="app/resources/Uploaded/pdf-lib.js";
document.head.appendChild(PDFLib);
PDFLib.onload = async function(){
// code to be executed when the script has finished loading

 //...
}

$scope.printPDF = async function() {
  


let popupArray = $scope.app.params.Page5Visibility === true? ["page_0", "page_1", "page_2", "page_3", "page_4", "page_5"] : ["page_0", "page_1", "page_2", "page_3", "page_4"];

if (customerPresent && $scope.app.params.Status === PHASES.SCW2Phase4) {   // For SCW2Phase4 and customer present, use only page_0
    
    popupArray = [ 
    "page_0",
  /*  "page_1",
    "page_2",
    "page_3",                      
    "page_4",
    "page_5"*/];
}
  
    let helper1 = document.querySelectorAll("div.twx-popup-container.ng-hide, ion-spinner.ng-hide");
    helper1.forEach(box => { box.classList.remove("ng-hide")} );

  //  Wait for 100ms before proceeding
  //  await new Promise(resolve => setTimeout(resolve, 100));
  
    let attributeContainer = [];

    const pdfDoc = await window.PDFLib.PDFDocument.create();

    for (const popupId of popupArray) {
      console.log(PHASES.SCW2Phase3);
      console.log($scope.app.params.Status);
      
      console.log("page: " + popupId + " erstellt");
     attributeContainer.push(document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).getAttribute("style"));
      document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", "width: 100%; overflow: visible;");

      let mergedElements = document.createElement('DIV');
      mergedElements.setAttribute('style', 'font-family: prometo !important; letter-spacing: 0.01px; overflow: visible');
      mergedElements.append(document.querySelector(`twx-widget[widget-id='${popupId}']`).cloneNode(true));

      let pdfHeight = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetHeight + 25,
          pdfWidth  = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetWidth + 20;

      let pdf = new jspdf.jsPDF('l', 'px', [pdfWidth, pdfHeight]);
      pdf.addFileToVFS('prometo-normal.ttf', font);
      pdf.addFont('prometo-normal.ttf', 'prometo', 'normal');
      pdf.setFont("prometo", 'normal');

      let donorPdfBytes;
      await pdf.html(mergedElements, {
        // this warning is wrong - everything is fine
        callback: receivedPDF => { donorPdfBytes = receivedPDF.output("datauristring"); },
        x: 5,
        y: 5
      });

      let donorPdfDoc = await window.PDFLib.PDFDocument.load(donorPdfBytes);
      let docLength = donorPdfDoc.getPageCount();

      for (let k = 0; k < docLength; k++) {
        const [donorPage] = await pdfDoc.copyPages(donorPdfDoc, [k]);
        pdfDoc.addPage(donorPage);
       /* console.log("jamal page "+ donorPage);*/
      }
    }


    const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
    let mergedPDF = pdfDataUri.substring(pdfDataUri.indexOf(',') + 1);
    helper1.forEach(box => { box.classList.add("ng-hide"); });

    popupArray.forEach((popupId, i) => {
        document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", attributeContainer[i]);
    });
 	const currentDate = new Date();
  	const currentDateString = `${currentDate.getFullYear()}-${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}-${currentDate.getMinutes().toString().padStart(2, '0')}_`;
   
    let helperName;
  
    if ($scope.app.params.Status == PHASES.SCW2Phase1) {
        helperName = "mechanic_EDTCD1";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase2) {
        helperName = "mechanic_EDTCD2";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase3) {
        helperName = "manager";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase4) {
      if(!customerPresent) {
        
        helperName = "manager";
      
      } else {
        helperName = "Inbetriebnehmer";
      }
     
    }

    let pdfName = $scope.view.wdg["0_5"].text + "-" + $scope.view.wdg["0_6"].text + "_" + currentDateString + helperName + ".pdf";

    let pdfPath = $scope.projectInfo.repositoryRelativePath + "/" + pdfName;

    twxDS("TestBench_Mobile.Controller", "savePDF", { "content": mergedPDF, "path": pdfPath });
    console.log(" PDF with the name '" + pdfName + "' created");
};

$scope.$on("savePDF.serviceInvokeComplete", function () {
  console.log("Save PDF done");
  $scope.customToastmessage("pdf wurde erfolgreich gespeichert.",3000);
  $scope.lockEntries(true);  
  
  twxDS("TestBench_Mobile.Controller", "saveSignatureImage", { "path": $scope.projectInfo.repositoryRelativePath, "name": signaturePictureName, "content": signaturePicture });
  
})


$scope.$on("saveSignatureImage.serviceInvokeComplete", function () {
  console.log("Signature saving done");

  $scope.app.params.Status = $scope.app.params.Status.replace(/\d+$/, match => parseInt(match) + 1);
  
    twxDS("TestBench_Mobile.Controller", "submitReport", {
    "entryTimeStamp" 	: $scope.view.wdg["0_1"].text,
    "Position" 			: $scope.view.wdg["0_3"].text,
    "Location" 			: $scope.app.params.Location,
    "status"			: $scope.app.params.Status, 
    "metaData"			: JSON.stringify($scope.generateJSON()),
    "UserID"			: $scope.app.params.UserID,
    "changeDate"		: + $scope.projectInfo.changeDate 
      
  }); 
  
})


$scope.$on("submitReport.serviceInvokeComplete", function () {
  console.log("Submit durchgeführt");
  $scope.view.wdg["loading-screen"].visible = false;
  $scope.customToastmessage("submit war erfolgreich", 3000);
  
  // TODO : disable buttons? or instantly restartApp - because people can abuse the system if not
  $timeout(function () {
    $scope.restartApp();
  }, 3500); 
  
});

 

 

 

Hi @MA8731174 ,

thanks for the feedback! I see that is quite complex workflow with several callbacks - so I think that this is and syncornisation issue so that the different function and events are called assyncronoussly at all and possibly there is accessing of the data form a call which is not finished yet.

So what you can do .

So first to understand better the problem you can  split  described step and call they with  different buttons.

  1. first step takeSignature ... and check if the singature is received - is that working then on view ... etc

the entry point in you script is that

$scope.submitBtnPressed()
where called first the signature twx.app.fn.triggerWidgetService('zfSignature', 'takeSignature');

//## >> then next point is the 
$scope.$on("signatureTaken", function () {
  console.log("Signature Taken!");
  triggerPDF = true;  
  $scope.saveDataBtnPressed(); //-> goes in the next item
});

>>> how is the signatureTaken defined as evetnt?
so that when this is that you expect to received from the servcie called takeSignature and the service in added in the External data section then possibly the name is something like this:

$scope.$root.$on("signatureTaken-complete", function (event,args) {
console.warn(JSON.stringify(event))// is the data received
console.warn(JSON.stringify(args))// what are the args?
// ... what you do after it comm back from service

$timeout($scope.saveDataBtnPressed,500)
})


....

    2.) when 1.) is ok then check the next in extra button: to generate the Json - So this is involved then in the function saveDataBrnPressed

      - I see the saveDataBrnPressed will call twxDS - no idea what this method do - so posswibly some interaction with TWX? There is called the $scope.generteJSON() inside the arguments of the twxDS call. So possibly you can define a syncronisation that the twxDS method is called when the scope.genrateJSON() is completed - e.g. by call promise concept : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

and in the then () call the twxDS call

3.) call the method for PDF generation in extra button when you ensure that every thing was completed

-- I see that 

$scope.$on("saveReport.serviceInvokeComplete", function () {

what same as 

$scope.$root.$on("saveReport-complete", function (event,args) {

when the saveRport is added in the External data secton. But acctualy I could not see this service "saveReport"  called anywere... that  I belived that is involed by some service which called by the twxDS() -> TestBench_mobile.Controller ... what ever that will do

 

4.) in extra button when PDF egenerated  saved it to twx when 3 complete

5.) call in button to save the singature image when 4 complete

 

- I think such way to split the workflow in steps possibly will help you to understand where is the issue - you can try to merge some calls later and see when the issue occurs

-another point is that you callede  some forms inside your continous workflow process, means  inside or betweens  the function/events/ - there is also some timout expiring when calling  of the   $ionicPopup. 

Possibly you can try to simplify the workflow making it more straight away meass by calling all data at the begining and when the data is collected  there to call the next step. So possibly extra step in UI via  stage button to involve this - because I do not see a difference in the UI logic when you call $ionicPopup inside a call backs or events or start every step with separte button when the previous step is completed.

Unfortunately I did not have the time to check and understand  your code very detailed y  and therefore possibly my suggestion are incomplete. Thanks BR.

Hey @RolandRaytchev 

I appreciate your feedback and time. I would like to give you some more insights yes i would like to try your suggested approach to run the things once the previous operation finished but if i see the sequence of implementation its already behaving in that way. The problem occurs only in the pdf generation and i feel like there memory execssive or overflow somehow in it. 

 

VUFORIA STUDIO (BROWSER)

If i run the same code on browser it takes longer may be around 40 seconds to generate the pdf but it never fails means my code always works. It has never happened that the process fail in browser that means my code is somehow right and sequence of flow is also fine.

 

VIEW APP (IPAD)

Now come to the ipad in view app there the process takes less time to finish which is good  that its fast but sometimes it fails and it only happens while generating the PDF. For eg: sometimes it just generates page 1 page 2 then suddenly process fails. It is somehow related to memory or loop. 

 

IMPORTANT: Currently i have around 6 popup (pages) for  generation of PDF. I did one interesting test yesterday i decrease these 6 pages to 3 pages and then in IPAD this test never fails. It was always working. What do you think the cause can be? I have added my code below the function which generates the PDF for me.. Would you please have an overview of it. I have also put logs screenshot and there you can see when process stops not doing anything then FPS: 30.0 is coming on  and on... There is no animation or model in my expereince it is simple checklist which user fills and then signs then generates PDF and send it to backend on thingworx... Would you please see my code what actually is the problem.. Decreasing the pages makes the app running perfectly fine in VIEW APP and definitly faster then even before. 

 

 

 

 

$scope.printPDF = async function() {
  


let popupArray = $scope.app.params.Page5Visibility === true? ["page_0", "page_1", "page_2","page_3","page_4", "page_5"] : ["page_0",  "page_1", "page_2","page_3","page_4"];

if (customerPresent && $scope.app.params.Status === PHASES.SCW2Phase4) {   
    
    popupArray = [ 
    "page_0",
 ];
}
  
    let helper1 = document.querySelectorAll("div.twx-popup-container.ng-hide, ion-spinner.ng-hide");
    helper1.forEach(box => { box.classList.remove("ng-hide")} );

 
  
    let attributeContainer = [];

    const pdfDoc = await window.PDFLib.PDFDocument.create();

    for (const popupId of popupArray) {
      console.log(PHASES.SCW2Phase3);
      console.log($scope.app.params.Status);
      
      console.log("page: " + popupId + " erstellt");
     attributeContainer.push(document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).getAttribute("style"));
      document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", "width: 100%; overflow: visible;");

      let mergedElements = document.createElement('DIV');
      mergedElements.setAttribute('style', 'font-family: prometo !important; letter-spacing: 0.01px; overflow: visible');
      mergedElements.append(document.querySelector(`twx-widget[widget-id='${popupId}']`).cloneNode(true));

      let pdfHeight = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetHeight + 25,
          pdfWidth  = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetWidth + 20;

      let pdf = new jspdf.jsPDF('l', 'px', [pdfWidth, pdfHeight]);
      pdf.addFileToVFS('prometo-normal.ttf', font);
      pdf.addFont('prometo-normal.ttf', 'prometo', 'normal');
      pdf.setFont("prometo", 'normal');

      let donorPdfBytes;
      await pdf.html(mergedElements, {
        // this warning is wrong - everything is fine
        callback: receivedPDF => { donorPdfBytes = receivedPDF.output("datauristring"); },
        x: 5,
        y: 5
      });

      let donorPdfDoc = await window.PDFLib.PDFDocument.load(donorPdfBytes);
      let docLength = donorPdfDoc.getPageCount();

      for (let k = 0; k < docLength; k++) {
        const [donorPage] = await pdfDoc.copyPages(donorPdfDoc, [k]);
        pdfDoc.addPage(donorPage);
    
      }
    }


    const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
    let mergedPDF = pdfDataUri.substring(pdfDataUri.indexOf(',') + 1);
    helper1.forEach(box => { box.classList.add("ng-hide"); });

    popupArray.forEach((popupId, i) => {
        document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", attributeContainer[i]);
    });
 	const currentDate = new Date();
  	const currentDateString = `${currentDate.getFullYear()}-${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}-${currentDate.getMinutes().toString().padStart(2, '0')}_`;
   
    let helperName;
  
    if ($scope.app.params.Status == PHASES.SCW2Phase1) {
        helperName = "mechanic_EDTCD1";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase2) {
        helperName = "mechanic_EDTCD2";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase3) {
        helperName = "manager";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase4) {
      if(!customerPresent) {
        
        helperName = "manager";
      
      } else {
        helperName = "Inbetriebnehmer";
      }
     
    }

    let pdfName = $scope.view.wdg["0_5"].text + "-" + $scope.view.wdg["0_6"].text + "_" + currentDateString + helperName + ".pdf";

    let pdfPath = $scope.projectInfo.repositoryRelativePath + "/" + pdfName;

    twxDS("TestBench_Mobile.Controller", "savePDF", { "content": mergedPDF, "path": pdfPath });
    console.log(" PDF with the name '" + pdfName + "' created");
};

  

Jamal8548_1-1716532298263.png

 

One Additional thing in my function printPDF there is this piece of code which give me warning but still it works and maybe that i causing a problem. I have attached a screenshot below about this warning when i hover on it and without hover so that you can see the code chunk...

 

Jamal8548_0-1716538527496.pngJamal8548_1-1716538530587.png

 

 

NEW UPDATE: So now i have refactor the code and put callback function outside the loop and now warning is gone! I have put so many console.logs to see whats a point of failure actually. it seems the main bottleneck and failure point is the pdf.html method .This is likely due to the limited resources and processing power of the iPad compared to a PC. The point of failure is this small snippet in below function which is this 

 

let donorPdfBytes = await new Promise(resolve => {

pdf.html(mergedElements, {

callback: receivedPDF => resolve(receivedPDF.output("datauristring")),

x: 5,

y: 5

});

});

 

On here when it is generating pages its just stops in middle may be after creating page 0 and page 1. It would just stop then. On PC its working well.

 

 

 

 

$scope.printPDF = async function() {
    let popupArray = $scope.app.params.Page5Visibility === true ? ["page_0", "page_1", "page_2", "page_3", "page_4", "page_5"] : ["page_0", "page_1", "page_2", "page_3", "page_4"];

    if (customerPresent && $scope.app.params.Status === PHASES.SCW2Phase4) {   // For SCW2Phase4 and customer present, use only page_0
        popupArray = ["page_0"];
    }

    let helper1 = document.querySelectorAll("div.twx-popup-container.ng-hide, ion-spinner.ng-hide");
    helper1.forEach(box => { box.classList.remove("ng-hide") });

  

    let attributeContainer = [];
    const pdfDoc = await window.PDFLib.PDFDocument.create();

    const processPopup = async (popupId) => {
        console.log(PHASES.SCW2Phase3);
        console.log($scope.app.params.Status);

        console.log("page: " + popupId + " erstellt");
        attributeContainer.push(document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).getAttribute("style"));
        console.log("problem cause 1");
        document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", "width: 100%; overflow: visible;");
        console.log("problem cause 2");
        let mergedElements = document.createElement('DIV');
        console.log("problem cause 3");
        mergedElements.setAttribute('style', 'font-family: prometo !important; letter-spacing: 0.01px; overflow: visible');
        console.log("problem cause 4");
        mergedElements.append(document.querySelector(`twx-widget[widget-id='${popupId}']`).cloneNode(true));
        console.log("problem cause 5");

        let pdfHeight = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetHeight + 25,
            pdfWidth = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetWidth + 20;
        console.log("problem cause 6");
        let pdf = new jspdf.jsPDF('l', 'px', [pdfWidth, pdfHeight]);
        console.log("problem cause 7");
        pdf.addFileToVFS('prometo-normal.ttf', font);
        console.log("problem cause 8");
        pdf.addFont('prometo-normal.ttf', 'prometo', 'normal');
        console.log("problem cause 9");
        pdf.setFont("prometo", 'normal');
        console.log("problem cause 10");

        let donorPdfBytes = await new Promise(resolve => {
            pdf.html(mergedElements, {
                callback: receivedPDF => resolve(receivedPDF.output("datauristring")),
                x: 5,
                y: 5
            });
        });

        console.log("problem cause 11");
        let donorPdfDoc = await window.PDFLib.PDFDocument.load(donorPdfBytes);
        console.log("problem cause 12");
        let docLength = donorPdfDoc.getPageCount();
        console.log("problem cause 13");
        for (let k = 0; k < docLength; k++) {
            const [donorPage] = await pdfDoc.copyPages(donorPdfDoc, [k]);
            pdfDoc.addPage(donorPage);
            /* console.log("jamal page "+ donorPage);*/
        }
    };

    for (const popupId of popupArray) {
        await processPopup(popupId);
    }
    console.log("problem cause 14");

    const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
    console.log("problem cause 15");
    let mergedPDF = pdfDataUri.substring(pdfDataUri.indexOf(',') + 1);
    console.log("problem cause 16");
    helper1.forEach(box => { box.classList.add("ng-hide"); });
    console.log("problem cause 17");

    popupArray.forEach((popupId, i) => {
        document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", attributeContainer[i]);
    });
    console.log("problem cause 18");
    const currentDate = new Date();
    console.log("problem cause 19");
    const currentDateString = `${currentDate.getFullYear()}-${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}-${currentDate.getMinutes().toString().padStart(2, '0')}_`;
   
    let helperName;

    if ($scope.app.params.Status == PHASES.SCW2Phase1) {
        helperName = "mechanic_EDTCD1";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase2) {
        helperName = "mechanic_EDTCD2";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase3) {
        helperName = "manager";
    }
    if ($scope.app.params.Status == PHASES.SCW2Phase4) {
        if (!customerPresent) {
            helperName = "manager";
        } else {
            helperName = "Inbetriebnehmer";
        }
    }

    let pdfName = $scope.view.wdg["0_5"].text + "-" + $scope.view.wdg["0_6"].text + "_" + currentDateString + helperName + ".pdf";

    let pdfPath = $scope.projectInfo.repositoryRelativePath + "/" + pdfName;

    twxDS("TestBench_Mobile.Controller", "savePDF", { "content": mergedPDF, "path": pdfPath });
    console.log(" PDF with the name '" + pdfName + "' created");
};

 

 

 

 

Hi @MA8731174 ,

thanks for sharing the resutls . I see that you have a lot of progress! 

It seems that removing /reducing the of the popup will be helpful. Is it possible the change the design so that first collect all required informaiton via calling  poupup one time and then staring the workflow without interruption.

I believe that PDF lib is used from the extension. I checked and see that there are 2 different version of this extension:

 Custom PDF Widget extension (PDFViewer_0.3.zip Tested on Vuforia 8.5.x and 9.0.0) or Custom PDF Widget extension (PDFViewer_0.5.1.zip Tested on Vuforia 9.0.3). Which version did you test /use? I remember that somebody mentioned that the second one is more stable. Do you use the second version?

Unfortunately to be able to say more about this problem  respectively to report this to R&D team  I need to check it  myself and try to reproduce it. Unfortunately this will be possible first next week there I hope could check it more detailed.

Thanks , BR

 

Hi @RolandRaytchev  Thank you for your constant support and feedback! I have got a root problem which was causing this whole issue and my problem is resolved now 🙂

 

 

My Old Approach (Causing Issue)

  1. Generating All Pages Together:
    • In the original approach, I was collecting the content of all the pages and then generating a single PDF document.
    • This approach can consume a lot of memory and processing power, especially on devices with limited resources like an iPad.

New Approach

  1. Generating Each Page Separately:
    • The key change is that each page is now generated as a separate PDF, and then these individual PDFs are merged into one final document.

This reduces the peak memory usage because only one page is processed at a time.

Learning Points For New Comers

  • Memory Management: When working with limited resources, it's crucial to break down tasks into smaller chunks to avoid overwhelming the system.
  • Incremental Processing: Processing data incrementally can help manage resources better and improve performance.

My Code below for generating PDF from POPUPs

 

 

 

 

async function generatePagePDF(popupId, pdfWidth, pdfHeight, font) {
    const pdf = new jspdf.jsPDF('l', 'px', [pdfWidth, pdfHeight]);

    // If font is necessary, uncomment the following lines
     pdf.addFileToVFS('prometo-normal.ttf', font);
     pdf.addFont('prometo-normal.ttf', 'prometo', 'normal');
     pdf.setFont("prometo", 'normal');

    const mergedElements = document.createElement('DIV');
    mergedElements.setAttribute('style', 'font-family: prometo !important; letter-spacing: 0.01px; overflow: visible');
    mergedElements.append(document.querySelector(`twx-widget[widget-id='${popupId}']`).cloneNode(true));

    const donorPdfBytes = await new Promise((resolve, reject) => {
        pdf.html(mergedElements, {
            callback: receivedPDF => resolve(receivedPDF.output("datauristring")),
            x: 5,
            y: 5,
            html2canvas: {
                scale: 1, // Reduce the scale if necessary to reduce memory usage
            }
        });
    });

    return donorPdfBytes;
}


$scope.printPDF = async function() {
    let popupArray = $scope.app.params.Page5Visibility === true ? ["page_0", "page_1", "page_2", "page_3", "page_4", "page_5"] : ["page_0", "page_1", "page_2", "page_3", "page_4"];

    if (customerPresent && $scope.app.params.Status === PHASES.SCW2Phase4) {   // For SCW2Phase4 and customer present, use only page_0
        popupArray = ["page_0"];
    }

    let helper1 = document.querySelectorAll("div.twx-popup-container.ng-hide, ion-spinner.ng-hide");
    helper1.forEach(box => { box.classList.remove("ng-hide") });


    let attributeContainer = [];
    const pdfDoc = await window.PDFLib.PDFDocument.create();

    for (const popupId of popupArray) {
        try {
            console.log(PHASES.SCW2Phase3);
            console.log($scope.app.params.Status);

            console.log("page: " + popupId + " erstellt");
            attributeContainer.push(document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).getAttribute("style"));
            document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", "width: 100%; overflow: visible;");
            let pdfHeight = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetHeight + 25,
                pdfWidth = document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup .gridLayout`).offsetWidth + 20;

            let donorPdfBytes = await generatePagePDF(popupId, pdfWidth, pdfHeight, font);

            let donorPdfDoc = await window.PDFLib.PDFDocument.load(donorPdfBytes);
            let docLength = donorPdfDoc.getPageCount();

            for (let k = 0; k < docLength; k++) {
                const [donorPage] = await pdfDoc.copyPages(donorPdfDoc, [k]);
                pdfDoc.addPage(donorPage);
            }

            attributeContainer.pop();
            document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", attributeContainer[attributeContainer.length - 1]);

           // await new Promise(resolve => setTimeout(resolve, 200)); // Adding delay to manage resources
        } catch (error) {
            console.error('Error processing page ' + popupId + ':', error);
        }
    }

    try {
        const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
        let mergedPDF =         pdfDataUri.substring(pdfDataUri.indexOf(',') + 1);
        helper1.forEach(box => { box.classList.add("ng-hide"); });

        popupArray.forEach((popupId, i) => {
            document.querySelector(`twx-widget[widget-id='${popupId}'] .twx-popup`).setAttribute("style", attributeContainer[i]);
        });

        const currentDate = new Date();
        const currentDateString = `${currentDate.getFullYear()}-${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}-${currentDate.getMinutes().toString().padStart(2, '0')}_`;

        let helperName;
        if ($scope.app.params.Status == PHASES.SCW2Phase1) {
            helperName = "mechanic_EDTCD1";
        } else if ($scope.app.params.Status == PHASES.SCW2Phase2) {
            helperName = "mechanic_EDTCD2";
        } else if ($scope.app.params.Status == PHASES.SCW2Phase3) {
            helperName = "manager";
        } else if ($scope.app.params.Status == PHASES.SCW2Phase4) {
            helperName = customerPresent ? "Inbetriebnehmer" : "manager";
        }

        let pdfName = $scope.view.wdg["0_5"].text + "-" + $scope.view.wdg["0_6"].text + "_" + currentDateString + helperName + ".pdf";
        let pdfPath = $scope.projectInfo.repositoryRelativePath + "/" + pdfName;

        twxDS("TestBench_Mobile.Controller", "savePDF", { "content": mergedPDF, "path": pdfPath });
        console.log(" PDF with the name '" + pdfName + "' created");
    } catch (error) {
        console.error('Error saving PDF:', error);
    }
};

 

 

 

  In order to inculde libraries in vuforia studio for fonts and PDF generation. 

 

 

 

let myscript = document.createElement('script');
myscript.src="app/resources/Uploaded/jspdf.js";
document.head.appendChild(myscript);
myscript.onload = async function(){
// code to be executed when the script has finished loading
    
 //...
console.log("stop here");

}

let html2canvas = document.createElement('script');
html2canvas.src="app/resources/Uploaded/html2canvas.js";
document.head.appendChild(html2canvas);
html2canvas.onload = function(){
// code to be executed when the script has finished loading

 //...
}

let PDFLib = document.createElement('script');
PDFLib.src="app/resources/Uploaded/pdf-lib.js";
document.head.appendChild(PDFLib);
PDFLib.onload = async function(){
// code to be executed when the script has finished loading

 //...
}

 

 

 

 

 

HI @MA8731174  thanks for the feedback! that is really great news. I am heapy to hear that is working now. Was not really easy issue 😊... but possibly interessing point with helpful results! BR

Hi @RolandRaytchev Unfortunately the issue is still there. 😞 Is it possible for you that your IOT team will reproduce it on the vuforia view app. The extension which you have shared with me that was pdf viewer. For me it is different i have library installed to make the pdf in javascript and i am generating pdf from all 6 popups and then sending it to backend thats it. It was working for few days and now issue is back. For me its unbelieveable that why view app behaves like that. Nothing has been changed so far still its not working now. It would be nice if you would spare some time for the issue so that we can find the root cause. I am ready to share my code or further information if you need any. I am just taking the classes of popups which were assigned already to already from Develoeprs of ptc and i am just generating a pdf. 

Announcements

Top Tags