#' @importFrom R6 R6Class
#' @include impl-idfobj.R
NULL
#' Create and Modify an EnergyPlus Object
#'
#' `IdfObject` is an abstraction of a single object in an [Idf]. It provides
#' more detail methods to modify object values and comments. An `IdfObject`
#' object can also be created using function [idf_object()] or from methods of a
#' parent [Idf] object, using `$object()`, `$objects_in_class()` and equivalent.
#'
#' @importFrom R6 R6Class
#' @docType class
#' @name IdfObject
#' @seealso [Idf] class
#' @author Hongyuan Jia
NULL
#' @export
# IdfObject {{{
IdfObject <- R6::R6Class(classname = "IdfObject",
public = list(
# INITIALIZE {{{
#' @description
#' Create an `IdfObject` object
#'
#' @details
#' It is not recommended to directly use `$new()` method to create an
#' `IdfObject` object, instead considering to use [idf_object],
#' \href{../../eplusr/html/Idf.html#method-object}{\code{Idf$object()}}
#' and other equivalent to create `IdfObject` objects. They provide
#' more user-friendly interfaces. `$new()` is a lower level API which is
#' mainly used inside methods in other classes.
#'
#' @param object An integer specifying an object ID.
#' @param class An integer specifying a class index.
#' @param parent An [Idf] object specifying the parent object.
#'
#' @return An `IdfObject` object.
#' @export
#'
#' @examples
#' \dontrun{
#' # example model shipped with eplusr from EnergyPlus v8.8
#' path_idf <- system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr") # v8.8
#' idf <- read_idf(path_idf, use_idd(8.8, "auto"))
#'
#' roof <- IdfObject$new(26, parent = idf)
#'
#' # get the IdfObject of material named "C5 - 4 IN HW CONCRETE"
#' mat <- idf$Material[["C5 - 4 IN HW CONCRETE"]]
#' }
#'
#' @importFrom checkmate assert_count
initialize = function(object, class = NULL, parent) {
if (missing(parent) || (!is_idf(parent) && !is_epw(parent))) {
abort(paste("IdfObject can only be created based a parent Idf object.",
"Please give 'parent', which should be an Idf object.")
)
} else {
private$m_parent <- parent
}
object <- assert_count(object, positive = TRUE, coerce = TRUE)
if (!is.null(class)) {
class <- assert_count(class, positive = TRUE, coerce = TRUE)
} else {
class <- get_idf_object(private$idd_env(), private$idf_env(), NULL, object)$class_id
}
private$m_object_id <- object
private$m_class_id <- class
},
# }}}
# PUBLIC FUNCTIONS {{{
# version {{{
#' @description
#' Get the version of parent `Idf`
#'
#' @details
#' `$version()` returns the version of parent `Idf` in a
#' [base::numeric_version()] format. This makes it easy to direction
#' compare versions of different `IdfObject`s, e.g. `idfobj$version() > 8.6` or
#' `idfobj1$version() > idfobj2$version()`.
#'
#' @return A [base::numeric_version()] object.
#'
#' @examples
#' \dontrun{
#' # get version
#' roof$version()
#' }
#'
version = function()
idfobj_version(self, private),
# }}}
# parent {{{
#' @description
#' Get parent [Idf]
#'
#' @details
#' `$parent()` returns parent [Idf] object.
#'
#' @return A [Idf] object.
#'
#' @examples
#' \dontrun{
#' roof$parent()
#' }
#'
parent = function()
idfobj_parent(self, private),
# }}}
# id {{{
#' @description
#' Get the unique ID for current object
#'
#' @details
#' In [Idf], each object is assigned with an integer as an universally
#' unique identifier (UUID) in the context of current [Idf]. UUID is
#' not reused even if the object associated is deleted.
#'
#' `$id()` returns an integer of current object unique ID.
#'
#' @return A single integer.
#'
#' @examples
#' \dontrun{
#' roof$id()
#' }
#'
id = function()
idfobj_id(self, private),
# }}}
# name {{{
#' @description
#' Get the name for current object.
#'
#' @details
#' In `Idf`, each object is assigned with a single string as the name
#' for it, if the class it belongs to has name attribute, e.g. class
#' `RunPeriod`, `Material` and etc. That name should be unique among all
#' objects in that class. EnergyPlus will fail with an error if
#' duplications are found among object names in a class.
#'
#' `$name()` returns a single string of current object name. If
#' specified class does not have name attribute, `NA` is returned.
#'
#' @return A single string.
#'
#' @examples
#' \dontrun{
#' roof$name()
#'
#' # NA will be returned if the class does not have name attribute. For example,
#' # "Version" class
#' idf$Version$name()
#' }
#'
name = function()
idfobj_name(self, private),
# }}}
# group_name {{{
#' @description
#' Get name of group for current object.
#'
#' @details
#' `$group_name()` returns a single string of group name current
#' `IdfObject` belongs to.
#'
#' @return A single string.
#'
#' @examples
#' \dontrun{
#' roof$group_name()
#' }
#'
group_name = function()
idfobj_group_name(self, private),
# }}}
# class_name {{{
#' @description
#' Get name of class for current object.
#'
#' @details
#' `$class_name()` returns a single string of class name current
#' `IdfObject` belongs to.
#'
#' @return A single string.
#'
#' @examples
#' \dontrun{
#' roof$class_name()
#' }
#'
class_name = function()
idfobj_class_name(self, private),
# }}}
# definition {{{
#' @description
#' Get the [IddObject] object for current class.
#'
#' @details
#' `$definition()` returns an [IddObject] of current class. [IddObject]
#' contains all data used for parsing and creating current `IdfObject`.
#' For details, please see [IddObject] class.
#'
#' @return An [IddObject] object.
#'
#' @examples
#' \dontrun{
#' roof$definition()
#' }
#'
definition = function()
idfobj_definition(self, private),
# }}}
# comment {{{
#' @description
#' Get and modify object comments
#'
#' @details
#' `$comment()` returns current `IdfObject` comments if `comment` is not
#' given, or modifies current `IdfObject` comments if `comment` is given.
#' If no comments found, `NULL` is returned.
#'
#' @param comment A character vector.
#' * If missing, current comments are returned. If there is no
#' comment in current `IdfObject`, `NULL` is returned.
#' * If `NULL`, all comments in current `IdfObject` is deleted.
#' * If a character vector, it is inserted as comments depending on
#' the `append` value.
#'
#' @param append Only applicable when `commment` is a character vector.
#' Default: `FALSE`.
#' * If `NULL`, existing comments is deleted before adding `comment`.
#' * If `TRUE`, comment will be appended to existing comments.
#' * If `FALSE`, `comment` is prepended to existing currents.
#'
#' @param width A positive integer giving the target width for wrapping
#' inserted `comment`.
#'
#' @return If calling without any argument, a character vector or `NULL`
#' (if no comments) is return. Otherwise, the modified object itself.
#'
#' @examples
#' \dontrun{
#' # get object comments
#' roof$comment()
#'
#' # add new object comments
#' roof$comment(c("This is a material named `WD01`", "This object has an ID of 47"))
#' roof$comment()
#'
#' # append new comments
#' roof$comment("This is an appended comment")
#' roof$comment()
#'
#' # prepend new comments
#' roof$comment("This is a prepended comment", append = FALSE)
#' roof$comment()
#'
#' # wrap long comments
#' roof$comment("This is a very long comment that is needed to be wrapped.", width = 30)
#' roof$comment()
#'
#' # delete old comments and add new one
#' roof$comment("This is the only comment", append = NULL)
#' roof$comment()
#'
#' # delete all comments
#' roof$comment(NULL)
#' roof$comment()
#' }
#'
comment = function(comment, append = TRUE, width = 0L)
idfobj_comment(self, private, comment, append, width),
# }}}
# value {{{
#' @description
#' Get object field values.
#'
#' @details
#' `$value()` takes an integer vector of valid field indexes or a
#' character vector of valid field names, and returns a named list
#' containing values of specified fields when `simplify` is `FALSE` and
#' a character vector when `simplify` is `TRUE`.
#'
#' eplusr also provides custom S3 method of `$` and \code{[[} which make
#' it more convenient to get a single value of current `IdfObject`.
#' Basically, `idfobj$FieldName` and \code{idfobj[[Field]]} is
#' equivalent to \code{idfobj$value(FieldName)[[1]]} and
#' \code{idfobj$value(Field)[[1]]}.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param all If `TRUE`, values of all possible fields in current class
#' the `IdfObject` belongs to are returned. Default: `FALSE`
#'
#' @param simplify If `TRUE`, values of fields are converted into
#' characters and the converted character vector is returned.
#'
#' @param unit If `TRUE`, values of numeric fields are assigned with
#' units using [units::set_units()] if applicable. Default:
#' `FALSE`.
#'
#' @return A named list.
#'
#' @examples
#' \dontrun{
#' # get all existing field values
#' str(mat$value())
#'
#' # get values of field 1, 3, 5
#' str(mat$value(c(1, 3, 5)))
#'
#' # get character format values instead of a named list
#' mat$value(c(1, 3, 5), simplify = TRUE)
#'
#' # get values of all field even those that are not set
#' str(roof$value())
#' str(roof$value(all = TRUE))
#'
#' # get field values using shortcuts
#' mat$Roughness
#' mat[["Specific_Heat"]]
#' mat[c(1,2)]
#' mat[c("Name", "Density")]
#' }
#'
value = function(which = NULL, all = FALSE, simplify = FALSE, unit = FALSE)
idfobj_value(self, private, which, all, simplify, unit),
# }}}
# set {{{
#' @description
#' Modify object field values.
#'
#' @details
#' `$set()` takes new field value definitions in `field = value` format
#' or in a single list format, sets new values for fields specified, and
#' returns the modified `IdfObject`. Unlike `$set()` method in [Idf]
#' class, the special element `.comment` is **not allowed**. To modify
#' object comments, please use `$comment()`.
#'
#' @note
#'
#' * Only one single list is allowed, e.g. `idfobj$set(lst1)` where `lst1 <-
#' list(field1 = value1)` is allowed, but `idfobj$set(lst1, lst2)` is not.
#' * You can delete a field by assigning `NULL` to it, e.g. `iddobj$set(fld =
#' NULL)` means to delete the value of field `fld`. If `.default` is FALSE,
#' also `fld` is not a required field and the index of `fld` is larger than
#' the number minimum fields required for that class, it will be deleted.
#' Otherwise it will be left as blank. If `.default` is `TRUE`, that field
#' will be filled with default value if applicable and left as blank if not.
#' * By default, trailing empty fields that are not required will be removed and
#' only minimum required fields are kept. You can keep the trailing empty
#' fields by setting `.empty` to `TRUE`.
#' * New fields that currently do not exist in that object can also be set. They
#' will be automatically added on the fly.
#' * Field name matching is **case-insensitive**. For convenience,
#' underscore-style field names are also allowed, e.g. `eNd_MoNtH` is
#' equivalent to `End Month`.
#' * If not all field names are given, positions of those values without field
#' names are determined after those values with names. E.g. in
#' `model$set(Construction = list("out_layer", name = "name"))`, `"out_layer"`
#' will be treated as the value of field `Outside Layer` in `Construction`, as
#' value of field `Name` has been given as `"name"`.
#'
#' eplusr also provides custom S3 method of `$<-` and
#' \code{[[<-} which makes it more convenient to set a single field value of an
#' `IdfObject`. Basically, `idfobj$FieldName <- value` and \code{idfobj[[Field]]
#' <- value} is equivalent to `idfobj$set(FieldName = value)` and
#' `idfobjset(Field = value)`.
#'
#' @param ... New field value definitions in `field = value` format or a
#' single list in format:
#' ```
#' list(field1 = value1, field2 = value2)
#' ```
#'
#' @param .default If `TRUE`, default values are used for those blank
#' fields if possible. Default: `TRUE`.
#'
#' @param .empty If `TRUE`, trailing empty fields are kept. Default: `FALSE`.
#'
#' @examples
#' \dontrun{
#' # set field values
#' mat$set(name = "new_name", Thickness = 0.02)
#' mat[c("Name", "Thickness")]
#'
#' # When `default` argument is set to TRUE and input field values are empty, i.e.
#' # NULL, the field values will be reset to defaults.
#' mat[c("Thermal Absorptance", "Solar Absorptance")]
#'
#' mat$set(visible_absorptance = NULL, Solar_Absorptance = NULL, .default = TRUE)
#' mat[c("Visible Absorptance", "Solar Absorptance")]
#'
#' # set field values using shortcuts
#' mat$Name <- "another_name"
#' mat$Name
#' mat[["Thickness"]] <- 0.019
#' mat$Thickness
#' }
#'
set = function(..., .default = TRUE, .empty = FALSE)
idfobj_set(self, private, ..., .default = .default, .empty = .empty),
# }}}
# value_possible {{{
#' @description
#' Get possible object field values.
#'
#' @details
#' `$value_possible()` takes an integer vector of valid field indexes or a character
#' vector of valid field names, and returns all possible values for specified
#' fields. For a specific field, there are 5 types of possible values:
#'
#' * `auto`: Whether the field can be filled with `Autosize` and
#' `Autocalculate`. This field attribute can also be retrieved using:
#'
#' ```
#' idfobj$definition()$is_autosizable_field()
#' idfobj$definition()$is_autocalculatable_field()
#' ```
#'
#' * `default`: The default value. This value can also be retrieved using
#' `idfobj$defintion()$field_default()`.
#' * `choice`: The choices which the field can be set. This value can also be
#' retrieved using `idfobj$definition()$field_choice()`.
#' * `range`: The range which the field value should fall in. This range can
#' also be retrieved using `idfobj$definition()$field_range()`.
#' * `source`: All values from other objects that current field can refer to.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param type A character vector. What types of possible values should
#' be returned. Should be one of or a combination of `"auto"`,
#' `"default"`, `"choice"`, `"range"` and `"source"`. Default:
#' All of those.
#'
#' @return `$value_possible()` returns an `IdfValuePossible` object
#' which is a [data.table][data.table::data.table()] with at most 15
#' columns:
#'
#' * `class_id`: index of class that current `IdfObject` belongs to
#' * `class_name`: name of class that current `IdfObject` belongs to
#' * `object_id`: ID of current `IdfObject`
#' * `object_name`: name of current `IdfObject`
#' * `field_id`: indexes (at Idd level) of object fields specified
#' * `field_index`: indexes of object fields specified
#' * `field_name`: names (without units) of object fields specified
#' * `value_id`: value indexes (at Idf level) of object fields specified
#' * `value_chr`: values (converted to characters) of object fields specified
#' * `value_num`: values (converted to numbers in SI units) of object fields
#' specified.
#' * `auto`: Exists only when `"auto"` is one of `type`. Character type.
#' Possible values are: `"Autosize"`, `"Autocalculate"` and `NA` (if current
#' field is neither `autosizable` nor `autocalculatable`).
#' * `default`: Exists only when `"default"` is one of `type`. List type. The
#' default value of current field. The value is converted into number if
#' corresponding field type yells so. Note that if current field is a numeric
#' field but the default value is `"Autosize"` or `"Autocalculate"`, it is
#' left as it is, leaving the returned type being a string instead of a
#' number.
#' * `range`: Exists only when `"range"` is one of `type`. List type. The range
#' that field value should fall in. Every range has four components: `minimum`
#' (lower limit), `lower_incbounds` (`TRUE` if the lower limit should be
#' included), `maximum` (upper limit), and `upper_incbounds` (`TRUE` if the
#' upper limit should be included). For fields of character type, empty lists
#' are returned. For fields of numeric types with no specified ranges,
#' `minimum` is set to `-Inf`, `lower_incbounds` is set to FALSE, `upper` is
#' set to `Inf`, and `upper_incbounds` is set to FALSE. The field range is
#' printed in number interval denotation.
#' * `source`: Exists only when `"source"` is one of `type`. List type. Each
#' element is a character vector which includes all values from other objects
#' that current field can use as sources and refers to.
#'
#' @examples
#' \dontrun{
#' mat$value_possible()
#' }
#'
value_possible = function(which = NULL, type = c("auto", "default", "choice", "range", "source"))
idfobj_value_possible(self, private, which, type),
# }}}
# validate {{{
#' @description
#' Check possible object field value errors
#'
#' @details
#' `$validate()` checks if there are errors in current `IdfObject` object
#' under specified validation level and returns an `IdfValidity` object.
#'
#' `$validate()` is useful to help avoid some common errors before
#' running the model. By default, validation is performed when calling
#' all methods that modify objects, e.g.
#' \href{../../eplusr/html/IdfObject.html#method-set}{\code{$set()}}
#' and etc.
#'
#' In total, there are 10 different validate checking components:
#'
#' * `required_object`: Check if required objects are missing in current
#' `Idf`.
#' * `unique_object`: Check if there are multiple objects in one
#' unique-object class. An unique-object class means that there should
#' be at most only one object existing in that class.
#' * `unique_name`: Check if all objects in each class have unique names.
#' * `extensible`: Check if all fields in an extensible group have
#' values. An extensible group is a set of fields that should be
#' treated as a whole, such like the X, Y and Z vertices of a building
#' surfaces. An extensible group should be added or deleted together.
#' `extensible` component checks if there are some, but not all,
#' fields in an extensible group are empty.
#' * `required_field`: Check if all required fields have values.
#' * `auto_field`: Check if all fields filled with value `"Autosize"` and
#' `"Autocalculate"` are actual autosizable and autocalculatable
#' fields or not.
#' * `type`: Check if all fields have value types complied with their
#' definitions, i.e. character, numeric and integer fields should be
#' filled with corresponding type of values.
#' * `choice`: Check if all choice fields are filled with valid choice
#' values.
#' * `range`: Check if all numeric fields have values within prescibed
#' ranges.
#' * `reference`: Check if all fields whose values refer to other fields
#' are valid.
#'
#' The `level` argument controls what checkings should be performed.
#' `level` here is just a list of 10 element which specify the toggle
#' status of each component. You can use helper [custom_validate()] to
#' get that list and pass it directly to `level`.
#'
#' There are 3 predefined validate level that indicates different
#' combinations of checking components, i.e. `none`, `draft` and
#' `final`. Basically, `none` level just does not perform any
#' checkings; `draft` includes 5 components, i.e. `auto_field`, `type`,
#' `unique_name`, `choice` and `range`; and `final` level includes all
#' 10 components. You can always get what components each level contains
#' using [level_checks()]. By default, the result from
#' `eplusr_option("validate_level")` is passed to `level`. If not set,
#' `final` level is used.
#'
#' Underneath, an `IdfValidity` object which `$validate()` returns is a
#' list of 13 element as shown below. Each element or several elements
#' represents the results from a single validation checking component.
#'
#' * `missing_object`: Result of `required_object` checking.
#' * `duplicate_object`: Result of `unique_object` checking.
#' * `conflict_name`: Result of `unique_name` checking.
#' * `incomplete_extensible`: Result of `extensible` checking.
#' * `missing_value`: Result of `required_field` checking.
#' * `invalid_autosize`: Result of `auto_field` checking for invalid
#' `Autosize` field values.
#' * `invalid_autocalculate`: Result of `auto_field` checking for
#' invalid `Autocalculate` field values.
#' * `invalid_character`: Result of `type` checking for invalid
#' character field values.
#' * `invalid_numeric`: Result of `type` checking for invalid
#' numeric field values.
#' * `invalid_integer`: Result of `type` checking for invalid
#' integer field values.
#' * `invalid_choice`: Result of `choice` checking.
#' * `invalid_range`: Result of `range` checking.
#' * `invalid_reference`: Result of `reference` checking.
#'
#' Except `missing_object`, which is a character vector of class names
#' that are missing, all other elements are
#' [data.table][data.table::data.table()] with 9 columns containing data
#' of invalid field values:
#'
#' * `object_id`: IDs of objects that contain invalid values
#' * `object_name`: names of objects that contain invalid values
#' * `class_id`: indexes of classes that invalid objects belong to
#' * `class_name`: names of classes that invalid objects belong to
#' * `field_id`: indexes (at Idd level) of object fields that are invalid
#' * `field_index`: indexes of object fields in corresponding that are invalid
#' * `field_name`: names (without units) of object fields that are invalid
#' * `units`: SI units of object fields that are invalid
#' * `ip_units`: IP units of object fields that are invalid
#' * `type_enum`: An integer vector indicates types of invalid fields
#' * `value_id`: indexes (at Idf level) of object field values that are invalid
#' * `value_chr`: values (converted to characters) of object fields that are
#' invalid
#' * `value_num`: values (converted to numbers in SI units) of object fields
#' that are invalid
#'
#' Knowing the internal structure of `IdfValidity`, it is easy to extract
#' invalid [IdfObject]s you interested in. For example, you can get all IDs of
#' objects that contain invalid value references using
#' `model$validate()$invalid_reference$object_id`. Then using
#' \href{../../eplusr/html/IdfObject.html#method-set}{\code{$set()}}
#' method to correct them.
#'
#' Different validate result examples are shown below:
#'
#' * No error is found:
#'
#' ```
#' v No error found.
#' ```
#'
#' Above result shows that there is no error found after conducting all
#' validate checks in specified validate level.
#'
#' * Errors are found:
#'
#' ```
#' x [2] Errors found during validation.
#' =========================================================================
#'
#' -- [2] Invalid Autocalculate Field --------------------------------------
#' Fields below cannot be `autocalculate`:
#'
#' Class: <AirTerminal:SingleDuct:VAV:Reheat>
#' \- Object [ID:176] <SPACE5-1 VAV Reheat>
#' +- 17: AUTOCALCULATE, !- Maximum Flow per Zone Floor Area During Reheat {m3/s-m2}
#' \- 18: AUTOCALCULATE; !- Maximum Flow Fraction During Reheat
#' ```
#'
#' Above result shows that after all validate components
#' performed under current validate level, 2 invalid field values
#' are found. All of them are in a object named `SPACE5-1 VAV Reheat`
#' with ID `176`. They are invalid because those two fields do not
#' have an autocalculatable attribute but are given `AUTOCALCULATE`
#' value. Knowing this info, one simple way to fix the
#' error is to correct those two fields by doing:
#'
#' ```
#' idf$set(..176 =
#' list(`Maximum Flow per Zone Floor Area During Reheat` = "autosize",
#' `Maximum Flow Fraction During Reheat` = "autosize"
#' )
#' )
#' ```
#'
#' @param level One of `"none"`, `"draft"`, `"final"` or a list of 10
#' elements with same format as [custom_validate()] output.
#'
#' @return An `IdfValidity` object.
#'
#' @examples
#' \dontrun{
#' mat$validate()
#'
#' # check at predefined validate level
#' mat$validate("none")
#' mat$validate("draft")
#' mat$validate("final")
#'
#' # custom validate checking components
#' mat$validate(custom_validate(auto_field = TRUE, choice = TRUE))
#' }
#'
validate = function(level = eplusr_option("validate_level"))
idfobj_validate(self, private, level),
# }}}
# is_valid {{{
#' @description
#' Check if there is any error in current object
#'
#' @details
#' `$is_valid()` returns `TRUE` if there is no error in current `IdfObject`
#' object under specified validation level and `FALSE` otherwise.
#'
#' `$is_valid()` checks if there are errors in current `IdfObject` object
#' under specified validation level and returns `TRUE` or `FALSE`
#' accordingly. For detailed description on validate checking, see
#' \href{../../eplusr/html/IdfObject.html#method-validate}{\code{$validate()}}
#' documentation above.
#'
#' @param level One of `"none"`, `"draft"`, `"final"` or a list of 10
#' elements with same format as [custom_validate()] output.
#'
#' @return A single logical value of `TRUE` or `FALSE`.
#'
#' @examples
#' \dontrun{
#' mat$is_valid()
#'
#' mat$definition()$field_range("Density")
#' eplusr_option(validate_level = "none") # have to set validate to "none" to do so
#' mat$Density <- -1
#' eplusr_option(validate_level = "final") # change back to "final" validate level
#' mat$is_valid()
#'
#' # check at predefined validate level
#' mat$is_valid("none")
#' mat$is_valid("draft")
#' mat$is_valid("final")
#'
#' # custom validate checking components
#' mat$is_valid(custom_validate(auto_field = TRUE, choice = TRUE))
#' }
#'
is_valid = function(level = eplusr_option("validate_level"))
idfobj_is_valid(self, private, level),
# }}}
# value_relation {{{
#' @description
#' Get value relations
#'
#' @details
#' Many fields in [Idd] can be referred by others. For example, the
#' `Outside Layer` and other fields in `Construction` class refer to the
#' `Name` field in `Material` class and other material related classes.
#' Here it means that the `Outside Layer` field **refers to** the `Name`
#' field and the `Name` field is **referred by** the `Outside Layer`. In
#' EnergyPlus, there is also a special type of field called `Node`,
#' which together with `Branch` and `BranchList` define the topography
#' of the HVAC connections. A outlet node of a component can be referred
#' by another component as its inlet node, but can also exists
#' independently, such as zone air node.
#'
#' `$value_relation()` provides a simple interface to get this kind of
#' relation. It takes field indexes or field names, together a relation
#' direction, and returns an `IdfRelation` object which contains data
#' presenting such relation described above. For instance, if
#' `idfobj$value_relation("Name", "ref_by")` gives results below:
#'
#' ```
#' -- Referred by Others ------------------------
#' \- 1: "WALL-1"; !- Name
#' ^~~~~~~~~~~~~~~~~~~~~~~~~
#' \- Class: <BuildingSurface:Detailed>
#' \- Object [ID:3] <WALL-1PF>
#' \- 3: "WALL-1"; !- Construction Name
#' ```
#'
#' This means that the value `"WALL-1"` of field `Name` is referred by
#' field `Construction Name` in a surface named `WALL-1PF`. All those
#' objects can be further easily extracted using `$ref_by_object()`
#' method.
#'
#' Note that `$value_relation()` shows all fields specified, even some of them
#' may do not have relation.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param direction The relation direction to extract. Should be either
#' `"all"`, `"ref_to"` or "ref_by".
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param depth If > 0, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. If `NULL`,
#' all possible recursive relations are returned. Default: `0`.
#'
#' @param keep If `TRUE`, all input fields are returned regardless they
#' have any relations with other objects or not. If `FALSE`, only
#' fields in input that have relations with other objects are
#' returned. Default: `FALSE`.
#'
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of referring to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return An `IdfRelation` object, which is a list of 3
#' [data.table::data.table()]s named `ref_to`, `ref_by` and `node`.
#' Each [data.table::data.table()] contains 24 columns.
#'
#' @examples
#' \dontrun{
#' # check each layer's reference of a construction named FLOOR
#' roof$value_relation("zone name", "ref_to")
#'
#' # check where is this construction being used
#' roof$value_relation("name", direction = "ref_by")
#' }
#'
value_relation = function(which = NULL, direction = c("all", "ref_to", "ref_by", "node"),
object = NULL, class = NULL, group = NULL, depth = 0L, keep = FALSE,
class_ref = c("both", "none", "all"))
idfobj_value_relation(self, private, which, match.arg(direction),
object = object, class = class, group = group,
depth = depth, keep = keep, class_ref = match.arg(class_ref)),
# }}}
# ref_to_object {{{
#' @description
#' Extract multiple `IdfObject` objects referred by specified field values
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$ref_to_object()` takes an integer vector of field indexes or a
#' character vector of field names, and returns a list of `IdfObject`s
#' that specified fields refer to.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param depth If > 0, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. If `NULL`,
#' all possible recursive relations are returned. Default: `0`.
#'
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of referring to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return A named list of `IdfObject` objects.
#'
#' @examples
#' \dontrun{
#' # get other objects that this object refereces
#' mat$ref_to_object() # not referencing other objects
#' }
#'
ref_to_object = function(which = NULL, object = NULL, class = NULL, group = NULL, depth = 0L, class_ref = c("both", "none", "all"))
idfobj_ref_to_object(self, private, which, object = NULL, class = class, group = group, depth = depth, class_ref = match.arg(class_ref)),
# }}}
# ref_by_object {{{
#' @description
#' Extract multiple `IdfObject` objects referring to specified field values
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$ref_by_object()` takes an integer vector of field indexes or a
#' character vector of field names, and returns a list of `IdfObject`s
#' that refer to specified fields.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param depth If > 0, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. If `NULL`,
#' all possible recursive relations are returned. Default: `0`.
#'
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of referring to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return A named list of `IdfObject` objects.
#'
#' @examples
#' \dontrun{
#' # get other objects that reference this object
#' mat$ref_by_object() # referenced by construction "FLOOR"
#' }
#'
ref_by_object = function(which = NULL, object = NULL, class = NULL, group = NULL, depth = 0L, class_ref = c("both", "none", "all"))
idfobj_ref_by_object(self, private, which, object = object, class = class, group = group, depth = depth, class_ref = match.arg(class_ref)),
# }}}
# ref_to_node {{{
#' @description
#' Extract multiple `IdfObject` objects referring to same nodes
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$ref_to_node()` takes an integer vector of field indexes or a
#' character vector of field names, and returns a list of `IdfObject`s
#' whose nodes are referred by specified fields.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param depth If > 0, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. If `NULL`,
#' all possible recursive relations are returned. Default: `0`.
#'
#' @return A named list of `IdfObject` objects.
#'
#' @examples
#' \dontrun{
#' if (is_avail_eplus(8.8)) {
#' path <- file.path(eplus_config(8.8)$dir, "ExampleFiles/5Zone_Transformer.idf")
#' idf_5z <- read_idf(path)
#' idf_5z$NodeList$OutsideAirInletNodes$ref_to_node()
#' }
#' }
#'
ref_to_node = function(which = NULL, object = NULL, class = NULL, group = NULL, depth = 0L)
idfobj_ref_to_node(self, private, which, object = NULL, class = class, group = group, depth = depth),
# }}}
# has_ref_to {{{
#' @description
#' Check if object field values refer to others
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$has_ref_to()` takes an integer vector of field indexes or a
#' character vector of field names, and returns a logical vector showing
#' whether specified fields refer to other object values or not.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param recursive If `TRUE`, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. Default: `FALSE`.
#'
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of referring to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return A logical vector with the same length as specified field.
#'
#' @examples
#' \dontrun{
#' mat$has_ref_to()
#' }
#'
has_ref_to = function(which = NULL, object = NULL, class = NULL, group = NULL, recursive = FALSE, class_ref = c("both", "none", "all"))
idfobj_has_ref_to(self, private, which, object = object, class = class, group = group, recursive = recursive, class_ref = match.arg(class_ref)),
# }}}
# has_ref_by {{{
#' @description
#' Check if object field values are referred by others
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$has_ref_by()` takes an integer vector of field indexes or a
#' character vector of field names, and returns a logical vector showing
#' whether there are other object values ref to specified fields.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param recursive If `TRUE`, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. Default: `FALSE`.
#'
#' @param class_ref Specify how to handle class-name-references. Class
#' name references refer to references in like field `Component 1
#' Object Type` in `Branch` objects. Their value refers to other
#' many class names of objects, instaed of referring to specific
#' field values. There are 3 options in total, i.e. `"none"`,
#' `"both"` and `"all"`, with `"both"` being the default.
#' * `"none"`: just ignore class-name-references. It is a reasonable
#' option, as for most cases, class-name-references always come
#' along with field value references. Ignoring
#' class-name-references will not impact the most part of the
#' relation structure.
#' * `"both"`: only include class-name-references if this object
#' also reference field values of the same one. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, only the object that is referenced in the
#' next field `Component 1 Name` is treated as referenced by
#' `Component 1 Object Type`. This is the default option.
#' * `"all"`: include all class-name-references. For example, if the
#' value of field `Component 1 Object Type` is
#' `Coil:Heating:Water`, all objects in `Coil:Heating:Water` will
#' be treated as referenced by that field. This is the most
#' aggressive option.
#'
#' @return A logical vector with the same length as specified field.
#'
#' @examples
#' \dontrun{
#' mat$has_ref_by()
#' }
#'
has_ref_by = function(which = NULL, object = NULL, class = NULL, group = NULL, recursive = FALSE, class_ref = c("both", "none", "all"))
idfobj_has_ref_by(self, private, which, object = object, class = class, group = group, recursive = recursive, class_ref = match.arg(class_ref)),
# }}}
# has_ref_node {{{
#' @description
#' Check if object field values refer to other nodes
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$has_ref_node()` takes an integer vector of field indexes or a
#' character vector of field names, and returns a logical vector showing
#' whether specified fields refer to other objects' nodes.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param recursive If `TRUE`, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. Default: `FALSE`.
#'
#' @return A logical vector with the same length as specified field.
#'
#' @examples
#' \dontrun{
#' mat$has_ref_node()
#' }
#'
has_ref_node = function(which = NULL, object = NULL, class = NULL, group = NULL, recursive = FALSE)
idfobj_has_ref_node(self, private, which, object = object, class = class, group = group, recursive = recursive),
# }}}
# has_ref {{{
#' @description
#' Check if object field values refer to or are referred by others
#'
#' @details
#' For details on field value relations, see
#' \href{../../eplusr/html/IdfObject.html#method-value_relation}{\code{$value_relation()}}.
#'
#' `$has_ref()` takes an integer vector of field indexes or a character
#' vector of field names, and returns a logical vector showing whether
#' there are other object values ref to specified field values or
#' specified field values refer to other object values or specified
#' field values refer to other objects' nodes.
#'
#' @param which An integer vector of field indexes or a character vector
#' of field names.
#'
#' @param object A character vector of object names or an integer vector
#' of object IDs used for searching relations. Default: `NULL`.
#'
#' @param class A character vector of class names used for searching
#' relations. Default: `NULL`.
#'
#' @param group A character vector of group names used for searching
#' relations. Default: `NULL`.
#'
#' @param recursive If `TRUE`, the relation is searched recursively. A
#' simple example of recursive reference: one material named
#' `mat` is referred by a construction named `const`, and `const`
#' is also referred by a surface named `surf`. Default: `FALSE`.
#'
#' @return A logical vector with the same length as specified field.
#'
#' @examples
#' \dontrun{
#' # check if having any referenced objects or is referenced by other objects
#' mat$has_ref()
#' }
#'
has_ref = function(which = NULL, object = NULL, class = NULL, group = NULL, recursive = FALSE)
idfobj_has_ref(self, private, which, object = object, class = class, group = group, recursive = recursive),
# }}}
# to_table {{{
#' @description
#' Format `IdfObject` as a data.frame
#'
#' @details
#' `$to_table()` returns a [data.table][data.table::data.table()] that
#' contains core data of current `IdfObject`. It has 6 columns:
#'
#' * `id`: Integer type. Object IDs.
#' * `name`: Character type. Object names.
#' * `class`: Character type. Current class name.
#' * `index`: Integer type. Field indexes.
#' * `field`: Character type. Field names.
#' * `value`: Character type if `string_value` is `TRUE` or list type if
#' `string_value` is `FALSE` or `group_ext` is not `"none"`. Field values.
#'
#' Note that when `group_ext` is not `"none"`, `index` and `field`
#' values will not match the original field indices and names. In this
#' case, `index` will only indicate the indices of sequences. For
#' `field` column, specifically:
#'
#' * When `group_ext` is `"group"`, each field name in a extensible group
#' will be abbreviated using [abbreviate()] with `minlength` being
#' `10L` and all abbreviated names will be separated by `|` and
#' combined together. For example, field names in the extensible group
#' (`Vertex 1 X-coordinate`, `Vertex 1 Y-coordinate`, `Vertex 1
#' Z-coordinate`) in class `BuildiBuildingSurface:Detailed` will be
#' merged into one name `Vrtx1X-crd|Vrtx1Y-crd|Vrtx1Z-crd`.
#' * When `group_ext` is `"index"`, the extensible group indicator in field
#' names will be removed. Take the same example as above, the
#' resulting field names will be `Vertex X-coordinate`, `Vertex
#' Y-coordinate`, and `Vertex Z-coordinate`.
#'
#' @param string_value If `TRUE`, all field values are returned as
#' character. If `FALSE`, `value` column in returned
#' [data.table][data.table::data.table()] is a list column with
#' each value stored as corresponding type. Note that if the
#' value of numeric field is set to `"Autosize"` or
#' `"Autocalculate"`, it is left as it is, leaving the returned
#' type being a string instead of a number. Default: `TRUE`.
#'
#' @param unit Only applicable when `string_value` is `FALSE`. If
#' `TRUE`, values of numeric fields are assigned with units using
#' [units::set_units()] if applicable. Default: `FALSE`.
#'
#' @param wide Only applicable if target objects belong to a same class.
#' If `TRUE`, a wide table will be returned, i.e. first three
#' columns are always `id`, `name` and `class`, and then every
#' field in a separate column. Note that this requires all
#' objects specified must from the same class.
#' Default: `FALSE`.
#'
#' @param all If `TRUE`, all available fields defined in IDD for the
#' class that objects belong to will be returned. Default:
#' `FALSE`.
#'
#' @param group_ext Should be one of `"none"`, `"group"` or `"index"`.
#' If not `"none"`, `value` column in returned
#' [data.table::data.table()] will be converted into a list.
#' If `"group"`, values from extensible fields will be grouped by the
#' extensible group they belong to. For example, coordinate
#' values of each vertex in class `BuildingSurface:Detailed` will
#' be put into a list. If `"index"`, values from extensible fields
#' will be grouped by the extensible field indice they belong to.
#' For example, coordinate values of all x coordinates will be
#' put into a list. If `"none"`, nothing special will be done.
#' Default: `"none"`.
#'
#' @return A [data.table][data.table::data.table()] with 6 columns (if
#' `wide` is `FALSE`) or at least 6 columns (if `wide` is `TRUE`).
#'
#' @examples
#' \dontrun{
#' # get all object data in a data.table format without field units
#' str(mat$to_table(unit = FALSE))
#'
#' # get all object data in a data.table format where all field values are put in a
#' # list column and field names without unit
#' str(mat$to_table(string_value = FALSE, unit = FALSE))
#'
#' # get all object data in a data.table format, including tailing empty fields
#' str(idf$Zone$`ZONE ONE`$to_table(all = TRUE))
#'
#' # get all object data in a data.table format where each field becomes a column
#' str(mat$to_table(wide = TRUE))
#'
#' # group extensible by extensible group number
#' surf <- idf$BuildingSurface_Detailed[["Zn001:Roof001"]]
#' surf$to_table(group_ext = "group")
#'
#' # group extensible by extensible group number and convert into a wide table
#' surf$to_table(group_ext = "group", wide = TRUE)
#'
#' # group extensible by extensible field index
#' surf$to_table(group_ext = "index")
#'
#' # group extensible by extensible field index and convert into a wide table
#' surf$to_table(group_ext = "index", wide = TRUE)
#'
#' # when grouping extensible, 'string_value' and 'unit' still take effect
#' surf$to_table(group_ext = "index", wide = TRUE, string_value = FALSE, unit = TRUE)
#' }
#'
to_table = function(string_value = TRUE, unit = TRUE, wide = FALSE, all = FALSE, group_ext = c("none", "group", "index"))
idfobj_to_table(self, private, all, string_value, unit, wide, match.arg(group_ext)),
# }}}
# to_string {{{
#' @description
#' Format current object as a character vector
#'
#' @details
#' `$to_string()` returns the text format of current object.
#'
#' @param comment If `FALSE`, all comments will not be included.
#' Default: `TRUE`.
#'
#' @param leading Leading spaces added to each field. Default: `4L`.
#'
#' @param sep_at The character width to separate value string and field
#' string. Default: `29L` which is the same as IDF Editor.
#'
#' @param all If `TRUE`, all available fields defined in IDD for the
#' class that objects belong to will be returned. Default:
#' `FALSE`.
#'
#' @return A character vector.
#'
#' @examples
#' \dontrun{
#' # get string format object
#' mat$to_string()
#'
#' # get string format of object, and decrease the space between field values and
#' # field names
#' mat$to_string(sep_at = 15)
#'
#' # get string format of object, and decrease the leading space of field values
#' mat$to_string(leading = 0)
#' }
#'
to_string = function(comment = TRUE, leading = 4L, sep_at = 29L, all = FALSE)
idfobj_to_string(self, private, comment, leading, sep_at, all),
# }}}
# print {{{
#' @description
#' Print `IdfObject` object
#'
#' @details
#' `$print()` prints the `IdfObject`. Basically, the print output can be
#' divided into 3 parts:
#'
#' * OBJECT: Class name, object id and name (if applicable).
#' * COMMENTS: Object comments if exist.
#' * VALUES: fields and values of current `IdfObject`. Required fields are marked
#' with start `*`. String values are quoted. Numeric values are printed as
#' they are. Blank string values are printed as `<"Blank">` and blank number
#' values are printed as `<Blank>`.
#'
#' @param comment If `FALSE`, all comments are not included.
#'
#' @param auto_sep If `TRUE`, values and field names are separated at
#' the largest character length of values. Default: `FALSE`.
#'
#' @param brief If `TRUE`, only OBJECT part is printed. Default:
#' `FALSE`.
#'
#' @param all If `TRUE`, all fields defined in [Idd] are printed even
#' they do not exist in current object. Default: `FALSE`.
#'
#' @return The `IdfObject` itself, invisibly.
#'
#' @examples
#' \dontrun{
#' # print the object without comment
#' mat$print(comment = FALSE)
#'
#' # print the object, and auto separate field values and field names at the
#' # largetst character length of field values
#' mat$print(auto_sep = TRUE)
#' }
#'
print = function(comment = TRUE, auto_sep = TRUE, brief = FALSE, all = FALSE)
idfobj_print(self, private, comment, auto_sep, brief, all)
# }}}
# }}}
),
private = list(
# PRIVATE FIELDS {{{
# shared data from parent Idf object
m_parent = NULL,
m_object_id = NULL,
m_class_id = NULL,
# }}}
# PRIVATE FUNCTIONS {{{
idf_env = function() get_priv_env(private$m_parent)$idf_env(),
idd_env = function() get_priv_env(private$m_parent)$idd_env(),
log_env = function() get_priv_env(private$m_parent)$m_log
# }}}
)
)
# }}}
#' Create an `IdfObject` object.
#'
#' `idf_object()` takes a parent `Idf` object, an object name or class name, and
#' returns a corresponding [IdfObject].
#'
#' If `object` is not given, an empty [IdfObject] of specified class is created,
#' with all field values filled with defaults, if possible. Note that
#' validation is performed when creating, which means that an error may occur if
#' current [validate level][level_checks()] does not allow empty required fields.
#'
#' The empty [IdfObject] is directly added into the parent [Idf] object. It is
#' recommended to use `$validate()` method in [IdfObject] to see what kinds of
#' further modifications are needed for those empty fields and use `$set()`
#' method to set field values.
#'
#' @param parent An [Idf] object.
#' @param object A valid object ID (an integer) or name (a string). If `NULL`
#' and `class` is not `NULL`, an empty [IdfObject] is created with all fields
#' fill with default values if possible. Default: `NULL`.
#' @param class A valid class name (a string). If `object` is not `NULL`,
#' `class` is used to further specify what class is the target object belongs
#' to. If `object` is `NULL`, an empty [IdfObject] of `class` is created.
#' @return An [IdfObject] object.
#' @export
#' @examples
#' \dontrun{
#' model <- read_idf(system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr"))
#'
#' # get an IdfObject using object ID
#' idf_object(model, 14)
#'
#' # get an IdfObject using object name (case-insensitive)
#' idf_object(model, "zone one")
#'
#' # `class` argument is useful when there are objects with same name in
#' # different class
#' idf_object(model, "zone one", "Zone")
#'
#' # create a new zone
#' eplusr_option(validate_level = "draft")
#' zone <- idf_object(model, class = "Zone")
#' zone
#' eplusr_option(validate_level = "final")
#' zone$validate()
#' }
#' @export
# idf_object {{{
#' @importFrom checkmate assert_string
idf_object <- function(parent, object = NULL, class = NULL) {
if (missing(parent) || !is_idf(parent)) {
abort(paste("IdfObject can only be created based a parent Idf object.",
"Please give `parent`, which should be an Idf object.")
)
}
idd_env <- get_priv_env(parent)$idd_env()
idf_env <- get_priv_env(parent)$idf_env()
# add an empty object
if (is.null(object)) {
if (is.null(class)) stop("'class' must be given when 'object' is not")
assert_string(class)
lst <- list(list())
names(lst) <- class
obj <- parent$add(lst)[[1L]]
verbose_info(
paste0("New empty object [ID:", obj$id(), "] in class ",
surround(obj$class_name()), " created."
)
)
} else {
obj <- get_idf_object(idd_env, idf_env, class, object, ignore_case = TRUE)
object <- obj$object_id
class <- obj$class_id
obj <- IdfObject$new(object, class, parent)
}
obj
}
# }}}
# idfobj_version {{{
idfobj_version <- function(self, private) {
private$m_parent$version()
}
# }}}
# idfobj_parent {{{
idfobj_parent <- function(self, private) {
private$m_parent
}
# }}}
# idfobj_id {{{
idfobj_id <- function(self, private) {
private$m_object_id
}
# }}}
# idfobj_name {{{
idfobj_name <- function(self, private) {
private$idf_env()$object[J(private$m_object_id), on = "object_id", object_name]
}
# }}}
# idfobj_group_name {{{
idfobj_group_name <- function(self, private) {
private$idd_env()$group[
J(private$idd_env()$class[J(private$m_class_id), on = "class_id", group_id]),
on = "group_id",
group_name
]
}
# }}}
# idfobj_class_name {{{
idfobj_class_name <- function(self, private) {
private$idd_env()$class[J(private$m_class_id), on = "class_id", class_name]
}
# }}}
# idfobj_definition {{{
idfobj_definition <- function(self, private) {
IddObject$new(private$m_class_id, get_priv_env(private$m_parent)$m_idd)
}
# }}}
# idfobj_comment {{{
#' @importFrom checkmate assert_character
idfobj_comment <- function(self, private, comment, append = TRUE, width = 0L) {
if (missing(comment)) {
return(private$idf_env()$object[J(private$m_object_id), on = "object_id"]$comment[[1L]])
}
assert_character(comment, null.ok = TRUE)
obj <- set_idfobj_comment(private$idd_env(), private$idf_env(), private$m_object_id,
comment = comment, append = append, width = width
)
log_add_order(private$log_env(), obj$object_id)
log_unsaved(private$log_env())
log_new_uuid(private$log_env())
# update object in parent
private$idf_env()$object[obj, on = "object_id", `:=`(comment = i.comment)]
self
}
# }}}
# idfobj_value {{{
idfobj_value <- function(self, private, which = NULL, all = FALSE, simplify = FALSE, unit = FALSE) {
get_idfobj_value(private$idd_env(), private$idf_env(), private$m_object_id, which, all, simplify, unit)
}
# }}}
# idfobj_set {{{
idfobj_set <- function(self, private, ..., .default = TRUE, .empty = FALSE) {
# support value input in a list format
lst <- list(...)
if (!(length(lst) == 1L && is.list(lst[[1L]]))) lst <- list(lst)
names(lst) <- paste0("..", private$m_object_id)
idf_set(get_self_env(private$m_parent), get_priv_env(private$m_parent),
lst, .default = .default, .empty = .empty
)
self
}
# }}}
# idfobj_value_possible {{{
idfobj_value_possible <- function(self, private, which = NULL, type = c("auto", "default", "choice", "range", "source")) {
get_idfobj_possible(private$idd_env(), private$idf_env(), private$m_object_id, which, type)
}
# }}}
# idfobj_validate {{{
idfobj_validate <- function(self, private, level = eplusr_option("validate_level")) {
obj <- get_idf_object(private$idd_env(), private$idf_env(), object = private$m_object_id)
val <- get_idf_value(private$idd_env(), private$idf_env(), object = private$m_object_id)
validate_on_level(private$idd_env(), private$idf_env(), obj, val, level)
}
# }}}
# idfobj_is_valid {{{
idfobj_is_valid <- function(self, private, level = eplusr_option("validate_level")) {
count_check_error(idfobj_validate(self, private, level)) == 0L
}
# }}}
# idfobj_value_relation {{{
idfobj_value_relation <- function(self, private, which = NULL,
direction = c("all", "ref_to", "ref_by", "node"),
object = object, class = NULL, group = NULL, depth = 0L,
keep = FALSE, class_ref = c("both", "none", "all")) {
direction <- match.arg(direction)
val <- get_idf_value(private$idd_env(), private$idf_env(),
object = private$m_object_id, field = which
)
get_idfobj_relation(private$idd_env(), private$idf_env(), object_id = NULL,
value_id = val$value_id, name = TRUE, direction = direction, object = object,
class = class, group = group, depth = depth, keep_all = keep, class_ref = match.arg(class_ref))
}
# }}}
# idfobj_ref_to_object {{{
idfobj_ref_to_object <- function(self, private, which = NULL, object = NULL,
class = NULL, group = NULL, depth = 0L,
class_ref = c("both", "none", "all")) {
val <- get_idf_value(private$idd_env(), private$idf_env(),
object = private$m_object_id, field = which
)
# exclude invalid references
rel <- get_idf_relation(private$idd_env(), private$idf_env(),
value_id = val$value_id, direction = "ref_to", class_ref = match.arg(class_ref),
object = object, class = class, group = group, depth = depth
)[!is.na(src_value_id)]
if (!nrow(rel)) {
if (in_verbose()) {
msg <- paste("Target object does not refer to any objects")
if (is.null(object) && is.null(class) && is.null(group)) {
verbose_info(msg, ".")
} else {
verbose_info(msg, " specifed.")
}
}
return(invisible())
} else {
rel <- rel[, list(src_object_id = unique(src_object_id)), by = "object_id"]
res <- apply2(
rel$src_object_id,
private$idf_env()$object[J(rel$src_object_id), on = "object_id", class_id],
IdfObject$new, list(parent = private$m_parent)
)
setattr(res, "names", private$idf_env()$object[J(rel$src_object_id), on = "object_id", object_name])
res
}
}
# }}}
# idfobj_ref_by_object {{{
idfobj_ref_by_object <- function(self, private, which = NULL, object = NULL,
class = NULL, group = NULL, depth = 0L,
class_ref = c("both", "none", "all")) {
val <- get_idf_value(private$idd_env(), private$idf_env(),
object = private$m_object_id, field = which
)
# exclude invalid references
rel <- get_idf_relation(private$idd_env(), private$idf_env(),
value_id = val$value_id, direction = "ref_by", class_ref = match.arg(class_ref),
object = object, class = class, group = group, depth = depth
)[!is.na(value_id)]
if (!nrow(rel)) {
if (eplusr_option("verbose_info")) {
msg <- paste("Target object is not referred by any objects")
if (is.null(object) && is.null(class) && is.null(group)) {
verbose_info(msg, ".")
} else {
verbose_info(msg, " specifed.")
}
}
return(invisible())
} else {
rel <- rel[, list(object_id = unique(object_id)), by = "src_object_id"]
res <- apply2(
rel$object_id,
private$idf_env()$object[J(rel$object_id), on = "object_id", class_id],
IdfObject$new, list(parent = private$m_parent)
)
setattr(res, "names", private$idf_env()$object[J(rel$object_id), on = "object_id", object_name])
res
}
}
# }}}
# idfobj_ref_to_node {{{
idfobj_ref_to_node <- function(self, private, which = NULL, object = NULL, class = NULL, group = NULL, depth = 0L) {
val <- get_idf_value(private$idd_env(), private$idf_env(),
object = private$m_object_id, field = which
)
# exclude invalid references
rel <- get_idf_node_relation(private$idd_env(), private$idf_env(),
value_id = val$value_id, object = object, class = class, group = group, depth = depth
)[!is.na(value_id)]
if (!nrow(rel)) {
if (eplusr_option("verbose_info")) {
msg <- paste("Target object has no node or their nodes have no reference to any objects")
if (is.null(object) && is.null(class) && is.null(group)) {
verbose_info(msg, ".")
} else {
verbose_info(msg, "specifed.")
}
}
return(invisible())
} else {
rel <- rel[, list(object_id = unique(object_id)), by = "src_object_id"]
res <- apply2(
rel$object_id,
private$idf_env()$object[J(rel$object_id), on = "object_id", class_id],
IdfObject$new, list(parent = private$m_parent)
)
setattr(res, "names", private$idf_env()$object[J(rel$object_id), on = "object_id", object_name])
res
}
}
# }}}
# idfobj_has_ref {{{
idfobj_has_ref <- function(self, private, which = NULL, object = NULL,
class = NULL, group = NULL, type = c("all", "ref_to", "ref_by", "node"),
recursive = FALSE, class_ref = c("both", "none", "all")) {
depth <- if (recursive) NULL else 0L
type <- match.arg(type)
if (is.null(which)) {
rel <- get_idfobj_relation(private$idd_env(), private$idf_env(), private$m_object_id,
NULL, FALSE, direction = type, object = object, class = class, group = group, depth = depth,
keep_all = TRUE, class_ref = match.arg(class_ref))
} else {
val <- get_idf_value(private$idd_env(), private$idf_env(),
object = private$m_object_id, field = which
)
rel <- get_idfobj_relation(private$idd_env(), private$idf_env(),
value_id = val$value_id, direction = type, object = object,
class = class, group = group, depth = depth, keep_all = TRUE,
class_ref = match.arg(class_ref))
}
if (type == "all") {
rel$ref_to[, list(.N > 0 && any(!is.na(src_value_id))), by = "value_id"]$V1 |
rel$ref_by[, list(.N > 0 && any(!is.na(value_id))), by = "src_value_id"]$V1 |
rel$node[, list(.N > 0 && any(!is.na(value_id))), by = "src_value_id"]$V1
} else if (type == "ref_to") {
rel$ref_to[, list(.N > 0 && any(!is.na(src_value_id))), by = "value_id"]$V1
} else if (type == "ref_by") {
rel$ref_by[, list(.N > 0 && any(!is.na(value_id))), by = "src_value_id"]$V1
} else {
rel$node[, list(.N > 0 && any(!is.na(value_id))), by = "src_value_id"]$V1
}
}
# }}}
# idfobj_has_ref_to {{{
idfobj_has_ref_to <- function(self, private, which = NULL, object = NULL,
class = NULL, group = NULL, recursive = FALSE,
class_ref = c("both", "none", "all")) {
idfobj_has_ref(self, private, which, object = object, class = class, group = group,
recursive = recursive, type = "ref_to", class_ref = match.arg(class_ref))
}
# }}}
# idfobj_has_ref_by {{{
idfobj_has_ref_by <- function(self, private, which = NULL, object = NULL,
class = NULL, group = NULL, recursive = FALSE,
class_ref = c("both", "none", "all")) {
idfobj_has_ref(self, private, which, object = object, class = class, group = group,
recursive = recursive, type = "ref_by", class_ref = match.arg(class_ref))
}
# }}}
# idfobj_has_ref_node {{{
idfobj_has_ref_node <- function(self, private, which = NULL, object = NULL, class = NULL, group = NULL, recursive = FALSE) {
idfobj_has_ref(self, private, which, object = object, class = class, group = group, recursive = recursive, type = "node")
}
# }}}
# idfobj_to_table {{{
idfobj_to_table <- function(self, private, all = FALSE, string_value = TRUE,
unit = TRUE, wide = FALSE, group_ext = c("none", "group", "index")) {
get_idfobj_table(private$idd_env(), private$idf_env(), private$m_object_id,
all = all, unit = unit, wide = wide, string_value = string_value,
group_ext = match.arg(group_ext)
)
}
# }}}
# idfobj_to_string {{{
idfobj_to_string <- function(self, private, comment = TRUE, leading = 4L, sep_at = 29L, all = FALSE) {
get_idfobj_string(private$idd_env(), private$idf_env(), private$m_object_id,
comment = comment, leading = leading, sep_at = sep_at, all = all
)
}
# }}}
# idfobj_print {{{
idfobj_print <- function(self, private, comment = TRUE, auto_sep = FALSE, brief = FALSE, all = FALSE) {
obj <- get_idf_object(private$idd_env(), private$idf_env(), object = private$m_object_id)
if (is.na(obj$object_name)) {
h <- paste0("<IdfObject: ", surround(obj$class_name), "> [ID:", obj$object_id, "]")
} else {
h <- paste0("<IdfObject: ", surround(obj$class_name), "> [ID:", obj$object_id, "] `", obj$object_name, "`")
}
cli::cat_line(h)
if (brief) return(invisible(self))
val <- get_idf_value(private$idd_env(), private$idf_env(), object = private$m_object_id, all = all)
# comment
if (comment && !is.null(obj$comment[[1L]])) {
cli::cat_rule("COMMENTS")
cli::cat_line(cli::ansi_strtrim(paste0("!", obj$comment[[1L]])))
cli::cat_rule("VALUES")
}
if (auto_sep) {
sep_at <- max(nchar(val$value_chr, "width", keepNA = FALSE)) + 4L
if (sep_at > 20L) sep_at <- 20L
if (sep_at < 12L) sep_at <- 12L
} else {
sep_at <- 20L
}
# value
add_joined_cols(private$idd_env()$field, val, "field_id", c("units", "ip_units", "type_enum", "required_field"))
fmt <- format_objects(val, c("class", "value"), brief = FALSE, sep_at = sep_at, required = TRUE)$out[[1L]]
# remove trailing blank line
cli::cat_line(cli::ansi_strtrim(fmt[-length(fmt)]))
invisible(self)
}
# }}}
#' Format an IdfObject
#'
#' Format an [IddObject] into a character vector in the same way as in IDF Editor.
#'
#' @param x An [IdfObject] object.
#' @param comment If `FALSE`, all comments will not be included. Default: `TRUE`.
#' @param leading Leading spaces added to each field. Default: `4L`.
#' @param sep_at The character width to separate value string and field string.
#' Default: `29L` which is the same as IDF Editor.
#' @param all If `TRUE`, values of all possible fields in current class the
#' [IdfObject] belongs to are returned. Default: `FALSE`
#' @param ... Further arguments passed to or from other methods.
#' @return A character vector.
#' @examples
#' \dontrun{
#' idf <- read_idf(system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr"),
#' idd = use_idd("8.8", download = "auto"))
#'
#' # get the IdfObject of material named "C5 - 4 IN HW CONCRETE"
#' mat <- idf$Material[["C5 - 4 IN HW CONCRETE"]]
#'
#' cat(format(mat, leading = 0, sep_at = 10))
#' }
#'
#' @export
# format.IdfObject {{{
format.IdfObject <- function(x, comment = TRUE, leading = 4L, sep_at = 29L, all = FALSE, ...) {
paste0(x$to_string(comment = comment, leading = leading, sep_at = sep_at, all = all), collapse = "\n")
}
# }}}
#' Coerce an IdfObject into a Character Vector
#'
#' Coerce an [IdfObject] into a character vector in the same way as in IDF Editor.
#'
#' @inherit format.IdfObject
#' @return A character vector.
#' @examples
#' \dontrun{
#' idf <- read_idf(system.file("extdata/1ZoneUncontrolled.idf", package = "eplusr"),
#' idd = use_idd("8.8", download = "auto"))
#'
#' # get the IdfObject of material named "C5 - 4 IN HW CONCRETE"
#' mat <- idf$Material[["C5 - 4 IN HW CONCRETE"]]
#'
#' as.character(mat, leading = 0, sep_at = 10)
#' }
#'
#' @export
# as.character.IdfObject {{{
as.character.IdfObject <- function(x, comment = TRUE, leading = 4L, sep_at = 29L, all = FALSE, ...) {
x$to_string(comment = comment, leading = leading, sep_at = sep_at, all = all)
}
# }}}
#' @export
# str.IdfObject {{{
str.IdfObject <- function(object, ...) {
object <- object$value()
NextMethod()
}
# }}}
#' @export
# print.IdfObject {{{
print.IdfObject <- function(x, comment = TRUE, auto_sep = TRUE, brief = FALSE, all = FALSE, ...) {
x$print(comment = comment, auto_sep = auto_sep, brief = brief, all = all)
}
# }}}
#' @export
# [.IdfObject {{{
'[.IdfObject' <- function(x, i, j, ...) {
if (!missing(j)) stop("incorrect number of dimensions")
.subset2(x, "value")(i)
}
# }}}
#' @export
# .DollarNames.IdfObject {{{
.DollarNames.IdfObject <- function(x, pattern = "") {
private <- get_priv_env(x)
fld_id <- private$idf_env()$value[J(private$m_object_id), on = "object_id", field_id]
fld_nm <- private$idd_env()$field[J(fld_id), on = "field_id", field_name]
grep(pattern, c(fld_nm, names(x)), value = TRUE)
}
# }}}
#' @export
# $.IdfObject {{{
'$.IdfObject' <- function(x, name) {
if (name %chin% ls(x)) return(NextMethod())
self <- get_self_env(x)
private <- get_priv_env(x)
# In order to make sure `idfobj$nAmE` is not acceptable
fld_nm <- private$idd_env()$field[J(private$m_class_id), on = "class_id", field_name]
fld_idx <- chmatch(name, fld_nm)
if (is.na(fld_idx)) fld_idx <- chmatch(name, underscore_name(fld_nm))
if (!is.na(fld_idx)) {
tryCatch(
get_idfobj_value(private$idd_env(), private$idf_env(),
private$m_object_id, which = fld_idx
)[[1L]],
eplusr_error_invalid_field_name = function(e) NextMethod()
)
} else {
NextMethod()
}
}
# }}}
#' @export
# [[.IdfObject {{{
'[[.IdfObject' <- function(x, i) {
if (length(i) != 1L) return(NextMethod())
if (as.character(i) %chin% ls(x)) {
NextMethod()
} else {
self <- get_self_env(x)
private <- get_priv_env(x)
# In order to make sure `idfobj$nAmE` is not acceptable
if (checkmate::test_count(i, positive = TRUE) || i %chin% private$idd_env()$field[J(private$m_class_id), on = "class_id", field_name]) {
tryCatch(
get_idfobj_value(private$idd_env(), private$idf_env(), private$m_object_id, which = i)[[1L]],
eplusr_error_invalid_field_name = function(e) NextMethod()
)
} else {
NextMethod()
}
}
}
# }}}
#' @export
# $<-.IdfObject {{{
#' @importFrom checkmate assert_scalar
`$<-.IdfObject` <- function(x, name, value) {
# all field names start with a capital letter
if (!substr(name, 1, 1) %chin% LETTERS && name %chin% ls(x)) return(NextMethod())
self <- get_self_env(x)
private <- get_priv_env(x)
# In order to make sure `idfobj$nAmE <- "a"` is not acceptable
fld_nm <- private$idd_env()$field[J(private$m_class_id), on = "class_id", field_name]
fld_idx <- chmatch(name, fld_nm)
if (is.na(fld_idx)) fld_idx <- chmatch(name, underscore_name(fld_nm))
if (!is.na(fld_idx)) {
if (is.null(value)) value <- list(NULL)
names(value) <- name
tryCatch(do.call(.subset2(x, "set"), c(as.list(value), .default = FALSE, .empty = FALSE)),
eplusr_error_invalid_field_name = function(e) NextMethod())
invisible(x)
} else {
NextMethod()
}
}
# }}}
#' @export
# [[<-.IdfObject {{{
'[[<-.IdfObject' <- function(x, i, value) {
if (length(i) != 1) return(NextMethod())
if (!substr(as.character(i), 1, 1) %chin% LETTERS && as.character(i) %chin% ls(x)) return(NextMethod())
self <- get_self_env(x)
private <- get_priv_env(x)
# In order to make sure only standard field name is not acceptable
fld_nm <- private$idd_env()$field[J(private$m_class_id), on = "class_id", field_name]
if (checkmate::test_count(i, positive = TRUE)) {
fld_idx <- i
nm <- paste0("..", i)
} else {
fld_idx <- chmatch(i, fld_nm)
nm <- i
}
if (!is.na(fld_idx)) {
if (is.null(value)) value <- list(NULL)
names(value) <- nm
tryCatch(do.call(.subset2(x, "set"), c(as.list(value), .default = FALSE, .empty = FALSE)),
eplusr_error_invalid_field_name = function(e) NextMethod())
invisible(x)
} else {
NextMethod()
}
}
# }}}
#' @export
# ==.IdfObject {{{
`==.IdfObject` <- function(e1, e2) {
if (!is_idfobject(e2)) return(FALSE)
identical(
get_priv_env(get_priv_env(e1)$m_parent)$m_log$uuid,
get_priv_env(get_priv_env(e2)$m_parent)$m_log$uuid
) &&
identical(get_priv_env(e1)$m_object_id, get_priv_env(e2)$m_object_id)
}
# }}}
#' @export
# !=.IdfObject {{{
`!=.IdfObject` <- function(e1, e2) {
Negate(`==.IdfObject`)(e1, e2)
}
# }}}
# vim: set fdm=marker:
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.