[IGN-8155]Gradient Paints - Big Performance Hit on MacOS

One minor improvement: MultipleGradientPaint is the supertype for all the gradient paints, so you can just isinstance() for that, instead of importing all the subclasses. The rest of it looks pretty solid though.

1 Like

Thanks Paul! That definitely helped clean up that ugly part of the script. In case it helps anyone, here is the current iteration of the script module. Added in the optional ability to replace the gradient paints with an approximate average color representation of the gradient (place in a script module and then call the main() function passing in a reference to your window root container).

"""MacOS has significant issues in the Designer with gradient paints due 
to a bug, but there is no anticipated fix date. Instead need to change 
all gradient paint properties to a solid paint. This module is to help 
speed up the process of locating properties with gradient data types and, 
if desired, automatically replacing values with a solid color.

Todo:
	* Fix docstring Args type hints.

"""


from java.awt import Color

from com.inductiveautomation.factorypmi.application.components.template import VisionTemplate
from com.inductiveautomation.ignition.client.util.gui.paints import MultipleGradientPaint



def get_avg_gradient_color(gradient):
	"""Takes a gradient paint value and returns the rough approximation of the average of 
	the gradient colors. Note that this does not account for the spacing of the gradient 
	inflection points and therefore will give equal weight to all colors in the gradient.
	
	Args:
		gradient (MultipleGradientPaint): A gradient paint class object of the 
			MultipleGradientPaint class or sub classes.
	
	Returns:
		java.awt.Color: Avg color of the input gradient paint.
	
	""" 
	colors = gradient.getColors()
	r, g, b = 0, 0, 0
	for c in colors:
		r += c.getRed()**2
		g += c.getGreen()**2
		b += c.getBlue()**2
	avg_color = Color(
		int((r / len(colors)) ** 0.5),
		int((g / len(colors)) ** 0.5),
		int((b / len(colors)) ** 0.5)
	)
	return avg_color
	

def _gradient_find_and_replace(comp, replace_color=False, path="", template_name=None):
	"""Recursively iterates through nested objects to find properties 
	that are gradient paint data types and prints the object path/property 
	to the console. Can optionally replace the property values with a solid
	color.
	
	Args:
		comp (component): Object to start the search at. Typically pass in the window
			root container.
		replace_color (bool, optional): Defaults to False. If true, the function will
			set the properties that contain gradient paints to a solid color that is an
			average of the gradient colors. Note that this CANNOT change Template Instance
			properties permanently (they will revert when you reopen the window). You
			would need to run this script within a template definition and save it.
		path (str, optional): Used in the recursion to keep track of the object path. When
			calling this function, this arg should be omitted.
		template_name (str, optional):  Used in the recursion to keep track if one of the
			objects parents is a template. When calling this function, this arg should be 
			omitted.
	
	Returns:
		None
		
		Function is purely a helper function that prints to the console.
	
	"""
	path = "{}/{}".format(path, comp.name)
	for obj in comp.getComponents():
		# Iterate through objects that contain other objects.
		if hasattr(obj, "getComponents"):
			if isinstance(obj, VisionTemplate):
				template_name, path = obj.name, ""
			_gradient_find_and_replace(obj, replace_color, path, template_name)
		
		# Find object properties that have gradient data types
		root_type = "Window" if template_name is None else "Template"
		print_strings = ["{} Path: {}/{}".format(root_type, path, obj.name)]
		for el in dir(obj):
			try:
				if isinstance(getattr(obj, el), MultipleGradientPaint):
					sub_print_str = "  Property Name: {}"
					# Replace with the average color if indicated.
					if replace_color:
						setattr(obj, el, get_avg_gradient_color(getattr(obj, el)))
						sub_print_str += " (color replaced)"
					print_strings.append(sub_print_str.format(el))
			except:
				# Many legitimate reasons the try can fail.
				pass
		
		if len(print_strings) > 1:
			print "\n".join(print_strings)
	return


def main(comp, replace_color=False):
	"""Used as an abstraction layer on top of the gradient util function(s) to simplify
	running the helper module
	
	Arguments:
		comp (component): Object to start the search at. Typically pass in the window
			root container.
		replace_color (bool, optional): Defaults to False. If true, the function will
			set the properties that contain gradient paints to a solid color that is an
			average of the gradient colors. Note that this CANNOT change Template Instance
			properties permanently (they will revert when you reopen the window). You
			would need to run this script within a template definition and save it.
	
	Return:
		None
	
	"""
	print "\nGradient paint search starting.\n"
	_gradient_find_and_replace(comp, replace_color)
	print "\nGradient paint search complete.\n"
	return