Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@ profile/

*.sublime-workspace

# Qt-generated files
src/openstudio_lib/moc_*.cpp
# Qt moc/rcc-generated files, wherever CMake drops them in the tree

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 is also happening because you are building in-source.

moc_*.cpp
moc_*.cpp_parameters
qrc_*.cpp
*.qrc.depends
**/__/
*_autogen/
.lupdate/
.qt/

*.sublime-project
cmake-build-debug
Expand All @@ -45,3 +52,61 @@ clang_format.patch
conan-cache
.ccache
CMakeUserPresets.json

# This repo is built in-place at the repo root (not an out-of-source build/ dir), so

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 suggest not building in-source

# CMake/Conan/MSBuild drop generated files alongside tracked source. Ignore those.
/CMakeCache.txt
/CMakePresets.json
/CPackConfig.cmake
/CPackSourceConfig.cmake
/cmakedeps_macros.cmake
/conan_toolchain.cmake
/conanbuild.bat
/conanbuildenv-*.bat
/conandeps_legacy.cmake
/conanrun.bat
/conanrunenv-*.bat
/deactivate_conanbuild.bat
/deactivate_conanrun.bat
/*-Target-release.cmake
/*-release-x86_64-data.cmake
/*Config.cmake
/*ConfigVersion.cmake
/*Targets.cmake
/*-config.cmake
/*-config-version.cmake
/Find*.cmake
/module-*.cmake
/get_output_vars.rb
/output_vars_list.txt
/aqtinstall.log
/build_output.txt
/Products/
/_deps/
/OpenStudio-3.11.0/

# CMake-generated Visual Studio projects/solution, wherever they land
CMakeFiles/
cmake_install.cmake
*.vcxproj
*.vcxproj.filters
*.sln

# Compiled translation binaries (built from the tracked .ts sources)
translations/*.qm

__pycache__/

# EnergyPlus local-docs workflow output (see ENERGYPLUS_DOCS_WORKFLOW.md, itself gitignored)
/ENERGYPLUS_DOCS_WORKFLOW.md
/EnergyPlus/
/EnergyPlusDocsArchive/

# configure_file()-generated from tracked .in templates; AnalyticsHelperSecrets.hxx in
# particular is populated with real secret values at configure time -- never commit it.
src/openstudio_lib/AnalyticsHelperSecrets.hxx
src/openstudio_lib/AnalyticsHelperSecrets.hxx.tmp
src/openstudio_app/AboutBox.hpp
src/model_editor/AboutBox.hpp
src/openstudio_app/OpenStudioApp.rc
src/utilities/OpenStudioApplicationPathHelpers.cxx
58 changes: 58 additions & 0 deletions src/openstudio_app/Resources/default/hvac_library.osm
Original file line number Diff line number Diff line change
Expand Up @@ -6999,6 +6999,64 @@ OS:AirLoopHVAC:UnitarySystem,
, !- Heat Recovery Water Outlet Node Name
; !- Design Specification Multispeed Object Name

OS:Coil:Heating:Desuperheater,
{bdb03443-be0a-4953-8eb4-e6014bbced83}, !- Handle
Desuperheater Reheat Coil, !- Name
{9f54092d-a4a8-41b8-a381-c4c332ecb843}, !- Availability Schedule Name
0.25, !- Heat Reclaim Recovery Efficiency
, !- Air Inlet Node Name
, !- Air Outlet Node Name
{983b9c20-5a9e-49eb-9770-5c1545cc6b65}, !- Heating Source Name
0; !- On Cycle Parasitic Electric Load {W}

OS:AirLoopHVAC:UnitarySystem,
{dbc42aea-40d9-42a6-9016-87cc16813b11}, !- Handle
Unitary - Single Speed DX cooling - Elec heat - CAV - Desuperheater reheat, !- Name
Load, !- Control Type
, !- Controlling Zone or Thermostat Location
CoolReheat, !- Dehumidification Control Type
{9f54092d-a4a8-41b8-a381-c4c332ecb843}, !- Availability Schedule Name
, !- Air Inlet Node Name
, !- Air Outlet Node Name
{0c016d27-85cb-4254-8b85-9a16c4586de5}, !- Supply Fan Name
BlowThrough, !- Fan Placement
{9f54092d-a4a8-41b8-a381-c4c332ecb843}, !- Supply Air Fan Operating Mode Schedule Name
{ca3b3fed-58b0-4f64-85f9-257f45d033a5}, !- Heating Coil Name
1, !- DX Heating Coil Sizing Ratio
{983b9c20-5a9e-49eb-9770-5c1545cc6b65}, !- Cooling Coil Name
No, !- Use DOAS DX Cooling Coil
2, !- DOAS DX Cooling Coil Leaving Minimum Air Temperature {C}
LatentOrSensibleLoadControl, !- Latent Load Control
{bdb03443-be0a-4953-8eb4-e6014bbced83}, !- Supplemental Heating Coil Name
SupplyAirFlowRate, !- Supply Air Flow Rate Method During Cooling Operation
autosize, !- Supply Air Flow Rate During Cooling Operation {m3/s}
, !- Supply Air Flow Rate Per Floor Area During Cooling Operation {m3/s-m2}
, !- Fraction of Autosized Design Cooling Supply Air Flow Rate
, !- Design Supply Air Flow Rate Per Unit of Capacity During Cooling Operation {m3/s-W}
SupplyAirFlowRate, !- Supply Air Flow Rate Method During Heating Operation
autosize, !- Supply Air Flow Rate During Heating Operation {m3/s}
, !- Supply Air Flow Rate Per Floor Area during Heating Operation {m3/s-m2}
, !- Fraction of Autosized Design Heating Supply Air Flow Rate
, !- Design Supply Air Flow Rate Per Unit of Capacity During Heating Operation {m3/s-W}
None, !- Supply Air Flow Rate Method When No Cooling or Heating is Required
, !- Supply Air Flow Rate When No Cooling or Heating is Required {m3/s}
, !- Supply Air Flow Rate Per Floor Area When No Cooling or Heating is Required {m3/s-m2}
, !- Fraction of Autosized Design Cooling Supply Air Flow Rate When No Cooling or Heating is Required
, !- Fraction of Autosized Design Heating Supply Air Flow Rate When No Cooling or Heating is Required
, !- Design Supply Air Flow Rate Per Unit of Capacity During Cooling Operation When No Cooling or Heating is Required {m3/s-W}
, !- Design Supply Air Flow Rate Per Unit of Capacity During Heating Operation When No Cooling or Heating is Required {m3/s-W}
Yes, !- No Load Supply Air Flow Rate Control Set To Low Speed
80, !- Maximum Supply Air Temperature {C}
21, !- Maximum Outdoor Dry-Bulb Temperature for Supplemental Heater Operation {C}
, !- Outdoor Dry-Bulb Temperature Sensor Node Name
0, !- Ancilliary On-Cycle Electric Power {W}
0, !- Ancilliary Off-Cycle Electric Power {W}
, !- Design Heat Recovery Water Flow Rate {m3/s}
80, !- Maximum Temperature for Heat Recovery {C}
, !- Heat Recovery Water Inlet Node Name
, !- Heat Recovery Water Outlet Node Name
; !- Design Specification Multispeed Object Name

OS:Fan:OnOff,
{e803e540-d848-409c-9bad-0c0dc9c7b50a}, !- Handle
Fan On Off 14, !- Name
Expand Down
118 changes: 118 additions & 0 deletions src/openstudio_lib/HVACSystemsController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
#include <openstudio/model/CoilHeatingElectric_Impl.hpp>
#include <openstudio/model/CoilHeatingWater.hpp>
#include <openstudio/model/CoilHeatingWater_Impl.hpp>
#include <openstudio/model/ParentObject.hpp>
#include <openstudio/model/ParentObject_Impl.hpp>
#include <openstudio/model/Model.hpp>
#include <openstudio/model/Model_Impl.hpp>
#include <openstudio/model/Node.hpp>
Expand Down Expand Up @@ -126,6 +128,8 @@
#include <openstudio/utilities/core/Compare.hpp>
#include <openstudio/utilities/idd/OS_ComponentData_FieldEnums.hxx>

#include <map>

#include <QMessageBox>
#include <QTimer>
#include <QPushButton>
Expand Down Expand Up @@ -519,6 +523,117 @@ void HVACLayoutController::addLibraryObjectToTopLevel(const OSItemId& itemId) {
message.exec();
}

namespace {
// ModelObject::clone() clones each child individually and reattaches it via setParent(), so
// true parent-child edges are correctly remapped to point within the new subtree. But children
// are cloned one at a time rather than as a batch sharing a single old->new handle table, so any
// *lateral* object-list reference between two siblings in the cloned subtree (e.g.
// CoilHeatingDesuperheater::heatingSource() pointing at a sibling cooling coil, or a
// SetpointManager's node references) is left broken on the clone -- for some field/type
// combinations still pointing at the original object, for others (empirically, heatingSource())
// cleared to empty outright. The functions below fix that up generically, for any object type,
// rather than special casing each lateral-reference field as it's discovered.

// ParentObject::children() is true ownership (e.g. a UnitarySystem's fan/coils) but does *not*
// include HVACComponents placed on a Loop's supply/demand branches -- a UnitarySystem sitting on
// an AirLoopHVAC's supply branch is reachable via supplyComponents(), not children(). The same
// gap exists one level deeper: equipment on an AirLoopHVACOutdoorAirSystem's outdoor-air/relief
// branches (e.g. an evaporative cooler, or a SetpointManager:MixedAir sitting on one of those
// nodes) is reachable only via oaComponents()/reliefComponents(), not children() either. Cloning
// a whole loop (the "copy system" toolbar action) needs all of these, or the branch equipment --
// and anything lateral-referenced from inside it -- is never visited at all.
//
// Returned as separate groups, each matched and recursed into independently: cloning a loop does

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 makes sense to me but I wonder two things:

  1. Why has this not been an issue before? Does the current workflow just make you have to go through the new airloop and reattach things to each other?
  2. Should this be proposed to the SDK as an improvement to AirLoopHVAC::clone? Or maybe even ModelObject::clone?

// not carry over the connected thermal zones on the demand side, so original vs. clone
// demandComponents() can legitimately have different sizes. Treating everything as one combined
// list would let a benign demand-side mismatch abort recursion into the other groups too, where
// the equipment (and any lateral references inside it) *does* line up 1:1 with the original.
std::vector<std::vector<model::ModelObject>> childSubtreeObjectGroups(const model::ModelObject& object) {
std::vector<std::vector<model::ModelObject>> groups;
if (boost::optional<model::Loop> loop = object.optionalCast<model::Loop>()) {
groups.push_back(loop->supplyComponents());
groups.push_back(loop->demandComponents());
}
if (boost::optional<model::AirLoopHVACOutdoorAirSystem> oaSystem = object.optionalCast<model::AirLoopHVACOutdoorAirSystem>()) {
groups.push_back(oaSystem->oaComponents());
groups.push_back(oaSystem->reliefComponents());
}
if (boost::optional<model::ParentObject> parent = object.optionalCast<model::ParentObject>()) {
groups.push_back(parent->children());
}
return groups;
}

// Walks `original` and `clone` in parallel -- childSubtreeObjectGroups() order is deterministic
// and preserved by clone() -- building a map from each original object's handle to its
// corresponding clone.
void buildCloneHandleMap(const model::ModelObject& original, const model::ModelObject& clone,
std::map<Handle, model::ModelObject>& handleMap) {
handleMap.emplace(original.handle(), clone);

std::vector<std::vector<model::ModelObject>> originalGroups = childSubtreeObjectGroups(original);
std::vector<std::vector<model::ModelObject>> cloneGroups = childSubtreeObjectGroups(clone);
for (size_t g = 0; g < originalGroups.size() && g < cloneGroups.size(); ++g) {
const std::vector<model::ModelObject>& originalChildren = originalGroups[g];
const std::vector<model::ModelObject>& cloneChildren = cloneGroups[g];
if (originalChildren.size() == cloneChildren.size()) {
for (size_t i = 0; i < originalChildren.size(); ++i) {
buildCloneHandleMap(originalChildren[i], cloneChildren[i], handleMap);
}
}
}
}

// For every lateral object-list field on `original` whose target was itself cloned (i.e. has an
// entry in handleMap), forces the corresponding field on `clonedObject` to point at that clone.
// Fields are read from `original`, not from `clonedObject`: clone() doesn't necessarily leave a
// lateral reference pointing at the stale original object -- for CoilHeatingDesuperheater's
// heatingSource() it clears the field outright -- so the only reliable source of "what this
// field is supposed to point at" is the original.
void remapLateralReferences(const model::ModelObject& original, model::ModelObject clonedObject,
const std::map<Handle, model::ModelObject>& handleMap) {
IddObject iddObject = original.iddObject();
for (unsigned index = 0; index < original.numFields(); ++index) {
if (iddObject.objectLists(index).empty()) {
continue;
}
boost::optional<WorkspaceObject> originalTarget = original.getTarget(index);
if (!originalTarget) {
continue;
}
auto it = handleMap.find(originalTarget->handle());
if (it == handleMap.end()) {
continue;
}
boost::optional<WorkspaceObject> clonedTarget = clonedObject.getTarget(index);
if (clonedTarget && clonedTarget->handle() == it->second.handle()) {
continue;
}
clonedObject.setPointer(index, it->second.handle());
}

std::vector<std::vector<model::ModelObject>> originalGroups = childSubtreeObjectGroups(original);
std::vector<std::vector<model::ModelObject>> cloneGroups = childSubtreeObjectGroups(clonedObject);
for (size_t g = 0; g < originalGroups.size() && g < cloneGroups.size(); ++g) {
const std::vector<model::ModelObject>& originalChildren = originalGroups[g];
const std::vector<model::ModelObject>& cloneChildren = cloneGroups[g];
if (originalChildren.size() == cloneChildren.size()) {
for (size_t i = 0; i < originalChildren.size(); ++i) {
remapLateralReferences(originalChildren[i], cloneChildren[i], handleMap);
}
}
}
}

// `original` is the subtree that was cloned; `clonedObject` is its clone. Re-links any lateral
// reference in the clone that still points into (or should point into) the original subtree.
void fixupClonedReferences(const model::ModelObject& original, model::ModelObject clonedObject) {
std::map<Handle, model::ModelObject> handleMap;
buildCloneHandleMap(original, clonedObject, handleMap);
remapLateralReferences(original, clonedObject, handleMap);
}
} // namespace

void HVACLayoutController::addLibraryObjectToModelNode(const OSItemId& itemId, model::HVACComponent& comp) {
model::OptionalModelObject object;
bool remove = false;
Expand All @@ -527,7 +642,9 @@ void HVACLayoutController::addLibraryObjectToModelNode(const OSItemId& itemId, m
object = doc->getModelObject(itemId);
if (object) {
if (!doc->fromModel(itemId)) {
model::ModelObject original = object.get();
object = object->clone(comp.model());
fixupClonedReferences(original, object.get());
remove = true;
}
}
Expand Down Expand Up @@ -893,6 +1010,7 @@ void HVACSystemsController::onCopySystemClicked() {
auto loop = currentLoop();
if (loop) {
auto clone = loop->clone(loop->model());
fixupClonedReferences(loop.get(), clone);
setCurrentHandle(toQString(clone.handle()));
}
}
Expand Down
Loading