1 2 // 3 // This source file is part of appleseed. 4 // Visit https://appleseedhq.net/ for additional information and resources. 5 // 6 // This software is released under the MIT license. 7 // 8 // Copyright (c) 2017-2018 Artem Bishev, The appleseedhq Organization 9 // 10 // Permission is hereby granted, free of charge, to any person obtaining a copy 11 // of this software and associated documentation files (the "Software"), to deal 12 // in the Software without restriction, including without limitation the rights 13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 // copies of the Software, and to permit persons to whom the Software is 15 // furnished to do so, subject to the following conditions: 16 // 17 // The above copyright notice and this permission notice shall be included in 18 // all copies or substantial portions of the Software. 19 // 20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 // THE SOFTWARE. 27 // 28 29 #pragma once 30 31 // Boost headers. 32 #include "boost/algorithm/string.hpp" 33 #include "boost/lexical_cast.hpp" 34 #include "boost/unordered_map.hpp" 35 36 // Standard headers. 37 #include <string> 38 #include <vector> 39 40 // Foundation headers. 41 #include "foundation/core/exceptions/exception.h" 42 #include "foundation/utility/foreach.h" 43 #include "foundation/utility/string.h" 44 #include "foundation/utility/test.h" 45 46 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseFormat); 47 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_IgnoreEmptyLines); 48 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_DoNotIgnoreEmptyLines); 49 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, CheckEmpty); 50 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseKeywords); 51 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Empty); 52 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Good); 53 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Bad); 54 55 namespace foundation 56 { 57 58 // 59 // Class for parsing IESNA LM-63 Photometric Data File 60 // and storing its contents. 61 // 62 63 class IESParser 64 { 65 public: 66 // Constructor. 67 IESParser(); 68 69 // Exception that can store erroneous line number. 70 class Exception 71 : public foundation::Exception 72 { 73 public: 74 Exception(const char* message, const int line); 75 line()76 virtual int line() const 77 { 78 return m_line_number; 79 } 80 81 protected: 82 int m_line_number; 83 }; 84 85 // Exception thrown when file format violates the IES specifications. 86 class ParsingException : public Exception 87 { 88 public: ParsingException(const char * message,int line)89 ParsingException(const char* message, int line) 90 : Exception(message, line) 91 { 92 } 93 }; 94 95 // Exception thrown when the feature of IES file is not supported by this parser. 96 class NotSupportedException : public Exception 97 { 98 public: NotSupportedException(const char * message,int line)99 NotSupportedException(const char* message, int line) 100 : Exception(message, line) 101 { 102 } 103 }; 104 105 // Dictionary containing IES file keywords and corresponding values. 106 typedef boost::unordered_map<std::string, std::string> KeywordsDictionary; 107 108 // Type of IES file format. 109 enum Format 110 { 111 UnknownFormat, 112 Format1986, 113 Format1991, 114 Format1995, 115 Format2002, 116 }; 117 118 // How TILT information is specified in the file. 119 enum TiltSpecification 120 { 121 IncludeTilt, // TILT is included in this IES file 122 TiltFromFile, // TILT is specified in the separate file 123 NoTilt // the lamp output does not vary as a function 124 // of the luminaire tilt angle (and TILT is not specified) 125 }; 126 127 // Lamp-to-luminaire geometry types. 128 // For more information, see the IESNA LM-63 specifications 129 enum LampToLuminaireGeometry 130 { 131 GeometryNotSpecified, 132 VerticalGeometry, 133 HorizontalInvariantGeometry, 134 HorizontalNonInvariantGeometry 135 }; 136 137 // Photometric types. 138 enum PhotometricType 139 { 140 PhotometricTypeNotSpecified, 141 PhotometricTypeC, 142 PhotometricTypeB, 143 PhotometricTypeA 144 }; 145 146 // Types of symmetry that candela values have with respect to horziontal angle 147 enum SymmetryType 148 { 149 NoSymmetry, 150 SymmetricHalvesX, // bilaterally symmetric about the 0-180 degree axis 151 SymmetricHalvesY, // bilaterally symmetric about the 90-270 degree axis 152 SymmetricQuadrants, // symmetric in each quadrant 153 FullySymmetric // full rotational symmetry 154 }; 155 156 // Shape of luminous opening. 157 struct LuminousOpeningShape 158 { 159 enum UnitsType 160 { 161 Feet, 162 Meters 163 }; 164 165 UnitsType m_units_type; 166 double m_width; 167 double m_length; 168 double m_height; 169 }; 170 171 typedef std::vector<std::vector<double>> PhotometricGrid; 172 173 // 174 // Options. 175 // 176 177 bool m_ignore_allowed_keywords; // if true, all keywors are allowed not depending on format version 178 bool m_ignore_required_keywords; // if true, some required keywors can be missing 179 bool m_ignore_empty_lines; // if true, than the file can contain whitespace lines 180 bool m_ignore_tilt; // if true, TILT section is not parsed 181 182 // 183 // Main methods. 184 // 185 186 // Parse input stream containing IESNA LM-63 Photometric Data. 187 void parse(std::istream& input_stream); 188 189 // Check if the keyword is allowed by IESNA LM-63-2002 standard. 190 static bool is_keyword_allowed_by_iesna02(const std::string& keyword); 191 192 // Check if the keyword is allowed by IESNA LM-63-95 standard. 193 static bool is_keyword_allowed_by_iesna95(const std::string& keyword); 194 195 // Check if the keyword is allowed by IESNA LM-63-91 standard. 196 static bool is_keyword_allowed_by_iesna91(const std::string& keyword); 197 198 // 199 // Getters. 200 // 201 get_format()202 Format get_format() const 203 { 204 return m_format; 205 } 206 get_lamp_to_luminaire_geometry()207 LampToLuminaireGeometry get_lamp_to_luminaire_geometry() const 208 { 209 return m_lamp_to_luminaire_geometry; 210 } 211 get_tilt_angles()212 const std::vector<double>& get_tilt_angles() const 213 { 214 return m_tilt_angles; 215 } 216 get_tilt_multiplying_factors()217 const std::vector<double>& get_tilt_multiplying_factors() const 218 { 219 return m_tilt_multiplying_factors; 220 } 221 get_number_of_lamps()222 int get_number_of_lamps() const 223 { 224 return m_number_of_lamps; 225 } 226 get_lumens_per_lamp()227 double get_lumens_per_lamp() const 228 { 229 return m_lumens_per_lamp; 230 } 231 is_absolute_photometry()232 bool is_absolute_photometry() const 233 { 234 return m_absolute_photometry; 235 } 236 get_candela_multiplier()237 double get_candela_multiplier() const 238 { 239 return m_candela_multiplier; 240 } 241 get_number_of_vertical_angles()242 int get_number_of_vertical_angles() const 243 { 244 return m_number_of_vertical_angles; 245 } 246 get_number_of_horizontal_angles()247 int get_number_of_horizontal_angles() const 248 { 249 return m_number_of_horizontal_angles; 250 } 251 get_photometric_type()252 PhotometricType get_photometric_type() const 253 { 254 return m_photometric_type; 255 } 256 get_luminous_opening()257 const LuminousOpeningShape& get_luminous_opening() const 258 { 259 return m_luminous_opening; 260 } 261 get_ballast_factor()262 double get_ballast_factor() const 263 { 264 return m_ballast_factor; 265 } 266 get_ballast_lamp_photometric_factor()267 double get_ballast_lamp_photometric_factor() const 268 { 269 return m_ballast_lamp_photometric_factor; 270 } 271 get_input_watts()272 double get_input_watts() const 273 { 274 return m_input_watts; 275 } 276 get_symmetry()277 SymmetryType get_symmetry() const 278 { 279 return m_symmetry; 280 } 281 get_vertical_angles()282 const std::vector<double>& get_vertical_angles() const 283 { 284 return m_vertical_angles; 285 } 286 get_horizontal_angles()287 const std::vector<double>& get_horizontal_angles() const 288 { 289 return m_horizontal_angles; 290 } 291 get_candela_values()292 const PhotometricGrid& get_candela_values() const 293 { 294 return m_candela_values; 295 } 296 get_keywords_dictionary()297 const KeywordsDictionary& get_keywords_dictionary() const 298 { 299 return m_keywords_dictionary; 300 } 301 get_keyword_value(const std::string & keyword)302 const std::string& get_keyword_value(const std::string& keyword) const 303 { 304 return m_keywords_dictionary.at(keyword); 305 } 306 307 private: 308 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseFormat); 309 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_IgnoreEmptyLines); 310 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_DoNotIgnoreEmptyLines); 311 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, CheckEmpty); 312 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseKeywords); 313 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Empty); 314 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Good); 315 GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Bad); 316 317 static const char* const KeywordLineRegex; 318 static const char* const TiltLineRegex; 319 320 // Reset parser. 321 void reset(std::istream& input_stream); 322 323 // Read line and increase the counter. 324 void read_line(std::istream& input_stream); 325 326 // Read line, trim it and increase the counter. 327 // When ignore_empty_lines set to true this method 328 // ignores all lines consisting of whitespace characters. 329 void read_trimmed_line(std::istream& input_stream); 330 331 // Retrieve format version. 332 // LM_63_1986 is set if this line is not one of supported version strings. 333 void parse_format_version(std::istream& input_stream); 334 335 // Parse all data before TILT=<...> line 336 // and store keywords and corresponding values. 337 void parse_keywords_and_tilt(std::istream& input_stream); 338 339 // Parse TILT section. 340 void parse_tilt_data(std::istream& input_stream); 341 342 // Parse all photometric data after the TILT section 343 void parse_photometric_data(std::istream& input_stream); 344 345 void parse_angles(std::istream& input_stream); 346 347 void parse_candela_values(std::istream& input_stream); 348 349 // Check if the line defines a valid keyword-value pair. 350 static bool is_keyword_line(const std::string& line); 351 352 // Check if the line is a valid TILT=<...> line. 353 static bool is_tilt_line(const std::string& line); 354 355 // Parse the line containing keyword-value pair 356 // and store this pair in the dictionary. 357 void parse_keyword_line(const std::string& line); 358 359 // Parse TILT=<...> line. 360 void parse_tilt_line(const std::string& line); 361 362 // Process BLOCK and ENDBLOCK keywords. 363 void process_block_keyword(const std::string& keyword); 364 365 // Check if the specified standard allows this keyword. 366 void accept_keyword(const std::string& keyword); 367 368 // Check if all required keywords were found. 369 void check_required_keywords() const; 370 371 void check_iesna02_required_keywords() const; 372 373 void check_iesna91_required_keywords() const; 374 375 // Check if line is not empty and EOF is not reached. 376 void check_empty(std::istream& input_stream) const; 377 378 // Read lines, split each line and write the values to the vector, 379 // until the total number of values does not exceed the specified size. 380 // If number of values in the last parsed line it too large, raise a ParsingException. 381 // Can also raise a boost::bad_cast exception, when a token cannot be converted 382 // to the value of specified type. 383 template <typename ValueType> parse_to_vector(std::istream & input_stream,size_t count)384 std::vector<ValueType> parse_to_vector(std::istream& input_stream, size_t count) 385 { 386 std::vector<ValueType> output; 387 output.reserve(count); 388 389 while (output.size() < count) 390 { 391 check_empty(input_stream); 392 393 std::vector<std::string> tokens; 394 boost::split(tokens, m_line, isspace, boost::token_compress_on); 395 if (output.size() + tokens.size() > count) 396 { 397 std::string expected_number_of_values = 398 boost::lexical_cast<std::string>(count - output.size()); 399 static const char* ErrorMsg = "Too many values in the line, expected {0}"; 400 throw ParsingException( 401 format(ErrorMsg, expected_number_of_values).c_str(), m_line_counter); 402 } 403 404 for (each<std::vector<std::string>> token = tokens; token; ++token) 405 { 406 output.push_back(boost::lexical_cast<ValueType>(*token)); 407 } 408 409 read_trimmed_line(input_stream); 410 } 411 412 return output; 413 } 414 415 // 416 // All information retrieved from file: 417 // 418 419 // File format. 420 Format m_format; 421 422 // TILT specification. 423 TiltSpecification m_tilt_specification; 424 std::string m_tilt_specification_filename; 425 426 // TILT section. 427 LampToLuminaireGeometry m_lamp_to_luminaire_geometry; 428 std::vector<double> m_tilt_angles; 429 std::vector<double> m_tilt_multiplying_factors; 430 431 // Photometric information. 432 int m_number_of_lamps; 433 double m_lumens_per_lamp; 434 bool m_absolute_photometry; 435 double m_candela_multiplier; 436 int m_number_of_vertical_angles; 437 int m_number_of_horizontal_angles; 438 PhotometricType m_photometric_type; 439 LuminousOpeningShape m_luminous_opening; 440 double m_ballast_factor; 441 double m_ballast_lamp_photometric_factor; 442 double m_input_watts; 443 SymmetryType m_symmetry; 444 std::vector<double> m_vertical_angles; 445 std::vector<double> m_horizontal_angles; 446 PhotometricGrid m_candela_values; 447 448 // Keywords. 449 KeywordsDictionary m_keywords_dictionary; 450 451 // 452 // Variables that describe the current parsing state: 453 // 454 455 KeywordsDictionary::iterator m_last_added_keyword; 456 int m_line_counter; 457 std::string m_line; 458 }; 459 460 } // namespace foundation 461