Repository: NREL/OpenStudio (language: python)

Issue:
Workflow: Unify RunOptions/ForwardTranslator Options accross entire code base
Enhancement Request
--------------

- filetypes/WorkflowJSON.hpp uses filetypes/RunOptions
- filetypes/RunOptions stores the ForwardTranslatorOptions as a json string which is highly inconvenient
- The CLI has flags to set these RunOptions/ForwardTranslatorOptions
- The E+ ForwardTranslator has individual methods to see what corresponds to the ForwardTranslator options

## Possible Implementation

Add a filetypes/ForwardTranslatorOptions class. Make RunOptions use that type directly.

Use the same classes accross the entire code base (in E+ ForwardTranslator and CLI)

The RunOptions/ForwardTranslatorOptions passed in the CLI should be **MERGED** with the WorkflowJSON ones (eg: anything that is NOT defaulted in the CLI should override the WorkflowJSON one (=user supplied values trump everything))
Workflow: Unify RunOptions/ForwardTranslator Options accross entire code base
Enhancement Request
--------------

- filetypes/WorkflowJSON.hpp uses filetypes/RunOptions
- filetypes/RunOptions stores the ForwardTranslatorOptions as a json string which is highly inconvenient
- The CLI has flags to set these RunOptions/ForwardTranslatorOptions
- The E+ ForwardTranslator has individual methods to see what corresponds to the ForwardTranslator options

## Possible Implementation

Add a filetypes/ForwardTranslatorOptions class. Make RunOptions use that type directly.

Use the same classes accross the entire code base (in E+ ForwardTranslator and CLI)

The RunOptions/ForwardTranslatorOptions passed in the CLI should be **MERGED** with the WorkflowJSON ones (eg: anything that is NOT defaulted in the CLI should override the WorkflowJSON one (=user supplied values trump everything))


Discussion / hints:

Fix patch:
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index 1ca1aa92af7..dd7f7c8ee1b 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -89,6 +89,7 @@ if(BUILD_TESTING)
     COMMAND $<TARGET_FILE:openstudio> run -w compact_ruby_only.osw
     WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/Examples/compact_osw/"
   )
+  set_tests_properties(OpenStudioCLI.run_compact_ruby_only_osw PROPERTIES RESOURCE_LOCK "compact_osw")
 
   add_test(NAME OpenStudioCLI.Labs.openstudio_version
     COMMAND $<TARGET_FILE:openstudio> labs openstudio_version
@@ -137,21 +138,25 @@ if(BUILD_TESTING)
     COMMAND $<TARGET_FILE:openstudio> labs run -w compact_ruby_only.osw
     WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/Examples/compact_osw/"
   )
+  set_tests_properties(OpenStudioCLI.Labs.Run_RubyOnly PROPERTIES RESOURCE_LOCK "compact_osw")
 
   add_test(NAME OpenStudioCLI.Labs.Run_PythonOnly
     COMMAND $<TARGET_FILE:openstudio> labs run -w compact_python_only.osw
     WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/Examples/compact_osw/"
   )
+  set_tests_properties(OpenStudioCLI.Labs.Run_PythonOnly PROPERTIES RESOURCE_LOCK "compact_osw")
 
-  add_test(NAME OpenStudioCLI.Labs.Run_RubyPythonOnly_TwoMeasure
+  add_test(NAME OpenStudioCLI.Labs.Run_PythonOnly_TwoMeasure
     COMMAND $<TARGET_FILE:openstudio> labs run -w compact_python_only_twomeasures.osw
     WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/Examples/compact_osw/"
   )
+  set_tests_properties(OpenStudioCLI.Labs.Run_PythonOnly_TwoMeasure PROPERTIES RESOURCE_LOCK "compact_osw")
 
   add_test(NAME OpenStudioCLI.Labs.Run_RubyPython
     COMMAND $<TARGET_FILE:openstudio> labs run -w compact_ruby_and_python.osw
     WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/Examples/compact_osw/"
   )
+  set_tests_properties(OpenStudioCLI.Labs.Run_RubyPython PROPERTIES RESOURCE_LOCK "compact_osw")
 
   file(GLOB RUBY_TEST_SRC
     # find all CLI test
diff --git a/src/cli/RunCommand.cpp b/src/cli/RunCommand.cpp
index 11648641451..eeba28a5426 100644
--- a/src/cli/RunCommand.cpp
+++ b/src/cli/RunCommand.cpp
@@ -28,10 +28,13 @@
 ***********************************************************************************************************************/
 
 #include "RunCommand.hpp"
-#include "../workflow/RunOptions.hpp"
+#include "../utilities/filetypes/ForwardTranslatorOptions.hpp"
+#include "../workflow/WorkflowRunOptions.hpp"
+
 #include "../workflow/OSWorkflow.hpp"
 #include "../scriptengine/ScriptEngine.hpp"
 
+#include <cstdint>
 #include <fmt/format.h>
 
 #include <memory>
@@ -39,8 +42,6 @@
 namespace openstudio {
 namespace cli {
 
-  void setupRunFtOptions(CLI::App* app, FtOptions& ftOptions);
-
   void setupRunOptions(CLI::App* parentApp, ScriptEngineInstance& ruby, ScriptEngineInstance& python, std::function<void()>& runSetupEmbeddedGems,
                        std::function<void()>& runSetupPythonPath) {
     /// Set up a subcommand and capture a shared_ptr to a struct that holds all its options.
@@ -63,9 +64,11 @@ namespace cli {
       },
       "Only run the OpenStudio and EnergyPlus measures");
 
-    app->add_flag("-p,--postprocess_only", opt->post_process, "Only run the reporting measures");
+    app->add_flag("-p,--postprocess_only", opt->post_process_only, "Only run the reporting measures");
 
-    app->add_flag("--export-epJSON", opt->ep_json, "export epJSON file format. The default is IDF");
+    app->add_flag(
+      "--export-epJSON", [opt](std::int64_t val) { (val != 0) && opt->runOptions.setEpjson((val == 1)); },
+      "export epJSON file format. The default is IDF");
 
     app->add_option("-s,--socket", opt->socket_port, "Pipe status messages to a socket on localhost PORT")->option_text("PORT");
 
@@ -79,67 +82,87 @@ namespace cli {
       ->needs(stdout_opt)
       ->group("Stdout Options");
 
-    app->add_flag("--debug", opt->debug, "Includes additional outputs for debugging failing workflows and does not clean up the run directory");
+    app->add_flag(
+      "--debug", [opt](std::int64_t val) { (val != 0) && opt->runOptions.setDebug((val == 1)); },
+      "Includes additional outputs for debugging failing workflows and does not clean up the run directory");
 
     // FT options
-    setupRunFtOptions(app, opt->ft_options);
-
-    app->callback([opt, &ruby, &python, &runSetupEmbeddedGems, &runSetupPythonPath] {
-      // TODO: eventually we want to delay the call to runSetupEmbeddedGems until we KNOW we need ruby measures, so probably want to forward to
-      // OSWorkflow and deal with it there. Same for runSetupPythonPath
-      runSetupEmbeddedGems();
-      runSetupPythonPath();
-      openstudio::OSWorkflow workflow(*opt, ruby, python);
-      workflow.run();
-    });
-  }
-
-  void setupRunFtOptions(CLI::App* app, FtOptions& ftOptions) {
-    app->add_flag("--runcontrolspecialdays,!--no-runcontrolspecialdays", ftOptions.runcontrolspecialdays, "Include RunControlSpecialDays (Holidays)")
-      ->group(FtOptions::group_name);
+    static constexpr auto ftGroupName = "Forward Translator Options";
+    app
+      ->add_flag(
+        "--runcontrolspecialdays,!--no-runcontrolspecialdays",
+        [opt](std::int64_t val) {
+          if (val != 0) {
+            opt->runOptions.forwardTranslatorOptions().setKeepRunControlSpecialDays((val == 1));
+          }
+        },
+        "Include RunControlSpecialDays (Holidays) [Default: True]")
+      ->group(ftGroupName);
 
-    app->add_flag("--set-ip-tabular-output", ftOptions.ip_tabular_output, "Request IP units from E+ Tabular (HTML) Report [Default: False]")
-      ->group(FtOptions::group_name);
+    app
+      ->add_flag(
+        "--set-ip-tabular-output",
+        [opt](std::int64_t val) {
+          if (val != 0) {
+            opt->runOptions.forwardTranslatorOptions().setIPTabularOutput((val == 1));
+          }
+        },
+        "Request IP units from E+ Tabular (HTML) Report [Default: False]")
+      ->group(ftGroupName);
 
     app
       ->add_flag(
         "--lifecyclecosts,!--no-lifecyclecosts",
-        [&ftOptions](std::int64_t val) {
-          fmt::print("val={}\n", val);
-          ftOptions.no_lifecyclecosts = (val != 1);
+        [opt](std::int64_t val) {
+          if (val != 0) {
+            opt->runOptions.forwardTranslatorOptions().setExcludeLCCObjects((val != 1));
+          }
         },
         "Include LifeCycleCosts [Default: True]")
-      ->group(FtOptions::group_name);
+      ->group(ftGroupName);
 
     app
       ->add_flag(
         "--sqlite-output,!--no-sqlite-output",
-        [&ftOptions](std::int64_t val) {
-          fmt::print("val={}\n", val);
-          ftOptions.no_sqlite_output = (val != 1);
+        [opt](std::int64_t val) {
+          if (val != 0) {
+            opt->runOptions.forwardTranslatorOptions().setExcludeSQliteOutputReport((val != 1));
+          }
         },
         "Request Output:SQLite from E+ [Default: True]")
-      ->group(FtOptions::group_name);
+      ->group(ftGroupName);
 
     app
       ->add_flag(
         "--html-output,!--no-html-output",
-        [&ftOptions](std::int64_t val) {
-          fmt::print("val={}\n", val);
-          ftOptions.no_html_output = (val != 1);
+        [opt](std::int64_t val) {
+          if (val != 0) {
+            opt->runOptions.forwardTranslatorOptions().setExcludeHTMLOutputReport((val != 1));
+          }
         },
         "Request Output:Table:SummaryReports from E+ [Default: True]")
-      ->group(FtOptions::group_name);
+      ->group(ftGroupName);
 
     app
       ->add_flag(
         "--space-translation,!--no-space-translation",
-        [&ftOptions](std::int64_t val) {
-          fmt::print("val={}\n", val);
-          ftOptions.no_space_translation = (val != 1);
+        [opt](std::int64_t val) {
+          if (val != 0) {
+            opt->runOptions.forwardTranslatorOptions().setExcludeSpaceTranslation((val != 1));
+          }
         },
         "Add individual E+ Space [Default: True]")
-      ->group(FtOptions::group_name);
+      ->group(ftGroupName);
+
+    // Subcommand callback
+    app->callback([opt, &ruby, &python, &runSetupEmbeddedGems, &runSetupPythonPath] {
+      // TODO: eventually we want to delay the call to runSetupEmbeddedGems until we KNOW we need ruby measures, so probably want to forward to
+      // OSWorkflow and deal with it there. Same for runSetupPythonPath
+      runSetupEmbeddedGems();
+      runSetupPythonPath();
+      openstudio::OSWorkflow workflow(*opt, ruby, python);
+      workflow.run();
+    });
   }
 
 }  // namespace cli
diff --git a/src/energyplus/EnergyPlus.i b/src/energyplus/EnergyPlus.i
index 0804c6fe366..fdafa8f52da 100644
--- a/src/energyplus/EnergyPlus.i
+++ b/src/energyplus/EnergyPlus.i
@@ -46,20 +46,9 @@
 %ignore ForwardTranslatorInitializer;
 %ignore openstudio::energyplus::detail::ForwardTranslatorInitializer;
 
-%template(ForwardTranslatorOptionKeyMethodVector) std::vector<openstudio::energyplus::ForwardTranslatorOptionKeyMethod>;
-
 %include <energyplus/ErrorFile.hpp>
 %include <energyplus/ForwardTranslator.hpp>
 %include <energyplus/ReverseTranslator.hpp>
 
-// extend classes
-%extend openstudio::energyplus::ForwardTranslatorOptionKeyMethod {
-  std::string __str__() {
-    std::ostringstream os;
-    os << *$self;
-    return os.str();
-  }
-};
-
 #endif //ENERGYPLUS_I
 
diff --git a/src/energyplus/ForwardTranslator.cpp b/src/energyplus/ForwardTranslator.cpp
index 5d088e239d5..28141e63f97 100644
--- a/src/energyplus/ForwardTranslator.cpp
+++ b/src/energyplus/ForwardTranslator.cpp
@@ -118,19 +118,19 @@ namespace energyplus {
     m_logSink.setChannelRegex(boost::regex("openstudio\\.energyplus\\.ForwardTranslator"));
     m_logSink.setThreadId(std::this_thread::get_id());
     createFluidPropertiesMap();
+  }
+
+  ForwardTranslatorOptions ForwardTranslator::forwardTranslatorOptions() const {
+    return m_forwardTranslatorOptions;
+  }
 
-    m_keepRunControlSpecialDays = true;  // At 3.1.0 this was changed to true.
-    m_ipTabularOutput = false;
-    m_excludeLCCObjects = false;
-    m_excludeSQliteOutputReport = false;
-    m_excludeHTMLOutputReport = false;
-    m_excludeVariableDictionary = false;
-    m_excludeSpaceTranslation = false;  // At 3.4.1, this was changed to false.
+  void ForwardTranslator::setForwardTranslatorOptions(ForwardTranslatorOptions forwardTranslatorOptions) {
+    m_forwardTranslatorOptions = std::move(forwardTranslatorOptions);
   }
 
   Workspace ForwardTranslator::translateModel(const Model& model, ProgressBar* progressBar) {
 
-    // When m_excludeSpaceTranslation is false, could we skip the (expensive) clone since we aren't combining spaces?
+    // When m_forwardTranslatorOptions.excludeSpaceTranslation() is false, could we skip the (expensive) clone since we aren't combining spaces?
     // No, we are still doing stuff like removing orphan loads, spaces not part of a thermal zone, etc
     Model modelCopy = model.clone(true).cast<Model>();
 
@@ -177,60 +177,45 @@ namespace energyplus {
   }
 
   void ForwardTranslator::setKeepRunControlSpecialDays(bool keepRunControlSpecialDays) {
-    m_keepRunControlSpecialDays = keepRunControlSpecialDays;
+    m_forwardTranslatorOptions.setKeepRunControlSpecialDays(keepRunControlSpecialDays);
   }
 
   void ForwardTranslator::setIPTabularOutput(bool isIP) {
-    m_ipTabularOutput = isIP;
+    m_forwardTranslatorOptions.setIPTabularOutput(isIP);
   }
 
   void ForwardTranslator::setExcludeLCCObjects(bool excludeLCCObjects) {
-    m_excludeLCCObjects = excludeLCCObjects;
+    m_forwardTranslatorOptions.setExcludeLCCObjects(excludeLCCObjects);
   }
 
   void ForwardTranslator::setExcludeSQliteOutputReport(bool excludeSQliteOutputReport) {
-    m_excludeSQliteOutputReport = excludeSQliteOutputReport;
+    m_forwardTranslatorOptions.setExcludeSQliteOutputReport(excludeSQliteOutputReport);
   }
 
   void ForwardTranslator::setExcludeHTMLOutputReport(bool excludeHTMLOutputReport) {
-    m_excludeHTMLOutputReport = excludeHTMLOutputReport;
+    m_forwardTranslatorOptions.setExcludeHTMLOutputReport(excludeHTMLOutputReport);
   }
 
   void ForwardTranslator::setExcludeVariableDictionary(bool excludeVariableDictionary) {
-    m_excludeVariableDictionary = excludeVariableDictionary;
+    m_forwardTranslatorOptions.setExcludeVariableDictionary(excludeVariableDictionary);
   }
 
   void ForwardTranslator::setExcludeSpaceTranslation(bool excludeSpaceTranslation) {
-    m_excludeSpaceTranslation = excludeSpaceTranslation;
-  }
-
-  std::vector<ForwardTranslatorOptionKeyMethod> ForwardTranslator::forwardTranslatorOptionKeyMethods() {
-    return std::vector<ForwardTranslatorOptionKeyMethod>{{{"runcontrolspecialdays", "setKeepRunControlSpecialDays"},
-                                                          {"ip_tabular_output", "setIPTabularOutput"},
-                                                          {"no_lifecyclecosts", "setExcludeLCCObjects"},
-                                                          {"no_sqlite_output", "setExcludeSQliteOutputReport"},
-                                                          {"no_html_output", "setExcludeHTMLOutputReport"},
-                                                          {"no_variable_dictionary", "setExcludeVariableDictionary"},
-                                                          {"no_space_translation", "setExcludeSpaceTranslation"}}};
-  }
-
-  std::ostream& operator<<(std::ostream& out, const openstudio::energyplus::ForwardTranslatorOptionKeyMethod& opt) {
-    out << "(" << opt.json_name << ", " << opt.ft_method_name << ")";
-    return out;
+    m_forwardTranslatorOptions.setExcludeSpaceTranslation(excludeSpaceTranslation);
   }
 
   // Figure out which object
   // * If the load is assigned to a space,
-  //     * m_excludeSpaceTranslation = true: translate and return the IdfObject for the Zone
-  //     * m_excludeSpaceTranslation = false: translate and return the IdfObject for Space
+  //     * m_forwardTranslatorOptions.excludeSpaceTranslation() = true: translate and return the IdfObject for the Zone
+  //     * m_forwardTranslatorOptions.excludeSpaceTranslation() = false: translate and return the IdfObject for Space
   // * If the load is assigned to a spaceType:
-  //     * translateAndMapModelObjec(spaceType) (which will return a ZoneList if m_excludeSpaceTranslation is true, SpaceList otherwise)
+  //     * translateAndMapModelObjec(spaceType) (which will return a ZoneList if m_forwardTranslatorOptions.excludeSpaceTranslation() is true, SpaceList otherwise)
   IdfObject ForwardTranslator::getSpaceLoadParent(const model::SpaceLoad& sp, bool allowSpaceType) {
 
     OptionalIdfObject relatedIdfObject;
 
     if (boost::optional<Space> space_ = sp.space()) {
-      if (m_excludeSpaceTranslation) {
+      if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
         if (auto thermalZone_ = space_->thermalZone()) {
           relatedIdfObject = translateAndMapModelObject(thermalZone_.get());
         } else {
@@ -308,7 +293,7 @@ namespace energyplus {
       }
     }
 
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
       // next thing to do is combine all spaces in each thermal zone
       // after this each zone will have 0 or 1 spaces and each space will have 0 or 1 zone
       for (ThermalZone thermalZone : model.getConcreteModelObjects<ThermalZone>()) {
@@ -493,7 +478,7 @@ namespace energyplus {
       }
     }
 
-    if (!m_keepRunControlSpecialDays) {
+    if (!m_forwardTranslatorOptions.keepRunControlSpecialDays()) {
       LOG(Warn, "You have manually choosen to not translate the RunPeriodControlSpecialDays, ignoring them.");
       for (model::RunPeriodControlSpecialDays holiday : model.getConcreteModelObjects<model::RunPeriodControlSpecialDays>()) {
         holiday.remove();
@@ -503,7 +488,7 @@ namespace energyplus {
     if (fullModelTranslation) {
 
       // translate life cycle cost parameters
-      if (!m_excludeLCCObjects) {
+      if (!m_forwardTranslatorOptions.excludeLCCObjects()) {
         boost::optional<LifeCycleCostParameters> lifeCycleCostParameters = model.lifeCycleCostParameters();
         if (!lifeCycleCostParameters) {
           // only warn if costs are present
@@ -549,7 +534,7 @@ namespace energyplus {
       // ensure that output table summary reports exists
       // If the user manually added an OutputTableSummaryReports, but he also opted-in to exclude it on the FT, which decision do we keep?
       // Given that it's a much harder to set the option on the FT, I'll respect that one
-      if (!m_excludeHTMLOutputReport) {
+      if (!m_forwardTranslatorOptions.excludeHTMLOutputReport()) {
         auto optOutputTableSummaryReports = model.getOptionalUniqueModelObject<model::OutputTableSummaryReports>();
         // Add default one if none explicitly specified
         if (!optOutputTableSummaryReports) {
@@ -4401,24 +4386,24 @@ namespace energyplus {
   }
 
   void ForwardTranslator::createStandardOutputRequests(const model::Model& model) {
-    if (!m_excludeHTMLOutputReport) {
+    if (!m_forwardTranslatorOptions.excludeHTMLOutputReport()) {
       if (!model.getOptionalUniqueModelObject<model::OutputControlTableStyle>()) {
         IdfObject& tableStyle = m_idfObjects.emplace_back(IddObjectType::OutputControl_Table_Style);
         tableStyle.setString(OutputControl_Table_StyleFields::ColumnSeparator, "HTML");
-        if (m_ipTabularOutput) {
+        if (m_forwardTranslatorOptions.iPTabularOutput()) {
           tableStyle.setString(OutputControl_Table_StyleFields::UnitConversion, "InchPound");
         }
       }
     }
 
-    if (!m_excludeVariableDictionary) {
+    if (!m_forwardTranslatorOptions.excludeVariableDictionary()) {
       IdfObject rddRequest(IddObjectType::Output_VariableDictionary);
       rddRequest.setString(Output_VariableDictionaryFields::KeyField, "IDF");
       rddRequest.setString(Output_VariableDictionaryFields::SortOption, "Unsorted");
       m_idfObjects.push_back(rddRequest);
     }
 
-    if (!m_excludeSQliteOutputReport) {
+    if (!m_forwardTranslatorOptions.excludeSQliteOutputReport()) {
       if (!model.getOptionalUniqueModelObject<model::OutputSQLite>()) {
         IdfObject& sqliteOutput = m_idfObjects.emplace_back(IddObjectType::Output_SQLite);
         sqliteOutput.setString(Output_SQLiteFields::OptionType, "SimpleAndTabular");
@@ -4426,7 +4411,7 @@ namespace energyplus {
     }
 
     // ensure at least one life cycle cost exists to prevent crash in E+ 8
-    if (!m_excludeLCCObjects) {
+    if (!m_forwardTranslatorOptions.excludeLCCObjects()) {
       bool hasAtLeastOneCost = std::any_of(m_idfObjects.cbegin(), m_idfObjects.cend(), [](const auto& obj) {
         auto iddObjType = obj.iddObject().type();
         return (iddObjType == openstudio::IddObjectType::LifeCycleCost_NonrecurringCost)
diff --git a/src/energyplus/ForwardTranslator.hpp b/src/energyplus/ForwardTranslator.hpp
index 6c90b8a9a95..da16ac25651 100644
--- a/src/energyplus/ForwardTranslator.hpp
+++ b/src/energyplus/ForwardTranslator.hpp
@@ -37,6 +37,7 @@
 #include "../utilities/idf/Workspace.hpp"
 #include "../utilities/core/Logger.hpp"
 #include "../utilities/core/StringStreamLogSink.hpp"
+#include "../utilities/filetypes/ForwardTranslatorOptions.hpp"
 #include "../utilities/time/Time.hpp"
 
 #include "../utilities/core/Deprecated.hpp"
@@ -514,14 +515,6 @@ namespace energyplus {
     struct ForwardTranslatorInitializer;
   };
 
-  struct ForwardTranslatorOptionKeyMethod
-  {
-    std::string json_name;
-    std::string ft_method_name;
-  };
-
-  ENERGYPLUS_API std::ostream& operator<<(std::ostream& out, const openstudio::energyplus::ForwardTranslatorOptionKeyMethod& opt);
-
 #define ENERGYPLUS_VERSION "22.2"
 
   class ENERGYPLUS_API ForwardTranslator
@@ -545,6 +538,13 @@ namespace energyplus {
    */
     std::vector<LogMessage> errors() const;
 
+    ForwardTranslatorOptions forwardTranslatorOptions() const;
+
+    void setForwardTranslatorOptions(ForwardTranslatorOptions forwardTranslatorOptions);
+
+    /** @Convenience methods for ForwardTranslatorOptions (and for backward compatibility) */
+    //@{
+    //
     /** keepRunControlSpecialDays is enabled by default. You can use this method to NOT translate the holidays in the model.
    */
     void setKeepRunControlSpecialDays(bool keepRunControlSpecialDays);
@@ -577,7 +577,7 @@ namespace energyplus {
    *  Use this at your own risks */
     void setExcludeSpaceTranslation(bool excludeSpaceTranslation);
 
-    static std::vector<ForwardTranslatorOptionKeyMethod> forwardTranslatorOptionKeyMethods();
+    //@}
 
    private:
     REGISTER_LOGGER("openstudio.energyplus.ForwardTranslator");
@@ -1671,13 +1671,7 @@ namespace energyplus {
     ProgressBar* m_progressBar;
 
     // ForwardTranslator options
-    bool m_keepRunControlSpecialDays;
-    bool m_ipTabularOutput;
-    bool m_excludeLCCObjects;
-    bool m_excludeSQliteOutputReport;  // exclude Output:Sqlite
-    bool m_excludeHTMLOutputReport;    // exclude Output:Table:SummaryReports
-    bool m_excludeVariableDictionary;  // exclude Output:VariableDictionary
-    bool m_excludeSpaceTranslation;
+    ForwardTranslatorOptions m_forwardTranslatorOptions;
   };
 
 }  // namespace energyplus
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateElectricEquipmentITEAirCooled.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateElectricEquipmentITEAirCooled.cpp
index ab8c52b2c9a..77eaceda62e 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateElectricEquipmentITEAirCooled.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateElectricEquipmentITEAirCooled.cpp
@@ -104,7 +104,7 @@ namespace energyplus {
     // (1) assign to zone; (2) connect the supply air node (3) apply contraints
     // ITE object that is assigned to space type in OS has been switched to separate spaces in ForwardTranslator
 
-    // TODO: There might be work to be done in the case we translate to E+ Spaces (m_excludeSpaceTranslation = false)
+    // TODO: There might be work to be done in the case we translate to E+ Spaces (m_forwardTranslatorOptions.excludeSpaceTranslation() = false)
     // Per IDD for this ITE object: ZoneList and SpaceList names are not allowed.
     // But Spaces are allowed. Currently in the ForwardTranslator::translateModelPrivate we have rules to "hard-apply" ITE equipment assigned at
     // spacetype-level to each spaces (clone for each space, then remove the SpaceType one)
@@ -141,7 +141,7 @@ namespace energyplus {
       return openstudio::ascii_to_lower_copy(ite.electricEquipmentITEAirCooledDefinition().airFlowCalculationMethod());
     };
 
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
       // One Zone = One Space at this point
       auto ites = space.electricEquipmentITEAirCooled();
       std::transform(ites.begin(), ites.end(), std::inserter(methods, methods.begin()), caseInsensitiveMethodForITE);
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateEnergyManagementSystemActuator.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateEnergyManagementSystemActuator.cpp
index b2f6fdcffef..763efd4dd25 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateEnergyManagementSystemActuator.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateEnergyManagementSystemActuator.cpp
@@ -134,7 +134,7 @@ namespace energyplus {
             }
           }
           //Give WArning that spaceType has multiple spaces
-          if (m_excludeSpaceTranslation && spaceType->spaces().size() > 1) {
+          if (m_forwardTranslatorOptions.excludeSpaceTranslation() && spaceType->spaces().size() > 1) {
             LOG(Warn, "Actuator '" << modelObject.nameString() << "' references a SpaceLoad '" << load.get().name().get()
                                    << "' attached to SpaceType '" << spaceType.get().nameString()
                                    << "', with multiple spaces. Check your EMS programs to make sure your actuators are correct.");
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateInteriorPartitionSurface.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateInteriorPartitionSurface.cpp
index e5acd680cac..b4139400c50 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateInteriorPartitionSurface.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateInteriorPartitionSurface.cpp
@@ -79,7 +79,7 @@ namespace energyplus {
 
     idfObject.setString(InternalMassFields::ConstructionName, construction->nameString());
 
-    // TODO: when m_excludeSpaceTranslation is false, should we write the `Space or SpaceList Name` field? (cf InternalMass too)
+    // TODO: when m_forwardTranslatorOptions.excludeSpaceTranslation() is false, should we write the `Space or SpaceList Name` field? (cf InternalMass too)
     double multiplier = 1.0;
     boost::optional<InteriorPartitionSurfaceGroup> group = modelObject.interiorPartitionSurfaceGroup();
     if (group) {
@@ -94,7 +94,7 @@ namespace energyplus {
           idfObject.setString(InternalMassFields::ZoneorZoneListName, thermalZone->nameString());
         }
 
-        if (!m_excludeSpaceTranslation) {
+        if (!m_forwardTranslatorOptions.excludeSpaceTranslation()) {
           idfObject.setString(InternalMassFields::SpaceorSpaceListName, space->name().get());
         }
       }
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateInternalMass.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateInternalMass.cpp
index df33752eaf3..69d29b5b92e 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateInternalMass.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateInternalMass.cpp
@@ -58,7 +58,7 @@ namespace energyplus {
 
   boost::optional<IdfObject> ForwardTranslator::translateInternalMass(model::InternalMass& modelObject) {
 
-    // TODO: handle (m_excludeSpaceTranslation = false)
+    // TODO: handle (m_forwardTranslatorOptions.excludeSpaceTranslation() = false)
     // Note: E+kept a ‘Zone or ZoneList name’ field and added a ‘Space or SpaceList’ field.
     // It should have been a single ‘Zone or ZoneList or Space or SpaceList Name’ like it's done on many other objects
     // If former is specified as a **ZONELIST** name, later is omitted.
@@ -143,7 +143,7 @@ namespace energyplus {
         if (thermalZone) {
           idfObject.setString(InternalMassFields::ZoneorZoneListName, thermalZone->nameString());
         }
-        if (!m_excludeSpaceTranslation) {
+        if (!m_forwardTranslatorOptions.excludeSpaceTranslation()) {
           idfObject.setString(InternalMassFields::SpaceorSpaceListName, space.nameString());
         }
 
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateSpace.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateSpace.cpp
index 2f51f24492f..cedc1ddc3ae 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateSpace.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateSpace.cpp
@@ -75,7 +75,7 @@ namespace energyplus {
       translateAndMapModelObject(lifeCycleCost);
     }
 
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
       return boost::none;
     }
 
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateSpaceType.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateSpaceType.cpp
index a43a9f2dc10..50107a3b9b3 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateSpaceType.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateSpaceType.cpp
@@ -70,7 +70,7 @@ namespace openstudio {
 namespace energyplus {
 
   std::string ForwardTranslator::zoneListNameForSpaceType(const SpaceType& modelObject) const {
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
       return modelObject.nameString();
     } else {
       return modelObject.nameString() + " ZoneList";
@@ -82,7 +82,7 @@ namespace energyplus {
 
     // check if this is a dummy space type meant to prevent inheriting building space type
     // TODO: why is that needed in the first place? Also, children() doesn't include DesignSpecificationOutdoorAir!
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
       std::vector<ModelObject> children = modelObject.children();
       if (children.empty()) {
         LOG(Info, "SpaceType " << modelObject.name().get() << " has no children, it will not be translated");
@@ -117,7 +117,7 @@ namespace energyplus {
       return idfObject.get();
     };
 
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
 
       idfObject = makeZoneList(true);
 
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateSurface.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateSurface.cpp
index 843f8917f9e..b9a63d4fec2 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateSurface.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateSurface.cpp
@@ -95,7 +95,7 @@ namespace energyplus {
         idfObject.setString(BuildingSurface_DetailedFields::ZoneName, thermalZone->name().get());
       }
       // TODO: do we want that?
-      if (!m_excludeSpaceTranslation) {
+      if (!m_forwardTranslatorOptions.excludeSpaceTranslation()) {
         idfObject.setString(BuildingSurface_DetailedFields::SpaceName, space->nameString());
       }
     }
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateThermalZone.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateThermalZone.cpp
index 4a46540a537..6200e7e61b8 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateThermalZone.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateThermalZone.cpp
@@ -221,7 +221,7 @@ namespace energyplus {
     // Spaces
 
     // Figure out the x, y, z and whether we write floor area or not
-    if (m_excludeSpaceTranslation) {
+    if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
       OS_ASSERT(spaces.size() == 1);
       auto& space = spaces[0];
 
@@ -952,7 +952,7 @@ namespace energyplus {
       bool needToRegisterDSOAList = false;
       bool atLeastOneDSOAWasWritten = true;
 
-      if (!m_excludeSpaceTranslation && sizingZoneIdf) {
+      if (!m_forwardTranslatorOptions.excludeSpaceTranslation() && sizingZoneIdf) {
         // DO not register it yet! E+ will crash if the DSOA Space List ends up empty
         dsoaList = IdfObject(openstudio::IddObjectType::DesignSpecification_OutdoorAir_SpaceList);
         needToRegisterDSOAList = true;
@@ -979,7 +979,7 @@ namespace energyplus {
           // TODO: this isn't good. We also need to check the SpaceType-level DSOA...
           boost::optional<IdfObject> thisDSOA = translateAndMapModelObject(*designSpecificationOutdoorAir);
           if (sizingZoneIdf) {
-            if (m_excludeSpaceTranslation) {
+            if (m_forwardTranslatorOptions.excludeSpaceTranslation()) {
               // point the sizing object to the outdoor air spec
               sizingZoneIdf->setString(Sizing_ZoneFields::DesignSpecificationOutdoorAirObjectName, designSpecificationOutdoorAir->nameString());
             } else {
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp
index 8666f668e9d..f6d5a987f8d 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp
@@ -57,7 +57,7 @@ namespace energyplus {
     boost::optional<ModelObject> sourceZoneOrSpace = modelObject.sourceZoneOrSpace();
 
     auto getParentObjectName = [this](const ModelObject& mo) {
-      if (!m_excludeSpaceTranslation) {
+      if (!m_forwardTranslatorOptions.excludeSpaceTranslation()) {
         return mo.nameString();
       }
 
@@ -73,7 +73,7 @@ namespace energyplus {
     };
 
     if (!sourceZoneOrSpace) {
-      if (m_excludeSpaceTranslation && modelObject.space()) {
+      if (m_forwardTranslatorOptions.excludeSpaceTranslation() && modelObject.space()) {
         LOG(Warn, modelObject.briefDescription()
                     << " doesn't have a Source Zone or Space, it will not be translated. As you were using Space-Level ZoneMixing, and you are not "
                        "translating to Spaces, it's possible it was pointing to two spaces inside the same zone");
@@ -87,10 +87,10 @@ namespace energyplus {
       // This isn't going to happen, because zm.setSourceSpace(newSpace) in ThermalZone::combineSpaces will be rejected
       // Let's play it safe though
       LOG(Warn, modelObject.briefDescription() << " has the same Receiving and Source Zone or Space, it will not be translated.");
-      if (!m_excludeSpaceTranslation) {
-        // We don't allow this at model time, the only reason we expect this to happen is when m_excludeSpaceTranslation is true, we call
-        // combineSpaces, and if the user has a ZoneMixing pointing to two spaces from the same ThermalZone, you end up with matching Receiving and
-        // Source Spaces
+      if (!m_forwardTranslatorOptions.excludeSpaceTranslation()) {
+        // We don't allow this at model time, the only reason we expect this to happen is when m_forwardTranslatorOptions.excludeSpaceTranslation()
+        // is true, we call combineSpaces, and if the user has a ZoneMixing pointing to two spaces from the same ThermalZone,
+        // you end up with matching Receiving and Source Spaces
         OS_ASSERT(false);
       }
       return boost::none;
diff --git a/src/energyplus/Test/EMS_GTest.cpp b/src/energyplus/Test/EMS_GTest.cpp
index 4e249ac1dfe..aa7c14d4113 100644
--- a/src/energyplus/Test/EMS_GTest.cpp
+++ b/src/energyplus/Test/EMS_GTest.cpp
@@ -1917,7 +1917,7 @@ TEST_F(EnergyPlusFixture, ForwardTranslatorActuator_exampleModel_Lights_EMS) {
 TEST_F(EnergyPlusFixture, ForwardTranslatorActuator_exampleModel_Electric_EMS) {
   //use spacetype with multiple spaces
   //this is the issue with spaceloads if there are multiple spaces using a spaceload defined in a spaceType
-  //the zonelist (or spaceList if m_excludeSpaceTranslation = false) is created from the spaceType name,
+  //the zonelist (or spaceList if m_forwardTranslatorOptions.excludeSpaceTranslation() = false) is created from the spaceType name,
   //and the zones in the list are the space.thermalzone names
 
   Model model = exampleModel();
diff --git a/src/energyplus/Test/OutputControlTableStyle_GTest.cpp b/src/energyplus/Test/OutputControlTableStyle_GTest.cpp
index d1ae5ec784a..af9ad325bb6 100644
--- a/src/energyplus/Test/OutputControlTableStyle_GTest.cpp
+++ b/src/energyplus/Test/OutputControlTableStyle_GTest.cpp
@@ -82,7 +82,7 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_OutputControlTableStyle) {
     EXPECT_EQ("InchPound", idf_tablestyle.getString(OutputControl_Table_StyleFields::UnitConversion, false).get());
   }
 
-  // Not there, m_excludeHTMLOutputReport = True
+  // Not there, m_forwardTranslatorOptions.excludeHTMLOutputReport() = True
   {
     Model m;
     ForwardTranslator ft;
diff --git a/src/energyplus/Test/OutputSQLite_GTest.cpp b/src/energyplus/Test/OutputSQLite_GTest.cpp
index 8b8f8aafa95..b756873b53f 100644
--- a/src/energyplus/Test/OutputSQLite_GTest.cpp
+++ b/src/energyplus/Test/OutputSQLite_GTest.cpp
@@ -66,7 +66,7 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_OutputSQLite) {
     EXPECT_TRUE(idf_sqlite.isEmpty(Output_SQLiteFields::UnitConversionforTabularData));
   }
 
-  // Not there, m_excludeSQliteOutputReport = True
+  // Not there, m_forwardTranslatorOptions.excludeSQliteOutputReport() = True
   {
     Model m;
     ForwardTranslator ft;
diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt
index e74dc9fd967..b51b8ab9357 100644
--- a/src/utilities/CMakeLists.txt
+++ b/src/utilities/CMakeLists.txt
@@ -105,6 +105,9 @@ set(filetypes_src
   filetypes/RunOptions.hpp
   filetypes/RunOptions_Impl.hpp
   filetypes/RunOptions.cpp
+  filetypes/ForwardTranslatorOptions.hpp
+  filetypes/ForwardTranslatorOptions_Impl.hpp
+  filetypes/ForwardTranslatorOptions.cpp
   filetypes/StandardsJSON.hpp
   filetypes/StandardsJSON_Impl.hpp
   filetypes/StandardsJSON.cpp
diff --git a/src/utilities/filetypes/Filetypes.i b/src/utilities/filetypes/Filetypes.i
index 977f3c0c02d..5f6c561d053 100644
--- a/src/utilities/filetypes/Filetypes.i
+++ b/src/utilities/filetypes/Filetypes.i
@@ -6,6 +6,7 @@
 
   // Avoid triggering a SWIG warning: 'string' is a C# keyword
   %rename(toString) openstudio::RunOptions::string;
+  %rename(toString) openstudio::ForwardTranslatorOptions::string;
   %rename(toString) openstudio::WorkflowStepValue::string;
   %rename(toString) openstudio::WorkflowStepResult::string;
   %rename(toString) openstudio::WorkflowStep::string;
@@ -30,6 +31,7 @@
   #include <utilities/filetypes/EpwFile.hpp>
   #include <utilities/filetypes/RunOptions.hpp>
   #include <utilities/filetypes/RunOptions_Impl.hpp>
+  #include <utilities/filetypes/ForwardTranslatorOptions.hpp>
   #include <utilities/filetypes/WorkflowStepResult.hpp>
   #include <utilities/filetypes/WorkflowStep.hpp>
   #include <utilities/filetypes/WorkflowStep_Impl.hpp>
@@ -82,6 +84,8 @@
 %template(OptionalCustomOutputAdapter) boost::optional<openstudio::CustomOutputAdapter>;
 
 %template(OptionalRunOptions) boost::optional<openstudio::RunOptions>;
+%template(OptionalForwardTranslatorOptions) boost::optional<openstudio::ForwardTranslatorOptions>;
+%template(ForwardTranslatorOptionKeyMethodVector) std::vector<openstudio::ForwardTranslatorOptionKeyMethod>;
 
 %template(OptionalStepResult) boost::optional<openstudio::StepResult>;
 
@@ -112,6 +116,7 @@
 %include <utilities/filetypes/CSVFile.hpp>
 %include <utilities/filetypes/EpwFile.hpp>
 %include <utilities/filetypes/RunOptions.hpp>
+%include <utilities/filetypes/ForwardTranslatorOptions.hpp>
 %include <utilities/filetypes/WorkflowStepResult.hpp>
 %include <utilities/filetypes/WorkflowStep.hpp>
 %include <utilities/filetypes/WorkflowJSON.hpp>
@@ -126,6 +131,23 @@
   }
 };
 
+%extend openstudio::ForwardTranslatorOptions{
+  std::string __str__() {
+    std::ostringstream os;
+    os << *self;
+    return os.str();
+  }
+};
+
+// extend classes
+%extend openstudio::ForwardTranslatorOptionKeyMethod {
+  std::string __str__() {
+    std::ostringstream os;
+    os << *$self;
+    return os.str();
+  }
+};
+
 %extend openstudio::WorkflowStepResult{
   std::string __str__() {
     std::ostringstream os;
diff --git a/src/utilities/filetypes/ForwardTranslatorOptions.cpp b/src/utilities/filetypes/ForwardTranslatorOptions.cpp
new file mode 100644
index 00000000000..49d269b091d
--- /dev/null
+++ b/src/utilities/filetypes/ForwardTranslatorOptions.cpp
@@ -0,0 +1,463 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "ForwardTranslatorOptions.hpp"
+#include "ForwardTranslatorOptions_Impl.hpp"
+
+#include <json/json.h>
+
+#include <memory>
+#include <string>
+
+namespace openstudio {
+namespace detail {
+
+  void ForwardTranslatorOptions_Impl::reset() {
+    m_runcontrolspecialdays = DEFAULT_RUNCONTROLSPECIALDAYS;
+    m_is_runcontrolspecialdays_defaulted = true;
+
+    m_ip_tabular_output = DEFAULT_IP_TABULAR_OUTPUT;
+    m_is_ip_tabular_output_defaulted = true;
+
+    m_no_lifecyclecosts = DEFAULT_NO_LIFECYCLECOSTS;
+    m_is_no_lifecyclecosts_defaulted = true;
+
+    m_no_sqlite_output = DEFAULT_NO_SQLITE_OUTPUT;
+    m_is_no_sqlite_output_defaulted = true;
+
+    m_no_html_output = DEFAULT_NO_HTML_OUTPUT;
+    m_is_no_html_output_defaulted = true;
+
+    m_no_variable_dictionary = DEFAULT_NO_VARIABLE_DICTIONARY;
+    m_is_no_variable_dictionary_defaulted = true;
+
+    m_no_space_translation = DEFAULT_NO_SPACE_TRANSLATION;
+    m_is_no_space_translation_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::keepRunControlSpecialDays() const {
+    return m_runcontrolspecialdays;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isKeepRunControlSpecialDaysDefaulted() const {
+    return m_is_runcontrolspecialdays_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setKeepRunControlSpecialDays(bool keepRunControlSpecialDays) {
+    m_runcontrolspecialdays = keepRunControlSpecialDays;
+    m_is_runcontrolspecialdays_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetKeepRunControlSpecialDays() {
+    m_runcontrolspecialdays = DEFAULT_RUNCONTROLSPECIALDAYS;
+    m_is_runcontrolspecialdays_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::iPTabularOutput() const {
+    return m_ip_tabular_output;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isIPTabularOutputDefaulted() const {
+    return m_is_ip_tabular_output_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setIPTabularOutput(bool iPTabularOutput) {
+    m_ip_tabular_output = iPTabularOutput;
+    m_is_ip_tabular_output_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetIPTabularOutput() {
+    m_ip_tabular_output = DEFAULT_IP_TABULAR_OUTPUT;
+    m_is_ip_tabular_output_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::excludeLCCObjects() const {
+    return m_no_lifecyclecosts;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isExcludeLCCObjectsDefaulted() const {
+    return m_is_no_lifecyclecosts_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setExcludeLCCObjects(bool excludeLCCObjects) {
+    m_no_lifecyclecosts = excludeLCCObjects;
+    m_is_no_lifecyclecosts_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetExcludeLCCObjects() {
+    m_no_lifecyclecosts = DEFAULT_NO_LIFECYCLECOSTS;
+    m_is_no_lifecyclecosts_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::excludeSQliteOutputReport() const {
+    return m_no_sqlite_output;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isExcludeSQliteOutputReportDefaulted() const {
+    return m_is_no_sqlite_output_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setExcludeSQliteOutputReport(bool excludeSQliteOutputReport) {
+    m_no_sqlite_output = excludeSQliteOutputReport;
+    m_is_no_sqlite_output_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetExcludeSQliteOutputReport() {
+    m_no_sqlite_output = DEFAULT_NO_SQLITE_OUTPUT;
+    m_is_no_sqlite_output_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::excludeHTMLOutputReport() const {
+    return m_no_html_output;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isExcludeHTMLOutputReportDefaulted() const {
+    return m_is_no_html_output_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setExcludeHTMLOutputReport(bool excludeHTMLOutputReport) {
+    m_no_html_output = excludeHTMLOutputReport;
+    m_is_no_html_output_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetExcludeHTMLOutputReport() {
+    m_no_html_output = DEFAULT_NO_HTML_OUTPUT;
+    m_is_no_html_output_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::excludeVariableDictionary() const {
+    return m_no_variable_dictionary;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isExcludeVariableDictionaryDefaulted() const {
+    return m_is_no_variable_dictionary_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setExcludeVariableDictionary(bool excludeVariableDictionary) {
+    m_no_variable_dictionary = excludeVariableDictionary;
+    m_is_no_variable_dictionary_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetExcludeVariableDictionary() {
+    m_no_variable_dictionary = DEFAULT_NO_VARIABLE_DICTIONARY;
+    m_is_no_variable_dictionary_defaulted = true;
+  }
+
+  bool ForwardTranslatorOptions_Impl::excludeSpaceTranslation() const {
+    return m_no_space_translation;
+  }
+
+  bool ForwardTranslatorOptions_Impl::isExcludeSpaceTranslationDefaulted() const {
+    return m_is_no_space_translation_defaulted;
+  }
+
+  void ForwardTranslatorOptions_Impl::setExcludeSpaceTranslation(bool excludeSpaceTranslation) {
+    m_no_space_translation = excludeSpaceTranslation;
+    m_is_no_space_translation_defaulted = false;
+  }
+
+  void ForwardTranslatorOptions_Impl::resetExcludeSpaceTranslation() {
+    m_no_space_translation = DEFAULT_NO_SPACE_TRANSLATION;
+    m_is_no_space_translation_defaulted = true;
+  }
+
+  void ForwardTranslatorOptions_Impl::overrideValuesWith(const ForwardTranslatorOptions& other) {
+    if (!other.isKeepRunControlSpecialDaysDefaulted()) {
+      setKeepRunControlSpecialDays(other.keepRunControlSpecialDays());
+    }
+
+    if (!other.isIPTabularOutputDefaulted()) {
+      setIPTabularOutput(other.iPTabularOutput());
+    }
+
+    if (!other.isExcludeLCCObjectsDefaulted()) {
+      setExcludeLCCObjects(other.excludeLCCObjects());
+    }
+
+    if (!other.isExcludeSQliteOutputReportDefaulted()) {
+      setExcludeSQliteOutputReport(other.excludeSQliteOutputReport());
+    }
+
+    if (!other.isExcludeHTMLOutputReportDefaulted()) {
+      setExcludeHTMLOutputReport(other.excludeHTMLOutputReport());
+    }
+
+    if (!other.isExcludeVariableDictionaryDefaulted()) {
+      setExcludeVariableDictionary(other.excludeVariableDictionary());
+    }
+
+    if (!other.isExcludeSpaceTranslationDefaulted()) {
+      setExcludeSpaceTranslation(other.excludeSpaceTranslation());
+    }
+  }
+
+  Json::Value ForwardTranslatorOptions_Impl::json() const {
+    Json::Value value;
+
+    if (!m_is_runcontrolspecialdays_defaulted) {
+      value["runcontrolspecialdays"] = m_runcontrolspecialdays;
+    }
+
+    if (!m_is_ip_tabular_output_defaulted) {
+      value["ip_tabular_output"] = m_ip_tabular_output;
+    }
+
+    if (!m_is_no_lifecyclecosts_defaulted) {
+      value["no_lifecyclecosts"] = m_no_lifecyclecosts;
+    }
+
+    if (!m_is_no_sqlite_output_defaulted) {
+      value["no_sqlite_output"] = m_no_sqlite_output;
+    }
+
+    if (!m_is_no_html_output_defaulted) {
+      value["no_html_output"] = m_no_html_output;
+    }
+
+    if (!m_is_no_variable_dictionary_defaulted) {
+      value["no_variable_dictionary"] = m_no_variable_dictionary;
+    }
+
+    if (!m_is_no_space_translation_defaulted) {
+      value["no_space_translation"] = m_no_space_translation;
+    }
+
+    return value;
+  }
+
+  std::string ForwardTranslatorOptions_Impl::string() const {
+
+    // Write to string
+    Json::StreamWriterBuilder wbuilder;
+    // mimic the old StyledWriter behavior:
+    wbuilder["indentation"] = "   ";
+    std::string result = Json::writeString(wbuilder, json());
+
+    return result;
+  }
+
+}  // namespace detail
+
+ForwardTranslatorOptions::ForwardTranslatorOptions(std::shared_ptr<detail::ForwardTranslatorOptions_Impl> impl) : m_impl(std::move(impl)) {}
+ForwardTranslatorOptions::ForwardTranslatorOptions() : m_impl(std::make_shared<detail::ForwardTranslatorOptions_Impl>()) {}
+
+void ForwardTranslatorOptions::reset() {
+  m_impl->reset();
+}
+
+boost::optional<ForwardTranslatorOptions> ForwardTranslatorOptions::fromString(const std::string& s) {
+
+  // We let it fail with a warning message
+  Json::CharReaderBuilder rbuilder;
+  std::istringstream ss(s);
+  std::string formattedErrors;
+  Json::Value value;
+  bool parsingSuccessful = Json::parseFromStream(rbuilder, ss, &value, &formattedErrors);
+  if (!parsingSuccessful) {
+    LOG(Warn, "Couldn't parse ForwardTranslatorOptions from string s='" << s << "'. Error: '" << formattedErrors << "'.");
+    return boost::none;
+  }
+
+  return ForwardTranslatorOptions::fromJSON(value);
+}
+
+ForwardTranslatorOptions ForwardTranslatorOptions::fromJSON(const Json::Value& value) {
+
+  ForwardTranslatorOptions result;
+
+  if (value.isMember("runcontrolspecialdays") && value["runcontrolspecialdays"].isBool()) {
+    result.setKeepRunControlSpecialDays(value["runcontrolspecialdays"].asBool());
+  }
+  if (value.isMember("ip_tabular_output") && value["ip_tabular_output"].isBool()) {
+    result.setIPTabularOutput(value["ip_tabular_output"].asBool());
+  }
+  if (value.isMember("no_lifecyclecosts") && value["no_lifecyclecosts"].isBool()) {
+    result.setExcludeLCCObjects(value["no_lifecyclecosts"].asBool());
+  }
+  if (value.isMember("no_sqlite_output") && value["no_sqlite_output"].isBool()) {
+    result.setExcludeSQliteOutputReport(value["no_sqlite_output"].asBool());
+  }
+  if (value.isMember("no_html_output") && value["no_html_output"].isBool()) {
+    result.setExcludeHTMLOutputReport(value["no_html_output"].asBool());
+  }
+  if (value.isMember("no_variable_dictionary") && value["no_variable_dictionary"].isBool()) {
+    result.setExcludeVariableDictionary(value["no_variable_dictionary"].asBool());
+  }
+  if (value.isMember("no_space_translation") && value["no_space_translation"].isBool()) {
+    result.setExcludeSpaceTranslation(value["no_space_translation"].asBool());
+  }
+
+  return result;
+}
+
+Json::Value ForwardTranslatorOptions::json() const {
+  return m_impl->json();
+}
+
+std::string ForwardTranslatorOptions::string() const {
+  return m_impl->string();
+}
+
+bool ForwardTranslatorOptions::isKeepRunControlSpecialDaysDefaulted() const {
+  return m_impl->isKeepRunControlSpecialDaysDefaulted();
+}
+
+bool ForwardTranslatorOptions::keepRunControlSpecialDays() const {
+  return m_impl->keepRunControlSpecialDays();
+}
+
+void ForwardTranslatorOptions::setKeepRunControlSpecialDays(bool keepRunControlSpecialDays) {
+  m_impl->setKeepRunControlSpecialDays(keepRunControlSpecialDays);
+}
+
+void ForwardTranslatorOptions::resetKeepRunControlSpecialDays() {
+  m_impl->resetKeepRunControlSpecialDays();
+}
+
+bool ForwardTranslatorOptions::isIPTabularOutputDefaulted() const {
+  return m_impl->isIPTabularOutputDefaulted();
+}
+
+bool ForwardTranslatorOptions::iPTabularOutput() const {
+  return m_impl->iPTabularOutput();
+}
+
+void ForwardTranslatorOptions::setIPTabularOutput(bool iPTabularOutput) {
+  m_impl->setIPTabularOutput(iPTabularOutput);
+}
+
+void ForwardTranslatorOptions::resetIPTabularOutput() {
+  m_impl->resetIPTabularOutput();
+}
+
+bool ForwardTranslatorOptions::isExcludeLCCObjectsDefaulted() const {
+  return m_impl->isExcludeLCCObjectsDefaulted();
+}
+
+bool ForwardTranslatorOptions::excludeLCCObjects() const {
+  return m_impl->excludeLCCObjects();
+}
+
+void ForwardTranslatorOptions::setExcludeLCCObjects(bool excludeLCCObjects) {
+  m_impl->setExcludeLCCObjects(excludeLCCObjects);
+}
+
+void ForwardTranslatorOptions::resetExcludeLCCObjects() {
+  m_impl->resetExcludeLCCObjects();
+}
+
+bool ForwardTranslatorOptions::isExcludeSQliteOutputReportDefaulted() const {
+  return m_impl->isExcludeSQliteOutputReportDefaulted();
+}
+
+bool ForwardTranslatorOptions::excludeSQliteOutputReport() const {
+  return m_impl->excludeSQliteOutputReport();
+}
+
+void ForwardTranslatorOptions::setExcludeSQliteOutputReport(bool excludeSQliteOutputReport) {
+  m_impl->setExcludeSQliteOutputReport(excludeSQliteOutputReport);
+}
+
+void ForwardTranslatorOptions::resetExcludeSQliteOutputReport() {
+  m_impl->resetExcludeSQliteOutputReport();
+}
+
+bool ForwardTranslatorOptions::isExcludeHTMLOutputReportDefaulted() const {
+  return m_impl->isExcludeHTMLOutputReportDefaulted();
+}
+
+bool ForwardTranslatorOptions::excludeHTMLOutputReport() const {
+  return m_impl->excludeHTMLOutputReport();
+}
+
+void ForwardTranslatorOptions::setExcludeHTMLOutputReport(bool excludeHTMLOutputReport) {
+  m_impl->setExcludeHTMLOutputReport(excludeHTMLOutputReport);
+}
+
+void ForwardTranslatorOptions::resetExcludeHTMLOutputReport() {
+  m_impl->resetExcludeHTMLOutputReport();
+}
+
+bool ForwardTranslatorOptions::isExcludeVariableDictionaryDefaulted() const {
+  return m_impl->isExcludeVariableDictionaryDefaulted();
+}
+
+bool ForwardTranslatorOptions::excludeVariableDictionary() const {
+  return m_impl->excludeVariableDictionary();
+}
+
+void ForwardTranslatorOptions::setExcludeVariableDictionary(bool excludeVariableDictionary) {
+  m_impl->setExcludeVariableDictionary(excludeVariableDictionary);
+}
+
+void ForwardTranslatorOptions::resetExcludeVariableDictionary() {
+  m_impl->resetExcludeVariableDictionary();
+}
+
+bool ForwardTranslatorOptions::isExcludeSpaceTranslationDefaulted() const {
+  return m_impl->isExcludeSpaceTranslationDefaulted();
+}
+
+bool ForwardTranslatorOptions::excludeSpaceTranslation() const {
+  return m_impl->excludeSpaceTranslation();
+}
+
+void ForwardTranslatorOptions::setExcludeSpaceTranslation(bool excludeSpaceTranslation) {
+  m_impl->setExcludeSpaceTranslation(excludeSpaceTranslation);
+}
+
+void ForwardTranslatorOptions::resetExcludeSpaceTranslation() {
+  m_impl->resetExcludeSpaceTranslation();
+}
+
+void ForwardTranslatorOptions::overrideValuesWith(const ForwardTranslatorOptions& other) {
+  m_impl->overrideValuesWith(other);
+}
+
+std::ostream& operator<<(std::ostream& os, const ForwardTranslatorOptions& forwardTranslatorOptions) {
+  os << forwardTranslatorOptions.string();
+  return os;
+}
+
+std::vector<ForwardTranslatorOptionKeyMethod> ForwardTranslatorOptions::forwardTranslatorOptionKeyMethods() {
+  return std::vector<ForwardTranslatorOptionKeyMethod>{{{"runcontrolspecialdays", "setKeepRunControlSpecialDays"},
+                                                        {"ip_tabular_output", "setIPTabularOutput"},
+                                                        {"no_lifecyclecosts", "setExcludeLCCObjects"},
+                                                        {"no_sqlite_output", "setExcludeSQliteOutputReport"},
+                                                        {"no_html_output", "setExcludeHTMLOutputReport"},
+                                                        {"no_variable_dictionary", "setExcludeVariableDictionary"},
+                                                        {"no_space_translation", "setExcludeSpaceTranslation"}}};
+}
+
+std::ostream& operator<<(std::ostream& out, const ForwardTranslatorOptionKeyMethod& opt) {
+  out << "(" << opt.json_name << ", " << opt.ft_method_name << ")";
+  return out;
+}
+
+}  // namespace openstudio
diff --git a/src/utilities/filetypes/ForwardTranslatorOptions.hpp b/src/utilities/filetypes/ForwardTranslatorOptions.hpp
new file mode 100644
index 00000000000..3b45567f441
--- /dev/null
+++ b/src/utilities/filetypes/ForwardTranslatorOptions.hpp
@@ -0,0 +1,136 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef UTILITIES_FILETYPES_FORWARDTRANSLATOROPTIONS_HPP
+#define UTILITIES_FILETYPES_FORWARDTRANSLATOROPTIONS_HPP
+
+#include "../UtilitiesAPI.hpp"
+
+#include "../core/Logger.hpp"
+
+namespace Json {
+class Value;
+}
+
+namespace openstudio {
+namespace detail {
+  class WorkflowJSON_Impl;
+  class RunOptions_Impl;
+  class ForwardTranslatorOptions_Impl;
+}  // namespace detail
+
+struct ForwardTranslatorOptionKeyMethod
+{
+  std::string json_name;
+  std::string ft_method_name;
+};
+UTILITIES_API std::ostream& operator<<(std::ostream& out, const ForwardTranslatorOptionKeyMethod& opt);
+
+class UTILITIES_API ForwardTranslatorOptions
+{
+ public:
+  ForwardTranslatorOptions();
+
+  void reset();
+
+  /// Construct from JSON formatted string
+  static boost::optional<ForwardTranslatorOptions> fromString(const std::string& s);
+
+  /// Serialize to JSON formatted string
+  std::string string() const;
+
+  bool keepRunControlSpecialDays() const;
+  bool isKeepRunControlSpecialDaysDefaulted() const;
+  void setKeepRunControlSpecialDays(bool keepRunControlSpecialDays);
+  void resetKeepRunControlSpecialDays();
+
+  bool iPTabularOutput() const;
+  bool isIPTabularOutputDefaulted() const;
+  void setIPTabularOutput(bool iPTabularOutput);
+  void resetIPTabularOutput();
+
+  bool excludeLCCObjects() const;
+  bool isExcludeLCCObjectsDefaulted() const;
+  void setExcludeLCCObjects(bool excludeLCCObjects);
+  void resetExcludeLCCObjects();
+
+  bool excludeSQliteOutputReport() const;
+  bool isExcludeSQliteOutputReportDefaulted() const;
+  void setExcludeSQliteOutputReport(bool excludeSQliteOutputReport);
+  void resetExcludeSQliteOutputReport();
+
+  bool excludeHTMLOutputReport() const;
+  bool isExcludeHTMLOutputReportDefaulted() const;
+  void setExcludeHTMLOutputReport(bool excludeHTMLOutputReport);
+  void resetExcludeHTMLOutputReport();
+
+  bool excludeVariableDictionary() const;
+  bool isExcludeVariableDictionaryDefaulted() const;
+  void setExcludeVariableDictionary(bool excludeVariableDictionary);
+  void resetExcludeVariableDictionary();
+
+  bool excludeSpaceTranslation() const;
+  bool isExcludeSpaceTranslationDefaulted() const;
+  void setExcludeSpaceTranslation(bool excludeSpaceTranslation);
+  void resetExcludeSpaceTranslation();
+
+  /* Any non-defaulted value from other is brought over */
+  void overrideValuesWith(const ForwardTranslatorOptions& other);
+
+  // TODO: keep?
+  static std::vector<ForwardTranslatorOptionKeyMethod> forwardTranslatorOptionKeyMethods();
+
+ protected:
+  Json::Value json() const;
+  static ForwardTranslatorOptions fromJSON(const Json::Value& value);
+
+  // get the impl
+  std::shared_ptr<detail::ForwardTranslatorOptions_Impl> getImpl() const {
+    return m_impl;
+  }
+
+  explicit ForwardTranslatorOptions(std::shared_ptr<detail::ForwardTranslatorOptions_Impl> impl);
+
+  friend class detail::ForwardTranslatorOptions_Impl;
+  friend class RunOptions;
+  friend class detail::RunOptions_Impl;
+
+ private:
+  // configure logging
+  REGISTER_LOGGER("openstudio.ForwardTranslatorOptions");
+
+  // pointer to implementation
+  std::shared_ptr<detail::ForwardTranslatorOptions_Impl> m_impl;
+};
+
+UTILITIES_API std::ostream& operator<<(std::ostream& os, const ForwardTranslatorOptions& forwardTranslatorOptions);
+
+}  // namespace openstudio
+
+#endif  // UTILITIES_FILETYPES_FORWARDTRANSLATOROPTIONS_HPP
diff --git a/src/utilities/filetypes/ForwardTranslatorOptions_Impl.hpp b/src/utilities/filetypes/ForwardTranslatorOptions_Impl.hpp
new file mode 100644
index 00000000000..c55e48224b6
--- /dev/null
+++ b/src/utilities/filetypes/ForwardTranslatorOptions_Impl.hpp
@@ -0,0 +1,132 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef UTILITIES_FILETYPES_FORWARDTRANSLATOROPTIONS_IMPL_HPP
+#define UTILITIES_FILETYPES_FORWARDTRANSLATOROPTIONS_IMPL_HPP
+
+#include "../UtilitiesAPI.hpp"
+
+#include "../core/Logger.hpp"
+
+namespace Json {
+class Value;
+}
+
+namespace openstudio {
+class ForwardTranslatorOptions;
+
+namespace detail {
+
+  // TODO: is the nano onChange signal really needed for RunOptions?! If so, I need to implement one for ForwardTranslatorOptions too
+  class UTILITIES_API ForwardTranslatorOptions_Impl
+  {
+   public:
+    ForwardTranslatorOptions_Impl() = default;
+
+    void reset();
+
+    std::string string() const;
+    Json::Value json() const;
+
+    bool keepRunControlSpecialDays() const;
+    bool isKeepRunControlSpecialDaysDefaulted() const;
+    void setKeepRunControlSpecialDays(bool keepRunControlSpecialDays);
+    void resetKeepRunControlSpecialDays();
+
+    bool iPTabularOutput() const;
+    bool isIPTabularOutputDefaulted() const;
+    void setIPTabularOutput(bool iPTabularOutput);
+    void resetIPTabularOutput();
+
+    bool excludeLCCObjects() const;
+    bool isExcludeLCCObjectsDefaulted() const;
+    void setExcludeLCCObjects(bool excludeLCCObjects);
+    void resetExcludeLCCObjects();
+
+    bool excludeSQliteOutputReport() const;
+    bool isExcludeSQliteOutputReportDefaulted() const;
+    void setExcludeSQliteOutputReport(bool excludeSQliteOutputReport);
+    void resetExcludeSQliteOutputReport();
+
+    bool excludeHTMLOutputReport() const;
+    bool isExcludeHTMLOutputReportDefaulted() const;
+    void setExcludeHTMLOutputReport(bool excludeHTMLOutputReport);
+    void resetExcludeHTMLOutputReport();
+
+    bool excludeVariableDictionary() const;
+    bool isExcludeVariableDictionaryDefaulted() const;
+    void setExcludeVariableDictionary(bool excludeVariableDictionary);
+    void resetExcludeVariableDictionary();
+
+    bool excludeSpaceTranslation() const;
+    bool isExcludeSpaceTranslationDefaulted() const;
+    void setExcludeSpaceTranslation(bool excludeSpaceTranslation);
+    void resetExcludeSpaceTranslation();
+
+    /* Any non-defaulted value from other is brought over */
+    void overrideValuesWith(const ForwardTranslatorOptions& other);
+
+   private:
+    // configure logging
+    REGISTER_LOGGER("openstudio.ForwardTranslatorOptions");
+
+    static constexpr bool DEFAULT_RUNCONTROLSPECIALDAYS = true;  // At 3.1.0 this was changed to true.
+    static constexpr bool DEFAULT_IP_TABULAR_OUTPUT = false;
+    static constexpr bool DEFAULT_NO_LIFECYCLECOSTS = false;
+    static constexpr bool DEFAULT_NO_SQLITE_OUTPUT = false;
+    static constexpr bool DEFAULT_NO_HTML_OUTPUT = false;
+    static constexpr bool DEFAULT_NO_VARIABLE_DICTIONARY = false;
+    static constexpr bool DEFAULT_NO_SPACE_TRANSLATION = false;  // At 3.4.1, this was changed to false.
+
+    bool m_runcontrolspecialdays = DEFAULT_RUNCONTROLSPECIALDAYS;
+    bool m_is_runcontrolspecialdays_defaulted = true;
+
+    bool m_ip_tabular_output = DEFAULT_IP_TABULAR_OUTPUT;
+    bool m_is_ip_tabular_output_defaulted = true;
+
+    bool m_no_lifecyclecosts = DEFAULT_NO_LIFECYCLECOSTS;
+    bool m_is_no_lifecyclecosts_defaulted = true;
+
+    bool m_no_sqlite_output = DEFAULT_NO_SQLITE_OUTPUT;
+    bool m_is_no_sqlite_output_defaulted = true;
+
+    bool m_no_html_output = DEFAULT_NO_HTML_OUTPUT;
+    bool m_is_no_html_output_defaulted = true;
+
+    bool m_no_variable_dictionary = DEFAULT_NO_VARIABLE_DICTIONARY;
+    bool m_is_no_variable_dictionary_defaulted = true;
+
+    bool m_no_space_translation = DEFAULT_NO_SPACE_TRANSLATION;
+    bool m_is_no_space_translation_defaulted = true;
+  };
+
+}  // namespace detail
+}  // namespace openstudio
+
+#endif  //UTILITIES_FILETYPES_FORWARDTRANSLATOROPTIONS_IMPL_HPP
diff --git a/src/utilities/filetypes/RunOptions.cpp b/src/utilities/filetypes/RunOptions.cpp
index 81d666b4727..762220bd3af 100644
--- a/src/utilities/filetypes/RunOptions.cpp
+++ b/src/utilities/filetypes/RunOptions.cpp
@@ -29,24 +29,18 @@
 
 #include "RunOptions.hpp"
 #include "RunOptions_Impl.hpp"
+#include "ForwardTranslatorOptions.hpp"
 
 #include "../core/Assert.hpp"
+
 #include <json/json.h>
 
+#include <memory>
+#include <string>
+
 namespace openstudio {
 namespace detail {
 
-  RunOptions_Impl::RunOptions_Impl()
-    : m_debug(false),
-      m_epjson(false),
-      m_fast(false),
-      m_preserveRunDir(false),
-      m_skipExpandObjects(false),
-      m_skipEnergyPlusPreprocess(false),
-      m_cleanup(true) {}
-
-  RunOptions_Impl::~RunOptions_Impl() {}
-
   void RunOptions_Impl::onUpdate() {
     this->onChange.nano_emit();
   }
@@ -54,6 +48,7 @@ namespace detail {
   std::string RunOptions_Impl::string() const {
     Json::Value value;
 
+    // TODO: Why is this only writing if not default? I find it weird
     if (m_debug) {
       value["debug"] = m_debug;
     }
@@ -97,17 +92,8 @@ namespace detail {
       value["output_adapter"] = outputAdapter;
     }
 
-    if (!m_forwardTranslateOptions.empty()) {
-      Json::Value options;
-      Json::CharReaderBuilder rbuilder;
-      std::istringstream ss(m_forwardTranslateOptions);
-      std::string formattedErrors;
-      bool parsingSuccessful = Json::parseFromStream(rbuilder, ss, &options, &formattedErrors);
-      if (parsingSuccessful) {
-        value["ft_options"] = options;
-      } else {
-        LOG(Warn, "Couldn't parse forwardTranslateOptions's options='" << m_forwardTranslateOptions << "'. Error: '" << formattedErrors << "'.");
-      }
+    if (auto ft_opt = m_forwardTranslatorOptions.json()) {
+      value["ft_options"] = ft_opt;
     }
 
     // Write to string
@@ -239,18 +225,18 @@ namespace detail {
     onUpdate();
   }
 
-  std::string RunOptions_Impl::forwardTranslateOptions() const {
-    return m_forwardTranslateOptions;
+  ForwardTranslatorOptions RunOptions_Impl::forwardTranslatorOptions() const {
+    return m_forwardTranslatorOptions;
   }
 
-  bool RunOptions_Impl::setForwardTranslateOptions(const std::string& options) {
-    m_forwardTranslateOptions = options;
+  bool RunOptions_Impl::setForwardTranslatorOptions(const ForwardTranslatorOptions& forwardTranslatorOptions) {
+    m_forwardTranslatorOptions = ForwardTranslatorOptions(forwardTranslatorOptions.getImpl());
     onUpdate();
     return true;
   }
 
-  void RunOptions_Impl::resetForwardTranslateOptions() {
-    m_forwardTranslateOptions.clear();
+  void RunOptions_Impl::resetForwardTranslatorOptions() {
+    m_forwardTranslatorOptions.reset();
     onUpdate();
   }
 
@@ -271,14 +257,13 @@ std::string CustomOutputAdapter::options() const {
   return m_options;
 }
 
-RunOptions::RunOptions() : m_impl(std::shared_ptr<detail::RunOptions_Impl>(new detail::RunOptions_Impl())) {
+RunOptions::RunOptions() : m_impl(std::make_shared<detail::RunOptions_Impl>()) {
   OS_ASSERT(getImpl<detail::RunOptions_Impl>());
 }
 
-RunOptions::~RunOptions() {}
+RunOptions::~RunOptions() = default;
 
 boost::optional<RunOptions> RunOptions::fromString(const std::string& s) {
-  boost::optional<RunOptions> result;
 
   // We let it fail with a warning message
   Json::CharReaderBuilder rbuilder;
@@ -288,33 +273,33 @@ boost::optional<RunOptions> RunOptions::fromString(const std::string& s) {
   bool parsingSuccessful = Json::parseFromStream(rbuilder, ss, &value, &formattedErrors);
   if (!parsingSuccessful) {
     LOG(Warn, "Couldn't parse RunOptions from string s='" << s << "'. Error: '" << formattedErrors << "'.");
-    return result;
+    return boost::none;
   }
 
-  result = RunOptions();
+  RunOptions result;
 
   if (value.isMember("epjson") && value["epjson"].isBool()) {
-    result->setEpjson(value["epjson"].asBool());
+    result.setEpjson(value["epjson"].asBool());
   }
 
   if (value.isMember("debug") && value["debug"].isBool()) {
-    result->setDebug(value["debug"].asBool());
+    result.setDebug(value["debug"].asBool());
   }
 
   if (value.isMember("fast") && value["fast"].isBool()) {
-    result->setFast(value["fast"].asBool());
+    result.setFast(value["fast"].asBool());
   }
 
   if (value.isMember("preserve_run_dir") && value["preserve_run_dir"].isBool()) {
-    result->setPreserveRunDir(value["preserve_run_dir"].asBool());
+    result.setPreserveRunDir(value["preserve_run_dir"].asBool());
   }
 
   if (value.isMember("skip_expand_objects") && value["skip_expand_objects"].isBool()) {
-    result->setSkipExpandObjects(value["skip_expand_objects"].asBool());
+    result.setSkipExpandObjects(value["skip_expand_objects"].asBool());
   }
 
   if (value.isMember("skip_energyplus_preprocess") && value["skip_energyplus_preprocess"].isBool()) {
-    result->setSkipEnergyPlusPreprocess(value["skip_energyplus_preprocess"].asBool());
+    result.setSkipEnergyPlusPreprocess(value["skip_energyplus_preprocess"].asBool());
   }
 
   if (value.isMember("output_adapter")) {
@@ -330,28 +315,12 @@ boost::optional<RunOptions> RunOptions::fromString(const std::string& s) {
       std::string optionString = Json::writeString(wbuilder, options);
 
       CustomOutputAdapter coa(customFileName, className, optionString);
-      result->setCustomOutputAdapter(coa);
+      result.setCustomOutputAdapter(coa);
     }
   }
 
-  if (value.isMember("ft_options")) {
-    Json::Value options = value["ft_options"];
-
-    // Do some filtering to avoid passing bogus values to workflow-gem, and checking that it's correctly a boolean
-    Json::Value cleaned_options;
-    for (auto& known_opt : {"runcontrolspecialdays", "ip_tabular_output", "no_lifecyclecosts", "no_sqlite_output", "no_html_output",
-                            "no_variable_dictionary", "no_space_translation"}) {
-      if (options.isMember(known_opt) && options[known_opt].isBool()) {
-        cleaned_options[known_opt] = options[known_opt].asBool();
-      }
-    }
-
-    Json::StreamWriterBuilder wbuilder;
-    // mimic the old StyledWriter behavior:
-    wbuilder["indentation"] = "   ";
-    std::string optionString = Json::writeString(wbuilder, cleaned_options);
-
-    result->setForwardTranslateOptions(optionString);
+  if (value.isMember("ft_options") && value["ft_options"].isObject()) {
+    result.setForwardTranslatorOptions(ForwardTranslatorOptions::fromJSON(value["ft_options"]));
   }
 
   return result;
@@ -456,18 +425,44 @@ void RunOptions::resetCustomOutputAdapter() {
   getImpl<detail::RunOptions_Impl>()->resetCustomOutputAdapter();
 }
 
+ForwardTranslatorOptions RunOptions::forwardTranslatorOptions() const {
+  return getImpl<detail::RunOptions_Impl>()->forwardTranslatorOptions();
+}
+
+bool RunOptions::setForwardTranslatorOptions(const ForwardTranslatorOptions& forwardTranslatorOptions) {
+  return getImpl<detail::RunOptions_Impl>()->setForwardTranslatorOptions(forwardTranslatorOptions);
+}
+
+void RunOptions::resetForwardTranslatorOptions() {
+  getImpl<detail::RunOptions_Impl>()->resetForwardTranslatorOptions();
+}
+
+/// @cond
+
+// DEPRECATED
 std::string RunOptions::forwardTranslateOptions() const {
-  return getImpl<detail::RunOptions_Impl>()->forwardTranslateOptions();
+  LOG(Warn, "As of 3.5.1, (std::string) forwardTranslateOptions is deprecated. Use (ForwardTranslatorOptions) forwardTranslatorOptions instead. "
+            "It will be removed within three releases.");
+  return getImpl<detail::RunOptions_Impl>()->forwardTranslatorOptions().string();
 }
 
 bool RunOptions::setForwardTranslateOptions(const std::string& options) {
-  return getImpl<detail::RunOptions_Impl>()->setForwardTranslateOptions(options);
+  LOG(Warn, "As of 3.5.1, setForwardTranslateOptions(std::string) is deprecated. Use setForwardTranslatorOptions(ForwardTranslatorOptions) instead. "
+            "It will be removed within three releases.");
+  if (auto ftOptions_ = ForwardTranslatorOptions::fromString(options)) {
+    return getImpl<detail::RunOptions_Impl>()->setForwardTranslatorOptions(ftOptions_.get());
+  }
+  return false;
 }
 
 void RunOptions::resetForwardTranslateOptions() {
-  getImpl<detail::RunOptions_Impl>()->resetForwardTranslateOptions();
+  LOG(Warn, "As of 3.5.1, resetForwardTranslateOptions is deprecated. Use resetForwardTranslateOptions instead. "
+            "It will be removed within three releases.");
+  getImpl<detail::RunOptions_Impl>()->resetForwardTranslatorOptions();
 }
 
+/// @endcond
+
 std::ostream& operator<<(std::ostream& os, const RunOptions& runOptions) {
   os << runOptions.string();
   return os;
diff --git a/src/utilities/filetypes/RunOptions.hpp b/src/utilities/filetypes/RunOptions.hpp
index ef69ff6288a..e679da59588 100644
--- a/src/utilities/filetypes/RunOptions.hpp
+++ b/src/utilities/filetypes/RunOptions.hpp
@@ -33,11 +33,20 @@
 #include "../UtilitiesAPI.hpp"
 
 #include "../core/Logger.hpp"
+#include "../core/Deprecated.hpp"
+
+namespace Json {
+class Value;
+}
 
 namespace openstudio {
+
+class ForwardTranslatorOptions;
+
 namespace detail {
   class WorkflowJSON_Impl;
   class RunOptions_Impl;
+  class ForwardTranslatorOptions_Impl;
 }  // namespace detail
 
 class UTILITIES_API CustomOutputAdapter
@@ -60,6 +69,7 @@ class UTILITIES_API RunOptions
  public:
   RunOptions();
 
+  // TODO: uneeded
   virtual ~RunOptions();
 
   /// Construct from JSON formatted string
@@ -100,9 +110,13 @@ class UTILITIES_API RunOptions
   bool setCustomOutputAdapter(const CustomOutputAdapter& adapter);
   void resetCustomOutputAdapter();
 
-  std::string forwardTranslateOptions() const;
-  bool setForwardTranslateOptions(const std::string& options);
-  void resetForwardTranslateOptions();
+  OS_DEPRECATED std::string forwardTranslateOptions() const;
+  OS_DEPRECATED bool setForwardTranslateOptions(const std::string& options);
+  OS_DEPRECATED void resetForwardTranslateOptions();
+
+  ForwardTranslatorOptions forwardTranslatorOptions() const;
+  bool setForwardTranslatorOptions(const ForwardTranslatorOptions& forwardTranslatorOptions);
+  void resetForwardTranslatorOptions();
 
  protected:
   // get the impl
diff --git a/src/utilities/filetypes/RunOptions_Impl.hpp b/src/utilities/filetypes/RunOptions_Impl.hpp
index 2ad2a122d82..586164a7c21 100644
--- a/src/utilities/filetypes/RunOptions_Impl.hpp
+++ b/src/utilities/filetypes/RunOptions_Impl.hpp
@@ -30,6 +30,8 @@
 #ifndef UTILITIES_FILETYPES_RUNOPTIONS_IMPL_HPP
 #define UTILITIES_FILETYPES_RUNOPTIONS_IMPL_HPP
 
+#include "ForwardTranslatorOptions.hpp"
+
 #include "../UtilitiesAPI.hpp"
 
 #include "../core/Logger.hpp"
@@ -41,26 +43,14 @@
 #include <nano/nano_signal_slot.hpp>
 
 namespace openstudio {
-namespace detail {
+class CustomOutputAdapter;
 
-  class FtOptions
-  {
-   public:
-    bool runcontrolspecialdays = true;
-    bool ip_tabular_output = false;
-    bool no_lifecyclecosts = false;
-    bool no_sqlite_output = false;
-    bool no_html_output = false;
-    bool no_variable_dictionary = false;
-    bool no_space_translation = false;
-  };
+namespace detail {
 
   class UTILITIES_API RunOptions_Impl
   {
    public:
-    RunOptions_Impl();
-
-    virtual ~RunOptions_Impl();
+    RunOptions_Impl() = default;
 
     std::string string() const;
 
@@ -96,9 +86,9 @@ namespace detail {
     bool setCustomOutputAdapter(const CustomOutputAdapter& adapter);
     void resetCustomOutputAdapter();
 
-    std::string forwardTranslateOptions() const;
-    bool setForwardTranslateOptions(const std::string& options);
-    void resetForwardTranslateOptions();
+    ForwardTranslatorOptions forwardTranslatorOptions() const;
+    bool setForwardTranslatorOptions(const ForwardTranslatorOptions& options);
+    void resetForwardTranslatorOptions();
 
     // Emitted on any change
     Nano::Signal<void()> onChange;
@@ -110,14 +100,14 @@ namespace detail {
     // configure logging
     REGISTER_LOGGER("openstudio.RunOptions");
 
-    bool m_debug;
-    bool m_epjson;
-    bool m_fast;
-    bool m_preserveRunDir;
-    bool m_skipExpandObjects;
-    bool m_skipEnergyPlusPreprocess;
-    bool m_cleanup;
-    std::string m_forwardTranslateOptions;
+    bool m_debug = false;
+    bool m_epjson = false;
+    bool m_fast = false;
+    bool m_preserveRunDir = false;
+    bool m_skipExpandObjects = false;
+    bool m_skipEnergyPlusPreprocess = false;
+    bool m_cleanup = true;  // TODO this does absolutely nothing in the workflow-gem currently
+    ForwardTranslatorOptions m_forwardTranslatorOptions;
     boost::optional<CustomOutputAdapter> m_customOutputAdapter;
   };
 
diff --git a/src/workflow/CMakeLists.txt b/src/workflow/CMakeLists.txt
index fe9a00be78f..51912cf1526 100644
--- a/src/workflow/CMakeLists.txt
+++ b/src/workflow/CMakeLists.txt
@@ -2,6 +2,8 @@ add_library(openstudio_workflow
   OBJECT
   OSWorkflow.hpp
   OSWorkflow.cpp
+  WorkflowRunOptions.hpp
+  WorkflowRunOptions.cpp
 )
 
 target_link_libraries(openstudio_workflow PRIVATE openstudiolib)
diff --git a/src/workflow/OSWorkflow.cpp b/src/workflow/OSWorkflow.cpp
index 1f0cd02b474..3e69d6cb807 100644
--- a/src/workflow/OSWorkflow.cpp
+++ b/src/workflow/OSWorkflow.cpp
@@ -1,4 +1,5 @@
-#include "./OSWorkflow.hpp"
+#include "OSWorkflow.hpp"
+#include "WorkflowRunOptions.hpp"
 
 #include "../osversion/VersionTranslator.hpp"
 #include "../measure/OSMeasure.hpp"
@@ -34,7 +35,7 @@ OSWorkflow::OSWorkflow(const filesystem::path& oswPath, ScriptEngineInstance& ru
     workflowJSON(oswPath) {
 }
 
-OSWorkflow::OSWorkflow(WorkflowRunOptions t_runOptions, ScriptEngineInstance& ruby, ScriptEngineInstance& python)
+OSWorkflow::OSWorkflow(const WorkflowRunOptions& t_workflowRunOptions, ScriptEngineInstance& ruby, ScriptEngineInstance& python)
   :
 #if USE_RUBY_ENGINE
     rubyEngine(ruby),
@@ -42,8 +43,33 @@ OSWorkflow::OSWorkflow(WorkflowRunOptions t_runOptions, ScriptEngineInstance& ru
 #if USE_PYTHON_ENGINE
     pythonEngine(python),
 #endif
-    workflowJSON(t_runOptions.osw_path),
-    runOptions(std::move(t_runOptions)) {
+    workflowJSON(t_workflowRunOptions.osw_path),
+    m_no_simulation(t_workflowRunOptions.no_simulation),
+    m_post_process_only(t_workflowRunOptions.post_process_only),
+    m_show_stdout(t_workflowRunOptions.show_stdout),
+    m_add_timings(t_workflowRunOptions.add_timings),
+    m_style_stdout(t_workflowRunOptions.style_stdout) {
+
+  if (t_workflowRunOptions.runOptions.debug() || (workflowJSON.runOptions() && workflowJSON.runOptions()->debug())) {
+    fmt::print("Original workflowJSON={}\n", workflowJSON.string());
+    t_workflowRunOptions.debug_print();
+    fmt::print("m_no_simulation={}, m_post_process_only={}, m_show_stdout={}, m_add_timings={}, m_style_stdout={}", m_no_simulation,
+               m_post_process_only, m_show_stdout, m_add_timings, m_style_stdout);
+  }
+  auto runOpt_ = workflowJSON.runOptions();
+  if (!runOpt_) {
+    workflowJSON.setRunOptions(t_workflowRunOptions.runOptions);
+  } else {
+    auto ori_ftOptions = runOpt_->forwardTranslatorOptions();
+    workflowJSON.setRunOptions(t_workflowRunOptions.runOptions);
+    // user supplied CLI flags trump everything
+    ori_ftOptions.overrideValuesWith(t_workflowRunOptions.runOptions.forwardTranslatorOptions());
+    workflowJSON.runOptions()->setForwardTranslatorOptions(ori_ftOptions);
+  }
+
+  if (workflowJSON.runOptions()->debug()) {
+    fmt::print("workflowJSON={}\n", workflowJSON.string());
+  }
 }
 
 void OSWorkflow::applyArguments(measure::OSArgumentMap& argumentMap, const std::string& argumentName, const openstudio::Variant& argumentValue) {
@@ -79,7 +105,7 @@ void OSWorkflow::applyArguments(measure::OSArgumentMap& argumentMap, const std::
 }
 
 void OSWorkflow::run() {
-  if (runOptions.debug) {
+  if (workflowJSON.runOptions()->debug()) {
     openstudio::Logger::instance().standardOutLogger().setLogLevel(Debug);
   }
 
@@ -155,13 +181,7 @@ void OSWorkflow::run() {
       // Save final Model
       model.save(runDir / "in.osm", true);
       openstudio::energyplus::ForwardTranslator ft;
-      auto& ftOptions = runOptions.ft_options;
-      ft.setKeepRunControlSpecialDays(ftOptions.runcontrolspecialdays);
-      ft.setIPTabularOutput(ftOptions.ip_tabular_output);
-      ft.setExcludeLCCObjects(ftOptions.no_lifecyclecosts);
-      ft.setExcludeSQliteOutputReport(ftOptions.no_sqlite_output);
-      ft.setExcludeHTMLOutputReport(ftOptions.no_html_output);
-      ft.setExcludeSpaceTranslation(ftOptions.no_space_translation);
+      ft.setForwardTranslatorOptions(workflowJSON.runOptions()->forwardTranslatorOptions());
       workspace_ = ft.translateModel(model);
     }
 
diff --git a/src/workflow/OSWorkflow.hpp b/src/workflow/OSWorkflow.hpp
index 24ca51c6082..8aa9ae890a7 100644
--- a/src/workflow/OSWorkflow.hpp
+++ b/src/workflow/OSWorkflow.hpp
@@ -1,7 +1,6 @@
 #ifndef WORKFLOW_OSWORKFLOW_HPP
 #define WORKFLOW_OSWORKFLOW_HPP
 
-#include "RunOptions.hpp"
 #include "../measure/OSRunner.hpp"
 #include "../scriptengine/ScriptEngine.hpp"
 #include "../utilities/core/Filesystem.hpp"
@@ -13,6 +12,7 @@
 
 namespace openstudio {
 
+struct WorkflowRunOptions;
 class Variant;
 
 namespace measure {
@@ -24,7 +24,7 @@ class OSWorkflow
 {
  public:
   OSWorkflow(const filesystem::path& oswPath, ScriptEngineInstance& ruby, ScriptEngineInstance& python);
-  OSWorkflow(WorkflowRunOptions t_runOptions, ScriptEngineInstance& ruby, ScriptEngineInstance& python);
+  OSWorkflow(const WorkflowRunOptions& t_workflowRunOptions, ScriptEngineInstance& ruby, ScriptEngineInstance& python);
 
   void run();
 
@@ -38,7 +38,14 @@ class OSWorkflow
 #endif
   WorkflowJSON workflowJSON;
   measure::OSRunner runner{workflowJSON};
-  WorkflowRunOptions runOptions;
+
+  bool m_no_simulation = false;
+  bool m_post_process_only = false;
+
+  // stdout stuff
+  bool m_show_stdout = false;
+  bool m_add_timings = false;
+  bool m_style_stdout = false;
 
   static void applyArguments(measure::OSArgumentMap& argumentMap, const std::string& argumentName, const openstudio::Variant& argumentValue);
 };
diff --git a/src/workflow/RunOptions.cpp b/src/workflow/WorkflowRunOptions.cpp
similarity index 80%
rename from src/workflow/RunOptions.cpp
rename to src/workflow/WorkflowRunOptions.cpp
index 6185b3fd36c..1465cb3bcd9 100644
--- a/src/workflow/RunOptions.cpp
+++ b/src/workflow/WorkflowRunOptions.cpp
@@ -27,38 +27,24 @@
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ***********************************************************************************************************************/
 
-#include "RunOptions.hpp"
+#include "WorkflowRunOptions.hpp"
 
 #include <fmt/format.h>
 
 namespace openstudio {
 
 void WorkflowRunOptions::debug_print() const {
-  fmt::print("\nRunOptions:\n");
+  fmt::print("\nWorkflowRunOptions:\n");
   fmt::print("osw_path={}\n", this->osw_path.string());
-  fmt::print("debug={}\n", this->debug);
   fmt::print("no_simulation={}\n", this->no_simulation);
-  fmt::print("post_process={}\n", this->post_process);
-  fmt::print("ep_json={}\n", this->ep_json);
   fmt::print("show_stdout={}\n", this->show_stdout);
   fmt::print("add_timings={}\n", this->add_timings);
   fmt::print("style_stdout={}\n", this->style_stdout);
-
   fmt::print("socket_port={}\n", this->socket_port);
 
-  this->ft_options.debug_print();
+  fmt::print("\nrunOptions={}\n", this->runOptions.string());
 
   fmt::print("\n\n");
 }
 
-void FtOptions::debug_print() const {
-  fmt::print("FtOptions:\n");
-  fmt::print("  * runcontrolspecialdays={}\n", runcontrolspecialdays);
-  fmt::print("  * ip_tabular_output={}\n", ip_tabular_output);
-  fmt::print("  * no_lifecyclecosts={}\n", no_lifecyclecosts);
-  fmt::print("  * no_sqlite_output={}\n", no_sqlite_output);
-  fmt::print("  * no_html_output={}\n", no_html_output);
-  fmt::print("  * no_space_translation={}\n", no_space_translation);
-}
-
 }  // namespace openstudio
diff --git a/src/workflow/RunOptions.hpp b/src/workflow/WorkflowRunOptions.hpp
similarity index 83%
rename from src/workflow/RunOptions.hpp
rename to src/workflow/WorkflowRunOptions.hpp
index a739db69e16..6f5aa9b9b63 100644
--- a/src/workflow/RunOptions.hpp
+++ b/src/workflow/WorkflowRunOptions.hpp
@@ -31,41 +31,41 @@
 #define WORKFLOW_RUNOPTIONS_HPP
 
 #include "../utilities/core/Filesystem.hpp"
+#include "../utilities/filetypes/RunOptions.hpp"
 
 namespace openstudio {
 
-struct FtOptions
-{
- public:
-  bool runcontrolspecialdays = true;
-  bool ip_tabular_output = false;
-  bool no_lifecyclecosts = false;
-  bool no_sqlite_output = false;
-  bool no_html_output = false;
-  bool no_variable_dictionary = false;
-  bool no_space_translation = false;
-
-  void debug_print() const;
-
-  static constexpr auto group_name = "Forward Translator Options";
-};
-
 struct WorkflowRunOptions
 {
  public:
   void debug_print() const;
 
-  bool debug = false;
+  // TODO: move all these to the RunOption class?
+  // Alter the jobs to be run
   bool no_simulation = false;
-  bool post_process = false;
-  bool ep_json = false;
+  bool post_process_only = false;
+
+  // stdout stuff
   bool show_stdout = false;
   bool add_timings = false;
   bool style_stdout = false;
+
+  // TODO: Remove
   unsigned socket_port = 0;
+
   openstudio::path osw_path = "./workflow.osw";
 
-  FtOptions ft_options;
+  RunOptions runOptions;
+  /*
+    bool m_debug = false;
+    bool m_epjson = false;
+    bool m_fast = false;
+    bool m_preserveRunDir = false;
+    bool m_skipExpandObjects = false;
+    bool m_skipEnergyPlusPreprocess = false;
+    ForwardTranslatorOptions m_forwardTranslatorOptions;
+    boost::optional<CustomOutputAdapter> m_customOutputAdapter
+*/
 };
 
 }  // namespace openstudio


Test patch:
diff --git a/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp b/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp
index c4b2f1b5abb..c261981824f 100644
--- a/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp
+++ b/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp
@@ -34,6 +34,8 @@
 #include "../WorkflowStep.hpp"
 #include "../WorkflowStep_Impl.hpp"
 #include "../WorkflowStepResult.hpp"
+#include "../RunOptions.hpp"
+#include "../ForwardTranslatorOptions.hpp"
 
 #include "../../time/DateTime.hpp"
 
@@ -1098,7 +1100,7 @@ TEST(Filetypes, RunOptions) {
   EXPECT_EQ("MyOutputAdapter", workflow2->runOptions()->customOutputAdapter()->className());
 }
 
-TEST(Filetypes, RunOptions_ForwardTranslate) {
+TEST(Filetypes, RunOptions_ForwardTranslate_BackwardCompatibility) {
   WorkflowJSON workflow;
 
   EXPECT_FALSE(workflow.runOptions());
@@ -1110,21 +1112,42 @@ TEST(Filetypes, RunOptions_ForwardTranslate) {
   options.setEpjson(true);
   options.setCustomOutputAdapter(adapter);
 
-  // return string is formated with wbuilder["indentation"] = "   ";
-  std::string ft_options = "{\n   \"no_space_translation\" : false\n}";
-
-  //std::string ft_options = "{\n"
-  //                         "   \"runcontrolspecialdays\" : true,\n"
-  //                         "   \"ip_tabular_output\" : true,\n"
-  //                         "   \"no_lifecyclecosts\" : true,\n"
-  //                         "   \"no_sqlite_output\" : true,\n"
-  //                         "   \"no_html_output\" : true,\n"
-  //                         "   \"no_variable_dictionary\" : true,\n"
-  //                         "   \"no_space_translation\" : true\n"
-  //                         "}";
+  {
+    ForwardTranslatorOptions ftOptions = options.forwardTranslatorOptions();
+    EXPECT_TRUE(ftOptions.keepRunControlSpecialDays());
+    EXPECT_FALSE(ftOptions.iPTabularOutput());
+    EXPECT_FALSE(ftOptions.excludeLCCObjects());
+    EXPECT_FALSE(ftOptions.excludeSQliteOutputReport());
+    EXPECT_FALSE(ftOptions.excludeHTMLOutputReport());
+    EXPECT_FALSE(ftOptions.excludeVariableDictionary());
+    EXPECT_FALSE(ftOptions.excludeSpaceTranslation());
+  }
 
+  // This makes no sense, but we picked 3 spaces for some reason... return string is formated with wbuilder["indentation"] = "   ";
+  std::string ft_options = R"json({
+   "ip_tabular_output" : true,
+   "no_html_output" : true,
+   "no_lifecyclecosts" : true,
+   "no_space_translation" : true,
+   "no_sqlite_output" : true,
+   "no_variable_dictionary" : true,
+   "runcontrolspecialdays" : false
+})json";
+
+  // This settter internally creates a new ForwardTranslatorOptions, so we can't reuse the one above
   options.setForwardTranslateOptions(ft_options);
 
+  {
+    ForwardTranslatorOptions ftOptions = options.forwardTranslatorOptions();
+    EXPECT_FALSE(ftOptions.keepRunControlSpecialDays());
+    EXPECT_TRUE(ftOptions.iPTabularOutput());
+    EXPECT_TRUE(ftOptions.excludeLCCObjects());
+    EXPECT_TRUE(ftOptions.excludeSQliteOutputReport());
+    EXPECT_TRUE(ftOptions.excludeHTMLOutputReport());
+    EXPECT_TRUE(ftOptions.excludeVariableDictionary());
+    EXPECT_TRUE(ftOptions.excludeSpaceTranslation());
+  }
+
   workflow.setRunOptions(options);
 
   ASSERT_TRUE(workflow.runOptions());