Add script to dynamically created objects

Hi.
We are creating rectangle dynamically on vision client by button script and works fine.
We want to drag and drop that rectangle using Mouse Motion - Mouse Dragged script.
its works fine if we manually add script to that rectangle in design mode.
We are looking for to add Mouse Motion - Mouse Dragged script automatically to rectangle when we are creating rectangle dynamically on vision client by button
Not: Rectangles are created dynamically by sequence 1st click - Rectangle, 2nd Click- Rectangle1, Rectangle2…so on

This is an interesting idea. What is your code for dynamically creating the rectangles?

1 Like

from com.inductiveautomation.factorypmi.application.components import PMIRectangle

When I move shapes around dynamically in Vision, I usually do it with
system.gui.transform(componentName, newX, newY)

1 Like

Thanks…how can i add above script to rectangle…i create rectangle by button click and same time time i want to attached dragged event script to rectangle by writting script in button event itself

If you are dragging the rectangle, then this will need to be done from the onMouseDragged event handler

Thanks… i want to add script under onMouseDragged event in Rectangle component from outside. Once i create rectangle dynamically on vision client then i cant go back in designer and add mouse dragged event script manually

You will have to make the component name in your script a variable, and determine which object to pass into the script based on the current positions of the rectangles relative to the mouse pointer during the mousePressed event. If two rectangles are overlapping, you will have to find a way to get the z order of the dynamically created objects.

You could probably create a custom property to hold onto the variable name until the mouseReleased event. Please note that I have never attempted anything like what you are doing in ignition. These are just my ideas on how to go about it.

I can’t imagine adding new components in this way to actually be easier than using e.g. the Paintable Canvas and just doing all the work yourself.

I would just construct a dataset with four columns - x1, y1, x2, y2 and add new points to it at runtime. A mouse event handler on the canvas could easily loop through the dataset, checking bounding boxes, and determine if an event is inside of a rectangle.

3 Likes

Okay, so I had the night off, and I since I was [inadvertently] given credit for having solved this one, I figured I would go ahead and take a crack at it. Plus, I found this challenge interesting. Here is the solution I came up with:
image

First, I used a paintable canvas and added a dataset custom property named rectangles with all of the parameters I believed I would need to keep track of: keyIndex, name, x, y, width, height, and zOrder

Then, I added two additional custom properties for tracking rectangle selection: currentKeyIndex and currentRectangle:
image

I used a simple button to create the rectangles using this script on the actionPerformed event handler:

rectangles = event.source.parent.getComponent('Paintable Canvas').rectangles
keyIndex = rectangles.rowCount
name = "rectangle"+str(keyIndex)
x = 10
y = 200
width = 100
height = 100
zOrder = 1
newRectangle = [keyIndex, name, x, y, width, height, zOrder]
for rectangle in range(rectangles.rowCount):
	rectangles = event.source.parent.getComponent('Paintable Canvas').rectangles
	rectangleIndex = rectangles.getValueAt(rectangle, "keyIndex")
	zOrder = rectangles.getValueAt(rectangle, "zOrder")
	newZOrder = zOrder+1
	zOrderChange = system.dataset.updateRow(rectangles, rectangleIndex, {"zOrder":newZOrder})
	event.source.parent.getComponent('Paintable Canvas').rectangles = zOrderChange
rectangles = event.source.parent.getComponent('Paintable Canvas').rectangles
updatedRectangles = system.dataset.addRow(rectangles, keyIndex, newRectangle)
event.source.parent.getComponent('Paintable Canvas').rectangles = updatedRectangles

The script loads the default parameters for the new rectangle, giving the new rectangle the lowest zOrder. It then loops through all of the other rectangles and increments their zOrders accordingly.

To drag a rectangle, I developed a script for rectangle selection using the mousePressed event handler. It loops through all of the rectangles and builds a list of all rectangles that are underneath the mouse pointer. It then loops through that sublist to determine which rectangle has the lowest zOrder. Finally, it gives the selected rectangle a zOrder of one and updates all of the zOrders accordingly. Here is the code:

rectangles = event.source.rectangles
rectanglesInRange = []
currentRectangle = "noneSelected"
currentKeyIndex = None
lowestZOrder = rectangles.rowCount
e = event
for rectangle in range(rectangles.rowCount):
	x1 = rectangles.getValueAt(rectangle, "x") 
	x2 = x1 + rectangles.getValueAt(rectangle, "width")
	y1 = rectangles.getValueAt(rectangle, "y") 	
	y2 = y1 + rectangles.getValueAt(rectangle, "height")
	if e.x > x1 and e.x < x2 and e.y > y1 and e.y < y2:
		rectanglesInRange.append(rectangles.getValueAt(rectangle, "keyIndex"))
if len(rectanglesInRange) > 0:
	for rectangle, keyIndex in enumerate(rectanglesInRange):
		zOrder = rectangles.getValueAt(keyIndex, "zOrder")
		if zOrder <= lowestZOrder:
			lowestZOrder = zOrder
			currentRectangle = rectangles.getValueAt(keyIndex, "name")
			currentKeyIndex =  rectangles.getValueAt(keyIndex, "keyIndex")
	for rectangle in range(rectangles.rowCount):
		rectangles = event.source.rectangles
		zOrder = rectangles.getValueAt(rectangle, "zOrder")
		if zOrder == lowestZOrder:
			zOrderChange = system.dataset.updateRow(rectangles, rectangle, {"zOrder":1})
			event.source.rectangles = zOrderChange
		elif zOrder < lowestZOrder:
			zOrderChange = system.dataset.updateRow(rectangles, rectangle, {"zOrder":(zOrder+1)})
			event.source.rectangles = zOrderChange
event.source.currentRectangle = currentRectangle
event.source.currentKeyIndex = currentKeyIndex

To move the rectangles, using the mouseDragged event handler, I simply offset the mouse cursor position by half the length and width of the rectangle, so the cursor remains centered on the rectangle. Then, I continuously update the dataset with the offset mouse cursor position. Here is the code:

currentRectangle = event.source.currentRectangle
keyIndex = event.source.currentKeyIndex
if currentRectangle != "noneSelected":
	rectangles = event.source.rectangles
	currentX = event.x - (.5*rectangles.getValueAt(keyIndex, "width"))
	currentY = event.y - (.5*rectangles.getValueAt(keyIndex, "height"))
	positionChange = system.dataset.updateRow(rectangles, keyIndex, {"x":currentX,"y":currentY})
	event.source.rectangles = positionChange

Finally, I remove the rectangle selection at the mouseReleased event. Here is that code:

event.source.currentRectangle = "noneSelected"
event.source.currentKeyIndex = None
1 Like

Here is a video of the result:

I almost forgot. To paint the rectangles, I developed this code that I used on the paintable canvas’s onPaint event handler:

from java.awt import Color
from java.awt.geom import Rectangle2D
rectangles = system.dataset.sort(event.source.rectangles, "zOrder", False)
for rectangle in range(event.source.rectangles.rowCount):
	keyIndex = rectangles.getValueAt(rectangle, "keyIndex") 
	x = rectangles.getValueAt(rectangle, "x") 
	y = rectangles.getValueAt(rectangle, "y") 	
	width  = rectangles.getValueAt(rectangle, "width") 	
	height = rectangles.getValueAt(rectangle, "height") 	
	rectangle = Rectangle2D.Float( x, y, width, height)
	g = event.graphics
	if keyIndex % 10 == 0 or keyIndex % 10 == 5:
		g.setColor(Color.RED)
	elif keyIndex % 10 == 1 or keyIndex % 10 == 6:
		g.setColor(Color.BLUE)
	elif keyIndex % 10 == 2 or keyIndex % 10 == 7:
		g.setColor(Color.GREEN)
	elif keyIndex % 10 == 3 or keyIndex % 10 == 8:
		g.setColor(Color.YELLOW)
	else:
		g.setColor(Color.BLACK)
	g.fill(rectangle)

It sorts the dataset by descending zOrder, then loops through to paint all of the rectangles. I also added code that varies the color of the rectangles based upon the ones digit of the keyIndex, just to make the zOrder corrections more obvious.

Edit: Thanks @PGriffith for the paintable canvas direction. I found it to be intuitive and easy to implement.

10 Likes

Thanks…Excellent

1 Like

A message I received in response to this post indicated that I should clarify datatypes.

The paintable canvas custom property datatypes are as follows:

currentKeyIndex = Integer
currentRectangle = String
rectangles = Dataset

The rectangles dataset column datatypes are as follows:

keyIndex = Integer
name = String
x = Float
y = Float
width = Float
height = Float
zOrder = Integer
2 Likes

Just for fun:

7 Likes

Excellent