Skip to content

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

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

Ski90Moo wants to merge 3 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.
  • Move Central Heat Pump System into Plant Loop Heat Pumps in the Library, matching My Model, and remove the separate "Central Heat Pump Systems" category.

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. Dropping it from My Model onto another loop moves that mode there.
  • 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.
  • HeatPumpAirToWaterType { Wrapper, Heating, Cooling } is looked up by IddObjectType. Deleting from a loop, the loop drawing and the inspector check it first, so other objects skip the wrapper lookup.
  • 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 opens an Edit tab that shows, on one scrolling page, the owning HeatPump:AirToWater, then the selected mode, then the other mode, each with its speed data. 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.
  • Curves come from EnergyPlus example files:
    • HeatPump:AirToWater: the speed 1 heating and cooling curves from PlantLoopHeatPump_EIR_AirSource_and_AWHP.idf. The cooling mode is rated at the EnergyPlus defaults of 30 °C inlet air and 8 °C leaving water.
    • Air-source HeatPump:PlantLoop:EIR: the heating and cooling capacity and EIR curves from PlantLoopHeatPump_EIR_AirSource_Hospital_wSixPipeHeatRecovery.idf, instead of the SDK's flat 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/, images/svg/)

  • 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. Both now show f(x) to mark them as equation fit.
  • Air-source units show propeller fans inside the cowling, matching the existing air-cooled chiller icon.
  • The SVG sources are in src/openstudio_lib/images/svg/, with a README on exporting them to PNG. The app doesn't load them.

Translations

  • New and renamed strings were added to all OpenStudioApp_*.ts files, and the removed "Central Heat Pump Systems" string was deleted from them. I edited 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):
    • each of the three object types is identified by IddObjectType, and unrelated objects are not;
    • the suggested mode follows the loop's sizing type;
    • one wrapper's two modes can be placed on separate loops;
    • a missing mode, or Wrapper as the mode to place, 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: 14/14 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, and Central Heat Pump System is in Plant Loop Heat Pumps;
    • the Edit tab shows the wrapper, the selected mode, then the other mode, for both heating and cooling loops;
    • the wrapper can be dropped from the Library and from My Model onto a supply-side "Drag From Library" box, and a placed mode can be moved to another loop;
    • drops on the demand side or an air loop are refused with the usual message;
    • ✕ disconnects the first mode and deletes the whole unit with the last.

Notes for reviewers

🤖 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. The water-to-water heating and cooling icons now show f(x) next to the heat waves or snowflake, and the mini icons have a smaller f(x) above the colour band.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Central Heat Pump System is now in Plant Loop Heat Pumps in the Library as well, matching My Model, and the separate "Central Heat Pump Systems" category is gone.

Comment thread src/openstudio_lib/InspectorView.cpp Outdated
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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. The Edit tab now shows the HeatPump:AirToWater first, then the mode you selected, then the other mode, all on one page.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Could you share the steps you used? We tested these drops on the "Drag From Library" box on a plant loop's supply side:

  • from the Library and from My Model, each time asking for Heating or Cooling;
  • placing heating and cooling on separate loops;
  • moving a mode that's already placed to another loop, which removes it from the first loop.

All of these worked, and the unit tests cover the splitter case. Dropping on the demand side or on an air loop shows the usual "not allowed" message.

}

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I replaced HeatPumpAirToWaterMode with HeatPumpAirToWaterType { Wrapper, Heating, Cooling } and added heatPumpAirToWaterType(), which switches on the IddObjectType. heatPumpAirToWaterForMode() and removeHeatPumpAirToWaterModeFromLoop() check it first. So deleting from a loop, the loop drawing and the inspector only look up the wrapper for the two mode types. I added two tests for it.


! 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That file only contains HeatPump:AirToWater objects, so I used its CapCurveFuncTempHT/CL and EIRCurveFuncTempHT/CL for the library's air-to-water unit. Before, the SDK's defaults gave the cooling mode the heating curves and heating rating conditions (8 °C inlet air, 40 °C leaving water); it now uses the EnergyPlus defaults of 30 °C and 8 °C. The curve you commented on belongs to the HeatPump:PlantLoop:EIR air-source units, which had the SDK's flat curves. They now use the heating and cooling capacity and EIR curves from PlantLoopHeatPump_EIR_AirSource_Hospital_wSixPipeHeatRecovery.idf, the EnergyPlus example with fitted curves for air-source EIR heat pumps. I've also opened an SDK issue for the cooling defaults: NatLabRockies/OpenStudio#5675

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch BTW.

- Move Central Heat Pump System into the Plant Loop Heat Pumps library
  category and drop the separate category (and its translations)
- Replace HeatPumpAirToWaterMode with HeatPumpAirToWaterType
  { Wrapper, Heating, Cooling } and look it up by IddObjectType, so
  non air-to-water objects skip the wrapper lookup
- Inspector for an air-to-water mode shows the wrapper, the selected
  mode, then the other mode
- Library HeatPump:AirToWater uses the speed 1 heating and cooling
  curves from EnergyPlus PlantLoopHeatPump_EIR_AirSource_and_AWHP.idf
  and the EnergyPlus cooling rating defaults (30 C air, 8 C water);
  the SDK defaults gave the cooling mode heating values
  (NatLabRockies/OpenStudio#5675)
- Library air-source HeatPump:PlantLoop:EIR units use the capacity and
  EIR curves from PlantLoopHeatPump_EIR_AirSource_Hospital_wSixPipeHeatRecovery.idf
  instead of flat curves
- Add f(x) to the water-to-water equation fit icons
- Add the SVG sources for the Plant Loop Heat Pump icons

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

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Added the SVG sources in src/openstudio_lib/images/svg/, with a README on exporting them to PNG. The review changes are in db185d0.

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