Measure writing guide: add section about I/O file + modernize #25
Measure writing guide: add section about I/O file + modernize #25tijcolem merged 3 commits intoNatLabRockies:masterfrom
Conversation
| Please note the original namespace in versions of OpenStudio < 2.X `OpenStudio::Ruleset` is deprecated and replaced with `OpenStudio::Measure`. | ||
| Measure classes prior to 2.X are also deprecated: | ||
|
|
||
| | < 2.X (Deprecated) | >= 2.X | | ||
| |------------------------------------------|-----------------------------------| | ||
| | OpenStudio::Ruleset::ModelUserScript | OpenStudio::Measure::ModelMeasure | | ||
| | OpenStudio::Ruleset::WorkspaceUserScript | OpenStudio::Measure::ModelMeasure | |
There was a problem hiding this comment.
I did some changed re OpenStudio::Ruleset > OpenStudio::Measure
| args = OpenStudio::Measure::OSArgumentVector.new | ||
| insl_thckn = OpenStudio::Measure::makeDoubleArgument('insl_thckn',true) |
| ### Reporting Measure arguments Method | ||
|
|
||
| ```ruby | ||
| def arguments(model = nil) | ||
| ``` | ||
|
|
| ### Outputting a HTML (or other) file | ||
|
|
||
| You can create any file in the current working directory named `report*.*` and it will be copied over to the `reports/` directory via the openstudio-workflow gem. | ||
| The name of the resulting file is computed from the measure class name and the filename. | ||
|
|
||
| If your measure class name is `ReportingMeasureName`: | ||
|
|
||
| ```ruby | ||
|
|
||
| class ReportingMeasureName < OpenStudio::Measure::ReportingMeasure | ||
|
|
||
| [...] | ||
|
|
||
| def run(runner, user_arguments) | ||
| super(runner, user_arguments) | ||
|
|
||
| # Outputs to: reports/reporting_measure_name_report_one.html | ||
| File.open('./report_one.html', 'w') do |file| | ||
| # Write file | ||
| end | ||
|
|
||
| # Outputs to: reports/reporting_measure_name_qa_qc.csv | ||
| File.open('./qa_qc.csv', 'w') do |file| | ||
| # Write file | ||
| end | ||
| end | ||
| end | ||
|
|
||
| FooBar.new.registerWithApplication | ||
| ``` | ||
|
|
||
|
|
There was a problem hiding this comment.
Document the classic case: output a report file from a ReportingMeasure.
| ## Using files and using ExternalFile in a measure | ||
|
|
||
| It is sometimes needed to create support files in the process of running the measure. This section describes how path handling works in the context of a measure. | ||
|
|
||
| When running a measure, the current working directory is something like './run/000_measure_class_name/' (this is the output that `File.realpath('./')` will give you). | ||
| None of the files created in this directory will actually be copied over, unless it is the special case described in the section 'Reporting Measures' > 'Outputting an HTML (or other file)'. | ||
|
|
||
| ### Measure resource files | ||
|
|
||
| #### Where to place them | ||
| Resource files for a measure should be placed in the `resources/` subfolder like the Measure File Structure section indicates to do with additional ruby code. | ||
| Note that nested levels are not accepted, meaning that given the below tree, anything in `resources/subfolder` is not valid: `anotherschedulefile.csv` will not be copied over with the measure. | ||
|
|
||
| ``` | ||
| ├── measure.rb | ||
| ├── measure.xml | ||
| ├── resources | ||
| │ ├── schedulefile.csv | ||
| │ ├── subfolder | ||
| │ │ ├── anotherschedulefile.csv | ||
| ``` | ||
|
|
||
| #### How to access them inside a measure | ||
|
|
||
| You can locate your measure resources by using a relative path to the `measure.rb` you are running by using `File.join(File.dirname(__FILE__), 'schedulefile.csv')` | ||
|
|
||
| #### How to use an ExternalFile inside a measure | ||
|
|
||
| The constructor for `ExternalFile` will automatically copy the file at the path you provide to the first element in `WorkflowJSON::filePaths[0]`. When running a measure, the openstudio-workflow gem prepends the `generated_files` subdirectory. | ||
|
|
||
| ```ruby | ||
| class CreateScheduleFile < OpenStudio::Measure::ModelMeasure | ||
|
|
||
| [...] | ||
|
|
||
| def run(model, runner, user_arguments) | ||
|
|
||
| # Locate the resource file (this resolves to something like './measures/resources/schedulefile.csv') | ||
| csv_in_path = File.join(File.dirname(__FILE__), 'schedulefile.csv') | ||
|
|
||
| # Instantiate an External File: this will automatically copy to first path in WorkflowJSON: `runner.workflow.filePaths[0]`, typically './generated_files/' | ||
| externalFile_ = OpenStudio::Model::ExternalFile::getExternalFile(model, csv_in_path) | ||
| if (!externalFile_) | ||
| runner.registerError("Failed to instantiate an External File") | ||
| return false | ||
| end | ||
|
|
||
| column = 1 | ||
| rowsToSkip = 1 | ||
| scheduleFile = OpenStudio::Model::ScheduleFile.new(externalFile_.get(), column, rowsToSkip) | ||
| scheduleFile.setName("ExampleScheduleFile") | ||
|
|
||
| return true | ||
| end | ||
| ``` |
There was a problem hiding this comment.
This section is fair game and general enough. I definitely think it should be added.
| #### How to output any other file | ||
|
|
||
| To output any file you may need that isn't an `ExternalFile`, you should rely on two things, in order of preferences: | ||
| * `runner.workflow.filePaths[0]`: this will typically resolve to `./generated_files` | ||
| * `runner.workflow.absoluteRootDir`: this will resolve to '.' | ||
|
|
||
| `.` is the location defined as the `root` key inside the `workflow.osw`, or if not specified the location of the `workflow.osw` itself. |
There was a problem hiding this comment.
I find that relevant, but it's possible other people might not?
| # Locate the resource file (this resolves to something like './measures/resources/schedulefile.csv') | ||
| csv_in_path = File.join(File.dirname(__FILE__), 'schedulefile.csv') | ||
|
|
||
| # Canonical way: write to the ./generated_files directory | ||
| out_file = File.join(runner.workflow.filePaths[0].to_s, 'myfile.csv') | ||
| File.open(out_file, 'w') do |f| | ||
| f << "Hello" | ||
| end |
There was a problem hiding this comment.
I think that part is definitely fine.
| # Prefer the above, but one valid use case would be to output to the reports/ folder | ||
| # (this is a *ModelMeasure*, you cannot just name it './report.html' | ||
| # and have it copied automatically like described above in Reporting measure section) | ||
|
|
||
| # either /tmp/osmodel-1622719126-1/ApplyMeasureNow | ||
| # or ./<model_companion_dir>/ | ||
| rootDir = runner.workflow.absoluteRootDir.to_s | ||
|
|
||
| html_out_path = 'report.html' | ||
| if (File.basename(rootDir) == 'ApplyMeasureNow') | ||
| html_out_path = File.absolute_path( | ||
| File.join(rootDir, '..', 'resources', 'reports', html_out_path)) | ||
| else | ||
| html_out_path = File.absolute_path( | ||
| File.join(rootDir, 'reports', html_out_path)) | ||
| end | ||
| outDir = File.dirname(html_out_path) | ||
| if !File.exists?(outDir) | ||
| FileUtils.mkdir_p(outDir) | ||
| end | ||
| File.open(html_out_path, 'w') do |f| | ||
| f << "<html><head><title>My Custom Report that works with Apply Now!</title></head><body><h1>Hello World!</h1></body></html>" | ||
| end |
There was a problem hiding this comment.
So perhaps this section is overkill or an invitation for bad ideas. I don't know. But people are definitely already doing stuff like this (cf discussion on NatLabRockies/OpenStudio#4046) except they are not doing it right for lack of documentation.
| Regarding Note A in the above code, note that this is added to the `WorkflowStepResult` `step_files` entry (WorkflowJSON: root > "steps" [] > "result" > "step_files", see [Workflow JSON schema](https://github.com/NREL/OpenStudio-workflow-gem/blob/e569f910be364d33c3ddb1a655570c85f1b24bfa/spec/schema/osw_output.json#L251)) | ||
|
|
||
| It possible to capture the path to the stepFiles from a previous step inside a measure like the following: | ||
|
|
||
| ```ruby | ||
| if runner.workflow.currentStepIndex() > 0 | ||
| previousStep = runner.workflow.workflowSteps[runner.workflow.currentStepIndex() - 1] | ||
| if previousStep.result | ||
| previousStepResult = previousStep.result.get | ||
| runner.registerWarning("previousStepResult.stepFiles=#{previousStepResult.stepFiles.map{|p| p.to_s}}") | ||
| end | ||
| end | ||
| ``` |
There was a problem hiding this comment.
I don't have a good use case for this but perhaps @DavidGoldwasser might. I won't be offended if you don't lik that section
…e Template In conjunction with NatLabRockies/OpenStudio-user-documentation#25
|
@jmarrec Changes look good. Thanks for the updates. I just assigned this to myself. I'd like to build/convert the docs to html before merging to gh-pages branch. |
|
Looks good served up locally using mkdocs. |
Measure writing guide: add section about I/O file + modernize
Fixes NatLabRockies/OpenStudio#4046
Fixes NatLabRockies/OpenStudio#3609