Accessing Ignition Component Properties, Programmatic Placement of properties

Related to the above. I am trying to get labels to overlay rectangles that I move and stretch to represent time durations of various states to our operators during a run.

If I take the left edge of the rectangle by using event.source.getComponent('rectangle').relX, and set the position with system.gui.transform, and coordspace 1, the label and rectangles' right edges are properly aligned.

If I then try to sum event.source.getComponent('rectangle').relX and event.source.getComponent('rectangle').width, I get a value that seems to always be the largest possible x value my window will permit.

If I write the event.source.getComponent('rectangle').width value to a label, I see that it is, again, the width of my window. I'm guessing that the 'width' property accessible in the Vision property editor for the rectangle is NOT the '.width' attribute associated with event.source.getComponent('rectangle'). It's surprising because X and .relX work fine.

I tried listing the attributes, using dict but somehow the Ignition components are written in such a way that they lack the attribute dicitonary? Wasn't aware this was even possible, I even checked the Jython documentation to be sure it is the same in both versions.

What am I doing wrong?

Ignition components aren't written in Jython, they are written in Java. You can deep dive into the Java Docs if you want.

You might also find this useful.

However, putting that aside. JavaSwing which is what Vision uses, is terrible at scaling, so choosing to use system.gui.transform() here is gonna be a rough road. I would strongly recommend using a Paintable Canvas.

4 Likes

:100:

I took one semester of Java in undergrad and that was enough to convince me never to bother with it more than was absolutely necessary. I confess to not having 100% visibility into the backend of Ignition but the scripting language is jython and the components are generally manipulable with the standard Python methods. Except __ dict __, and vars(), for some reason.

The PYTHON function dir(), applied to the JYTHON component ‘rectangle’ returned all the available properties of the rectangle function, including the manual-undocumented ‘relWidth’ and ‘relHeight’.

For whatever reason, probably something Javonic, selecting ‘width’ or ‘height’ from a component gives you the width/height of the window (or possibly the container - my rectangles were contained by root). It’s inarguably bad nomenclature, but if that’s Java, I could believe it.

event.source.getComponent(‘rectangle’).x+event.source.getComponent(‘rectangle’).relWidth is the correct, Pythonic (but I repeat myself) answer.

interestingly,
event.source.getComponent(‘rectangle’).relX+event.source.getComponent(‘rectangle’).relWidth
also works. not sure what difference there is between .relX and .x, a mystery as obscure as why the ‘width’ property of an object would not refer to that object’s width, or why a python class would be created without a __ dict __.

Anyway, SOLVED.

Jython is a java-based implementation of python (the language). The python (implementation) most commonly called python is CPython, a C-based implementation of python. They are different in numerous, implementation-specific ways. Jython normally wraps java classes on the user’s behalf in ways that hide java’s true nature. That can be overridden by java programmers for gnarly cases. Oh, well.

Also, even CPython can ditch the normal __dict__ if __slots__ is defined.

I highly recommend you get over your aversion to java, or at least to the java standard library, as it is the foundation of Ignition. Use of java in jython in lieu of python stdlib is the key to its most stable deployment.

3 Likes

@pturmel You know much more than me so I’ll take that under advisement, however, it was entirely unnecessary to do anything unpythonic to find the info I needed, which was the kernel of my question. I also know Python is built on C, as well as C, and I’m familiar with Cython.

@lrose ‘Don’t do X do Y’ in response to ‘I want to do X’ is probably the worst pathology on internet forums. It’s smug and dismissive, and if you have a full understanding of why one is better than the other it’s incumbent to first explain X, then suggest and explain Y, then make your case for why you prefer Y vs X.
‘Deep dive into the Java docs indeed’. And then when doing X turns out to be not that difficult, it looks like the reason you suggest Y is because you never learned how to do X.

To be clear again, no concrete, actionable help was provided by by this thread. I updated specifically to answer my own question and in so doing, increase the searchable documentation on one component specifically, and solving similar problems with other components using python in general.

But to dive back into the Java/Python debate:
Drawing Rectangles Examples with Java Graphics2D
“In Java, to draw a rectangle (outlines) onto the current graphics context, we can use the following methods provided by the Graphics/Graphics2D class: drawRect(int x, int y, int width, int height)”

The decision to define the ‘width’ and ‘length’ properties contrary to how they are defined on the Vision Client Browser and in Java itself (and in common colloquial English), doesn’t seem to be a Python/Java issue. By default width is width is width. The width attribute of a rectangle component in Ignition is the width of the container it is created in, and relWidth is is the width of the object. That seems to me to be just an Ignition thing, that no amount of hitting the Java books will help.

Shape components in Vision are complicated because the problem is complicated. Users want to set things up in the designer, and then have them work in arbitrary clients at arbitrary screen resolutions. The relX, relY (and corresponding relWidth and relHeight) abstractions are, ultimately, due to the same thing as in your first post - the difference in “coordinate spaces” between the designer and the runtime.
width and height are still available because they’re core properties of every Java Swing component, even though they’re not particularly meaningful in this case.

I don’t know why this information has never been put in the manual, other than that generally speaking people don’t dynamically reposition components at runtime very much. If they do, the recommendation is to use system.gui.transform, since it papers over this abstraction.

Another option is the Paintable Canvas, as @lrose mentioned. I would agree with this recommendation if you have the specific requirement of “drawing an arbitrary number of graphical primitives at arbitrary positions”. The paintable canvas will be both more performant and likely easier to maintain once set up.

5 Likes

The first link was me using System.gui.transform. The issue was knowing how to define the location to which I wanted to transform the object. Not a complicated problem. The hard part was guessing what string of characters I needed to access the property, given that the string was not intuitive, and the usual tools for determining it were non-functional.

The answer, if you are a user who wants to solve a problem quickly because you have other work to do, is use ‘relX’, ‘relY’, ‘relWidth’, and ‘relHeight’. And now that solution is documented. You’re welcome.

Another answer, if you are a developer who hasn’t documented the correct answer, and has other work to do, is to tell people to teach themselves Java and use the paintable canvas. Shall I refer to the Igniton manual to learn how to use it?

As long as we're being pedantic, the question you asked in the original post was "what am I doing wrong". The suggestion was to go to the javadocs. If you print type(yourRectangleReference), you get the fully qualified Java class name of the given component. If you look for that in the javadocs, you get... a Javadoc, which lists all the properties, including relX and relWidth and everything else.

You could also use Phil's introspect script, also mentioned above, to get much the same information as the Javadoc:

Introspect
Python Type: <type 'com.inductiveautomation.vision.api.client.components.shapes.PathBasedVisionShape'>
Java Type:   <type 'com.inductiveautomation.vision.api.client.components.shapes.PathBasedVisionShape'>
  M: <type 'void'> setFillPaint(<type 'java.awt.Paint'>)
  M: <type 'java.awt.Paint'> getFillPaint()
  M: <type 'void'> mirrorHorizontal()
  M: <type 'void'> mirrorVertical()
  M: <type 'float'> getStrokeSize()
  M: getCenterpoint()
    returns <type 'java.awt.geom.Point2D'>
  M: <type 'void'> setBoundingRect(<type 'java.awt.geom.Rectangle2D'>)
  M: <type 'java.awt.Paint'> getStrokePaint()
  M: <type 'java.awt.Stroke'> getStrokeStyle()
  M: <type 'void'> alterDelegate(<type 'java.awt.geom.GeneralPath'>)
  M: <type 'void'> alterDelegate(<type 'com.inductiveautomation.vision.api.client.components.shapes.ShapeDelegate'>,<type 'double'>)
  M: <type 'void'> convertToPath()
  M: <type 'void'> setStrokeStyle(<type 'java.awt.Stroke'>)
  M: <type 'void'> setStrokePaint(<type 'java.awt.Paint'>)
  M: getShapeBounds(<type 'java.awt.geom.Rectangle2D'>)
    returns <type 'java.awt.geom.Rectangle2D'>
  M: <type 'java.awt.Shape'> getShape()
  M: <type 'void'> update()
  M: getDelegate()
    returns <type 'com.inductiveautomation.vision.api.client.components.shapes.ShapeDelegate'>
  M: <type 'java.awt.geom.Area'> getArea()
  M: <type 'java.awt.Shape'> getArea()
  M: <type 'void'> setDelegate(<type 'com.inductiveautomation.vision.api.client.components.shapes.ShapeDelegate'>)
  M: <type 'boolean'> isRotated()
  M: <type 'void'> paintShape(<type 'java.awt.Graphics2D'>)

From Type:   <type 'com.inductiveautomation.vision.api.client.components.shapes.AbstractVisionShape'>
  M: <type 'double'> getAngleDegrees()
  M: <type 'void'> setAngleDegrees(<type 'double'>)
  M: getRotationAnchor()
    returns <type 'java.awt.geom.Point2D'>
  M: <type 'void'> setAngleRadians(<type 'double'>)
  M: <type 'void'> setRelWidth(<type 'double'>)
  M: <type 'double'> getAngleRadians()
  M: <type 'double'> getRelWidth()
  M: <type 'void'> clearTempTransmform()
  M: <type 'double'> getRelHeight()
  M: getTempTransform()
    returns <type 'java.awt.geom.AffineTransform'>
  M: <type 'void'> setRelHeight(<type 'double'>)
  M: <type 'void'> setTempTransform(<type 'java.awt.geom.AffineTransform'>)
  M: getBoundingRect(<type 'java.awt.geom.Rectangle2D'>)
    returns <type 'java.awt.geom.Rectangle2D'>
  M: getBoundingRect()
    returns <type 'java.awt.geom.Rectangle2D'>
  M: <type 'void'> setOriginalBounds(<type 'java.awt.geom.Rectangle2D'>)
  M: getOriginalBounds()
    returns <type 'java.awt.geom.Rectangle2D'>
  M: <type 'void'> setRotationAnchor(<type 'java.awt.geom.Point2D'>)
  M: <type 'double'> getRelX()
  M: <type 'void'> setRelX(<type 'double'>)
  M: <type 'double'> getRelY()
  M: <type 'void'> setRelY(<type 'double'>)
  M: <type 'java.lang.String'> toString()
  M: <type 'boolean'> contains(<type 'int'>,<type 'int'>)
  M: <type 'void'> rotate(<type 'double'>,<type 'double'>,<type 'double'>)
  M: <type 'void'> rotate(<type 'double'>,<type 'java.awt.geom.Point2D'>)
  M: <type 'void'> setBorder(<type 'javax.swing.border.Border'>)
  M: <type 'void'> repaint()

From Type:   <type 'com.inductiveautomation.vision.api.client.components.model.AbstractVisionComponent'>
  M: <type 'java.util.TreeMap'> getDynamicProps()
  M: <type 'int'> getDataQuality()
  M: <type 'void'> setDataQuality(<type 'int'>)
  M: <type 'void'> updateQuality(<type 'java.lang.String'>,<type 'com.inductiveautomation.ignition.common.model.values.QualityCode'>)
  M: <type 'boolean'> isPropertyDefined(<type 'java.lang.String'>)
  M: <type 'void'> startupComponent(<type 'com.inductiveautomation.factorypmi.application.binding.VisionClientContext'>)
  M: <type 'void'> shutdownComponent()
  M: <type 'boolean'> isAntialias()
  M: <type 'void'> setAntialias(<type 'boolean'>)
  M: <type 'int'> getCursorCode()
  M: <type 'void'> setCursorCode(<type 'int'>)
  M: <type 'void'> setDynamicProps(<type 'java.util.TreeMap'>)
  M: <type 'void'> localeChanged(<type 'java.util.Locale'>)
  M: <type 'void'> setQuality(<type 'com.inductiveautomation.ignition.common.model.values.QualityCode'>)
  M: getQuality()
    returns <type 'com.inductiveautomation.ignition.common.model.values.QualityCode'>
  M: <type 'void'> setVisible(<type 'boolean'>)
  M: <type 'void'> setStyles(<type 'com.inductiveautomation.ignition.common.Dataset'>)
  M: getProperties()
    returns <type '[Lcom.inductiveautomation.factorypmi.application.binding.DynamicPropertyDescriptor;'>
  M: <type 'void'> setName(<type 'java.lang.String'>)
  M: <type 'void'> setToolTipText(<type 'java.lang.String'>)
  M: getStyles()
    returns <type 'com.inductiveautomation.ignition.common.Dataset'>
  M: getAppContext()
    returns <type 'com.inductiveautomation.factorypmi.application.binding.VisionClientContext'>
  M: <type 'java.lang.String'> getToolTipText()
  M: <type 'java.lang.Object'> getPropertyValue(<type 'java.lang.String'>)
  M: <type 'void'> setPropertyValue(<type 'java.lang.String'>,<type 'java.lang.Object'>)

You hit upon an unfortunate corner case - you're trying to do something generally not recommended (dynamically repositioning things at Vision runtime) and you're more familiar with CPython, so your "usual tools" weren't working. The question you posed was "what's wrong with the tools" and the answer you got was a different set of tools, because the tools you're used to just don't work the same way in the Jython environment. As a bonus, you were given a suggestion of a different avenue to go down that would also solve your problem.

You chose to engage with those suggestions in a very aggressive way. I'm sorry you wasted your time searching fruitlessly at first, but you asked a specific question and got a specific answer.

5 Likes

I didn’t engage with anything until after I’d found the answer. What I engaged with was the advice presented, the way the advice was presented, and the defensive response to that engagement.

dir(rectangle) gives the exact same information as the Javadoc and Introspect, we are aware of this yes? I solved the problem, so there must have been a way for me to access this info? I’m not pigheadedly refusing to solve a problem, it’s solved. And not in the way anyone suggested. In a simpler way. Without involving learning a new codebase or downloading homebrew scripts. You can keep insisting that your way is easier despite it being demonstrably not easier than typing dir() but I’m not letting you off the hook.

There were a lot of ways to fix this issue. First was properly document all of your components. ‘rectangle’ is about as simple as it gets, I assume it was one of the first defined components. If you have time to respond on a thread that has been solved four replies ago, I assume you’ve also taken time to update the manual?
I don’t know what your constaints are, perhaps ‘width’ and ‘height’ needed to be defined as they were. I’ll just say, second, it would have been nice if they weren’t defined that way.
Third would be define a __ dict __
Fourth would be to respond ‘It looks like you’re taking a python centric approach. If you have a spare hour, check these docs, if you don’t just use dir()’

People always act surprised when folks ‘get aggressive’ as though its a werewolf thing people snap into. I got glib, unhelpful answers, despite there being a very simple solution, which if I wasn’t able to find it myself, I would be stuck. I feel entitled to be a little annoyed with the very common ways the advice was presented to me unhelpfully, and if I’m starting at a bit of a higher pitch than normal, it’s because, as mentioned, the specific way in which this advice was unhelpful has been the main pathology with seeking advice on the internet for as long as I’ve been alive.

Not for nothing? I don’t come to these forums unless I’m completely stuck. I’m sure I’m not the only one.

@pturmel, again, the advice you gave me on the previous thread was bang on. Exactly right, and looking through the dropdown of the introspect, would haveworked if I hadn’t stumbled upon dir(). Everyone else, my advice is read the python docs.

I am sorry that you felt my response unhelpful. I assure you it wasn't intended to be that way.

I didn't provide a concrete and actionable answer to your question, because I didn't know one.

I do know that dir() doesn't always provide the information that you might be looking for, and so I provided a link to a script which provides more information than standard dir(). In an attempt to give you more information with which to solve your problem, since I wasn't providing a clear solution.

I also knew this:

and this:

And so, I recommended using the Paintable Canvas Component.

Please find for me where I used the verbiage 'Don't do X do Y'? I provided a known pitfall (Java Swing's Scaling) and a recomendation as to how to avoid that. You chose not to, that's just fine. I was only attempting to help in the best way I knew how.

Many times if you're stuck, then the people on this forum will also be stuck. Expecting them to produce what you feel is in the end is a rather simple answer, is rather hypocritic seeing's as how you also hadn't found it yet and they know less about your application than you.

Again, I am sorry you felt my response was unhelpful, I will try to do better going forward.

1 Like

That's a mistake imnsho. You should come more often. You will pick up a lot of tips, tricks and most importantly the idiosyncrasies. This forum is probably the biggest one on the internet that actively discusses with jython and more importantly the issues with certain jython implementations (like requests for instance and why you should use system.net.* functions instead of that). Issues like that you would run into if you only took a jython-first approach without seeing Ignition has to offer.

Anyway hope you come back ( and before you have an unsolvable issue).

2 Likes

Guys, Learn Python.

Answers that fully explain the options and pros/cons as you suggest are fantastic. And somewhat rare. Overall, I've learned more from less complete answers than the comprehensive ones--due to the rarity of the latter. The less complete/accurate answers may be because the responder doesn't know more as you suggest. Or they don't have time to post more. Or they've misunderstood the question. Or maybe I haven't presented the problem clearly. Regardless, I appreciate people giving their time to help.

If we limited responses to people who know enough to give complete, accurate answers, the subset of those who run into the questions, and then to the subset willing to give their time to answer, we wouldn't get a lot of answers.

@byrnep, I'm glad you figured it out and took the time to post your solution. Thank you. I'm certain others' contributions here will help future readers too. Thank you @lrose, @pturmel, @PGriffith, and @bkarabinchak.psi for adding your perspectives. My first exploration of the paintable canvas (which did what I needed well) started with a suggestion to use it in another thread where the OP also ended up solving their problem another way.

5 Likes

This was not an invitation to a philosophical debate about the relative merits of forum boards. It was a question on how to position a rectangle on a screen, and it was solved days ago.

Gentlemen, please stop responding, and please recognize that everyone who does is only demonstrating why asking a question on the internet generally, and this board specifically, is a guaranteed chore and burden compared to the very un-guaranteed chance of getting real help.

And just to cut off the next answer, I’ll remind you all, that I solved the problem myself using a function identical in output to the next most useful suggestion, from Pturmel. I really don’t need seven more people coming out of the woodwork threatening to withhold their wisdom when they contributed nothing in the first place, and the sum of what was contributed was unhelpful, irrelevant, or equal to what I was able to on my own after giving up on the forum advice.

I’ve already spoken to the HR department of one particularly enthusiastic and profane jerk who slid into my DMs and refused to leave.

Let this thread die. No one cares that you care that someone doesn’t like your forum as much as you do. I can guarantee you that no one who stumbles across this thread in the future, looking for advice on how to position components programmatically is going to care in the slightest about what follows the ‘Solved’ comment, which by length is about 93% of this thread and growing.

We’re all here because we do this for work. Go do your jobs.