Skip to content

Note

3D floorplan graphics, and what can actually bind to them

A 3D floorplan is bought, not drawn in Workbench. The Niagara-side decisions are which file you ask for, whether anything on it can be bound, and what the controller pays to serve it.

  • PX graphics
  • JACE
  • Workbench

Written September 2026

Nobody draws the floorplan in Niagara

The long-running contractor threads on this are unanimous about the tooling and they never mention Niagara once. The work is done in Maya with V-Ray on GPU render cards, or in Blender, SketchUp, SweetHome3D, or traced into AutoCAD from a PDF when no CAD file exists. Turnaround from a specialist outfit runs to a couple of weeks, and the recurring conclusion is that doing it in house only pays if somebody is dedicated to it and the software subscriptions are already bought.

So the question that actually lands on the integrator is never "how do I model a building". It is three much smaller ones: what file do I ask the artist for, what on it can carry a live value, and what does the controller pay to serve it. Those have definite answers in the framework, and they are the reason the pretty version often cannot do the job it was bought for.

The format decides more than the look

Niagara's own type system draws a hard line between raster and vector. BImage is documented as "a representation of a raster image", and its byte-buffer factory says the buffer "should contain an image file in GIF, PNG, or JPEG format". SVG is not a case of that: it is its own file type, BSvgFile, serving the MIME type image/svg+xml.

What Workbench will actually open is narrower than what types exist. The image viewer ships in the raster module and is registered as an agent on exactly four file types — PNG, GIF, JPEG and SVG. WebP, BMP, APNG and ICO all have first-class file types in the same package, and nothing is registered to view them. A file the artist exported as WebP is a legitimate Niagara file with no viewer.

"This image can not be displayed due to an invalid image format." That exact sentence is a lexicon string in the raster module, and it is the message behind most broken-graphic reports. The viewer is chosen by file extension and then fails on the content, so it appears when a WebP or a TIFF has been renamed to .png, or when a browser "Save image as" produced something other than what the name claims. Check the bytes, not the name.

Which makes the artwork spec one line: PNG or JPEG for a photoreal render, SVG for anything line-based, nothing else, and a copy of the source project file for when a wall moves.

Tridium ships the same symbol set twice

There is no need to argue vector versus raster in the abstract, because both versions of the standard HVAC symbol library are in the install and can be measured against each other.

MeasureRaster kitSVG kit
Image files986 PNG + 87 GIF973 SVG
Jar on flash11.5 MB3.8 MB
Image bytes, stored11.17 MB3.47 MB
Image bytes, uncompressed11.25 MB10.9 MB

The last two rows are the interesting pair. Uncompressed, the two libraries are the same size — the vector version is not inherently less data. It is text, so it compresses inside the jar by about 3:1 while the PNGs compress by almost nothing. On a controller what lands on flash is the jar, so the same symbol set costs a third as much for being vector, and scales cleanly to whatever pixel density the operator's screen has.

The libraries are close to a one-for-one swap: 681 of the SVG kit's 772 distinct base names also exist in the raster kit, and the raster-only remainder is mostly manufacturer-branded plant — specific actuator and boiler brands. Animation survives the move too, differently: every one of the raster kit's 87 GIFs is an _On state, multi-frame, while 48 of the SVG files carry their animation inside the file as SMIL.

The exception is the 3D render itself. A photoreal image is photograph-shaped data, and tracing it into vector makes it bigger and worse. Render stays raster; everything drawn with lines should not be.

An image is not a bindable surface

This is the point the format argument is usually standing in front of. Look at how Tridium's own SVG kit makes a boiler change state in its palette: the widget is a ui:Picture, and the binding is on its image property, through a boolean-to-simple converter holding two ORDs — Boiler_Right_On.svg and Boiler_Right_Off.svg. Nothing inside the SVG is being driven. A different file is being chosen.

That is the ceiling for artwork of any kind, 3D included. A bound image can be swapped, moved, rotated, shown or hidden. It cannot have one room in it change colour, because the framework has no handle on anything inside it.

What is bindable is geometry. The stock bajaui palette has a Shape folder, and its ui:Polygon entry ships with three properties that matter here: fill, stroke and geom — a plain list of coordinate pairs, defaulted to a unit square. Path, Rect, Ellipse and Line sit beside it. A zone outline is a list of coordinate pairs, which is also exactly what a polyline in the CAD file already is.

And the colour rule is stock as well. The SpectrumBinding in the kitPx module is an agent on any bajaui:Widget; its widgetProperty defaults to fill, and it maps a numeric into a low / mid / high colour range — blue, white, red out of the box — with a setpoint of 50 and an extent of 100. Values below setpoint minus half the extent clamp to the low colour, above setpoint plus half the extent to the high colour, and everything between is interpolated channel by channel.

A zone heat map is stock, not development. One polygon per zone over the floor plan, fill bound with a spectrum binding, setpoint and extent set to the comfort band. No module, no JavaScript, no purchase. The only input it needs is the zone outlines as coordinates — which the CAD drawing has and the render does not.

What 200 zone temperatures actually cost

Every value on the sheet is one Binding, resolved asynchronously against a binder found by walking up the widget's parent tree. Two hundred values is two hundred bindings whether the sheet is beautiful or hideous, so the artwork is not where the cost is. In the Hx path the widget is handed a tree that is already mounted in a component space with its bindings active and leased; the subscription is live before anything is drawn.

Against that, a background image is nearly free. The web service's cache config caches png,jpg,gif,svg by default specifically so those stop revalidating on every request — the render is fetched once per operator and then stops being a cost at all.

The cost that does scale badly is file count. A JavaScript file registered without a build id "will always be downloaded unminified", so a widget-heavy view can make the controller answer twenty-odd requests to draw one page, for every operator who opens it. That is the number to watch, not the size of the background.

Then there is the constraint no renderer solves: 200 values that have to be read are 200 pieces of legible text. The working rule the experienced graphics people use is one sheet per wing, or a dozen like rooms, sized by whatever keeps the font readable. A whole-building view that fits 200 labels is a view where none of them can be read, in 2D or 3D.

Where the thing runs, and why the profile matters

Niagara's runtime profiles say plainly what each environment is: rt and ux are "Java 8 compact3", wb and se are "Java 8 SE". The distribution file for the controller platform in this install names its VM azul-zre-qnx71-compact3-arm32hf beside the QNX 7.1 ARMv7 core — so compact3 on the controller is in the shipped file, not an inference.

That has one sharp consequence for images: BImage documents an IllegalStateException when its buffer and pixel methods are called from a compact3 VM, and again for the SVG path. Server-side image manipulation is a Workbench and Supervisor activity. It does not stop a controller serving graphics, because the controller never draws them — the operator's browser does, and the controller only serves files and answers subscriptions.

The exception is Workbench itself. The image viewer module is built for the wb profile: when you look at a graphic inside Workbench you are looking at Workbench's own renderer and its embedded browser engine, which lags the browser on the same desk. A ux widget drawing its own canvas or SVG can therefore look right in Chrome and wrong in the PX editor, which is the usual explanation for a graphic that "works for me".

Is a heavy 3D scene on a controller-served graphic a real constraint? Honestly: mostly no, and people worry about the wrong half. A static render is one cached file and costs the controller nothing after the first request. A live WebGL or canvas scene spends the operator's GPU, not the controller's CPU. What is genuinely a controller constraint is bytes and requests: a multi-megabyte texture set fetched by six concurrent operators, and a view that pulls twenty unbundled scripts. Size the artwork and count the files; do not reject 3D on the grounds that the controller has to render it, because it does not.

One sheet, twelve floors

The reuse instinct is right and it has a limit worth naming. Relative binding is what makes one plant sheet serve forty identically shaped air handlers, and it is covered properly in the note on one PX sheet for every AHU. It does not make one floor sheet serve twelve floors, because relativising fixes the ORDs, and what differs between floors is the geometry. Twelve floors with identical point structures still have twelve different outlines.

What is reusable across floors is everything except the outline. A PxInclude embeds one PX file inside another and takes variables for ORD substitution, so the zone-detail popup, the legend, the colour rule and the header are made once and included on every floor with a different ORD passed in. Per floor you own a background and a set of zone polygons. Everything else is one file.

The coordinate system is worth setting up deliberately at the same time. A CanvasPane has a viewSize — a logical drawing space, 100 by 100 by default — and a scale property that defaults to none, with fit, fit-to-ratio and zoom modes available. Draw the plan in the plan's own units and let the pane scale it, rather than drawing the same floor three times for three screen sizes.

Is 3D the right answer?

For a view whose job is to show 200 zone temperatures: no. Three reasons, none of them aesthetic.

  • An isometric view occludes. Rooms are hidden behind other rooms, and interior zones on a lower floor of a rendered block cannot be shown at all. A view that structurally cannot display part of its data set is the wrong view for that data set.
  • It has one camera, so it has one scale. Zones far from the camera are smaller, and their labels are the ones that become unreadable first. Plan view gives every room the same treatment.
  • The geometry is not addressable. The live layer has to be drawn on top as shapes anyway, and those shapes only line up from that one viewpoint. Move a wall and you are back with the artist rather than editing a coordinate list — which is precisely the maintenance worry raised in those threads, and it is well founded.

What 3D is genuinely good at is being looked at once by the person signing for the job. That is a real requirement, not vanity: the one who pays is impressed by the render in the presentation and then expects the delivered system to look like the demo. So buy the render, once, and spend it where it earns — the site landing view, the proposal, the handover pack.

The working views underneath it are plan-view and bound: a floor outline taken from the CAD file, one polygon per zone, fill on a spectrum binding, labels only where a number must actually be read, and detail behind a click. That version is editable by your own engineers, survives a wall moving, and is cheaper to build than the thing it replaces. The honest cost of insisting on 3D everywhere is that every zone becomes artwork, and every layout change becomes a purchase order.

The graphics question that arrives as "who makes 3D floorplans" is usually a standard-sheet question wearing a costume. Plant views built once and pointed at every unit are PX graphics and standard sheets; a view that has to be responsive, branded, or do something no stock widget does is a bajaux widget. The two notes behind this one cover relative ORDs, which decide how many sheets you maintain, and what actually runs on a JACE, which decides what the controller can afford to serve.

Related

Where this comes up in the work

More notes

Other things worth writing down

Next step

Tell us the version, the hardware, and what it has to do.

You will get a written scope and a fixed price against it. If the honest answer is that you do not need us, you will get that instead.