Paintable Canvas

#1

I’ve got an upcoming project where I will be using a lot of dynamic graphics, so I have a very nice application for your paintable canvas component. I just want to make sure I approach this the right way.

Let’s say I have a maximum of 100 devices that could be displayed on the screen at any given time. Their positions and dynamic properties (fill level, color, size) can vary. Am I correct to assume that I only need to create one paintable canvas component, and each instance can be drawn programatically? Or if I know that I am going to have a maximum of 100 of these components, do I need to at least create all 100 in the designer, and “grab” and display them as needed?

0 Likes

#2

You can certainly have just 1 paintable canvas, and write your script nicely (hint: use Python’s object oriented-ness!) to draw all of the devices.

0 Likes

#3

Well, here’s my first feeble attempt. In the attached window, you can define the number of columns in a text field, and the slider will draw the required number of pumps. It in’t pretty, but I started making it into some reusable code. I took out most of the absolute values and now pass them in via nested lists that are defined outside of the class (these parameters could be passed in from an xml file or something). Also, I started restructuring the class a little bit by adding a seperate method in addition to the def init, but haven’t quite made the connection as to which functionality should be broken apart in individual methods.

One question I have is how something like this should actually work. Right now, as I iterate through the required pumps, each one is drawn individually, causing sort of a ripple effect. Is this pretty much how the component should work, or could the actual screen painting be deferred until all of them had been created after instantiation?

I had one other problem with scaling. No matter where I put the scaling code, it always scaled each pump interation down from the previous one, so they got smaller and smaller. It looks like self.g.scale should only be called at the end, but I think this is probably related to my class and method heirarchy. Time to really start cracking the books.

Don’t spend a lot of time on this, but if you have some quick suggestions or pseudocode, I’m listening.
Pumps.fwin (48.9 KB)

0 Likes

#4

Fwiw, I noticed that I was a couple of versions behind, and after I upgraded, the screen redraws were much more fluid. In fact, I’m stunned. The screens update almost instantaneously.

I modified the screen to add a check box to switch between the slider control and a random number generator. When the number of pumps bounces around, it really shows how fast the screen updates.
Pumps.fwin (57.1 KB)

0 Likes

#5

wow - sure does paint fast.

Are you still getting the error with the new version?

0 Likes

#6

I just tried it, and I’m not getting the error now. Sorry about the confusion- the sticky note above didn’t change, so I didn’t know there were new versions released lately.

0 Likes

#7

Sorry about the lack of version announcements - I’ll post them today.

As for your code, it looks pretty nice. One suggestion I would make: Instead of instantiating a new Pump object on every iteration of the loop, I’d make one Pump, and then each iteration would simply set its name, yoffest, and x offset, and ask it to draw itself again. That would make it even faster!

0 Likes

#8

That makes more sense than what I was doing, since I can’t treat an individual pump as a single instance outside the canvas anyway. If I change one, I have to update them all.

So, I did what you suggested, but now I have a problem I was having when I was playing around with the class heirarchy last night- all of the pumps are drawn, but only the last one updates correctly. Is there something like a repaint or redraw that I should be using after the last one is drawn? Or is it because the last item that is drawn determines the order of the fill, paint, drawstring, etc for the entire canvas?
Pumps.fwin (56.7 KB)

0 Likes

#9

Wow, you’re doing some wicked stuff with Python’s free-style type system. You’re storing the constructors of various classes that you want to deal with as classs members, and then using those members to instantiate new shapes…works well enough…

…except when you make a typo. The reason all of your pump “feet” were drawing on top is that these lines:

LINE 021: self.genPath = GeneralPath() ... LINE 074: self.leg = self.genPath
Needs to be:

LINE 021: self.genPath = GeneralPath ... LINE 074: self.leg = self.genPath()

You were instantiating a single general path that was being used for all legs - so all of the pumps’ legs were one single shape, so every time you drew/filled it, it drew on top. Use the second snippet and you should be all set.

0 Likes

#10

Thanks Carl, I’ll give that a try in few minutes. I woldn’t have caught that.

Is that good or bad?

The reason I did it this way was so the Pump class could be used for any generic object (I’ll rename it from Pump to something else later on) where all of the Java2D stuff is imported, and then my defs would be reserved for specific object types (def drawPump, def drawBottle, etc). It would have worked fine by putting everything in one class, but I want to start right off by organizing things well. I kind of got the idea from a Python Game Programming book where they had a sample program for a tank battle. I’m sure I’ll be refractoring this a number of times now that it’s sort of working.

I also spent a lot of time lately with wxPython’s AUI. Good lord, I thought my head would explode, but I’m kind of getting it now. It was a good way to start understanding class structure, and now all I have to do is apply it well.

0 Likes

#11

Step7,

You’re a Colonel and you’re still making basic mistakes like that??

I would have pointed those mistakes out too, but I didn’t have a clue what you were doing :laughing:

0 Likes

#12

Umm, neither, just hadn’t seen anyone do anything quite like that before. Carry on!

0 Likes

#13

I played around wiith this a little bit tonight, and thought I’d post some ideas to see if someone else has been working with the canvas yet. Personally, I see real possibilities with this.

I started using global variables instead of passing parameters into the function/method, which kind of made sense since they are only global within the scope of this canvas anyway (right?). The pump geometry parameters are still in a list, but my next step will be to make the list a list of individual pumps, so each one could look different based on tags. Basically, I want all of my pump geometry to reside in one place, and python lists are very fast.

I also simplified the offset calculation immensely by using the java translate function. Basically, on each iteration of a new pump, the x,y orgin is shifted to a new location. I didn’t think I could do that on individual items in the canvas, but it works fine. I can see how this could be used for some pretty cool animation.

Also, I added sliders to set the number of columns and dynamic scaling. It gives a good idea of how smoothly the graphics will update. Right now, the scaling is determined when the first pump is drawn, but could be set for each individual pump as well.

There are also two check boxes. One will generate a random number of pumps each timer cycle, and overrides the pump slider. The other is my “Whack-A-Mole” check box, and randomly selects a single pump out of the current list of pumps to display. There is a method to this madness: If I can match the mouse coordinates to a specific pump, I can change the appearance of that pump on the next update and make it look like it has been affected. There’s no better way to do that than making a whack-a-mole game to see how accurate my mouse clicks are.

Anyone else playing around with this stuff? I sure would like some pointers.
Pumps.fwin (73.3 KB)

0 Likes

#14

Don’t use globals like that - they are application-global. This would probably lead to bad results if you ever had 2 windows open using the same globals.

Looking good! I’m always impressed by just how fast Jython is - and it should be: the Jython engine is actually compiling your script down to native Java bytecode on the fly. I don’t know about you, but that just gets my language-nerd side all riled up.

0 Likes

#15

Hey, by the way, this would be an appropriate place to share this. I was messing around with paintable canvas components a while ago and made a handful of more useful components (dynamic pump, valve, tank, and motor). Here they are in a custom palette.
painted_components.fpal (12.1 KB)

0 Likes

#16

Ok, I wasn’t sure about that. But, I could use that to my advantage in some places. Something to keep in mind.

I had another question. I was playing with the rotate function, but it doesn’t work like I thought it would. It seems to override the translation, and all of the pumps draw in a single place like a kaleidoscope. Is there a way to set the rotation of an individual pump with an existing java command, or would I have to manipulate the geometry before it’s passed to the function?

0 Likes

#17

No, you can certainly get rotate to do what you need - and it doesn’t overwrite the current transform - it is applied to it. See java.sun.com/j2se/1.5.0/docs/api … ics2D.html

0 Likes

#18

Those are too cool! Anyone else play with Carl’s paintable canvas palette yet? I’m particularly intrigued by all the permutations with so few lines of code. Those transforms are amazing!

Those would be a great addition to the goodies!

0 Likes

#19

Thanks for that. I solved it by using a getTransform at the beginning of each iteration, performing the rotation, and the using setTransform to recall the orginal transform. This is a little different from concatenating the transform as I draw the picture, and I’ll tackle that next. It would probably be more efficient.

[quote=“Carl.Gould”]Don’t use globals like that - they are application-global. This would probably lead to bad results if you ever had 2 windows open using the same globals.
[/quote]

Quick question: Would that only matter if the variables were not initialized within each function? I mean, if I had this in Window 1

Global s
s = 40
more code…

and I had this in Window 2

s = [1,2,3]
more code…

there is no way Window 1 could affect Window 2, right? I’m not saying what I did is good programming practice, and I won’t do it again, but I want to make sure I understand the problem.

Also, I would like to define the list of basic geormetries for each paintable component only one time at startup (maybe read it from an xml file). This list would then be available for all paintable components as needed. Should I use a Global in that case? Or is there a better way to do that? I really don’t want to have specific geometries in my code at all.

0 Likes

#20

I suppose you’re right - since the GUI is single-threaded, a global variable if properly re-initialized in a function wouldn’t be affected by use in another function.

As for where to initialize geometries, I personally would write your components, including their geometries, as Python classes in the FactoryPMI library module (i.e. “app.*”, this way they would only be defined once) and then have the actual painting code in the paintable canvas simply create instances of these classes. Encapsulate all of the shape code in the classes, so that the painting code simply creates the object, sets an X and a Y, and tells it to paint on its graphics context.

0 Likes