Learn how to quickly create digital work instructions in Canvas Envision with the evCreate API by using the structure of a 3D model to generate step-by-step pages. It highlights how automation can save time and improve the accuracy of assembly documentation.
In our previous blog post, we showed how to deploy custom evCreate functionality as a button within Envision. Now that you know how to deploy custom code, it is time to explore examples of what is possible with evCreate API. This blog post dives deeper into an example of automation — showing you how to use evCreate to auto-generate step-by-step instruction pages from a 3D model’s assembly structure.
A 3D model’s assembly tree structure (EBOM) often closely resembles the actual intended manufacturing assembly order (MBOM). We can leverage this information by scripting instruction steps to be generated based on that structure. This saves significant time in the document creation process.
By exploring the evCreate API functions that are used in this example, it lays the groundwork for future customization and tailoring to your company’s specific needs.
We’ll create a script that:
To follow along, you’ll need:
A best practice for starting your code is to always ensure your script waits for the evCreate API to become available:
(async function () {
async function waitForEvCreateApiReady() {
const until = (predicateFn, intervalMsec) => new Promise(resolve => {
const poll = () => predicateFn() ? resolve() : setTimeout(poll,
intervalMsec);
poll();
});
await until(() => window.evCreate !== undefined, 500);
await window.evCreate.config.WhenApiReady();
}
To avoid errors and guide the end user of the custom button in Envision, we can check that the user has selected a 3D object:
async function addModelToNewPages() {
await waitForEvCreateApiReady();
const selected = await window.evCreate.object.selection.Get();
if (!selected.length) return alert("Please select a 3D model.");
const selectedAttr = await
window.evCreate.object.GetAttributes(selected[0]);
if (!selectedAttr || selectedAttr.type !== "model3D") return alert("Selected
object is not a 3D model.");
const modelInfo = await
window.evCreate.object.model.GetModelInfo(selected[0], { recursive: true });
We recursively walk the model tree and assign labels like “1”, “1.2”, or “2.1.3” to subassemblies to denote parent/child relationships:
// Label assemblies with hierarchical step numbers
const labeledNodes = [];
function labelAssemblies(node, path = [], levelIndex = {}) {
if (node.isPart === false && typeof node.childrenCount === "number" &&
node.childrenCount > 1) {
const level = path.length;
levelIndex[level] = (levelIndex[level] || 0) + 1;
const newPath = [...path.slice(0, level), levelIndex[level]];
const label = newPath.join(".");
const childObjectIds = (node.children || []).map(child =>
child.objectId);
labeledNodes.push({ label, name: node.name, childrenCount:
node.childrenCount, childObjectIds });
Object.keys(levelIndex).map(Number).filter(lvl => lvl >
level).forEach(lvl => delete levelIndex[lvl]);
for (const child of node.children || []) labelAssemblies(child, newPath,
levelIndex);
} else {
for (const child of node.children || []) labelAssemblies(child, path,
levelIndex);
}
}
labelAssemblies(modelInfo);
Now that we have the assembly steps labeled, we can order them based on the numbering to ensure we are building up our assemblies in a logical fashion:
// Sort deepest assemblies first, based on label depth and value
labeledNodes.sort((a, b) => {
const aParts = a.label.split('.').map(Number);
const bParts = b.label.split('.').map(Number);
const len = Math.min(aParts.length, bParts.length);
for (let i = 0; i < len; i++) {
if (aParts[i] !== bParts[i]) return aParts[i] - bParts[i];
}
return bParts.length - aParts.length;
});
For each step:
const selectedModel = selected[0];
for (const node of labeledNodes) {
const newPage = await window.evCreate.document.AddPage();
await window.evCreate.document.SetCurrentPage(newPage);
const [firstLayer] = await window.evCreate.page.GetLayers(newPage);
await window.evCreate.page.SetCurrentLayer(firstLayer);
const [dupedModel] = await
window.evCreate.object.Duplicate([selectedModel], 0, 0);
await window.evCreate.object.SetZOrder(dupedModel, { layer: firstLayer,
index: 0 });
await window.evCreate.edit3D.EditModel(dupedModel);
const fullModelInfo =
await window.evCreate.object.model.GetModelInfo(dupedModel, { recursive: true });
const partIdsToHide = [];
(function collectParts(node) {
if (node.isPart) partIdsToHide.push({ partId: node.objectId });
for (const child of node.children || []) collectParts(child);
})(fullModelInfo);
await window.evCreate.edit3D.SetPartVisible(partIdsToHide, false);
const partIdsToShow = node.childObjectIds.map(id => ({ partId: id }));
await window.evCreate.edit3D.SetPartVisible(partIdsToShow, true);
await window.evCreate.edit3D.FitCameraTo();
await window.evCreate.edit3D.Save();
}
}
addModelToNewPages();
})();
This technique automates a tedious part of 3D documentation. Future enhancements might include:
Cut errors, reduce costs, improve time to market and retain the best workforce. Talk to us today.
SaaS or self-hosted
Fully customizable
Integrate and embed
Get the job done right, every time, with Canvas Envision
© Canvas GFX 2024
35 Village Road. Suite 100.
Middleton, MA, 01949