-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Measure writing guide: add section about I/O file + modernize #25
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some changed re OpenStudio::Ruleset > OpenStudio::Measure
args = OpenStudio::Measure::OSArgumentVector.new | ||
insl_thckn = OpenStudio::Measure::makeDoubleArgument('insl_thckn',true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of many
### Reporting Measure arguments Method | ||
|
||
```ruby | ||
def arguments(model = nil) | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
### 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 NREL/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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 NREL/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 NREL/OpenStudio#4046
Fixes NREL/OpenStudio#3609