Skip to content

Plant Loop Heat Pumps: missing types, library defaults, and icons - #896

Open
Ski90Moo wants to merge 2 commits into
openstudiocoalition:developfrom
Ski90Moo:feature/library-plantloop-eir-heatpumps
Open

Ski90Moo wants to merge 2 commits into
openstudiocoalition:developfrom
Ski90Moo:feature/library-plantloop-eir-heatpumps

Conversation

@Ski90Moo

@Ski90Moo Ski90Moo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Plant Loop Heat Pumps: missing types, library defaults, and icons

Problem

Several plant-side heat pump types supported by the SDK could not be dragged from a library or from My Model, because the HVAC Library and My Model lists didn't include them. For example, an OS:HeatPump:PlantLoop:EIR:Heating in a custom library OSM did not appear anywhere. The existing "Heat Pumps" category name was also vague, since the app has many non-plant heat pumps.

Changes

Library / My Model lists (MainRightColumnController.cpp)

  • Rename the "Heat Pumps" category to "Plant Loop Heat Pumps".
  • Add the missing types:
    • HeatPump:PlantLoop:EIR:Heating / :Cooling
    • HeatPump:AirToWater:FuelFired:Heating / :Cooling
    • HeatPump:AirToWater. Both lists show only the wrapper; its operation modes are not listed separately.

Placing HeatPump:AirToWater (HVACSystemsController.cpp, new utilities/HeatPumpAirToWaterPlacement)

  • The wrapper can't be added to a node; its heating or cooling operation mode is what goes on the plant supply side.
  • When the wrapper is dropped on a plant loop, the app asks whether to place the Heating or Cooling mode, with the default chosen from the loop's sizing type. That mode is then added to the node or supply splitter. Dragging the wrapper from the Library clones both modes, so you can place the other mode on a second loop from My Model.
  • The SDK marks a mode owned by a wrapper as not removable, so the loop diagram had no delete button for it. The mode now gets one. Clicking it takes the mode off that loop, the same way a water-to-water unit on two loops is disconnected rather than deleted. The wrapper and its other mode stay in the model. When the mode removed is the last one on a loop, the whole unit is deleted: the wrapper, both modes, their speed data, curves and lists. The GUI offers no other way to delete the wrapper.
  • The placement logic lives in utilities, separate from the UI, so it can be unit tested.

Editing HeatPump:AirToWater (InspectorView.cpp)

  • Selecting a heating or cooling mode on a loop now opens an Edit tab that shows the owning HeatPump:AirToWater first, then the selected mode with its speed data, on one scrolling page. Selecting a mode that has no wrapper keeps the standard view.

Default library instances (hvac_library.osm)

  • Adds an air-source EIR Heating unit and an air-source EIR Cooling unit, a fuel-fired Heating unit and a fuel-fired Cooling unit, and a HeatPump:AirToWater wrapper with Heating and Cooling modes (one speed each), plus their curves.
  • These use the library's existing "Always On Discrete hvac_library" schedule.
  • Checked by copying each into a fresh model, placing it on HW/CHW loops, and forward translating: 0 errors and 0 warnings, and HeatPump:AirToWater is emitted.

Icons (IconLibrary.cpp, openstudio.qrc, images/, images/mini_icons/)

  • New icons for EIR Heating/Cooling, Fuel Fired Heating/Cooling and Air to Water. Each comes in large (100/200px) and mini (24/48px) sizes.
  • The Air-to-Water wrapper and its two modes share one icon because they are effectively one piece of equipment.
  • The existing Water-to-Water icons were redrawn in the same style. Heating and Cooling previously used near-identical images; Cooling now shows a snowflake.
  • Air-source units show propeller fans inside the cowling, matching the existing air-cooled chiller icon.

Translations

  • New and renamed strings were added to all OpenStudioApp_*.ts files. I inserted them by hand rather than running a full lupdate pass, because develop's .ts files are out of sync with the sources and lupdate would add about 26k lines of unrelated location changes. A check run of lupdate found no missing strings, and lrelease reports 0 unfinished.

Before:
Screenshot 2026-10-12 124613
After:
Screenshot 2026-10-12 124628

Tests

  • HeatPumpAirToWaterPlacement_GTest (new):
    • the suggested mode follows the loop's sizing type;
    • one wrapper's two modes can be placed on separate loops;
    • a missing mode is rejected;
    • placement on the demand side is rejected;
    • a target that is neither a node nor a splitter is rejected;
    • a mode finds its wrapper, while the wrapper itself, an orphaned mode, and unrelated objects do not;
    • deleting a mode while the other is still on a loop disconnects it from its own loop only and keeps it on the wrapper;
    • deleting the last mode on a loop removes the wrapper, both modes, and their speed data, whether the other mode was removed first or never placed.
  • IconLibrary_GTest (2 new tests):
    • the new types don't fall back to the Catchall icon;
    • the Air-to-Water wrapper and its modes share an image;
    • each Heating icon differs from its Cooling icon.
  • Local Windows release build, no warnings:
    • openstudioapp_utilities_tests: 12/12 passed.
    • openstudio_lib_tests: everything passed except the known baseline AnalyticsHelperSecrets, which needs CI secrets.
  • Manually checked in the app:
    • the new icons display correctly;
    • the Edit tab shows the wrapper above the selected mode;
    • ✕ disconnects the first mode and deletes the whole unit with the last.

Notes for reviewers

  • The SVG sources for the new icons are not included in this PR; I'm keeping them locally until we decide whether they should be published.
  • The EIR defaults are air source only. Water-source variants could be added later.

🤖 Generated with Claude Code

Ski90Moo and others added 2 commits October 12, 2026 10:30
Rename the "Heat Pumps" Library/My Model category to "Plant Loop Heat
Pumps" and list the plant heat pump types the app was missing:
HeatPump:PlantLoop:EIR Heating/Cooling, HeatPump:AirToWater:FuelFired
Heating/Cooling, and HeatPump:AirToWater (wrapper plus its Heating and
Cooling operation modes in My Model).

Dropping a HeatPump:AirToWater wrapper on a plant loop asks whether to
place its heating or cooling mode (preselected from the loop's sizing
type) and places that mode, since the wrapper itself cannot go on a
node. Placement logic lives in utilities/HeatPumpAirToWaterPlacement
with unit tests.

Add default instances of the new types to hvac_library.osm (EIR air
source, fuel-fired, and an air-to-water wrapper with one speed per
mode); verified to forward translate cleanly.

Add icons for the new types and redraw the water-to-water icons so
heating and cooling differ. The air-to-water wrapper and its modes share
one icon. Icon tests check distinctness and no Catchall fallback.

Translations: new strings hand-inserted into all .ts files.

Note: the SVG sources for the new icons are held outside the repo for
now, pending a decision on whether they should be published.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
My Model now lists only the HeatPump:AirToWater wrapper, not its
heating and cooling operation modes.

Selecting a mode on a loop opens an Edit tab that shows the owning
HeatPump:AirToWater first, then the selected mode with its speed data,
on one scrolling page (new HeatPumpAirToWaterModeInspectorView).

A mode owned by a wrapper is not removable on its own in the SDK, so the
loop diagram showed no delete button for it. It now gets one: clicking
it takes the mode off that loop, and when neither mode is left on a loop
the wrapper is removed along with both modes, their speed data, curves
and lists, since the GUI has no other way to delete it.

Translations: drop the two mode strings that are no longer listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@macumber macumber left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I left a few comments. I think it would be good to have the svgs so I would add them if you can.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think adding something like f(x) or something would be good to indicate the equation fit? I'll leave it up to you, just wanted to suggest it.

libraryWidget->addModelObjectType(IddObjectType::OS_HeatPump_AirToWater_FuelFired_Heating, tr("Heat Pump - Air to Water Fuel Fired - Heating"));
libraryWidget->addModelObjectType(IddObjectType::OS_HeatPump_AirToWater_FuelFired_Cooling, tr("Heat Pump - Air to Water Fuel Fired - Cooling"));
libraryWidget->addModelObjectType(IddObjectType::OS_HeatPump_AirToWater, tr("Heat Pump - Air to Water"));
libraryWidget->addModelObjectCategoryPlaceholder(tr("Plant Loop Heat Pumps"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it doesn't matter but shouldn't all the "Plant Loop Heat Pumps" be together?

m_gadgetsLayout->insertWidget(insertIndex++, gadget);
};

// The wrapper's own children are both operation modes, so hide them and show only the selected mode

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like this, wouldn't it be useful to see both children without having to switch views? Maybe you could order them so the current one is first?

return modeComponent->addToNode(node.get());
}

if (auto splitter = target.optionalCast<model::Splitter>()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't get this option to work?

}

bool removeHeatPumpAirToWaterModeFromLoop(const model::ModelObject& modelObject) {
boost::optional<model::HeatPumpAirToWater> heatPump = heatPumpAirToWaterForMode(modelObject);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is any way to optimize this it would benefit HVACLayoutController::removeModelObject, I think comparisons based on IddObjectType would be faster.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe replacing HeatPumpAirToWaterMode with an enum that includes the wrapper and doing a lookup based on IddObjectType would give a better option than calling heatPumpAirToWaterForMode when you just want to know if you are dealing with one of the three types?


! Plant Loop Heat Pumps

OS:Curve:Biquadratic,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants