Skip to content

Instantly share code, notes, and snippets.

@markbacker
Created March 27, 2022 08:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markbacker/e9f8e6a80b4ec702a40251d934f69927 to your computer and use it in GitHub Desktop.
Save markbacker/e9f8e6a80b4ec702a40251d934f69927 to your computer and use it in GitHub Desktop.
#jArchi Create a project model of a reference model
/**
* Create a project model of a reference model
* save a copy of the reference model and keep only the for the project relevant objects
*
* Use case
* Use part of a reference model as a start for a project model and keep that part in sync.
* The project reuses the shared concepts of the reference model and can freely extend with project details and scenario's.
*
* How to use this script
* - Create the project model file
* - specify the for the project relevant objects in the reference model
* - create a 'project folder' with the name of the project in the reference model
* - create one or more view(s) with for the project relevant objects in the 'project folder'
* - select the created 'project folder'
* - run this script
* - use the new project model which is now opened in the model tree
* - add everything you need for the project,
* - don't change the objects from the reference model, these are maintained in the reference model
* - Sync changes in the reference model to the project model
* - you have 2 models open in Archi
* - an updated reference model
* - the project model with added project concepts and views
* - run this script again in the updated reference model
* - import the just create project file in your project model
* - select your project model
* - import with 'File > Import > Another model into the selected model'
* - do not sync model properties; uncheck the option 'Update model information and top-level folders'
*
* What this script does
* - save a copy of the reference model as the project model
* - delete everything not relevant to the project
* - delete all views not in the project folder
* - delete unused elements (elements not on a view anymore)
* - delete empty folders
*
* Author: Mark Backer
* Version: 1 (2021-03)
*/
const PROP_ID = "Object ID";
const MODEL_TYPE = "Type model";
initConsoleLog(__FILE__, true);
try {
let projectFolders = $(selection).filter("folder");
if (projectFolders.size() == 1) {
let projectFolder = projectFolders.first();
console.log(`Selected project folder is "${projectFolder.name}"`);
let currentModelPath = model.getPath();
let projectModelName = `Projectmodel ${projectFolder.name}`;
// save a copy of the reference model as the projectmodel
createProjectModel(projectModelName);
// delete everything not relevant to the project
filterProjectModel(projectFolder, projectModelName);
// reopen the reference model
$.model.load(currentModelPath).setAsCurrent();
model.openInUI();
} else {
console.log("Select one folder with views containing all the elements of the project architecture");
}
} catch (error) {
console.error(`> ${typeof error.stack == "undefined" ? error : error.stack}`);
}
finishConsoleLog();
/**
* Save a copy of the current model, prompt for a file name
*/
function createProjectModel(projectModelName) {
let fileName_suggestion = projectModelName.replace(/\s/g, "-").replace(/[\[\]\(\)\#\\\/\"\.:;,–]/gi, "");
let projectFile = window.promptSaveFile({
title: `Save projectmodel`,
filterExtensions: [`*.archimate`],
fileName: `${fileName_suggestion}.archimate`,
});
console.log(`Create projectmodel "${projectModelName}"`);
// create and open project model
model.save(projectFile);
return;
}
/**
* delete everything not relevant to the project
*/
function filterProjectModel(projectFolder, projectModelName) {
// set project model properties for start of project model
// model properties should not be imported when syncing
model.name = projectModelName;
model.prop(PROP_ID, generateUUID());
model.prop(MODEL_TYPE, "Projectmodel");
// delete views not in selected project folder
projectViews = selectObjects(projectFolder, "view");
console.log(`\nFilter projectmodel`);
console.log(`Only keep objects referenced on the ${projectViews.size()} views in the folder ${projectFolder.name}`);
viewsToDelete = $("view").not(projectViews);
console.log(`\nDelete ${viewsToDelete.size()} views`);
viewsToDelete.each((v) => {
console.log(` - ${v.name}`);
v.delete();
});
// delete elements not on a view
elementsToDelete = $("element").filter((e) => $(e).viewRefs().size() == 0);
console.log(`\nDelete ${elementsToDelete.size()} unused elements`);
elementsToDelete.each((e) => {
console.log(` - ${e}`);
e.delete();
});
// delete empty folders
let nrFoldersStart = $(model).find("folder").size();
console.log(`\nDelete empty folders:`);
deleteEmptyFolders($(model).first());
let nrFoldersRemaining = $(model).find("folder").size();
console.log(`Deleted ${nrFoldersStart - nrFoldersRemaining} empty folders of total of ${nrFoldersStart} folders`);
// save filtered model
model.save();
console.log(`\nProjectmodel saved in: "${model.getPath()}"`);
}
/**
* delete empty folders by recursing over the folder tree
*
* @param {object} folderObj - current folder to traverse. For root folder, use $(model).first()
* @param {integer} level - current level of recursion
* @param {string} folderPath - path of traversed parent folders
*/
function deleteEmptyFolders(folderObj, level = 0, folderPath = "") {
// stop if folder has no subfolders
let subFolders = $(folderObj).children("folder");
if (subFolders.size() == 0) return;
subFolders.each((subFolder) => {
deleteEmptyFolders(subFolder, level + 1, `${folderPath}/${subFolder.name}`);
// delete empty folder
if ($(subFolder).children().size() == 0 && level > 0) {
console.log(` - ${subFolder.name != "" ? `${folderPath}/${subFolder.name}` : `${folderPath}/## no name ##`}`);
subFolder.delete();
}
});
}
/**
* return a generated UUID
* from : https://stackoverflow.com/questions/105034/how-to-create-guid-uuid
*/
function generateUUID() {
// Public Domain/MIT
var d = new Date().getTime(); //Timestamp
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = Math.random() * 16; //random number between 0 and 16
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
}
/**
* return a collection of the contained objects in the selection
*
* @param {object} startSelection - selection containing Archi objects
* @param {string} selector - Archi selector for filtering the type of contained objects
* @returns {object} - collection with selected objects
*/
function selectObjects(startSelection, selector) {
if (model == null || model.id == null)
throw "Nothing selected. Select one or more objects in the model tree or a view";
// create an empty collection
var selectedColl = $();
$(startSelection).each((obj) => addObjectInList(obj, selector, selectedColl));
return selectedColl;
}
/**
* recursive function
* add the selected object to a collection.
* if the object is a container (model, view or folder), add all contained objects
*/
function addObjectInList(obj, selector, coll) {
if ($(obj).is(selector)) {
let o = obj;
if (selector != "view") o = concept(obj);
// check for duplicates, than add element to the list
if (coll.filter((a) => a.id == o.id).size() == 0) {
coll.add(o);
}
}
$(obj)
.children()
.each((child) => addObjectInList(child, selector, coll));
return coll;
}
function concept(o) {
return o.concept ? o.concept : o;
}
/**
* initConsoleLog and finishconsole
* first and last call in an Archi script
*/
function initConsoleLog(currentScript, pClear) {
script_name = currentScript.replace(/^.*[\\\/]/, "");
console.show();
if (pClear) console.clear();
console.log(`\nRunning script "${script_name}"...\n`);
}
function finishConsoleLog() {
console.log(`\nScript "${script_name}" finished`);
console.log("==========================================\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment