Export all component text used in Ignition

vison or perspective?

Also its not all perfect as (im guessing) you also have texts that come from a db, tag or expression so that wont rly work on those…
But its a start i guess ive done it too xd

You'll probably need to manually write a script.

Here's a post about iterating through all the components on a perspective view -

Heres a way to do it in vision -

You'll need to modify these to try and look at .text fields of appropriate components (or just try to get the text field of all components and catch the errors when you do it to a non text component.).

However this will just give you the current values of those texts components as of now - not all possible text values for every binding possibility. But it should still help you get a lot of the text you use from the program.

Here is a script i used to automatically add all text values to the translate manager in perspective
By checking the resource files… warning this may take a while xd

	import os
	import json
	from com.inductiveautomation.ignition.gateway import IgnitionGateway
	from com.inductiveautomation.ignition.gateway.script import GatewaySystemUtilities
	
	package = IgnitionGateway.get().getLocalizationManager().loadFullPackage()
	oldKeys = package.getAvailableKeys()
	context = IgnitionGateway.get()
	pathToProjectFolder = str(context.systemManager.dataDir.absoluteFile).replace('\\','/') + '/projects'	
	pathToProject = pathToProjectFolder + "/" + system.perspective.getProjectInfo().name + "/com.inductiveautomation.perspective/views/"
	
	def json_extract(obj, key):
	    """Recursively fetch values from nested JSON."""
	    arr = []
	
	    def extract(obj, arr, key):
	        """Recursively search for values of key in JSON tree."""
	        if isinstance(obj, dict):
	            for k, v in obj.items():
	                if isinstance(v, (dict, list)):
	                    extract(v, arr, key)
	                elif k == key:
	                    arr.append(v)
	        elif isinstance(obj, list):
	            for item in obj:
	                extract(item, arr, key)
	        return arr
	
	    values = extract(obj, arr, key)
	    return values

	newKeys = []
	for root, dirs, files in os.walk(pathToProject):
		try:
			for x in json_extract(json.loads(open(root + '/view.json').read()), 'text'):
				if x not in newKeys and x not in oldKeys and x != "":
					newKeys.append(x)
		except:
			pass
	
	for key in newKeys:			
		system.util.modifyTranslation(str(key),str(key),'en')
	

i got one for vision too but the gateway isnt on rn you need it? xd

3 Likes

@Remco_Thijssen Ignore my approach, @victordcq would be much easier to use and implement and much more thorough.

1 Like

How does this exactly works, i’m very new with programming but i tried to do my best haha. Is this a custom script or a script behind a button or something like that? And do you import a json file and what kind of json file is it? I need it for Vision and Perspective, what do you mean by gateway?

And what does os mean by import os.

Sorry for all the questions, i feel myself as a very dumb person right now hahaha

You can put this on a button to run it once

No need to import any files, the script finds the json files of the perspective projects which are stored on the gateway

Alright ill need a moment to fetch it

are you reffering to the script or my saying its not turned on?

os = operating system. its a library which can browse through your files so it can read all the resources

no problem haha this is an advanded script :stuck_out_tongue:

This should work in a button for vision… pro tip dont try to understand this one xD There is a bucnh of things that dont get used or are not needed but… it works xd
(again dont do this to often, it may take long xd)

from com.inductiveautomation.factorypmi.application.binding.action import ActionAdapter

class ProjectResources:
	
	def __init__(self):

		import system
		from javax.swing import JFrame
		from java.awt.event import WindowEvent
		
		if system.util.getSystemFlags() & system.util.DESIGNER_FLAG:
			try:
				from com.inductiveautomation.ignition.designer import IgnitionDesigner
			except ImportError:
				pass
			try:
				self.context = IgnitionDesigner.getFrame().getContext()
			except AttributeError:
				pass
		else:
			from com.inductiveautomation.factorypmi.application.runtime import ClientPanel
			"""
			Attempt to resolve client context. By creating a frame it prevents the need to traverse the object heirarchy. i.e. parent.parent....
			"""
			projName = system.util.getProjectName()
			frame_name = 'ThrowAway'
			frm = JFrame(frame_name)
			frm.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)
			windows = frm.getWindows()
			frm.dispose()
			for window in windows:
				try:
					if projName in window.getTitle():
						pane =  window.getContentPane()
						if isinstance(pane, ClientPanel):
							if hasattr(pane, 'getClientContext'):
								self.context = pane.getClientContext()
								break
				except AttributeError:
					pass
		if not hasattr(self.context, 'getProject'):
			raise Exception(self.getInvalidContextError(context))	

	def getAllProjectResources(self):
		return self.context.getProject().getResources()
	
	def getProjectResource(self, resourcepaths):
		list = []
		for resource in self.getAllProjectResources():
			if str(resource.getResourcePath().getFolderPath()) in resourcepaths :
				list.append(resource)
		return list	 	

	def getResourceInfo(self, resourcepath):
		for resource in self.getAllProjectResources():
			if str(resource.getResourcePath().getFolderPath()) == resourcepath :
				dresource = self.getDeSerializedResource(resource)
				if dresource != None:
					return {'Name' : dresource.getName(), 'Title' : dresource.getTitle() }
				else:
					return {}					
		return {}	 	

	def getAllComponentsForResource(self,resourcepaths):
		rows = []
		header = ['Project name', 'Resource Name','Resource Type', 'Component Type', 'Attribute', 'Name' ,'Value']
		resources = self.getProjectResource(resourcepaths)
		checkedResource=[]
		
		def resolveData(component):
			
			if component.getClass().getSimpleName() == u'TemplateHolder':
				if component.templatePath not in checkedResource:				
					checkedResource.append(component.templatePath)				
					tempResources = self.getProjectResource(component.templatePath)					
					for tempResource in tempResources:											
						dresource = self.getDeSerializedResource(tempResource)
						if dresource != None:
							rows.extend(resolveData(dresource))
							
							tempComponents = self.getAllComponentsForObject(dresource)
							for tempComponent in tempComponents:
								#print tempComponent.getName(), tempComponent.getClass().getSimpleName()								
								rows.extend(resolveData(tempComponent))
								
			#get resources
			arr = []
			if hasattr(component, 'text') and component.name != None and component.text:
				arr.append([resource.getProjectName(), resource.getResourceName(), resource.getResourceType().getTypeId(), component.getClass().getSimpleName(), 'Text', component.name, component.text])	
			if hasattr(component, 'toolTipText') and component.name != None and component.toolTipText:
				arr.append([resource.getProjectName(), resource.getResourceName(), resource.getResourceType().getTypeId(), component.getClass().getSimpleName(), 'ToolTip', component.name, component.toolTipText])	
			if hasattr(component, 'title') and component.name != None and component.title:
				arr.append([resource.getProjectName(), resource.getResourceName(), resource.getResourceType().getTypeId(), component.getClass().getSimpleName(), 'Title', component.name, component.title])	
			return arr			
		
			
		for resource in resources:
			dresource = self.getDeSerializedResource(resource)
			if dresource != None:
				
				rows.extend(resolveData(dresource))
				components = self.getAllComponentsForObject(dresource)
				for component in components:
					#print component.getName(), component.getClass().getSimpleName()					
					rows.extend(resolveData(component))
					
		return system.dataset.toDataSet(header, rows)		
		
	def getDeSerializedResource(self, resource):
		deserializer = self.context.createDeserializer()
		if not resource.isFolder():
			if str(resource.getResourceType().getTypeId()) == 'windows':
				try:				
					return deserializer.deserialize(resource.getData("window.bin")).getRootObjects()[0]
				except:
					pass
			elif str(resource.getResourceType().getTypeId()) == 'templates':
				try:				
					return deserializer.deserialize(resource.getData("template.bin")).getRootObjects()[0]
				except:
					pass
		return None

	def getAllComponentsForObject(self, object):
		arr = []
		try:
			for component in object.getComponents():
				arr.append(component)
				self.objectSearch(component, arr)
		except AttributeError:
			pass		
		return arr
		
	def objectSearch(self, object, array):
		try:
			for component in object.getComponents():
				array.append(component)
				self.objectSearch(component, array)
		except AttributeError:
			return		

allText = ProjectResources().getAllComponentsForResource(system.gui.getWindowNames())

for r in range(allText .getRowCount()):
	key = allText.getValueAt(r, 'Value')
	value = key 
	system.util.modifyTranslation(str(key), str(value), 'en')

Hi @victordcq victordcq

thanks for sharing this code, but i am not able to execute it in vision.
I tried to paste it in script console and keep getting this error that im not able to debug:

Traceback (most recent call last):
File “”, line 142, in
File “”, line 67, in getAllComponentsForResource
File “”, line 50, in getProjectResource
AttributeError: ‘com.inductiveautomation.ignition.common.project.Pr’ object has no attribute ‘getResourcePath’

Can you help me out?
Thanks

idk if it works in the script console, im not sure if the script console has any know of the projects...
yu might have to put it in a button script.

and for the error it seems you are looping over projects not projectResources
Are you sure you called getAllProjectResources?

Edit: i just copied the code in the scriptconsole (apart from the modify translate) and it works for me...
Did you copy everything?

im using the code just the way you pasted it above, there are no differences.
I use ignition 7.9.17 (vision 9.9.17), might that be a problem?

I keep getting the errors i mentioned above, any ideas?

seems like it is indeed a version thing

what version do you use

8.1+

you dont really need this part of the code tho, but im not sure if everything else works.

This bit just checks if the resource is the one you want (provided by system.gui.getWindowNames() allText = ProjectResources().getAllComponentsForResource(system.gui.getWindowNames()))
you will have to use a different compare methode or dont compare and pull in all files since thats probably what you want anyways.

maybe try
printing resource.getName()
or any of the properties of you find here to make a compare of the list you get of system.gui.getWindowNames()
(i think this is the link to the correct version, it should be close enough idk how to navigate the docs to your version xd)
http://files.inductiveautomation.com/sdk/javadoc/ignition79/794-beta1/com/inductiveautomation/ignition/common/project/ProjectResource.html

Now i converted my project to 8.1 for testing purposes, i was able to execute the code but unfortunately it only exported one text.

can you tell me, where does the text need to be in order to be exported in the translation manager?

every fixed property of "text, toolTipText and title" should be added. Bindings/expressions do not get added as these do not exist "yet"
What were you expecting to get?

now it works, thank you, for some reason i need to save the screen, then execute the code.

At this point, template parameters, that contain text, cannot be exported by that code?
I have one custom property, called “text 1”.

I tried to add 2 lines in your code:

		if hasattr(component, "text1") and component.name != None and component.text1:
			arr.append([resource.getProjectName(), resource.getResourceName(), resource.getResourceType().getTypeId(), component.getClass().getSimpleName(), 'text1', component.name, component.text1])	

but it did not work.

try this: component.getPropertyValue('text1')

if component.name != None and component.getPropertyValue('text1'):
 arr.append([resource.getProjectName(), resource.getResourceName(), resource.getResourceType().getTypeId(), component.getClass().getSimpleName(), 'text1', component.name, component.getPropertyValue('text1')])

edit yup should work

hasattr wont work but you might still want to check for component.name != None tho

Pssst! Just install Simulation Aids and the shortcuts will always work (component.text1) even for custom properties, and no matter what method you used to get the component reference. See this discussion:

1 Like

Hi @victordcq, but this script runs in designer only. Not in Client. But they developed that for both of designer and the client. But idk why this didn't run in client. Can you please clarify this one ?

Thanks,

i dont know, i did not make the one for vision. i dont rly work with vision often. ive heard some people having problemes with other versions, maybe this only works in client on the older version.

what error does it give?

still this i suppose?

well seems the context functions might have changed
in here somewhere

for window in windows:
				try:
					if projName in window.getTitle():
						pane =  window.getContentPane()
						if isinstance(pane, ClientPanel):
							if hasattr(pane, 'getClientContext'):
								self.context = pane.getClientContext()
								break
				except AttributeError:
					pass