Board index FlightGear Development Canvas

Canvas Help - Warning Panel

Canvas is FlightGear's new fully scriptable 2D drawing system that will allow you to easily create new instruments, HUDs and even GUI dialogs and custom GUI widgets, without having to write C++ code and without having to rebuild FlightGear.

Canvas Help - Warning Panel

Postby Avionyx » Thu Feb 18, 2021 4:41 pm

Hi All,

After years wrestling with canvas, despite lots of help, I'm going to commit some more time to a project which, to my simple mind, should be quite easy.

I've created a nice flat .ac file to have as my gauge base (object is named CanvasInstrumentFace in Canvas.ac)

I would like to make the Object called "GEAR" visible if the property "/controls/gear/gear-down" is true

Image

I've referenced Richard's instrument.nas from his excellent tutorial here: http://chateau-logic.com/sites/default/ ... rument.nas

How do I actually achieve this? If I can make it happen then I can save around 4 weeks worth of work creating individual 3d modelled objects, texturing them and then having them animated for a warning panel....it seems like Canvas is the perfect tool for the job but is it as easy as I think it should be? Any help would be hugely appreciated. Would like to keep it on here if possible because believe me I've read through the Wiki over and over again and just can't grasp it from there.

Many thanks!

Alex
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Hooray » Thu Feb 18, 2021 4:53 pm

Yes, Canvas is the right tool, and it can be easily done.
However, for starters, you should definitely consider ignoring the panel/cockpit portion of the work, since that's just 3 lines of code (the so called placement, i.e. the location where the canvas/texture is to be shown).

What you need to know is that a Canvas is just a texture, however rather than a static one created by an image editor, it's a dynamic one.
It can be created/registered and provided by FlightGear using Nasal scripting.
So basically you tell FlightGear that you need an empty texture with certain dimensions and a certain background color.
Then you have an empty placeholder which you can draw into.

For drawing to happen, there are drawing primitives which are to be used to draw different kinds of things into the texture (images, text strings, paths).

However, since a Canvas is a scene graph, the top-level element always is a root group - which is then used to register any child elements underneath.
This is analogous to how the property tree works.

So basically, you set up an empty canvas (for instance shown in a GUI dialog), then you add a root group, and then you can add a Canvas text element.

But again, for any of this to make sense to you, do yourself the favor and ignore your cockpit/panel, since the work is 99,9% identical - and you have a much more rapid development cycle by using a GUI dialog instead as the test bed for your project.
Indeed, you can do all of this using the Nasal console and some copy & paste:

https://wiki.flightgear.org/Nasal_Console
https://wiki.flightgear.org/Canvas_Snip ... GUI_Window
https://wiki.flightgear.org/Canvas_Snip ... t_Elements
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Canvas Help - Warning Panel

Postby Avionyx » Thu Feb 18, 2021 5:40 pm

Hi Hooray,

Many thanks for the detailed advice, it's much appreciated and I will take your point and not worry about making it a cockpit/panel implementation until it works.

so in that vein I've got this far.....excuse me if I'm being too simplistic but how would I set this so it only shows if a property was populated?

Image

Code: Select all
var (width,height) = (320,160);
var title = 'My new Window';

# create a new window, dimensions are WIDTH x HEIGHT, using the dialog decoration (i.e. titlebar)
var window = canvas.Window.new([width,height],"dialog")
 .set('title',title);


##
# the del() function is the destructor of the Window
# which will be called upon termination (dialog closing)
# you can use this to do resource management (clean up timers, listeners or background threads)
#window.del = func()
#{
#  print("Cleaning up window:",title,"\n");
# explanation for the call() technique at: http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls
#  call(canvas.Window.del, [], me);
#};

# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));

# Using specific css colors would also be possible:
# myCanvas.set("background", "#000000");

# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();

var myText = root.createChild("text")
      .setText("Amazing, it works")
      .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio
      .setColor(1,0,0,1)             # red, fully opaque
      .setAlignment("center-center") # how the text is aligned to where you place it
      .setTranslation(160, 80);     # where to place the text
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Hooray » Thu Feb 18, 2021 5:56 pm

Every canvas element has its own hide/show methods, see $FG_ROOT/Nasal/canvas/api.nas for details

You can then trigger those methods using a listener/timer as needed.
You could for example use a callback that is invoked whenever the property is changed, let's call that gearMonitor:

Code: Select all
var gearMonitor = func(node) {
print("gear property updated\n");
}

var path = "your property path";
var myListener = setlistener(path, gearMonitor);


You can change the print statement to update the canvas text node instead, by accessing "myText" inside that callback (assuming that you set up the monitor/listener at the bottom of your code)

Once you have that working, the next step will be replacing the text node with a texture/image node, i.e. for all the different flags you want to show (unless you want to continue using text strings instead of images for the annunciator)

EDIT: As an aside, once you have this responding to your properties as needed, it really is trivial to switch to a different canvas drawing mode (say to replace the text node with a static image/texture instead, or use a texture lookup to use a shared texture as a texture map).

However, the most common way to do this is typically using Canvas.Path based drawing - which makes it easy to create your annunciators via an SVG editor like Inkscape. This is basically GiMP for vector graphics.

But again, it's best to continue "as is" - switching from text drawing to raster images or to vector images, really is very easy to do. And the underlying boilerplate will remain the same.

Suffice to say you WILL NOT need to texture your object - instead, the texture will be replaced/provided by the Canvas system.

The general workflow would be such that you set up an empty texture inside the cockpit/panel, which is to be replaced by a dynamic/canvas one.
Then, your canvas gets certain dimensions/background color and a top-level root group.
The root group can then be populated with a handful of drawing primitives.
Typically, you would prepare your artwork in inkscape, and assign different IDs to each element that you want to control (hide/show/update/animate) independently.
Then, in Nasal/Canvas space, you would load the SVG file and obtain a handle to each SVG ID.
Once you have a handle, you can independently control those elements as needed.
That's a rough overview, I may have missed/skipped some of the more obvious steps, but I am sure we can work out everything here
Last edited by Hooray on Fri Feb 19, 2021 9:46 am, edited 1 time in total.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Canvas Help - Warning Panel

Postby Avionyx » Fri Feb 19, 2021 8:59 am

Hi Hooray,

Many thanks for all this so far. I really appreciate the time you're putting in to helping me learn.

I've constructed this with my property as advised and have put the listener at the bottom but am getting a parse error on the line with the property, where have I gone wrong? (For clarity it's the "gear/servicable" property I was trying to call. No leading/trailing slashes as that's how I'd normally do it in Nasal but I've also tried with/without the slashes at both ends to see if that helped but no dice.

Thanks once again.
Alex

Code: Select all
var gearMonitor = func(node) {
print("gear property updated\n");
}

var path = "gear/serviceable"
var myListener = setlistener(path, gearMonitor);
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Warty » Fri Feb 19, 2021 9:05 am

Missing semicolon?
User avatar
Warty
 
Posts: 378
Joined: Sun Mar 29, 2015 7:53 pm
Location: Spain
Callsign: Warty
Version: 2020.4.0
OS: Mac OS 13

Re: Canvas Help - Warning Panel

Postby Hooray » Fri Feb 19, 2021 9:44 am

right, my bad - I posted the pseudo code without testing anything, there needs to be a final semicolon after the path variable. Sorry about that.

Again, not your mistake but mine - this is the kind of "trivial" thing for which I wouldn't normally fire up fgfs, so there you have it :D
I will now edit my original posting to remove the mistake

PS: Once you have that working, it's worth noting that the "monitor" callback will be triggered whenever the property is updated - so, you'd need an additional check inside to differentiate between serviceable 0/1 - as it doesn't currently make a difference.
The monitor is just a way to get a notification, but to actually process the notification, you also need to look at the original property - either using geptrop() or by using the "node" variable passed to the monitor function.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Canvas Help - Warning Panel

Postby Avionyx » Fri Feb 19, 2021 12:35 pm

Ok we're making real progress here. Thank you.

What I've done though is seemed to ignore the listener entirely. I have something that works now so whenever it's executed it returns the correct value at the time of running....but it doesn't update.

I understand what I've done.... but I don't know how to fix it, perhaps because I've taken the wrong approach? Apologies if I have, I was trying to work out the next step for myself and gone totally wrong :shock:

Code: Select all
var (width,height) = (320,160);
var title = 'Gear Monitor';

var window = canvas.Window.new([width,height],"dialog")
 .set('title',title);
var myCanvas = window.createCanvas().set("background", "#000000");
var root = myCanvas.createGroup();
     
      var gearMonitor = func(node) {
print("gear property updated\n");
}

var path = "gear/serviceable";
var myListener = setlistener(path, gearMonitor);

if (getprop("gear/serviceable")) {
var myText = root.createChild("text")
      .setText("GEAR OK")
      .setFontSize(20, 0.9)     
      .setColor(1,0,0,1)         
      .setAlignment("center-center")
      .setTranslation(160, 80);   
} else {
var myText = root.createChild("text")
      .setText("GEAR INOP")
      .setFontSize(20, 0.9)         
      .setColor(1,0,0,1)     
      .setAlignment("center-center")
      .setTranslation(160, 80);
}
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Hooray » Fri Feb 19, 2021 1:07 pm

right, that would be the expected/normal behavior, because the code setting the text is only executed once - what you want it to do is to register a so called listener, which is basically called by the property tree when your particular property is being updated.

You should already seeing the right behavior when watching the console, i.e. update notifications being shown there ?
If so, the next step is taking your conditional block at the bottom of the code snippet and moving that right into the monitor function.

(basically, you can replace my print statement with your whole if/else block as is)

That will ensure that your code is also executed whenever the property is updated
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Canvas Help - Warning Panel

Postby Avionyx » Fri Feb 19, 2021 2:30 pm

Great! That seems to now be updating when the property is changed. Just what I wanted.

I (of course, and please accept my apologies for the endless questions) have another couple of behaviours I wasn't expecting.

This is how the script now looks:

Code: Select all
var (width,height) = (320,160);
var title = 'Gear Monitor';

var window = canvas.Window.new([width,height],"dialog")
 .set('title',title);
var myCanvas = window.createCanvas().set("background", "#000000");
var root = myCanvas.createGroup();
     
      var gearMonitor = func(node) {
if (getprop("gear/serviceable")) {
var filename = "/Aircraft/Westland_WS70/Models/Interior/Panels/Instruments/Canvas/Canvas.svg";
var svg_symbol = root.createChild('group');
canvas.parsesvg(svg_symbol, 2fueldark);
svg_symbol.setTranslation(width/2,height/2);
#svg_symbol.setScale(0.2);
#svg_symbol.setRotation(radians) 
} else {
var myText = root.createChild("text")
      .setText("GEAR")
      .setFontSize(20, 0.9)         
      .setColor("#00ff00")     
      .setAlignment("center-center")
      .setTranslation(160, 80);
}
}

var path = "gear/serviceable";
var myListener = setlistener(path, gearMonitor);



Point 1) It now doesn't show anything on execution. It will only show once the property has changed from how it is when the script is first executed. So I have to change the property to true/false to trigger it but once I've done that it prints the right text each time

Point 2:
I've tried to change it from text to a SVG element. I have created the SVG file and within that got an object called "2fueldark" which I'd like to be shown instead of the text. I'm getting a parse error on this line:

Code: Select all
canvas.parsesvg(svg_symbol, 2fueldark);


which I imagine is down to my misunderstanding of where I need to put the object name. Which I got by taking this snippet here: https://wiki.flightgear.org/Canvas_Snip ... tor_Images

Your ongoing help is hugely appreciated. I'm genuinely rather enjoying this journey of discovery.

Alex
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Hooray » Fri Feb 19, 2021 3:02 pm

Disclaimer: I am not familiar with the functionality/panel/aircraft you want here, so you may need to fill in some more details, so that we can provide an adequate solution.

That being said, the most "proper" (flexible) way is indeed using Canvas.Path in conjunction with SVG files. However, if you don't need that much flexibility, you can also just as well use a conventional texture map pre-created separately (say using GIMP).
MFDs and more sophisticated displays with a lot of keyboard/menu functionality are usually created using SVGs.
More flexible displays (moving map etc) are best created by procedurally drawing stuff, i.e. using Canvas.Path directly rather than through the SVG route and the parser.

If all you need is a matrix of say 10x10 warning/fault lights with mostly static behavior (say fixed positions, texts, varying colors) a texture map will suffice.
If you need texts and fonts to change in position, color, apperance (animation), Canvas.Text is your friend


That being said, here are some observations (without having actually executed your code, just based on skimming through the code):

- variable names cannot begin with a number: 2fueldark is thus not a good variable/id name
- if you want to show stuff on execution, you need to call the monitor once directly - i.e. after setting up the listener, or register a listener that fires once during registration (see the setlistener docs for details)
- note that for testing purposes, you can simply open the property browser and toggle properties there by CTRL + clicking them
- alternative,y for testing, you could also add a button/event listener to your dialog to do just that, again, just for testing/rapid prototyping (it will save you a ton of time that you can then use to tinker with other things)


Again, changing the name of your SVG ID/variable name would be a good idea - you seem to be mostly set apart from that.
I must admit however, that I didn't expect you to go directly down the SVG path (which I do think is the right thing to do, but the learning curve certainly is somewhat steeper than the texture map approach - unless you already happen to be familiar with SVG/XML or Inkscape of course).

Besides, SVG IDs should be enclosed in parentheses: "myid"

Also, thanks for following up on things so quickly - obviously, it would be greatly appreciated if you could help us review/improve the wiki docs to hopefully make them more accessible.
Alternatively, maybe you'd like to consider writing your own little Canvas tutorial based on your journey here ?
I will be there to provide code snippets, examples and answers, and you could create a simple wiki based tutorial with screen shots to work people through the whole process.
What do you think ?
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Canvas Help - Warning Panel

Postby Avionyx » Fri Feb 19, 2021 4:03 pm

Hi Hooray,

Thank you for that. First of all I do certainly intend on making a tutorial of some sort out of this. I'd also like to add some of the snippets to the Wiki.
The Wiki along with your help has been a wonderful resource but, from my view, it's lacking in actual working examples that contain enough to understand how it might be translated. Anything I can do to help others in their Canvas quest is most certainly on the cards and is the least I can do.

The only issue I've had so far is It's not picking up my SVG object. I'm getting an error. Here's my code:

Code: Select all
var filename = "Aircraft/Westland_WS70/Models/Interior/Panels/Instruments/Canvas/Canvas.svg";
var svg_symbol = root.createChild('group');
canvas.parsesvg(svg_symbol, "fuelbrighttwo");
svg_symbol.setTranslation(width/2,height/2);
#svg_symbol.setScale(0.2);
#svg_symbol.setRotation(radians) 
}


"Nasal runtime error: File not found fuelbrighttwo" - that's what is showing in the Nasal console. I have an SVG with a single object in it. The ID of that object is "fuelbrighttwo" is that not what I should be using?

You are entirely right in that, for this purpose, I'm creating a simple warning panel that needs to have warning lights a different colour depending on the property value. However I really want to learn the SVG method of doing things because I'd like to move on to other canvas gauges next and I would prefer to learn the more useful method from the start rather than making things simpler just for speed.
Hope that makes sense.

All the best,
Alex
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Hooray » Fri Feb 19, 2021 5:00 pm

Ok, some conceptual things first:

- hard coding paths is not a good idea - imagine someone installs your aircraft inside a different location
- this is why you should get a handle to the aircraft root using something like getprop("/sim/fg-aircraft") (best to check the property browser/docs, this is off the top of my head and could be wrong)

If however your current path (stored inside filename) is definitely correct, you could pass that to the canvas.parsesvg() function, since that takes a path to the file as 2nd argument, and the root group into which to read/load the SVG file.

It is only afterwards that you'd be obtaining a handle to the various elements (IDs) inside that SVG file: https://wiki.flightgear.org/Canvas_SVG_ ... ic_example

So, the first argument is the root group to be used for storing the SVG hierarchy, and the 2nd is a proper path + file name

Once that is working without any errors, your next step is getting a handle to the new Canvas.Path element from the SVG file by using the getElementById() method on the corresponding root group, this takes the ID of the SVG element as its only argument:

var someFlag = yourGroup.getElementById(yourID);

So your only problem is a conceptual one: you seem to be thinking that the parsesvg returns a SVG element ID, but it returns a whole group of elements, which is why you need to fetch each symbol separately.

It would definitely be appreciate if you could document your journey via the wiki (or maybe even using some screen shots/youtube tutorial) - I am not in a good position to write tutorials from scratch, because I take too much background knowledge for granted, since I already touched/wrote most of the Canvas stuff on the wiki - and it's definitely not beginner friendly that way...

And yes, using the Canvas/SVG approach is definitely the right approach given your goals. Also, it's really not that complicated - you are only missing a handful of steps right now. And I am sure we can work them out quickly.
We could even generalize your code so that you can reuse it for other avionics.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Canvas Help - Warning Panel

Postby Avionyx » Fri Feb 19, 2021 5:32 pm

I understand your point about hard coding paths, I'll absolutely remove those and go for a more flexible option once I've got this working.

This is where I'm at now, which I think reflects your comments correctly?

Code: Select all
var (width,height) = (320,160);
var title = 'Gear Monitor';

var window = canvas.Window.new([width,height],"dialog")
 .set('title',title);
var myCanvas = window.createCanvas().set("background", "#000000");
var root = myCanvas.createGroup();

     
      var gearMonitor = func(node) {
if (getprop("gear/serviceable")) {

var svg_symbol = root.createChild('group');
canvas.parsesvg(svg_symbol, "Aircraft/Westland_WS70/Models/Interior/Panels/Instruments/Canvas/Canvas.svg");
var msg = svg_symbol.getElementById("fuelbrighttwo");
} else {
var myText = root.createChild("text")
      .setText("GEAR")
      .setFontSize(20, 0.9)         
      .setColor("#00ff00")     
      .setAlignment("center-center")
      .setTranslation(160, 80);
}
}

var path = "gear/serviceable";
var myListener = setlistener(path, gearMonitor);


The error I'm now getting is:
Code: Select all
[INFO]:nasal      cannot change immutable string:


Which I think from reading this thread here: viewtopic.php?t=38619&p=380066

Is probably because my SVG file is not constructed right?
If that's the case then it feels like progress but I'd just like someone to eyeball the code and check I've not messed up there before I go digging in the SVG.

Many thanks,
Alex
Avionyx
 
Posts: 531
Joined: Mon Jan 11, 2010 4:07 pm
Location: EGMD
Callsign: G-AVYX
Version: 2020.4
OS: Manjaro

Re: Canvas Help - Warning Panel

Postby Hooray » Fri Feb 19, 2021 5:48 pm

can you tell us what line the error is referring to ? And if it's inside Nasal (console) or inside the SVG file ?
If in doubt, also share your XML file here - in fact, it might make sense to use a really trivial example to get going, see my example at the bottom of the page here: https://wiki.flightgear.org/SVG

Code: Select all
<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
  <rect id="myrect" x="10" y="10" height="130" width="500" style="fill: #000000"/>
</svg>


In general, you can have SVG/XML files validated online, which only takes a second: https://validator.w3.org/#validate_by_input

PS: Once you have this working, I would suggest to rename your variables because they're still carrying around your original assumptions: what you named "svg_symbol" is actually a group of symbols - this may cause further misconceptions down the road
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Next

Return to Canvas

Who is online

Users browsing this forum: No registered users and 4 guests