Presenting the new open source Enterprise Architect Shapescript Library

The new open source Enterprise Architect Shapescript Library in Github is an initiative to share shapescripts for Enterprise Architect within the user community.

Shapescripts in Enterprise Architect are used to define the presentation of elements on a diagram. They are mostly used when defining a UML profile in Enterprise Architect that extends UML or another modelling language with project specific elements and properties.

Shapescript examples

When developing UML profiles these shapescripts are stored in the _image attribute on the stereotype element, an they can be easily edited using the profile helper.

Shapescript editor

After a while however you’ll have a bunch of shapescripts scattered over different profiles, and projects, and it becomes hard to manage, or do any kind of version control. Finding a specific shapescript where you used a particular feature is next to impossible if you don’t remember exactly which stereotype this shapescript belonged to.

Exporting shapescripts

With the shapescript editor you can export each shapescript individually, but once you have more then a couple of shapescripts that quickly becomes a drag to save your shapescripts to files.

So I wrote a script called ExportAllShapeScripts that searches a repository for all stereotypes that have a shapescript, and exports all of these shapescripts in one go.

This script is part of the Enterprise-Architect-VBScript-Library. Read the article How to use the Enterprise Architect VBScript Library for instructions on downloading and using the Library in Enterprise Architect.

Running this script will save each shapescript as a textfile with extension .shapeScript, grouped in packages per profile.

Shapescripts saved

The code explained

The export script first uses an SQL query to find all shapescripts. Technically the shapescripts are stored in the default property of the attribute with the name _image. The script is stored as an xml tag Image in a zipped and base64 encoded format. A typical shapescript might look like this in the database:

<Image type="EAShapeScript 1.0" xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="bin.base64">
UEsDBBQAAAAIABpRIkdk2qflMQIAADIHAAAHABEAc3RyLmRhdFVUDQAHncrmVZ3K5lWdyuZV
jVXJTsMwFJxrkfiHCC5FqsRSaIsQhx44sohKcC5toUihqUjYxb8z8xw3jksLsmIn9sx43vOS
HFMMMccECZ749ogZNrGBL6sbSNn3gQwvKFg+DHmKLfZPcM+eZzIeqFGw76Tk3LPvneNj9JEb
Y8TxayoVHMmML+YLRyqWq8emmxP3ybcmuthDC222OyXS4fpEjjGgxh1rH0OTejP2PbF3whly
frewTw2puDbUcVp5kIM62437XHiPDeyy3FnkCXEuviG5D5YXjxrwvcAV6xluiR7za0qP7YUH
oa6X+E1zWzmOncdObsytcjuiSkpPqa3jZBFBgzFleDU/GcfloW1zhJqeJYxHLM8cK3VY1is5
xLJSGINW+5lo33dGDanMGYMyInYLB+hZLWddtqvUMpv9N8dNHJKnHdVZ4/hoMdtfmNhFPNdv
TmMdZSfGhNEUeAsy02eeRnYyuuZR7GPmp1U66lmODlmkKkwca91jj2zh1+XD6cQe6xjnIcbU
45gaI2Gb2c3xaSulvR/uWs9ZdX7210SjDCgPq336MxWjQqe53YXagQnPeWa3WXWWtCMLu+WE
GHE8tXiqc1tXXr4hHNKtkR7tgb8YB+U6Ca8V/R+rY7vdsTqm8B/Wsek7VpWvOi9ufXHf39bq
qd70KLvujteaPML9EbQnMvYldKF9MLQbODVGeDvXR76sTsr1uCz/RdLVf2Bgd391yyUlem44
7bq0RG7jgqfqnHfONr8qbN25f34AUEsBAhcLFAAAAAgAGlEiR2Tap+UxAgAAMgcAAAcACQAA
AAAAAAAAAACAAAAAAHN0ci5kYXRVVAUAB53K5lVQSwUGAAAAAAEAAQA+AAAAZwIAAAAA
</Image>

So the SQL selects all attributes owned by a element with stereotype «stereotype» with the name _image that have the tag <Image>  in their Default field.

'get all attributes with name _image that have shapescript in the default field and a parent with stereotype «stereotype»
dim sqlGetShapescriptAttributes
sqlGetShapescriptAttributes = "select a.ID from (t_attribute a " & _
							  " inner join t_object o on (o.Object_ID = a.Object_ID " & _
							  "						and o.Stereotype = 'stereotype')) " & _
							  " where a.Name = '_image' " & _
							  " and a.[Default] like '<Image type=""EAShapeScript" & getWC & "'"
dim shapeScriptAttributes
set shapeScriptAttributes = getAttributesByQuery(sqlGetShapescriptAttributes)

Then we loop the schapescript attributes, decode the shapescript and save it as an individual text file.

'loop the shape script attributes
for each shapeScriptAttribute in shapeScriptAttributes
	'get the stereotype
	dim stereotype as EA.Element
	set stereotype = Repository.GetElementByID(shapeScriptAttribute.ParentID)
	dim profile as EA.Package
	set profile = findProfilePackage(stereotype)
	'load the resultset in the xml document
	dim shapeScript
	shapeScript = decodeBase64zippedXML(shapeScriptAttribute.Default,"Image")
	if len(shapeScript) > 0 then
		dim scriptFile
		set scriptFile = New TextFile
		scriptfile.Contents = shapeScript
		'save the script
		scriptFile.FullPath = selectedFolder.FullPath & "\" & profile.Name & "\" & stereotype.Name & ".shapeScript"
		scriptFile.Save
		'debug info
		Session.Output "saving script: " & scriptFile.FullPath
	end if
next

The real difficulty int his script was to decode the shapescript, which is hidden in the method decodeBase64zippedXML  defined in the XML utility script. It first base64 decodes the contents of the tag, then saves it as a zip file, unzips the zip file, gets the text file in the zip file, and returns the contents of the text file. Then it deletes the temporary zip file and folder it was extracted to.

 Contributing

If you have shapescripts of your own that you wish to share you can send a github pull request, or an email to geert@bellekens.com.

Any bright ideas on how to document each of the shapescripts with an image of shape are welcome too.

 

How to use the Enterprise Architect VBScript Library

The Enterprise Architect VBScript Library is an open source library of VBScripts written to be used in  Enterprise Architect.

This article explains how to download, import and use the library in Enterprise Architect.

Initial Import

Initial scripts

The Enterprise Architect VBScript Library contains some scripts to load and save scripts from/to your file system, but before we can use those we have to import an initial set of scripts to get started.

After downloading the file select menu option Project|Data Management|Import Reference Data and choose the downloaded file. Make sure to select Automation scripts and click Import.

This will import the minimum set of scripts required to load other scripts from the file system

 

 

[ecwid_product store_id=”6824265″ product_id=”58361080″]

Download library from GitHub

From the Enterprise Architect VBScript Library project page on GitHub you can either choose to download the library as a zip file, or download the repository in GitHub desktop.

VBScript library github

Load library into Enterprise Architect

Select scripts folderOne of the scripts in the initial set is the script LoadScripts in the group Script Management. If you execute this script you can choose the library folder from the library downloaded from GitHub.

The script will then scan the entire folder tree and it will load any .vbs file it can find.

For starters it might be interesting to only load the Frameworks scripts as they have the most chance of being useful to anyone.

If the LoadScripts script finds a script that already exists in Enterprise Architect it will ask to overwrite the existing scripts.

In order to know in which group the script belongs it will look for a the group indicator in the script.

'[group=Script Management]

This will tell the script that this script should go in the group Script Management. If the script indicator is absent it will assume the group name is the name of the folder where it was found.

After loading the scripts into EA make sure to press the refresh button to make the scripts appear in the GUI.

Refresh script tree

Saving all your scripts

In the script management group there also a script to save all your scripts to the file system.

In order to control where the script should go you can add the path indicator to the script like this

'[path=\Framework\Tools\Script Management]

The path indicator will control where to place the script relative to the chosen folder.

If the path indicator is absent the name of the script group will be used as name of the folder.

Being able to save and load the scripts from/to the file system now allows us to use version control on these scripts.

The library structure

The library is structured in two main parts.

  • Projects
    Contains an assortment of various scripts written for various projects.
  • Framework
    Contains the framework scripts that are meant to be used by other scripts

    • Utils
      Contains helper scripts and classes such as TextFile, SQL, XML etc..
    • Wrappers
      Contains the wrapper classes for EA elements such as TaggedValue, Script, ScriptGroup

The scripts in the projects folder can be used for inspiration, but it is mainly the scripts int he Framework part that are useful when writing scripts.

Using the library

The first thing you need to do when you want to use any of the framework classes is to include the framework in your script

!INC Wrappers.Include

This will include the “Include” script that takes care of including all the other scripts of the framework, including those of the Utils folder.

Then you can use the classes defined in the library. For example, if you want to have the user select a directory you can use following code

'get the folder from the user
dim folder
set folder = new FileSystemFolder
set folder = folder.getUserSelectedFolder("")

The classes in the library contain both properties as operations. You can use the properties as you would expect from “real” classes.

'show messagebox with the name of each subfolder
dim subfolders, subfolder
for each subfolder in folder.SubFolders
    msgbox "subfolder name: " & subfolder.Name
next

Contributing

Have you written any VBScripts for Enterprise Architect you would like to share? If you would like to contribute to the Enterprise Architect VBScript library you can email me at geert@bellekens.com

Automatically trace elements to domain model in Enterprise Architect

This script will search the text in the comments, scenario’s and linked document of the selected elements for terms in the domain model. If it finds a match it will create a trace from your selected element to the element in the domain model.

The idea is that when we write descriptions and scenario’s we often use terms already defined in the domain model, so it would be good if we had a real trace from the element in the domain model and the element using it in its description.

Link to domain model example

But it is hard to keep the descriptions and scenario’s in sync with the traces to the domain model. This script does the hard part for us and keeps those traces in sync. All you need to do is type your description and run the script. The script will find the referenced elements in the domain model and create a trace link to it. It will also remove any automatic traces that are not valid anymore.

Next to the NameNotes, and the Linked Document, this script will also search the scenarios, both the Scenario Description, as the structured Scenario Steps. In case of a Scenario Step the script will insert to the name of the domain model element in the Uses column.Link to domain model scenario

Note that the script is case sensitive. That is the reason why RoomType does not appear in the uses column. Enterprise Architect itself isn’t that picky, and it underlines Roomtype anyway because it finds a relationship to RoomType.

In order to execute the script you have to select either one or more elements in the project browser, or select a package in the project browser.Link to domain model menu option

Then select Script|Link To Domain Model

Free download

[ecwid_product store_id=”6824265″ product_id=”56907555″]

The script

In the first part we ask the user to select the domain model package.
If you want to use this script on a particular model you may want to replace that by a hard coding the Domain models GUID into the script.
Since this script may run for a while on a large model, we also create an output tab so we can inform the user of our progress.

sub main
	'let the user select the domain model package
	dim response
	response = Msgbox ("Please select the domain model package", vbOKCancel+vbQuestion, "Selecte domain model")
	if response = vbOK then
		
		dim domainModelPackage as EA.Package
		set domainModelPackage = selectPackage()
		if not domainModelPackage is nothing then
			Repository.CreateOutputTab "Link to Domain Model"
			Repository.ClearOutput "Link to Domain Model"
			Repository.EnsureOutputVisible "Link to Domain Model"
			'link selected elements to the domain model elements
			linkSelectionToDomainModel(domainModelPackage)
			'tell the user we are done
			Repository.WriteOutput "Link to Domain Model", "Finished!", 0
		end if
	end if
end sub

Next we create a dictionary of all classes in domain model package and all of the sub-packages recursively.

	'first get the pattern from all the classes in the domain model
	dim dictionary
	Set dictionary = CreateObject("Scripting.Dictionary")
	
	'create domain model dictionary
	'tell the user what we are doing
	Repository.WriteOutput "Link to Domain Model", "Creating domain model dictionary", 0
	addToClassDictionary domainModelPackage.PackageGUID, dictionary

Based on this dictionary we can create one large Regular Expression that will tell us which of the terms in the dictionary are used in a certain text.

	' and prepare the regex object
	dim pattern
	'create the pattern based on the names in the dictionary
	pattern = createRegexPattern(dictionary)
	Dim regExp  
	Set regExp = CreateObject("VBScript.RegExp")
	regExp.Global = True   
	regExp.IgnoreCase = False
	regExp.Pattern = pattern

Then we check what is selected in the project browser. If the user has selected one or more elements we start process those elements. If a package is selected then we process the elements in the selected package.

	' Get the type of element selected in the Project Browser
	dim treeSelectedType
	treeSelectedType = Repository.GetTreeSelectedItemType()
	' Either process the selected Element, or process all elements in the selected package
	select case treeSelectedType
		case otElement
			' Code for when an element is selected
			dim selectedElements as EA.Collection
			set selectedElements = Repository.GetTreeSelectedElements()
			'link the selected elements with the 
			linkDomainClassesWithElements dictionary,regExp,selectedElements 
		case otPackage
			' Code for when a package is selected
			dim selectedPackage as EA.Package
			set selectedpackage = Repository.GetTreeSelectedObject()
			'link use domain classes with the elements in the selected package
			linkDomainClassesWithElementsInPackage dictionary, regExp,selectedPackage
		case else
			' Error message
			Session.Prompt "You have to select Elements or a Package", promptOK
	end select

Here’s the complete code for the script.

option explicit

!INC Local Scripts.EAConstants-VBScript

'
' Script Name: Link to Domain Model
' Author: Geert Bellekens
' Purpose: Link elements with classes in the domain model based on their name.
' Date: 15/11/2015
'


sub main
	'let the user select the domain model package
	dim response
	response = Msgbox ("Please select the domain model package", vbOKCancel+vbQuestion, "Selecte domain model")
	if response = vbOK then
		
		dim domainModelPackage as EA.Package
		set domainModelPackage = selectPackage()
		if not domainModelPackage is nothing then
			Repository.CreateOutputTab "Link to Domain Model"
			Repository.ClearOutput "Link to Domain Model"
			Repository.EnsureOutputVisible "Link to Domain Model"
			'link selected elements to the domain model elements
			linkSelectionToDomainModel(domainModelPackage)
			'tell the user we are done
			Repository.WriteOutput "Link to Domain Model", "Finished!", 0
		end if
	end if
end sub

'this is the actual call to the main function
main

function linkSelectionToDomainModel(domainModelPackage)
	'first get the pattern from all the classes in the domain model
	dim dictionary
	Set dictionary = CreateObject("Scripting.Dictionary")
	
	'create domain model dictionary
	'tell the user what we are doing
	Repository.WriteOutput "Link to Domain Model", "Creating domain model dictionary", 0
	addToClassDictionary domainModelPackage.PackageGUID, dictionary
	
	'tell the user what we are doing
	Repository.WriteOutput "Link to Domain Model", "Interpreting dictionary", 0
	' and prepare the regex object
	dim pattern
	'create the pattern based on the names in the dictionary
	pattern = createRegexPattern(dictionary)
	Dim regExp  
	Set regExp = CreateObject("VBScript.RegExp")
	regExp.Global = True   
	regExp.IgnoreCase = False
	regExp.Pattern = pattern
	
	' Get the type of element selected in the Project Browser
	dim treeSelectedType
	treeSelectedType = Repository.GetTreeSelectedItemType()
	' Either process the selected Element, or process all elements in the selected package
	select case treeSelectedType
		case otElement
			' Code for when an element is selected
			dim selectedElements as EA.Collection
			set selectedElements = Repository.GetTreeSelectedElements()
			'link the selected elements with the 
			linkDomainClassesWithElements dictionary,regExp,selectedElements 
		case otPackage
			' Code for when a package is selected
			dim selectedPackage as EA.Package
			set selectedpackage = Repository.GetTreeSelectedObject()
			'link use domain classes with the elements in the selected package
			linkDomainClassesWithElementsInPackage dictionary, regExp,selectedPackage
		case else
			' Error message
			Session.Prompt "You have to select Elements or a Package", promptOK
	end select
end function

'this function will get all elements in the given package and subpackages recursively and link them to the domain classes
function linkDomainClassesWithElementsInPackage(dictionary,regExp,selectedPackage)
	dim packageList 
	set packageList = getPackageTree(selectedPackage)
	dim packageIDString
	packageIDString = makePackageIDString(packageList)
	dim getElementsSQL
	getElementsSQL = "select o.Object_ID from t_object o where o.Package_ID in (" &amp; packageIDString &amp; ")"
	dim usecases
	set usecases = getElementsFromQuery(getElementsSQL)
	linkDomainClassesWithElements dictionary,regExp,usecases
end function


function linkDomainClassesWithElements(dictionary,regExp,elements)
	dim element as EA.Element
	'loop de elements
	for each element in elements
		'tell the user what we are doing
		Repository.WriteOutput "Link to Domain Model", "Linking element: " &amp; element.Name, 0
		'first remove all automatic traces
		removeAllAutomaticTraces element
		'match based on notes and linked document
		dim elementText
		'get full text (name +notes + linked document + scenario names + scenario notes)
		elementText = element.Name
		elementText = elementText &amp; vbNewLine &amp; Repository.GetFormatFromField("TXT",element.Notes)
		elementText = elementText &amp; vbNewLine &amp; getLinkedDocumentContent(element, "TXT")
		elementText = elementText &amp; vbNewLine &amp; getTextFromScenarios(element)
		dim matches
		set matches = regExp.Execute(elementText)
		'for each match create a «trace» link with the element
		linkMatchesWithelement matches, element, dictionary
		'link based on text in scenariosteps
		dim scenario as EA.Scenario
		'get all dependencies left
		dim dependencies
		set dependencies = getDependencies(element)
		'loop scenarios
		for each scenario in element.Scenarios
			dim scenarioStep as EA.ScenarioStep
			for each scenarioStep in scenario.Steps
				'first remove any additional terms in the uses field
				scenarioStep.Uses = removeAddionalUses(dependencies, scenarioStep.Uses)
				set matches = regExp.Execute(scenarioStep.Name)
				dim classesToMatch 
				set classesToMatch = getClassesToMatchDictionary(matches, dictionary)
				dim classToMatch as EA.Element
				for each classToMatch in classesToMatch.Items
					if not instr(scenarioStep.Uses,classToMatch.Name) &gt; 0 then
						scenarioStep.Uses = scenarioStep.Uses &amp; " " &amp; classToMatch.Name
					end if
					'create the dependency between the use case and the domain model class
					linkElementsWithAutomaticTrace element, classToMatch
				next
				'save scenario step
				scenarioStep.Update
				scenario.Update
			next
		next
	next
end function

function linkMatchesWithelement(matches, element, dictionary)
	dim classesToMatch
	'get the classes to match
	Set classesToMatch = getClassesToMatchDictionary(matches,dictionary)
	dim classToMatch as EA.Element
	'actually link the classes
	for each classToMatch in classesToMatch.Items
		linkElementsWithAutomaticTrace element, classToMatch
	next
end function


'get the text from the scenarios name and notes
function getTextFromScenarios(element)
	dim scenario as EA.Scenario
	dim scenarioText
	scenarioText = "" 
	for each scenario in element.Scenarios
		scenarioText = scenarioText &amp; vbNewLine &amp; scenario.Name
		scenarioText = scenarioText &amp; vbNewLine &amp; Repository.GetFormatFromField("TXT",scenario.Notes)
	next
	getTextFromScenarios = scenarioText
end function

function removeAddionalUses(dependencies, uses)
	dim dependency
	dim filteredUses
	filteredUses = ""
	if len(uses) &gt; 0 then
		for each dependency in dependencies.Keys
			if Instr(uses,dependency) &gt; 0 then
				if len(filteredUses) &gt; 0 then
					filteredUses = filteredUses &amp; " " &amp; dependency
				else
					filteredUses = dependency
				end if
			end if
		next
	end if
	removeAddionalUses = filteredUses
end function

'returns a dictionary of elements with the name as key and the element as value.
function getDependencies(element)
	dim getDependencySQL
	getDependencySQL =  "select dep.Object_ID from ( t_object dep " &amp; _
						" inner join t_connector con on con.End_Object_ID = dep.Object_ID)   " &amp; _
						" where con.Connector_Type = 'Dependency'  " &amp; _
						" and con.Start_Object_ID = " &amp; element.ElementID   
	set getDependencies = getElementDictionaryFromQuery(getDependencySQL)
end function

function removeAllAutomaticTraces(element)
		dim i
		dim connector as EA.Connector
		'remove all the traces to domain model classes
		for i = element.Connectors.Count -1 to 0 step -1
			set connector = element.Connectors.GetAt(i)
			if connector.Alias = "automatic" and connector.Stereotype = "trace" then
				element.Connectors.DeleteAt i,false 
			end if
		next
end function

function getClassesToMatchDictionary(matches, allClassesDictionary)
	dim match
	dim classesToMatch
	dim className
	Set classesToMatch = CreateObject("Scripting.Dictionary")
	'create list of elements to link
	For each match in matches
		if not allClassesDictionary.Exists(match.Value) then
			'strip the last 's'
			className = left(match.Value, len(match.Value) -1)
		else
			className = match.Value
		end if
		if not classesToMatch.Exists(className) then
			classesToMatch.Add className, allClassesDictionary(className)
		end if
	next
	set getClassesToMatchDictionary = classesToMatch
end function

'Create a «trace» relation between source and target with "automatic" as alias
function linkElementsWithAutomaticTrace(sourceElement, targetElement)
	dim linkExists 
	linkExists = false
	'first make sure there isn't already a trace relation between the two
	dim existingConnector as EA.Connector
	'make sure we are using the up-to-date connectors collection
	sourceElement.Connectors.Refresh
	for each existingConnector in sourceElement.Connectors
		if existingConnector.SupplierID = targetElement.ElementID _
		   and existingConnector.Stereotype = "trace" then
		   linkExists = true
		   exit for
		end if
	next
	if not linkExists then
		'tell the user what we are doing
		Repository.WriteOutput "Link to Domain Model", "Adding trace between " &amp;sourceElement.Name &amp; " and " &amp; targetElement.Name, 0
		dim trace as EA.Connector
		set trace = sourceElement.Connectors.AddNew("","trace")
		trace.Alias = "automatic"
		trace.SupplierID = targetElement.ElementID
		trace.Update
	end if
end function

function addToClassDictionary(PackageGUID, dictionary)
	dim package as EA.Package
	set package = Repository.GetPackageByGuid(PackageGUID)
	
	'get the classes in the dictionary (recursively
	addClassesToDictionary package, dictionary
end function

function addClassesToDictionary(package, dictionary)
	dim classElement as EA.Element
	dim subpackage as EA.Package
	'process owned elements
	for each classElement in package.Elements
		if classElement.Type = "Class" AND len(classElement.Name) &gt; 0 AND not dictionary.Exists(classElement.Name) then
			dictionary.Add classElement.Name,  classElement
		end if
	next
	'process subpackages
	for each subpackage in package.Packages
		addClassesToDictionary subpackage, dictionary
	next
end function


'Create a reges pattern like this "\b(name1|name2|name3)\b" based on the 
function createRegexPattern(dictionary)
	Dim patternString
	dim className
	'add begin
	patternString = "\b("
	dim addPipe
	addPipe = FALSE
	for each className in dictionary.Keys
			if addPipe then
				patternString = patternString &amp; "|"
			else
				addPipe = True
			end if
			patternString = patternString &amp; className
	next
	'add end
	patternString = patternString &amp; ")s?\b"
	'return pattern
	createRegexPattern = patternString
end function

'returns an ArrayList of the given package and all its subpackages recursively
function getPackageTree(package)
	dim packageList
	set packageList = CreateObject("System.Collections.ArrayList")
	addPackagesToList package, packageList
	set getPackageTree = packageList
end function

'make an id string out of the package ID of the given packages
function makePackageIDString(packages)
	dim package as EA.Package
	dim idString
	idString = ""
	dim addComma 
	addComma = false
	for each package in packages
		if addComma then
			idString = idString &amp; ","
		else
			addComma = true
		end if
		idString = idString &amp; package.PackageID
	next 
	'if there are no packages then we return "0"
	if packages.Count = 0 then
		idString = "0"
	end if
	'return idString
	makePackageIDString = idString
end function

'returns an ArrayList with the elements accordin tot he ObjectID's in the given query
function getElementsFromQuery(sqlQuery)
	dim elements 
	set elements = Repository.GetElementSet(sqlQuery,2)
	dim result
	set result = CreateObject("System.Collections.ArrayList")
	dim element
	for each element in elements
		result.Add Element
	next
	set getElementsFromQuery = result
end function

'returns a dictionary of all elements in the query with their name as key, and the element as value.
'for elements with the same name only one will be returned
function getElementDictionaryFromQuery(sqlQuery)
	dim elements 
	set elements = Repository.GetElementSet(sqlQuery,2)
	dim result
	set result = CreateObject("Scripting.Dictionary")
	dim element
	for each element in elements
		if not result.Exists(element.Name) then
		result.Add element.Name, element
		end if
	next
	set getElementDictionaryFromQuery = result
end function

'gets the content of the linked document in the given format (TXT, RTF or EA)
function getLinkedDocumentContent(element, format)
	dim linkedDocumentRTF
	dim linkedDocumentEA
	dim linkedDocumentPlainText
	linkedDocumentRTF = element.GetLinkedDocument()
	if format = "RTF" then
		getLinkedDocumentContent = linkedDocumentRTF
	else
		linkedDocumentEA = Repository.GetFieldFromFormat("RTF",linkedDocumentRTF)
		if format = "EA" then
			getLinkedDocumentContent = linkedDocumentEA
		else
			linkedDocumentPlainText = Repository.GetFormatFromField("TXT",linkedDocumentEA)
			getLinkedDocumentContent = linkedDocumentPlainText
		end if
	end if
end function

'let the user select a package
function selectPackage()
	dim documentPackageElementID 		
	documentPackageElementID = Repository.InvokeConstructPicker("IncludedTypes=Package") 
	if documentPackageElementID &gt; 0 then
		dim packageElement as EA.Element
		set packageElement = Repository.GetElementByID(documentPackageElementID)
		dim package as EA.Package
		set package = Repository.GetPackageByGuid(packageElement.ElementGUID)
	else
		set package = nothing
	end if 
	set selectPackage = package
end function

'add the given package and all subPackges to the list (recursively
function addPackagesToList(package, packageList)
	dim subPackage as EA.Package
	'add the package itself
	packageList.Add package
	'add subpackages
	for each subPackage in package.Packages
		addPackagesToList subPackage, packageList
	next
end function

Set all line styles in an Enterprise Architect diagram automatically

With this script you can change set all the lines styles on a diagram at once, to your preferred style per type of connector.

In Enterprise Architect you can choose from no less then 9 different line styles for the connectors.

Line Styles

Unfortunately you can only choose from the first three to be the default line style for new connectors. Additionally you can also specify the default for Generalization to be Tree Style, but that is it.

For me that is not enough. I have my own habits when making UML diagrams. For most connector types I use Orthogonal – Square, but not for dependencies, use case relations and note links. For those I like the Direct style. The last exception are the control flow, state flow and object flows, for which I use Orthogonal – Rounded.

LineStylesDiagram

With this script I can set all the line styles on a diagram to the styles that I like.  All I need to do is right click on a diagram and choose Scripts|Set Line Styles

Set Linestyles menu option

And the script will set all the line styles to the defaults set in the script. If you would like the line styles to be set to your preferences automatically without having to run the script you can use EA-Matic version of this script.

You can set your own preferences in this section of the script

'*********EDIT BETWEEN HERE*************
' set here the menu name
menuDefaultLines = "&Set default linestyles"

' set here the default style to be used
defaultStyle = lsOrthogonalSquareTree

' set there the style to be used for each type of connector
function determineStyle(connector)
    dim connectorType
    connectorType = connector.Type
    select case connectorType
        case "ControlFlow", "StateFlow","ObjectFlow","InformationFlow"
            determineStyle = lsOrthogonalRoundedTree
        case "Generalization", "Realization", "Realisation"
            determineStyle = lsTreeVerticalTree
        case "UseCase", "Dependency","NoteLink"
            determineStyle = lsDirectMode
        case else
            determineStyle = defaultStyle
    end select
end function
'************AND HERE****************

Free download

[ecwid_product store_id=”6824265″ product_id=”56907450″]

Video

This script has been previously featured in the webinar Introduction to Scripting with Enterprise Architect presented by yours truly.

The script

option explicit

!INC Local Scripts.EAConstants-VBScript

' Script Name: DefaultLineStyles
' Author: Geert Bellekens
' Purpose: Allows to change the linestyles to their default
' Date: 27/04/2015
'
dim lsDirectMode, lsAutoRouteMode, lsCustomMode, lsTreeVerticalTree, lsTreeHorizontalTree, _
lsLateralHorizontalTree, lsLateralVerticalTree, lsOrthogonalSquareTree, lsOrthogonalRoundedTree

lsDirectMode = "1"
lsAutoRouteMode = "2" 
lsCustomMode = "3"
lsTreeVerticalTree = "V"
lsTreeHorizontalTree = "H"
lsLateralHorizontalTree = "LH"
lsLateralVerticalTree = "LC"
lsOrthogonalSquareTree = "OS"
lsOrthogonalRoundedTree = "OR"

dim defaultStyle
dim menuDefaultLines


'*********EDIT BETWEEN HERE*************


' set here the default style to be used
defaultStyle = lsOrthogonalSquareTree

' set there the style to be used for each type of connector
function determineStyle(connector)
	dim connectorType
	connectorType = connector.Type
	select case connectorType
		case "StateFlow","ObjectFlow","InformationFlow"
			determineStyle = lsOrthogonalRoundedTree
		case "Generalization", "Realization", "Realisation"
			determineStyle = lsTreeVerticalTree
		case "UseCase", "Dependency","NoteLink"
			determineStyle = lsDirectMode
		case else
			determineStyle = defaultStyle
	end select
end function
'************AND HERE****************


sub main
		dim diagram 
		dim diagramLink
		dim connector
		dim dirty
		dirty = false
		set diagram = Repository.GetCurrentDiagram
		'save the diagram first
		Repository.SaveDiagram diagram.DiagramID
		'then loop all diagramLinks
		if not diagram is nothing then
			for each diagramLink in diagram.DiagramLinks
				set connector = Repository.GetConnectorByID(diagramLink.ConnectorID)
				if not connector is nothing then
					'set the connectorstyle
					setConnectorStyle diagramLink, determineStyle(connector)
					'save the diagramlink
					diagramLink.Update
					dirty = true
				end if
			next
			'reload the diagram if we changed something
			if dirty then
				'reload the diagram to show the link style
				Repository.ReloadDiagram diagram.DiagramID
			end if
		end if
end sub

main


'gets the diagram link object
function getdiagramLinkForConnector(connector, diagram)
	dim diagramLink 
	set getdiagramLinkForConnector = nothing
	for each diagramLink in diagram.DiagramLinks
		if diagramLink.ConnectorID = connector.ConnectorID then
			set getdiagramLinkForConnector = diagramLink
			exit for
		end if
	next
end function

'actually sets the connector style
function setConnectorStyle(diagramLink, connectorStyle)
	'split the style into its parts
	dim styleparts
	dim styleString
	styleString = diagramLink.Style
	styleparts = Split(styleString,";")
	dim stylePart
	dim mode
	dim modeIndex
	modeIndex = -1
	dim tree
	dim treeIndex
	treeIndex = -1
	mode = ""
	tree = ""
	dim i
	'find if Mode and Tree are already defined
	for i = 0 to Ubound(styleparts) -1 
		stylePart = styleparts(i)
		if Instr(stylepart,"Mode=") > 0 then
			modeIndex = i
		elseif Instr(stylepart,"TREE=") > 0 then
			treeIndex = i
		end if
	next
	'these connectorstyles use mode=3 and the tree
	if  connectorStyle = lsTreeVerticalTree or _
		connectorStyle = lsTreeHorizontalTree or _
		connectorStyle = lsLateralHorizontalTree or _
		connectorStyle = lsLateralVerticalTree or _
		connectorStyle = lsOrthogonalSquareTree or _
		connectorStyle = lsOrthogonalRoundedTree then
		mode = "3"
		tree = connectorStyle
	else
		mode = connectorStyle
	end if
	'set the mode value
	if modeIndex >= 0 then
		styleparts(modeIndex) = "Mode=" & mode
		diagramLink.Style = join(styleparts,";")
	else
		diagramLink.Style = "Mode=" & mode& ";"& diagramLink.Style
	end if
	'set the tree value
	if treeIndex >= 0 then
		if len(tree) > 0 then
			styleparts(treeIndex) = "TREE=" & tree
			diagramLink.Style = join(styleparts,";")
		else
			'remove tree part
			diagramLink.Style = replace(diagramLink.Style,styleparts(treeIndex)&";" , "")
		end if
	else
		diagramLink.Style = diagramLink.Style & "TREE=" & tree & ";"
	end if
end function

function getConnectorStyle(diagramLink)
	'split the style
	dim styleparts
	styleparts = Split(diagramLink.Style,";")
	dim stylePart
	dim mode
	dim tree
	mode = ""
	tree = ""
	for each stylepart in styleparts
		if Instr(stylepart,"Mode=") > 0 then
			mode = right(stylepart, 1)
		elseif Instr(stylepart,"TREE=") > 0 then
			tree = replace(stylepart, "TREE=", "")
		end if
	next
	if tree <> "" then
		getConnectorStyle = tree
	else
		getConnectorStyle = mode
	end if
end function

Tutorial: Generate complex documents from Enterprise Architect with a two-step semi-automated approach

  • Step 1: Create the virtual document with a script
  • Step 2: Generate the document from the virtual document

Document generation is important in most organisations that work with Enterprise Architect. Storing information in the model is good, but most stakeholders still require documents.

Documents are using as a tool for validation, as a work order, or just to give you an overview of a specific set of thing in the model. The main advantages of a document versus a model is that it has a limited scope and a linear order. You can read a document from start to finish, whereas you cannot do that with a model. Documents are, and will always be an important tool in developing applications.

Enterprise Architect comes with a sophisticated document generator that has seen great improvements in the last versions (v10, v11, v12). You also get a standard set of document templates that will quickly generate a document for a part of your model.

The problems arise however the moment you try to do something a bit more sophisticated with the document templates. Get information from different packages, use different templates for the same (set of) elements, etc… You will quickly realize that the approach of having one large template that generates the whole document is not useful for real-world document requirements.

Luckily EA has an alternative to the single template approach: Virtual Documents

Free Download

I’ve put together an example model and an MDG to demonstrate this approach. The MDG contains all scripts, templates and SQL Searches to generate a decent Use Case Document.

[ecwid_product store_id=”6824265″ product_id=”56803587″]

 

After you open the Hotellio.eap file, right click on the Reservations use case diagram and choose Create Use Case Document

Create Use Case Document

Then select the package to store the virtual document in and fill in the version.

Finally select the «master document» package and press F8 so start the document generation. Make sure to select the correct cover page and style sheet

Generate Document

Virtual Documents

Virtual Documents are used to define the contents of a document as a set of individual sections, each with its own source and template.

Virtual Documents structure

There are two types of elements that are used for virtual documents

  • Master Document
    Represents the document to be printed. You can give it some parameters that can be used in the document itself such as title, Version, Status, etc…
  • Model Document
    Represents a section in your document. The model document gets an RTFTemplate, and you have to define a source for this section. The source tell us which element(s) from the model should be used in this section. There are two ways to define the source. You can either drag one or more packages onto it, or you can fill in the Model Search and Search Term.

Master Documents get stored in the model as stereotyped packages and they contain stereotypes classes to represent the Model Documents

Virtual Document example

In the project browser this looks like thisVirtual Document project browser.

The Master Documents get the stereotype «master document» They contain tagged values to store things like ReportAlias, ReportTitle. Event though you can assign an RTF template to the master document this seems to be pointless since it gets ignored when printing a document.

Model Documents get the stereotype «model document». The links to the packages that serve as the source for a model document are created by dragging the package from the project browser onto the model document. This creates an attribute that maintains the link to the package.
Tagged values are used to indicate the template to use, the model search to use, and the search value to use on that search.

This mechanism of model documents allows you to very precisely control what will be printed in the document.

Templates

RTF templates are stored in the Resources section of your model. In order to see and manage them enable the resources view using View|Resources

template sections

Templates for document that have a package as source can start from the package tag and access all element, diagram etc.. in that package.

If your source is an SQL search that returns elements your template content can be placed in the element section

SQL Search

The SQL search I use to select a single element based on its GUID this following:

select c.ea_guid AS CLASSGUID,c.object_type AS CLASSTYPE,c.name AS Name, c.stereotype AS Stereotype,
package.name AS PackageName ,package_p1.name AS PackageLevel1,package_p2.name AS PackageLevel2,package_p3.name AS PackageLevel3 
from ((((t_object c 
inner join t_package  package on c.package_id = package.package_id) 
left join t_package  package_p1 on package_p1.package_id = package.parent_id) 
left join t_package  package_p2 on package_p2.package_id = package_p1.parent_id) 
left join t_package  package_p3 on package_p3.package_id = package_p2.parent_id) 
where 
c.ea_guid like '<Search Term>'

Printing

When you select the Master Document you can open up the document generation dialog by pressing F8

Generate Document

Here you can set the cover page, table of contents, and the style sheet to use.

Side note: Invest in the style sheet. It will save you time and frustration afterwards. It is difficult, and awkward to set styles using the RTF editor, but if you do it once in the stylesheet you don’t have to to it over and over again in each template. Also make sure to create your own “normal” style and call it “my normal” or something like that. Use this instead of the regular “normal”. There seems to be some weird behavior associated by the “normal” style, so I learned it’s best not to use it at all.

Then press generate, and the document generator will create one big document containing

  • The cover page
  • The table of contents page
  • All model documents in the order they appear in the project browser.

If you are not happy with the content of the document, or the order of the model documents then you can change them in EA and re-print the document.

One of the advantages is that the content of the document is stored in EA. So after a revision of the model you can print exactly the same content again and compare it with the previous version.

 Automatically generate virtual documents

Using virtual documents like that is all nice and good, but it still requires quite a lot of work to assemble the virtual document itself. For a somewhat realistic document you easily end up with tens of model documents, all pointing to different element or packages, and different template.
And you have to repeat that process for all the different documents you want to generate.

Doing the same thing over and over again is exactly what computers are build for. Instead of having to assemble the virtual document manually we will write a little script to do it for us.

The first thing well need to do is to make the Master Document package. This VBScript function does that for us.

function addMasterDocument(packageGUID, documentName,documentVersion,documentAlias)
	dim ownerPackage as EA.Package
	set ownerPackage = Repository.GetPackageByGuid(packageGUID)
	dim masterDocumentPackage as EA.Package
	set masterDocumentPackage = ownerPackage.Packages.AddNew(documentName, "package")
	masterDocumentPackage.Update
	masterDocumentPackage.Element.Stereotype = "master document"
	masterDocumentPackage.Alias = documentAlias
	masterDocumentPackage.Version = documentVersion
	masterDocumentPackage.Update
	'link to the master template
	dim templateTag as EA.TaggedValue
	for each templateTag in masterDocumentPackage.Element.TaggedValues
		if templateTag.Name = "RTFTemplate" then
			templateTag.Value = "(model document: master template)"
			templateTag.Notes = "Default: (model document: master template)"
			templateTag.Update
			exit for
		end if
	next
	'return
	set addMasterDocument= masterDocumentPackage
end function

Then we need to make the Model Documents.To make one with a Package as source we use this function

function addModelDocumentForPackage(masterDocument,package,name, treepos, template)
	dim modelDocElement as EA.Element
	set modelDocElement = masterDocument.Elements.AddNew(name, "Class")
	'set the position
	modelDocElement.TreePos = treepos
	modelDocElement.StereotypeEx = "model document"
	modelDocElement.Update
	'add tagged values
	dim templateTag as EA.TaggedValue
	for each templateTag in modelDocElement.TaggedValues
		if templateTag.Name = "RTFTemplate" then
			templateTag.Value = template
			templateTag.Notes = "Default: Model Report"
			templateTag.Update
			exit for
		end if
	next
	'add attribute
	dim attribute as EA.Attribute
	set attribute = modelDocElement.Attributes.AddNew(package.Name, "Package")
	attribute.ClassifierID = package.Element.ElementID
	attribute.Update
end function

To make one with an SQL Search as source we use this function

function addModelDocumentWithSearch(masterDocument, template,elementName, elementGUID, treepos, searchName)
	dim modelDocElement as EA.Element;
	set modelDocElement = masterDocument.Elements.AddNew(elementName, "Class")
	'set the position
	modelDocElement.TreePos = treepos
	modelDocElement.StereotypeEx = "model document"
	modelDocElement.Update
	dim templateTag as EA.TaggedValue
	if len(elementGUID) > 0 then
		for each templateTag in modelDocElement.TaggedValues
			if templateTag.Name = "RTFTemplate" then
				templateTag.Value = template
				templateTag.Notes = "Default: Model Report"
				templateTag.Update
			elseif templateTag.Name = "SearchName" then
				templateTag.Value = searchName
				templateTag.Update
			elseif templateTag.Name = "SearchValue" then
				templateTag.Value = elementGUID
				templateTag.Update
			end if
		next
	else
		'add tagged values
		for each templateTag in modelDocElement.TaggedValues
			if templateTag.Name = "RTFTemplate" then
				templateTag.Value = template
				templateTag.Notes = "Default: Model Report"
				templateTag.Update
				exit for
			end if
		next
		'no GUID provided. Set masterdocument package ID as dummy attribute to make the template work
		dim attribute as EA.Attribute
		set attribute = modelDocElement.Attributes.AddNew(masterDocument.Name, "Package")
		attribute.ClassifierID = masterDocument.Element.ElementID
		attribute.Update
	end if
end function

The the last script we is one to actually use these functions in order to create the virtual document. In the example provided I’ve split the script in two parts. One part that remains in the library, and one script that is put in the Diagram Group so we can call it from a diagram directly.

The script in the Diagram Group takes care of getting the user input

option explicit

!INC Bellekens DocGen.UseCaseDocument

' Script Name: Create Use Case Document
' Author: Geert Bellekens
' Purpose: Create the virtual document for a Use Case Document based on the open diagram
' 			Copy this script in a Diagram Group to call it from the diagram directly.
' Date: 11/11/2015
'

sub OnDiagramScript()
	dim documentsPackage as EA.Package
	'select the package to generate the virtual document in
	Msgbox "Please select the package to generate the virtual document in",vbOKOnly+vbQuestion,"Document Package"
	set documentsPackage = selectPackage()
	if not documentsPackage is nothing then
		' Get a reference to the current diagram
		dim currentDiagram as EA.Diagram
		set currentDiagram = Repository.GetCurrentDiagram()
		if not currentDiagram is nothing then
			createUseCaseDocument currentDiagram, documentsPackage.PackageGUID 
			Msgbox "Select the Master Document and press F8 to generate document",vbOKOnly+vbInformation,"Finished!"
		else
			Session.Prompt "This script requires a diagram to be visible", promptOK
		end if
	end if
end sub

OnDiagramScript

The script in the library will use the functions mentioned above to start building the virtual document


!INC Local Scripts.EAConstants-VBScript
!INC Bellekens DocGen.DocGenHelpers
!INC Bellekens DocGen.Util
'

' Script Name: UseCaseDocuemnt
' Author: Geert Bellekens
' Purpose: Create the virtual document for a Use Case Document based on the given diagram
' Date: 11/11/2015
'

dim useCaseDocumentsPackageGUID

function createUseCaseDocument( diagram, documentsPackageGUID)
	
	useCaseDocumentsPackageGUID = documentsPackageGUID
	'first create a master document
	dim masterDocument as EA.Package
	set masterDocument = makeUseCaseMasterDocument(diagram)
	if not masterDocument is nothing then
		dim i
		i = 0
		'use case diagram part 1
		addModelDocumentForDiagram masterDocument,diagram, i, "UCD_Use Case Diagram"
		i = i + 1
		'add Actors
		dim diagramPackage as EA.Package
		set diagramPackage = Repository.GetPackageByID(diagram.PackageID)
		addModelDocumentForPackage masterDocument, diagramPackage, diagram.Name & " Actors", i, "UCD_Actors"
		i = i + 1
		' We only want to report the use cases that are shown within the scope boundary on this diagram
		'get the boundary diagram object in the diagram
		dim boundaries
		set boundaries = getDiagramObjects(diagram,"Boundary")
		Session.Output boundaries.Count
		'get the use cases
		dim usecases		
		if boundaries.Count > 0 then
			set usecases = getElementsFromDiagramInBoundary(diagram, "UseCase",boundaries(0))
			Session.Output "boundary found"
		else
			set usecases = getElementsFromDiagram(diagram, "UseCase")
		end if
		
		'sort use cases alphabetically
		set usecases = sortElementsByName(usecases)
		
		'add the use cases
		i = addUseCases(masterDocument, usecases, i)
		
		Repository.RefreshModelView(masterDocument.PackageID)
		'select the created master document in the project browser
		Repository.ShowInProjectView(masterDocument)
	end if
end function


function makeUseCaseMasterDocument(currentDiagram)
	'we should ask the user for a version
	dim documentTitle
	dim documentVersion
	dim documentName
	dim diagramName
	set makeUseCaseMasterDocument = nothing
	diagramName = currentDiagram.Name
	'to make sure document version is filled in
	documentVersion = ""
	documentVersion = InputBox("Please enter the version of this document", "Document version", "x.y.z" )
	if documentVersion <> "" then
		'OK, we have a version, continue
		documentName = "UCD - " & diagramName & " v. " & documentVersion
		dim masterDocument as EA.Package
		set masterDocument = addMasterDocumentWithDetails(useCaseDocumentsPackageGUID, documentName,documentVersion,diagramName)
		set makeUseCaseMasterDocument = masterDocument
	end if
end function

'add the use cases to the document
function addUseCases(masterDocument, usecases, i)
	dim usecase as EA.Element
	for each usecase in usecases
		'use case part 1
		addModelDocument masterDocument, "UCD_Use Case details part1", "UC " & usecase.Name & " Part 1", usecase.ElementGUID, i
		i = i + 1
		
		'get the nested scenario diagram
		dim activity as EA.Element
		set activity = getActivityForUsecase(usecase)
		
		'add scenario diagram
		if not activity is nothing then
		addModelDocument masterDocument, "UCD_Use Case Scenarios Diagram", "UC " & usecase.Name & " Scenarios diagram", activity.ElementGUID, i
			i = i + 1
		end if
		
		'use case part 2
		addModelDocument masterDocument, "UCD_Use Case details part2","UC " &  usecase.Name & " Part 2", usecase.ElementGUID, i
		i = i + 1
		
	next
	'return the new i
	addUseCases = i
end function

function getActivityForUsecase(usecase)
	set getActivityForUsecase = getNestedDiagramOnwerForElement(usecase, "Activity")
end function

function getInteractionForUseCase(usecase)
	set getInteractionForUseCase = getNestedDiagramOnwerForElement(usecase, "Interaction")
end function

function getNestedDiagramOnwerForElement(element, elementType)
	dim diagramOnwer as EA.Element
	set diagramOnwer = nothing
	dim nestedElement as EA.Element
	for each nestedElement in element.Elements
		if nestedElement.Type = elementType and nestedElement.Diagrams.Count > 0 then
			set diagramOnwer = nestedElement
			exit for
		end if
	next
	set getNestedDiagramOnwerForElement = diagramOnwer
end function


'sort the elements in the given ArrayList of EA.Elements by their name 
function sortElementsByName (elements)
	dim i
	dim goAgain
	goAgain = false
	dim thisElement as EA.Element
	dim nextElement as EA.Element
	for i = 0 to elements.Count -2 step 1
		set thisElement = elements(i)
		set nextElement = elements(i +1)
		if  elementIsAfter(thisElement, nextElement) then
			elements.RemoveAt(i +1)
			elements.Insert i, nextElement
			goAgain = true
		end if
	next
	'if we had to swap an element then we go over the list again
	if goAgain then
		set elements = sortElementsByName (elements)
	end if
	'return the sorted list
	set sortElementsByName = elements
end function

'check if the name of the next element is bigger then the name of the first element
function elementIsAfter (thisElement, nextElement)
	dim compareResult
	compareResult = StrComp(thisElement.Name, nextElement.Name,1)
	if compareResult > 0 then
		elementIsAfter = True
	else
		elementIsAfter = False
	end if
end function

Configure your own default line styles in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

With this EA-Matic script you can configure your own default line-styles depending on the type of connector.

In Enterprise Architect you can choose from no less then 9 different line style for the connectors.

Line Styles

Unfortunately you can only choose from the first three to be the default line style for new connectors.

line style default options

Additionally you can also specify the default for Generalization to be Tree Style.

For me that is not enough. I have my own habits when making UML diagrams. For most connector types I use Orthogonal – Square, but not for dependencies, use case relations and note links. For those I like the Direct style. The last exception are the control flow, state flow and object flows, for which I use Orthogonal – Rounded.

LineStylesDiagram

This EA-Matic script gives me the control I want. Not only does it set the default line style for new connectors, with the context menu I can reset all connectors on a diagram to my own default styles.

Free download

[ecwid_product store_id=”6824265″ product_id=”48249914″]

The code

In order to define your own preferred style you can edit the indicated part in the script

'*********EDIT BETWEEN HERE*************
' set here the menu name
menuDefaultLines = "&Set default linestyles"

' set here the default style to be used
defaultStyle = lsOrthogonalSquareTree

' set there the style to be used for each type of connector
function determineStyle(connector)
    dim connectorType
    connectorType = connector.Type
    select case connectorType
        case "ControlFlow", "StateFlow","ObjectFlow","InformationFlow"
            determineStyle = lsOrthogonalRoundedTree
        case "Generalization", "Realization", "Realisation"
            determineStyle = lsTreeVerticalTree
        case "UseCase", "Dependency","NoteLink"
            determineStyle = lsDirectMode
        case else
            determineStyle = defaultStyle
    end select
end function
'************AND HERE****************

This script uses 3 EA events. The first one is the EA_OnPostNewConnector which gets triggered by EA the moment you create  a new connector.

'the event called by EA
function EA_OnPostNewConnector(Info)
    'get the connector id from the Info
    dim connectorID
    connectorID = Info.Get("ConnectorID")
    dim connector 
    set connector = Repository.GetConnectorByID(connectorID)
    'get the current diagram
    dim diagram
    set diagram = Repository.GetCurrentDiagram()
    if not diagram is nothing then
        'first save the diagram
        Repository.SaveDiagram diagram.DiagramID
        'get the diagramlink for the connector
        dim diagramLink
        set diagramLink = getdiagramLinkForConnector(connector, diagram)
        if not diagramLink is nothing then
            'set the connectorstyle
            setConnectorStyle diagramLink, determineStyle(connector)
            'save the diagramlink
            diagramLink.Update
            'reload the diagram to show the link style
            Repository.ReloadDiagram diagram.DiagramID
        end if
    end if
end function

The next even we use is the EA_GetMenuItems so we can show a menu option in the context menu of a diagram.

'Tell EA what the menu options should be
function EA_GetMenuItems(MenuLocation, MenuName)
    if MenuName = "" and MenuLocation = "Diagram" then
        'Menu Header
        EA_GetMenuItems = menuDefaultLines
    end if 
end function

The last EA event is the EA_MenuClick, triggered when the user actually clicks on the menu item.

'react to user clicking a menu option
function EA_MenuClick(MenuLocation, MenuName, ItemName)
    if ItemName = menuDefaultLines then
        dim diagram 
        dim diagramLink
        dim connector
        dim dirty
        dirty = false
        set diagram = Repository.GetCurrentDiagram
        'save the diagram first
        Repository.SaveDiagram diagram.DiagramID
        'then loop all diagramLinks
        if not diagram is nothing then
            for each diagramLink in diagram.DiagramLinks
                set connector = Repository.GetConnectorByID(diagramLink.ConnectorID)
                if not connector is nothing then
                    'set the connectorstyle
                    setConnectorStyle diagramLink, determineStyle(connector)
                    'save the diagramlink
                    diagramLink.Update
                    dirty = true
                end if
            next
            'reload the diagram if we changed something
            if dirty then
                'reload the diagram to show the link style
                Repository.ReloadDiagram diagram.DiagramID
            end if
        end if
    end if
end function

Then we have a small helper function to get the diagramLink object from the diagram

'gets the diagram link object
function getdiagramLinkForConnector(connector, diagram)
    dim diagramLink 
    set getdiagramLinkForConnector = nothing
    for each diagramLink in diagram.DiagramLinks
        if diagramLink.ConnectorID = connector.ConnectorID then
            set getdiagramLinkForConnector = diagramLink
            exit for
        end if
    next
end function

And last but not least the method that will actually set the connector style.

'actually sets the connector style
function setConnectorStyle(diagramLink, connectorStyle)
    'split the style into its parts
    dim styleparts
    dim styleString
    styleString = diagramLink.Style
    styleparts = Split(styleString,";")
    dim stylePart
    dim mode
    dim modeIndex
    modeIndex = -1
    dim tree
    dim treeIndex
    treeIndex = -1
    mode = ""
    tree = ""
    dim i
    'find if Mode and Tree are already defined
    for i = 0 to Ubound(styleparts) -1 
        stylePart = styleparts(i)
        if Instr(stylepart,"Mode=") > 0 then
            modeIndex = i
        elseif Instr(stylepart,"TREE=") > 0 then
            treeIndex = i
        end if
    next
    'these connectorstyles use mode=3 and the tree
    if  connectorStyle = lsTreeVerticalTree or _
        connectorStyle = lsTreeHorizontalTree or _
        connectorStyle = lsLateralHorizontalTree or _
        connectorStyle = lsLateralVerticalTree or _
        connectorStyle = lsOrthogonalSquareTree or _
        connectorStyle = lsOrthogonalRoundedTree then
        mode = "3"
        tree = connectorStyle
    else
        mode = connectorStyle
    end if
    'set the mode value
    if modeIndex >= 0 then
        styleparts(modeIndex) = "Mode=" & mode
        diagramLink.Style = join(styleparts,";")
    else
        diagramLink.Style = "Mode=" & mode& ";"& diagramLink.Style
    end if
    'set the tree value
    if treeIndex >= 0 then
        if len(tree) > 0 then
            styleparts(treeIndex) = "TREE=" & tree
            diagramLink.Style = join(styleparts,";")
        else
            'remove tree part
            diagramLink.Style = replace(diagramLink.Style,styleparts(treeIndex)&";" , "")
        end if
    else
        diagramLink.Style = diagramLink.Style & "TREE=" & tree & ";"
    end if
end function

Automatically synchronize overrides in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example EA-Matic script keeps the signature of the overriding operations in sync with the signature of the overridden operation.

This is especially useful when you are modelling an interface and a number of classes that realize this interface. EA has this neat feature Overrides and Implementations accessed by Ctrl-Shift-O to copy operations from an interface or superclass to the realizing/specializing class.

EA-Matic Overides and Implmentations

This works great when you have created a new operation, or added a new realizing class, but once you change the signature of your interface operation you are on your own. You need to track down all operations that once overrode your interface operation and change their signature accordingly. Not a pretty task.

EA-Matic Overides

This script ensures that the signature of the overrides is kept in sync with that of the overridden operation. Whenever you change the signature it alerts the user to the fact that there are overrides and asks if they need to be synchronized.

EA-Matic Synchronize

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

'gets a new instance of the EAAddinFramework and initializes it with the EA.Repository
function getEAAddingFrameworkModel()
    'Initialize the EAAddinFramework model
    dim model
    set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
    model.initialize(Repository)
    set getEAAddingFrameworkModel = model
end function

It is then further used to get the operation based on the GUID passed as parameter.

set operation = model.getOperationByGUID(GUID)

The model object also can be used to search elements based on an sql query

dim descendants
dim getdescendantsQuery
getdescendantsQuery = "select c.Start_Object_ID as Object_ID from (t_object o " _
				& "inner join t_connector c on c.End_Object_ID = o.Object_ID) " _
				& "where "_
				& "(c.[Connector_Type] like 'Generali_ation' "_
				& "or c.[Connector_Type] like 'Reali_ation' )"_
				& "and o.Object_ID = " & element.id
set descendants = model.toArrayList(model.getElementWrappersByQuery(getdescendantsQuery))

Note that we need to use model.toArrayList to convert the C# List<> that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Sychronize overrides. This script requires EA-Matic version 1.0.12.2 or higher to function.

There are two events that we use. EA_OnNotifyContextChanged to react to remember the overrides of the operation before it is changed, and EA_OnContextItemModified to update the overrides one the operation has been changed.

'Event Called when a new element is selected in the context. We use this operation to keep the id of the selected operation and a list of its overrides
'Because now is the only moment we are able to find it's overrides. Once changed we cannot find the overrides anymore because then they already
'have a different signature
function EA_OnContextItemChanged(GUID, ot)
	'we only want to do something when the selected element is an operation
	if ot = otMethod then
		'get the model
		dim model
		set model = getEAAddingFrameworkModel()
		'get the operation
		dim operation
		set operation = model.getOperationByGUID(GUID)
		'remember the operationID
		operationID = operation.id
		'remember the overrides
		set overrides = getOverrides(operation, model)
		Repository.WriteOutput "EA-Matic", overrides.Count & " overrides found for: " & operation.name,0
	end if
end function

'Event called when an element is changed. Unfortunately EA doesn't call it for an operation, only for the owner so we have to work with that.
function EA_OnNotifyContextItemModified(GUID, ot)
	'we only want to do something when the selected element is an operation
	if ot = otElement then
		'get the operation
		'Here we use the EA API object directly as most set methods are not implemented in EA Addin Framework
		dim wrappedOperation
		set wrappedOperation = Repository.GetMethodByID(operationID)
		dim modifiedElement
		set modifiedElement = Repository.GetElementByGuid(GUID)
		if not wrappedOperation is Nothing and not modifiedElement is Nothing then
			'check to be sure we have the same operation
			if modifiedElement.ElementID = wrappedOperation.ParentID AND overrides.Count > 0 then
				dim synchronizeYes
				synchronizeYes = MsgBox("Found " & overrides.Count & " override(s) for operation "& modifiedElement.Name & "." & wrappedOperation.Name & vbNewLine & "Synchronize?" _
										,vbYesNo or vbQuestion or vbDefaultButton1, "Synchronize overrides?")
				if synchronizeYes = vbYes then
					synchronizeOverrides wrappedOperation
					'log to output

					Repository.WriteOutput "EA-Matic", "Operation: " & wrappedOperation.name &" synchronized" ,0
				end if
				'reset operationID to avoid doing it all again
				operationID = 0
			end if
		end if
	end if
end function

In OnContextItemChanged we get the overrides by first selecting all operations with the same signature from the model with a query. Then we get all descendants of the owner of the operation (recursively) and filter the operation to only those owned by a descendant.

'gets the overrides of the given operation by first getting all operations with the same signature and then checking if they are owned by a descendant
function getOverrides(operation, model)
	'first get all operations with the exact same signature
	dim overrideQuery
	overrideQuery = "select distinct op2.OperationID from (((t_operation op " & _
					"inner join t_operation op2 on op2.[Name] = op.name) "& _
					"left join t_operationparams opp on op.OperationID = opp.OperationID) "& _
					"left join t_operationparams opp2 on opp2.OperationID = op2.OperationID) "& _
					"where op.OperationID = "& operation.id &" "& _
					"and op2.ea_guid <> op.ea_guid "& _
					"and (op2.TYPE = op.Type OR (op2.TYPE is null AND op.Type is null)) "& _
					"and (op2.Classifier = op.Classifier OR (op2.Classifier is null AND op.Classifier is null)) "& _
					"and (opp.Name = opp2.Name OR (opp.Name is null AND opp2.Name is null)) "& _
					"and (opp.TYPE = opp2.TYPE OR (opp.TYPE is null AND opp2.Type is null)) "& _
					"and (opp.DEFAULT = opp2.DEFAULT OR (opp.DEFAULT is null AND opp2.DEFAULT is null)) "& _
					"and (opp.Kind = opp2.Kind OR (opp.Kind is null AND opp2.Kind is null)) "& _
					"and (opp.Classifier = opp2.Classifier OR (opp.Classifier is null AND opp2.Classifier is null)) "
	dim candidateOverrides
	set candidateOverrides = model.ToArrayList(model.getOperationsByQuery(overrideQuery))
	'then get the descendants of the owner
	dim descendants
	dim descendant
	'first find all elements that either inherit from the owner or realize it
	dim owner
	set owner = model.toObject(operation.owner)
	set descendants = getDescendants(owner, model)
	'then filter the candidates to only those of the descendants
	'loop operations backwards
	dim i
	for i = candidateOverrides.Count -1 to 0 step -1
		dim found
		found = false
		for each descendant in descendants
			if descendant.id = model.toObject(candidateOverrides(i).owner).id then
				'owner is a descendant, operation can stay
				found = true
				exit for
			end if
		next
		'remove operation from non descendants
		if not found then
			candidateOverrides.RemoveAt(i)
		end if
	next
	set getOverrides = candidateOverrides
end function

'gets all descendant of an element. That is all subclasses and classes that Realize the element.
'Works recursively to get them all.
function getDescendants(element, model)
	dim descendants
	dim getdescendantsQuery
	getdescendantsQuery = "select c.Start_Object_ID as Object_ID from (t_object o " _
					& "inner join t_connector c on c.End_Object_ID = o.Object_ID) " _
					& "where "_
					& "(c.[Connector_Type] like 'Generali_ation' "_
					& "or c.[Connector_Type] like 'Reali_ation' )"_
					& "and o.Object_ID = " & element.id
	set descendants = model.toArrayList(model.getElementWrappersByQuery(getdescendantsQuery))
	'get the descendants descendants as well
	dim descendant
	dim descendantsChildren
	for each descendant in descendants
		if IsEmpty(descendantsChildren) then
			set descendantsChildren = getDescendants(descendant, model)
		else
			descendantsChildren.AddRange(getDescendants(descendant, model))
		end if
	next
	'add the descendantsChildren to the descendants
	if not IsEmpty(descendantsChildren) then
		if  descendantsChildren.Count > 0 then
			descendants.AddRange(descendantsChildren)
		end if
	end if
	set getDescendants = descendants
end function

Then when the signature of the operation has been changed we synchronize its signature with that of the with the overrides and we tell EA that the owner of the override has been changed.

'Synchronizes the operation with it's overrides
function synchronizeOverrides(wrappedOperation)
	dim override
	for each override in overrides
		dim wrappedOverride
		set wrappedOverride = override.WrappedOperation
		'synchronize the operation with the override
		synchronizeOperation wrappedOperation, wrappedOverride
		'tell EA something might have changed
		Repository.AdviseElementChange wrappedOverride.ParentID
	next
end function

'Synchronizes the operation with the given override
function synchronizeOperation(wrappedOperation, wrappedOverride)
	dim update
	update = false
	'check name
	if wrappedOverride.Name <> wrappedOperation.Name then
		wrappedOverride.Name = wrappedOperation.Name
		update = true
	end if
	'check return type
	if wrappedOverride.ReturnType <> wrappedOperation.ReturnType then
		wrappedOverride.ReturnType = wrappedOperation.ReturnType
		update = true
	end if
	'check return classifier
	if wrappedOverride.ReturnType <> wrappedOperation.ReturnType then
		wrappedOverride.ReturnType = wrappedOperation.ReturnType
		update = true
	end if
	if update then
		wrappedOverride.Update
	end if
	'check parameters
	synchronizeParameters wrappedOperation, wrappedOverride
end function

'Synchronizes the parameters of the given operatin with that of the overrride
function synchronizeParameters(wrappedOperation, wrappedOverride)
	'first make sure they both have the same number of parameters
	if wrappedOverride.Parameters.Count < wrappedOperation.Parameters.Count then
		'add parameters as required
		dim i
		for i = 0 to wrappedOperation.Parameters.Count - wrappedOverride.Parameters.Count -1
			dim newParameter
			set newParameter = wrappedOverride.Parameters.AddNew("parameter" & i,"")
			newParameter.Update
		next
		wrappedOverride.Parameters.Refresh
	elseif wrappedOverride.Parameters.Count > wrappedOperation.Parameters.Count then
		'remove parameters as required
		for i = wrappedOverride.Parameters.Count -1 to wrappedOperation.Parameters.Count step -1
			wrappedOverride.Parameters.DeleteAt i,false
		next
		wrappedOverride.Parameters.Refresh
	end if
	'make parameters equal
	dim wrappedParameter
	dim overriddenParameter
	dim j
	for j = 0 to wrappedOperation.Parameters.Count -1
		dim parameterUpdated
		parameterUpdated = false
		set wrappedParameter = wrappedOperation.Parameters.GetAt(j)
		set overriddenParameter = wrappedOverride.Parameters.GetAt(j)
		'name
		if overriddenParameter.Name <> wrappedParameter.Name then
			overriddenParameter.Name = wrappedParameter.Name
			parameterUpdated = true
		end if
		'type
		if overriddenParameter.Type <> wrappedParameter.Type then
			overriddenParameter.Type = wrappedParameter.Type
			parameterUpdated = true
		end if
		'default
		if overriddenParameter.Default <> wrappedParameter.Default then
			overriddenParameter.Default = wrappedParameter.Default
			parameterUpdated = true
		end if
		'kind
		if overriddenParameter.Kind <> wrappedParameter.Kind then
			overriddenParameter.Kind = wrappedParameter.Kind
			parameterUpdated = true
		end if
		'classifier
		if overriddenParameter.ClassifierID <> wrappedParameter.ClassifierID then
			overriddenParameter.ClassifierID = wrappedParameter.ClassifierID
			parameterUpdated = true
		end if
		'update the parameter if it was changed
		if parameterUpdated then
			overriddenParameter.Update
		end if
	next
end function

Synchronize object and classifier name in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example EA-Matic script keeps the names of objects synchronized with the name of their classifier.

When modelling with objects the name of the object is often left empty because it doesn’t have any significance at that point. One of the downsides of this practice is that in things like the traceability view, or in the links view the object shows up without a name, which of course doesn’t help much.

EA-Matic synchronize classifier

One of the solutions for this problem is to give the object the same name as its classifier. But that is a manual tedious process, and in case the classifier changes name you have the change the name of all the instances.

EA-Matic synchronize classifier_after

The script ensures that the name of the instances is always the same as the name of the classifier.

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

'gets a new instance of the EAAddinFramework and initializes it with the EA.Repository
function getEAAddingFrameworkModel()
    'Initialize the EAAddinFramework model
    dim model
    set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
    model.initialize(Repository)
    set getEAAddingFrameworkModel = model
end function

It is then further used to get the element based on the GUID passed as parameter

set element = model.getElementWrapperByGUID(GUID)

The model element can be used to search elements based on an sql query

 query = "select o.Object_ID from t_object o where o.classifier =" & element.id
 dim objects
 set objects = model.toArrayList(model.getElementWrappersByQuery(query))

Note that we need to use model.toArrayList to convert the C# List<> that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Sychronize object names

There are two events that we use. EA_OnNotifyContextItemModified to react to a name change of either the object or the classifier and EA_OnPostNewElement to change the name of a new object immediately after it has been created.

function EA_OnNotifyContextItemModified(GUID, ot)
    dim model
    set model = getEAAddingFrameworkModel()
    'only do something when the changed object is an element
    if ot = otElement then
        dim element
        set element = model.getElementWrapperByGUID(GUID)
        synchronizeObjectNames element, model
    end if
end function

function EA_OnPostNewElement(Info)
    'Get the model
    dim model
    set model = getEAAddingFrameworkModel()
    'get the elementID from Info
    dim elementID
    elementID = Info.Get("ElementID")
    'get the element being deleted
    dim element
    set element = model.getElementWrapperByID(elementID)
    synchronizeObjectNames element, model
end function

These two events then use the function synchronizeObjectNames to make sure the name of the object is the same as the name of the classifier

function synchronizeObjectNames(element, model)
    'first check if this is an object
    if element.WrappedElement.Type = "Object" AND element.WrappedElement.ClassifierID > 0 then
        dim classifier
        set classifier = model.getElementWrapperByID(element.WrappedElement.ClassifierID)
        if not classifier is nothing AND classifier.name <> element.name then
            element.name = classifier.name
            element.save
        end if
    else
        'get all objects having this element as their classifier
        dim query
        query = "select o.Object_ID from t_object o where o.classifier =" & element.id
        dim objects
        set objects = model.toArrayList(model.getElementWrappersByQuery(query))
        'loop objects
        dim obj
        for each obj in objects
            'rename the object if the name is different from the classifiers name
            if obj.name <> element.name then
                obj.name = element.name
                obj.save
            end if
        next
    end if
end function

Self-maintaining diagrams in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example shows how to make diagrams that maintain themselves to make sure they are always up-to-date.

The idea is that we want a diagram that shows all the relations of the owning object. The diagram should always be up-to-date without manual intervention.

EA-Matic auto_diagram

To indicate that this diagram is an automatic diagram we nest it under the class and prefix it with AUTO_ and on the diagram we place the owning object and its related elements

EA-Matic self-maintaining diagram

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

'gets a new instance of the EAAddinFramework and initializes it with the EA.Repository
function getEAAddingFrameworkModel()
    'Initialize the EAAddinFramework model
    dim model
    set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
    model.initialize(Repository)
    set getEAAddingFrameworkModel = model
end function

It is then further used to get the ConnectorWrapper based on the ElementID from the Info object

set connector = model.getRelationByID(connectorID)

This connector is then used to access the elements that are connected by this Connector

 'get the related elements
 dim relatedElements
 set relatedElements = model.toArrayList(connector.relatedElements)

Note that we need to use model.toArrayList to convert the C# generic List<> that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Self-maintaining diagram

In the first part we use the magic EA-Matic keyword to in order to tell EA-Matic it should use this script.

We also declare the important ID’s of the selected connector so we can compare that with the ID’s of this connector once it has changed.

The first event we use is the EA_OnPostNewConnector. This event will be fired after you create a new connector in EA.
In this event we add the newly related elements to each others auto-diagram, if any.

!INC Local Scripts.EAConstants-VBScript

' EA-Matic
' This script, when used with EA-Matic will maintain auto-updating diagrams for elements.
' A nested diagram with prefix AUTO_ will be considered an auto-updating diagram.
' The diagram will keep track of all elements related to the owner of the auto diagram.
'
' Author:     Geert Bellekens
' EA-Matic: http://bellekens.com/ea-matic/
'
'maintain a reference to the connector in context
dim contextConnectorID
dim oldClientID
dim oldSupplierID

'a new connector has been created. Add the related elements to the auto-diagram
function EA_OnPostNewConnector(Info)
     'get the connector id from the Info
     dim connectorID
     connectorID = Info.Get("ConnectorID")
     dim model
     'get the model
     set model = getEAAddingFrameworkModel()
     dim connector
     set connector = model.getRelationByID(connectorID)
     'get the related elements
     dim relatedElements
     set relatedElements = model.toArrayList(connector.relatedElements)
     'for i = 0 to attributes.Count - 1
     if relatedElements.Count = 2 then
        'once with the first
        addRelatedElementoAutoDiagram relatedElements(0), relatedElements(1), model
        'then with the second
        addRelatedElementoAutoDiagram relatedElements(1), relatedElements(0), model
     end if
end function

'adds the related element to the auto_updatediagrams if any
function addRelatedElementoAutoDiagram(element,relatedElement, model)
    'get the diagram owned by this element
    dim ownedDiagrams
    set ownedDiagrams = model.toArrayList(element.ownedDiagrams)
    for each diagram In ownedDiagrams
        'check the name of the diagram
        if Left(diagram.name,LEN("AUTO_")) = "AUTO_" then
            'add the related element to the diagram
            diagram.addToDiagram(relatedElement)
        end if
    next
end function

The next part deals with the deletion of a connector. Each time a connector is about to be deleted EA fires the EA_OnPreDeleteConnector event.
We remove the connected elements from their respective auto-diagrams, but only if this connector was the last connector between the two elements.

' A connector will be deleted. Remove the elements from the auto-diagram
function EA_OnPreDeleteConnector(Info)
     'get the connector id from the Info
     dim connectorID
     connectorID = Info.Get("ConnectorID")
     dim model
     'get the model
     set model = getEAAddingFrameworkModel()
     dim connector
     set connector = model.getRelationByID(connectorID)
     'get the related elements
     dim relatedElements
     set relatedElements = model.toArrayList(connector.relatedElements)
     'for i = 0 to attributes.Count - 1
     if relatedElements.Count = 2 then
        'we only need to remove the related element if they are not connected anymore after deleting the connector
        'so only if there is only one relationship between the two elements
        if sharedRelationsCount(relatedElements(0), relatedElements(1), model) <= 1 then
            'once with the first
            removeRelatedElemenFromAutoDiagram relatedElements(0), relatedElements(1), model
            'then with the second
            removeRelatedElemenFromAutoDiagram relatedElements(1), relatedElements(0), model
        end if
     end if
end function

'returns the number of relations that connecto both elements
function sharedRelationsCount(elementA, elementB, model)
    'start counting at zero
    sharedRelationsCount = 0
    'get the relationships for both objects
    dim relationsA
    set relationsA = model.toArrayList(elementA.relationships)
    dim relationsB
    set relationsB = model.toArrayList(elementB.relationships)
    for each relationA in relationsA
        for each relationB in relationsB
            'if both relations have the same ID then we have a shared relation
            if relationA.id = relationB.id then
                sharedRelationsCount = sharedRelationsCount +1
            end if
        next
    next
end function

' Removes the related element from the auto update diagram if any.
function removeRelatedElemenFromAutoDiagram(element,relatedElement, model)
    'get the diagram owned by this element
    dim ownedDiagrams
    set ownedDiagrams = model.toArrayList(element.ownedDiagrams)
    for each diagram In ownedDiagrams
        dim diagram
        set diagram = ownedDiagrams(i)
        'check the name of the diagram
        if Left(diagram.name,LEN("AUTO_")) = "AUTO_" then
            'Removing elements from a diagram in unfortunately not implemented in the EAAddinFramework so we'll have to do it in the script
            dim eaDiagram
            set eaDiagram = diagram.wrappedDiagram
            for i = 0 to eaDiagram.DiagramObjects.Count -1
                dim diagramObject
                set diagramObject = eaDiagram.DiagramObjects.GetAt(i)
                if diagramObject.ElementID = relatedElement.id then
                    'remove the diagramObject
                    eaDiagram.DiagramObjects.Delete(i)
                    'refresh the diagram after we changed it
                    diagram.reFresh()
                    'exit the loop we have delete the diagramobject
                    exit for
                end if
            next
        end if
    next
end function

The next part deals with the diagram being opened, or shown. Since we are adding elements automatically, we want to use EA’s automatic layout to make the diagram presentable. The downside of this however is that EA also opens the diagram when doing a layout. So instead of doing an auto-layout directly after editing the diagram, we do one as soon as the user opens the diagram, or switches to an already opened diagram. We use the events EA_OnPostOpenDiagram and EA_OnTabChanged

'autodiagrams are automatically layouted when opened
function EA_OnPostOpenDiagram(DiagramID)
    dim model
    'get the model
    set model = getEAAddingFrameworkModel()
    layoutAutoDiagram DiagramID, model
end function

'autodiagrams are automatically layouted when we tab is switched to them
function EA_OnTabChanged(TabName, DiagramID)
    if  DiagramID > 0 then
        dim model
        'get the model
        set model = getEAAddingFrameworkModel()
        layoutAutoDiagram DiagramID, model
    end if
end function

'layout the auto diagram
function layoutAutoDiagram(diagramID, model)
    dim diagram
    set diagram = model.getDiagramByID(DiagramID)
    'if the diagram is an auto diagram then we do an automatic layout
    if Left(diagram.name,LEN("AUTO_")) = "AUTO_" then
        'auto layout diagram
        dim diagramGUIDXml
        'The project interface needs GUID's in XML format, so we need to convert first.
        diagramGUIDXml = Repository.GetProjectInterface().GUIDtoXML(diagram.wrappedDiagram.DiagramGUID)
        'Then call the layout operation
        Repository.GetProjectInterface().LayoutDiagramEx diagramGUIDXml, lsDiagramDefault, 4, 20 , 20, false
    end if
end function

The only thing left now is to update the diagram in case a user changes a connector, and for instance drags one of the ends to another element. In order to do this we need to record the id’s of both ends of the connector once it is selected in EA_OnContextItemChanged and compare that to the id’s of this same connector after it has been changed in EA_OnNotifyContextItemModified

'keep a reference to the selected connector
function EA_OnContextItemChanged(GUID, ot)
    'we only do something when the context item is a connector
    if ot = otConnector then
        dim model
        'get the model
        set model = getEAAddingFrameworkModel()
        'get the connector
        dim contextConnector
        set contextConnector = model.getRelationByGUID(GUID)
        'MsgBox(TypeName(contextConnector))
        contextConnectorID = contextConnector.id
        oldClientID = contextConnector.WrappedConnector.ClientID
        oldSupplierID = contextConnector.WrappedConnector.SupplierID
    end if
end function

'a connector has changed, we need to update the auto-diagrams
function EA_OnNotifyContextItemModified(GUID, ot)
    'we only do something when the context item is a connector
    if ot = otConnector then
        dim model
        'get the model
        set model = getEAAddingFrameworkModel()
        'get the connector
        dim changedConnector
        set changedConnector = model.getRelationByGUID(GUID)
        'check if we are talking about the same connector
        if changedConnector.WrappedConnector.ConnectorID = contextConnectorID then
            dim supplier
            dim client
            'check the client side
            if changedConnector.WrappedConnector.ClientID <>  oldClientID then
                'get supplier
                set supplier = model.getElementWrapperByID(changedConnector.WrappedConnector.SupplierID)
                'remove old client from supplier and vice versa
                set client = model.getElementWrapperByID(oldClientID)
                if not client is nothing then
                    removeRelatedElemenFromAutoDiagram supplier,client, model
                    removeRelatedElemenFromAutoDiagram client, supplier, model
                end if
                'add new client
                set client = model.getElementWrapperByID(changedConnector.WrappedConnector.ClientID)
                addRelatedElementoAutoDiagram supplier,client, model
            end if
            'check the supplier side
            if changedConnector.WrappedConnector.SupplierID <> oldSupplierID then
                'get client
                set client = model.getElementWrapperByID(changedConnector.WrappedConnector.ClientID)
                'remove old supplier from client and vice versa
                set supplier = model.getElementWrapperByID(oldSupplierID)
                if not supplier is nothing then
                    removeRelatedElemenFromAutoDiagram client,supplier, model
                    removeRelatedElemenFromAutoDiagram supplier,client, model
                end if
                'add new supplier
                set supplier = model.getElementWrapperByID(changedConnector.WrappedConnector.SupplierID)
                addRelatedElementoAutoDiagram client,supplier, model
            end if
        end if
    end if
end function

Prevent accidental deletes with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example shows how you can prevent deleting elements that are still being used as a type in either an attribute or parameter.

The script uses the EA_OnPreDeleteElement event to first check whether the element is not still used.

If an attribute using this element as type is found then the element is not deleted and the user is informed with a messagebox.

EA-Matic cannot delete attribute

If it isn’t used as attribute but is used as a type in a parameter or the return type of an operation you get the following messagebox:

EA-Matic cannot delete parameter

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
model.initialize(Repository)

It is then further used to get the element based on the ElementID from the Info object

set element = model.getElementWrapperByID(elementID)

The element then gives us access the attributes and parameters using this element by means of

set usingAttributes =  model.toArrayList(element.getUsingAttributes())
set usingParameters = model.toArrayList(element.getUsingParameters())

Note that we need to use model.toArrayList to convert the C# HashSet that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Prevent accidental deletes

option explicit
'EA-Matic

function EA_OnPreDeleteElement(Info)
     'Start by setting false
     EA_OnPreDeleteElement = false
     dim usage
     'Initialize the EAAddinFramework model
     dim model
     set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
     model.initialize(Repository)
     'get the elementID from Info
     dim elementID
     elementID = Info.Get("ElementID")
     'get the element being deleted
     dim element
     set element = model.getElementWrapperByID(elementID)
     'Manual override is triggered by the name. If it starts with DELETED_ then the element may be deleted.
     if Left(element.name,LEN("DELETED_")) = "DELETED_" then
        'OK the element may be deleted
        EA_OnPreDeleteElement = true
     else
        dim usingAttributes
        set usingAttributes =  model.toArrayList(element.getUsingAttributes())
        'Check if the element is used as type in attributes
        if usingAttributes.Count = 0 then
            'Check if the element is used as type in a parameter
            dim usingParameters
            set usingParameters = model.toArrayList(element.getUsingParameters())
            if usingParameters.Count = 0 then
                'OK, no attributes or parameters use this element, it may be deleted
            EA_OnPreDeleteElement = true
            else
                usage = "parameter(s)"
            end if
        else
            usage = "attribute(s)"
        end if
     end if
     if EA_OnPredeleteElement = false then
          'NO the element cannot be deleted
          MsgBox "I'm sorry Dave, I'm afraid I can't do that" & vbNewLine _
          & element.name & " is used as type in " & usage , vbExclamation, "Cannot delete element"
     end if
end function