From a04fa1454141062c2411653539a0aa244853c95b Mon Sep 17 00:00:00 2001 From: est77 Date: Wed, 22 Apr 2020 12:47:05 +0200 Subject: [PATCH] New proj format --- .../mainwindow/mainwindow.cpp | 6 +- src/appleseed/CMakeLists.txt | 4 + .../project/appleseedprojectfilereader.cpp | 850 ++++++++++++ .../project/appleseedprojectfilereader.h | 61 + .../project/appleseedprojectfilewriter.cpp | 1181 +++++++++++++++++ .../project/appleseedprojectfilewriter.h | 50 + .../modeling/project/projectfilereader.cpp | 31 +- .../modeling/project/projectfilewriter.cpp | 7 + .../modeling/project/projectfilewriter.h | 3 +- 9 files changed, 2182 insertions(+), 11 deletions(-) create mode 100644 src/appleseed/renderer/modeling/project/appleseedprojectfilereader.cpp create mode 100644 src/appleseed/renderer/modeling/project/appleseedprojectfilereader.h create mode 100644 src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.cpp create mode 100644 src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.h diff --git a/src/appleseed.studio/mainwindow/mainwindow.cpp b/src/appleseed.studio/mainwindow/mainwindow.cpp index 42ce097d59..c2ef674493 100644 --- a/src/appleseed.studio/mainwindow/mainwindow.cpp +++ b/src/appleseed.studio/mainwindow/mainwindow.cpp @@ -265,10 +265,8 @@ bool MainWindow::save_project(QString filepath) if (!m_project_manager.is_project_open()) return false; - const QString Extension = "appleseed"; - - if (QFileInfo(filepath).suffix() != Extension) - filepath += "." + Extension; + if (QFileInfo(filepath).suffix().isEmpty()) + filepath += ".appleseed"; if (m_project_file_watcher) stop_monitoring_project_file(); diff --git a/src/appleseed/CMakeLists.txt b/src/appleseed/CMakeLists.txt index 0603196193..95d6a90e85 100644 --- a/src/appleseed/CMakeLists.txt +++ b/src/appleseed/CMakeLists.txt @@ -2009,6 +2009,10 @@ source_group ("renderer\\modeling\\postprocessingstage" FILES ) set (renderer_modeling_project_sources + renderer/modeling/project/appleseedprojectfilereader.cpp + renderer/modeling/project/appleseedprojectfilereader.h + renderer/modeling/project/appleseedprojectfilewriter.cpp + renderer/modeling/project/appleseedprojectfilewriter.h renderer/modeling/project/assethandler.cpp renderer/modeling/project/assethandler.h renderer/modeling/project/configuration.cpp diff --git a/src/appleseed/renderer/modeling/project/appleseedprojectfilereader.cpp b/src/appleseed/renderer/modeling/project/appleseedprojectfilereader.cpp new file mode 100644 index 0000000000..67a0ab1d1d --- /dev/null +++ b/src/appleseed/renderer/modeling/project/appleseedprojectfilereader.cpp @@ -0,0 +1,850 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Esteban Tovagliari, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "appleseedprojectfilereader.h" + +// appleseed.renderer headers. +#include "renderer/modeling/camera/camera.h" +#include "renderer/modeling/camera/camerafactoryregistrar.h" +#include "renderer/modeling/camera/icamerafactory.h" +#include "renderer/modeling/frame/frame.h" +#include "renderer/modeling/project/configurationcontainer.h" +#include "renderer/modeling/project/eventcounters.h" +#include "renderer/modeling/project/configuration.h" +#include "renderer/modeling/project/configurationcontainer.h" +#include "renderer/modeling/project/project.h" +#include "renderer/modeling/project/projectfilereader.h" +#include "renderer/modeling/scene/scene.h" + +// appleseed.foundation headers. +#include "foundation/utility/searchpaths.h" + +// Standard headers. +#include +#include + +using namespace foundation; + +namespace renderer +{ +namespace +{ + +const char* gEmptyStr = ""; + +class StringView final +{ + public: + StringView() + : m_chars(gEmptyStr) + , m_size(0) + { + } + + StringView(const char* str, const std::size_t len) + : m_chars(str) + , m_size(len) + { + assert(str); + m_chars = str; + m_size = len; + } + + StringView(const char* str) + { + assert(str); + m_chars = str; + m_size = std::strlen(str); + } + + StringView(const std::string& str) + : StringView(str.c_str(), str.length()) + { + } + + const char* c_str() const + { + return m_chars; + } + + bool empty() const + { + return m_size == 0; + } + + std::size_t lenght() const + { + return m_size; + } + + bool operator==(const StringView& other) const + { + if (m_size == other.m_size) + { + if (m_size) + return std::memcmp(m_chars, other.m_chars, m_size) == 0; + + return true; + } + + return false; + } + + bool operator==(const char* other) const + { + if (c_str() == other) + return true; + + return std::strcmp(c_str(), other) == 0; + } + + bool operator!=(const StringView& other) const + { + return !(*this == other); + } + + bool operator!=(const char* other) const + { + return !(*this == other); + } + + operator std::string() const + { + return std::string(c_str(), lenght()); + } + + private: + const char* m_chars; + std::size_t m_size; +}; + +class ParseException +{ + public: + ParseException( + const std::string& message, + const std::size_t line, + const std::size_t col) + { + m_msg = + std::string("Parse error, line ") + to_string(line) + + ", pos " + to_string(col) + + ", error: " + message; + } + + const char* what() const noexcept + { + return m_msg.c_str(); + } + + private: + std::string m_msg; +}; + +class Lexer +{ + public: + explicit Lexer(FILE* file) + : m_file(file) + , m_has_unget_token(false) + , m_line(1) + , m_col(0) + { + } + + // non copyable. + Lexer(const Lexer&) = delete; + Lexer& operator=(const Lexer&) = delete; + + std::size_t line() const + { + return m_line; + } + + std::size_t col() const + { + return m_col; + } + + StringView next_token() + { + if (m_has_unget_token) + { + m_has_unget_token = false; + return StringView(m_unget_token); + } + + m_token.clear(); + skip_whitespace(); + + while (true) + { + char c; + if (!get_char(c)) + break; + + if (std::isspace(c)) + break; + + m_token.push_back(c); + } + + return StringView(m_token); + } + + void unget_token(StringView token) + { + assert(!m_has_unget_token); + m_unget_token = std::string(token.c_str(), token.lenght()); + m_has_unget_token = true; + } + + bool next_token_is(const char* str) + { + const auto t = next_token(); + if (!t.empty()) + { + if (t == str) + return true; + + unget_token(t); + } + + return false; + } + + StringView get_string_literal() + { + if (m_has_unget_token) + { + m_token = m_unget_token; + m_has_unget_token = false; + } + else + m_token.clear(); + + skip_whitespace(); + + while (true) + { + char c; + if (!get_char(c)) + break; + + if (is_newline(c)) + break; + + m_token.push_back(c); + } + + return StringView(m_token); + } + + template + T get_value() + { + try + { + return from_string(next_token().c_str()); + } + catch (const ExceptionStringConversionError&) + { + throw_error("Conversion error!!!"); + return T(); + } + } + + void skip_line() + { + while (true) + { + char c; + + if (!get_char(c)) + break; + + if (is_newline(c)) + break; + } + } + + private: + FILE* m_file; + std::string m_token; + std::string m_unget_token; + bool m_has_unget_token; + std::size_t m_line; + std::size_t m_col; + + bool get_char(char& c) + { + const int next_c = std::fgetc(m_file); + if (next_c == EOF) + return false; + + c = next_c; + if (is_newline(c)) + { + ++m_line; + m_col = 0; + } + else + ++m_col; + + return true; + } + + bool is_newline(char c) + { + // TODO: handle windows end of lines here. + return c == '\n'; + } + + void skip_whitespace() + { + while (true) + { + const int next_c = std::fgetc(m_file); + if (next_c == EOF) + break; + + const char c = next_c; + if (!std::isspace(c)) + { + std::ungetc(next_c, m_file); + break; + } + + if (is_newline(c)) + { + ++m_line; + m_col = 0; + } + else + ++m_col; + } + } + + void throw_error(const std::string& message) + { + throw ParseException( + message, + line(), + col()); + } +}; + +class Parser +{ + public: + Parser( + const char* project_filepath, + Lexer& lexer, + const int options) + : m_lexer(lexer) + , m_project_filepath(project_filepath) + , m_options(options) + { + } + + auto_release_ptr parse() + { + while (true) + { + const auto token = m_lexer.next_token(); + + if (token.empty()) + break; + + // Handle comments. + if (token == "#") + { + m_lexer.skip_line(); + continue; + } + + if (token == "project") + { + if (m_project) + throw_error("Multiple project definitions"); + + parse_project(); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + + if (!m_project) + throw_error("No project in file"); + + return m_project; + } + + private: + Lexer& m_lexer; + const char* m_project_filepath; + const int m_options; + + auto_release_ptr m_project; + + void parse_project() + { + const auto name = m_lexer.get_string_literal(); + if (name.empty()) + throw_error("Missing project name"); + + validate_identifier(name); + m_project = ProjectFactory::create(name.c_str()); + m_project->set_path(m_project_filepath); + + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + if (!m_lexer.next_token_is("format_revision")) + throw_error("Missing format_revision entry"); + + const auto project_revision = m_lexer.get_value(); + if (project_revision == 0) + throw_error("Bad error!!!"); + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + if (token == "search_paths") + { + parse_search_paths(); + continue; + } + + if (token == "scene") + { + if (m_project->get_scene()) + throw_error("Multiple scene definitions"); + + parse_scene(); + continue; + } + + if (token == "frame") + { + if (m_project->get_frame()) + throw_error("Multiple frame definitions"); + + parse_frame(); + continue; + } + + if (token == "configurations") + { + //if (!m_project->configurations().empty()) + // throw_error("Multiple configurations definitions"); + + parse_configurations(); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + } + + void parse_search_paths() + { + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + while (true) + { + const auto path = m_lexer.get_string_literal(); + + if (path == "}") + break; + + if (path.empty()) + continue; + + if (!(m_options & ProjectFileReader::OmitSearchPaths)) + m_project->search_paths().push_back_explicit_path(path.c_str()); + } + } + + void parse_scene() + { + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + m_project->set_scene(SceneFactory::create()); + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + if (token == "camera") + { + parse_camera(); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + } + + void parse_camera() + { + const std::string model = m_lexer.next_token(); + + const CameraFactoryRegistrar cameraFactoryRegistrar; + const auto *cameraFactory = cameraFactoryRegistrar.lookup(model.c_str()); + if (!cameraFactory) + throw_error(std::string("Unknown camera model ") + model.c_str()); + + const std::string name = m_lexer.get_string_literal(); + if (name.empty()) + throw_error("Missing camera name"); + + validate_identifier(name); + + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + ParamArray params; + TransformSequence transformSequence; + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + if (token == "params") + { + parse_params(params); + continue; + } + + if (token == "transform_sequence") + { + parse_transform_sequence(transformSequence); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + + auto camera = cameraFactory->create(name.c_str(), params); + camera->transform_sequence() = transformSequence; + m_project->get_scene()->cameras().insert(camera); + } + + void parse_frame() + { + const auto name = m_lexer.get_string_literal(); + if (name.empty()) + throw_error("Missing frame name"); + + validate_identifier(name); + m_project->set_frame(FrameFactory::create(name.c_str(), ParamArray())); + + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + } + + void parse_configurations() + { + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + if (token == "configuration") + { + parse_configuration(); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + } + + void parse_configuration() + { + const std::string name = m_lexer.get_string_literal(); + if (name.empty()) + throw_error("Missing configuration name"); + + validate_identifier(StringView(name.c_str(), name.length())); + + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + std::string base_name; + ParamArray params; + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + if (token == "base") + { + base_name = m_lexer.get_string_literal(); + if (base_name.empty()) + throw_error("Missing configuration base name"); + + validate_identifier(StringView(base_name.c_str(), base_name.length())); + continue; + } + + if (token == "params") + { + parse_params(params); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + + auto configuration = + ConfigurationFactory::create( + name.c_str(), + params); + + // Handle configuration inheritance. + if (!base_name.empty()) + { + const Configuration* base = + m_project->configurations().get_by_name(base_name.c_str()); + + if (base) + configuration->set_base(base); + else + throw_error(std::string("configuration ") + base_name + " does not exist"); + } + + m_project->configurations().insert(configuration); + } + + void parse_params(Dictionary& params) + { + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + while (true) + { + if (m_lexer.next_token_is("}")) + break; + + const std::string key = m_lexer.next_token(); + validate_identifier(key); + + const std::string value = m_lexer.get_string_literal(); + + if (value == "{") + { + m_lexer.unget_token(value); + Dictionary dict; + parse_params(dict); + + if (!dict.empty()) + params.dictionaries().insert(key.c_str(), dict); + } + else + { + if (value.empty()) + throw_error("Premature end of file"); + + params.insert(key.c_str(), value.c_str()); + } + } + } + + void parse_transform_sequence(TransformSequence& transformSequence) + { + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + while (true) + { + const auto& token = m_lexer.next_token(); + + if (token == "}") + break; + + if (token == "matrix") + { + parse_matrix(transformSequence); + continue; + } + + throw_error(std::string("Unexpected token ") + token.c_str()); + } + } + + void parse_matrix(TransformSequence& transformSequence) + { + const auto time = m_lexer.get_value(); + + Matrix4d matrix; + parse_matrix(matrix); + + transformSequence.set_transform(time, Transformd(matrix)); + } + + void parse_matrix(Matrix4d& matrix) + { + if (!m_lexer.next_token_is("{")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + + for (std::size_t i = 0; i < 16; ++i) + matrix[i] = m_lexer.get_value(); + + if (!m_lexer.next_token_is("}")) + throw_error(std::string("Unexpected token ") + m_lexer.next_token().c_str()); + } + + template + auto_release_ptr parse_entity() + { + // TODO: implement me... + } + + void validate_identifier(const StringView& id) + { + if (id.empty()) + throw_error("Premature end of file"); + + // TODO: implement me... + } + + void throw_error(const std::string& message) + { + throw ParseException( + message, + m_lexer.line(), + m_lexer.col()); + } +}; + +auto_release_ptr load_project( + const char* project_filepath, + const int options, + EventCounters& event_counters, + const SearchPaths* /*search_paths*/) +{ + FILE* file = fopen(project_filepath, "r"); + + if (!file) + { + RENDERER_LOG_ERROR("Could not open project file %s", project_filepath); + return auto_release_ptr(); + } + + try + { + Lexer lexer(file); + Parser parser(project_filepath, lexer, options); + return parser.parse(); + } + catch (const ParseException& e) + { + RENDERER_LOG_ERROR("%s", e.what()); + event_counters.signal_error(); + } + catch(const std::exception& e) + { + RENDERER_LOG_ERROR("%s", e.what()); + event_counters.signal_error(); + } + catch(...) + { + RENDERER_LOG_ERROR("Unknown error while reading project."); + event_counters.signal_error(); + } + + return auto_release_ptr(); +} + + +} + +// +// AppleseedProjectFileReader class implementation. +// + +auto_release_ptr AppleseedProjectFileReader::read( + const char* project_filepath, + const int options, + EventCounters& event_counters) +{ + return load_project( + project_filepath, + options, + event_counters, + nullptr); +} + +auto_release_ptr AppleseedProjectFileReader::read_archive( + const char* archive_filepath, + const SearchPaths& search_paths, + const int options, + EventCounters& event_counters) +{ + return load_project( + archive_filepath, + options, + event_counters, + &search_paths); +} + +} // namespace renderer diff --git a/src/appleseed/renderer/modeling/project/appleseedprojectfilereader.h b/src/appleseed/renderer/modeling/project/appleseedprojectfilereader.h new file mode 100644 index 0000000000..a9b6ca0702 --- /dev/null +++ b/src/appleseed/renderer/modeling/project/appleseedprojectfilereader.h @@ -0,0 +1,61 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Esteban Tovagliari, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// appleseed.foundation headers. +#include "foundation/memory/autoreleaseptr.h" + +// Forward declarations. +namespace foundation { class SearchPaths; } +namespace renderer { class EventCounters; } +namespace renderer { class Project; } + +namespace renderer +{ + +// +// Appleseed project file reader. +// + +class AppleseedProjectFileReader +{ + public: + static foundation::auto_release_ptr read( + const char* project_filepath, + const int options, + EventCounters& event_counters); + + static foundation::auto_release_ptr read_archive( + const char* archive_filepath, + const foundation::SearchPaths& search_paths, + const int options, + EventCounters& event_counters); +}; + +} // namespace renderer diff --git a/src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.cpp b/src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.cpp new file mode 100644 index 0000000000..110a4eb3e8 --- /dev/null +++ b/src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.cpp @@ -0,0 +1,1181 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Esteban Tovagliari, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "appleseedprojectfilewriter.h" + +// appleseed.renderer headers. +#include "renderer/modeling/aov/aov.h" +#include "renderer/modeling/bsdf/bsdf.h" +#include "renderer/modeling/bssrdf/bssrdf.h" +#include "renderer/modeling/camera/camera.h" +#include "renderer/modeling/color/colorentity.h" +#include "renderer/modeling/display/display.h" +#include "renderer/modeling/edf/edf.h" +#include "renderer/modeling/environment/environment.h" +#include "renderer/modeling/environmentedf/environmentedf.h" +#include "renderer/modeling/environmentshader/environmentshader.h" +#include "renderer/modeling/frame/frame.h" +#include "renderer/modeling/light/light.h" +#include "renderer/modeling/material/material.h" +#include "renderer/modeling/object/curveobject.h" +#include "renderer/modeling/object/curveobjectwriter.h" +#include "renderer/modeling/object/meshobject.h" +#include "renderer/modeling/object/meshobjectwriter.h" +#include "renderer/modeling/object/object.h" +#include "renderer/modeling/postprocessingstage/postprocessingstage.h" +#include "renderer/modeling/project/assethandler.h" +#include "renderer/modeling/project/configuration.h" +#include "renderer/modeling/project/configurationcontainer.h" +#include "renderer/modeling/project/project.h" +#include "renderer/modeling/project/projectfilewriter.h" +#include "renderer/modeling/scene/assembly.h" +#include "renderer/modeling/scene/assemblyinstance.h" +#include "renderer/modeling/scene/containers.h" +#include "renderer/modeling/scene/objectinstance.h" +#include "renderer/modeling/scene/proceduralassembly.h" +#include "renderer/modeling/scene/scene.h" +#include "renderer/modeling/scene/textureinstance.h" +#include "renderer/modeling/shadergroup/shader.h" +#include "renderer/modeling/shadergroup/shaderconnection.h" +#include "renderer/modeling/shadergroup/shadergroup.h" +#include "renderer/modeling/shadergroup/shaderparam.h" +#include "renderer/modeling/surfaceshader/surfaceshader.h" +#include "renderer/modeling/texture/texture.h" +#include "renderer/modeling/volume/volume.h" +#include "renderer/utility/transformsequence.h" + +// appleseed.foundation headers. +#include "foundation/array/applyvisitor.h" +#include "foundation/array/array.h" +#include "foundation/array/arrayref.h" +#include "foundation/array/keyframedarray.h" +#include "foundation/containers/dictionary.h" +#include "foundation/core/appleseed.h" +#include "foundation/math/transform.h" +#include "foundation/platform/defaulttimers.h" +#include "foundation/string/string.h" +#include "foundation/utility/foreach.h" +#include "foundation/utility/indenter.h" +#include "foundation/utility/searchpaths.h" +#include "foundation/utility/stopwatch.h" +#include "foundation/utility/z85.h" + +// Boost headers. +#include "boost/filesystem.hpp" + +// Standard headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost; +using namespace foundation; +namespace bf = boost::filesystem; + +namespace renderer +{ + +// +// AppleseedProjectFileWriter class implementation. +// + +namespace +{ + // Floating-point formatting settings. + const char* MatrixFormat = "%.17f"; + const char* FloatFormat = "%.17f"; + const char* ColorValueFormat = "%.9f"; + + template + void write_vector( + FILE* file, + const Indenter& indenter, + const Vec& v, + const size_t size, + const size_t columns, + const char* fmt) + { + assert(columns > 0); + + for (size_t i = 0; i < size; ++i) + { + const size_t col = i % columns; + + if (col == 0) + fputs(indenter.c_str(), file); + else fputc(' ', file); + + fprintf(file, fmt, v[i]); + + if (col == columns - 1 || i == size - 1) + fputc('\n', file); + } + } + + class ScopedBlock + { + public: + ScopedBlock(FILE* file, Indenter& indenter) + : m_file(file) + , m_indenter(indenter) + { + fprintf(m_file, "%s{\n", m_indenter.c_str()); + ++m_indenter; + } + + ~ScopedBlock() + { + --m_indenter; + fprintf(m_file, "%s}\n", m_indenter.c_str()); + } + + private: + FILE* m_file; + Indenter& m_indenter; + }; + + class WriteArrayVisitor + { + public: + template< bool B, class T = void > + using EnableIf = typename std::enable_if::type; + }; + + class WriteAsciiArrayVisitor : public WriteArrayVisitor + { + public: + WriteAsciiArrayVisitor(FILE* file, const Indenter& indenter) + : m_file(file) + , m_indenter(indenter) + { + } + + template ::value, int> = 0> + void operator()(const ArrayView& view) + { + write_integer_array( + reinterpret_cast(view.begin()), + view.size()); + } + + void operator()(const ArrayView& view) + { + write_integer_array( + reinterpret_cast(view.begin()), + view.size() * 2); + } + + void operator()(const ArrayView& view) + { + write_float_array( + reinterpret_cast(view.begin()), + view.size()); + } + + template + void operator()(const ArrayView>& view) + { + write_float_array( + reinterpret_cast(view.begin()), + view.size() * view.item_size() / sizeof(float)); + } + + void operator()(const ArrayView& view) + { + write_float_array( + reinterpret_cast(view.begin()), + view.size() * view.item_size() / sizeof(float)); + } + + private: + FILE* m_file; + const Indenter& m_indenter; + + void write_float_array(const float* p, const size_t size) + { + write_vector(m_file, m_indenter, p, size, 8, FloatFormat); + } + + template + void write_integer_array(const T* p, const size_t size) + { + write_vector(m_file, m_indenter, p, size, 8, "%d"); + } + }; + + class WriteZ85EncodedArrayVisitor : public WriteArrayVisitor + { + public: + WriteZ85EncodedArrayVisitor(FILE* file, const Indenter& indenter) + : m_file(file) + , m_indenter(indenter) + { + } + + template + void operator()(const ArrayView& view) + { + write_z85_encoded_array( + reinterpret_cast(view.begin()), + view.size() * view.item_size()); + } + + private: + FILE* m_file; + const Indenter& m_indenter; + + void write_z85_encoded_array( + const unsigned char* ptr, + const std::size_t size_in_bytes) + { + assert(size_in_bytes % 4 == 0); + std::vector buffer(z85_encoded_size(size_in_bytes) + 1); + z85_encode(ptr, size_in_bytes, buffer.data()); + buffer[buffer.size() - 1] = 0; + fprintf(m_file, "%s%s\n", m_indenter.c_str(), buffer.data()); + } + }; + + class Writer + { + public: + // Constructor. + Writer( + const char* filepath, + FILE* file, + const int options) + : m_project_new_root_dir(filesystem::path(filepath).parent_path()) + , m_file(file) + , m_options(options) + , m_indenter(4) + { + } + + void write_project(const Project& project) + { + fprintf(m_file, "project %s\n", project.get_name()); + const auto block = begin_block(); + + fprintf( + m_file, + "%sformat_revision " FMT_SIZE_T "\n\n", + m_indenter.c_str(), + project.get_format_revision()); + + write_search_paths(project); + + if (project.get_display()) + write(*project.get_display()); + + if (project.get_scene()) + write_scene(*project.get_scene()); + + if (project.get_frame()) + write_frame(*project.get_frame()); + + write_configurations(project); + } + + private: + const filesystem::path m_project_new_root_dir; + FILE* m_file; + const int m_options; + Indenter m_indenter; + + // Return a lexicographically-sorted vector of references to entities. + template + std::vector> sorted(const Collection& collection) + { + return sorted_impl(collection.begin(), collection.end()); + } + + template + std::vector> sorted(Collection& collection) + { + return sorted_impl(collection.begin(), collection.end()); + } + + template + std::vector> sorted_impl(Iterator input_begin, Iterator input_end) + { + std::vector> result; + result.reserve(std::distance(input_begin, input_end)); + + for (Iterator it = input_begin; it != input_end; ++it) + result.emplace_back(*it); + + sort(result.begin(), result.end(), [](const Entity& lhs, const Entity& rhs) + { + return strcmp(lhs.get_name(), rhs.get_name()) < 0; + }); + + return result; + } + + ScopedBlock begin_block() + { + return ScopedBlock(m_file, m_indenter); + } + + void do_write_array(const Array& array) + { + if (m_options & ProjectFileWriter::AsciiArrays) + apply_visitor(array, WriteAsciiArrayVisitor(m_file, m_indenter)); + else + apply_visitor(array, WriteZ85EncodedArrayVisitor(m_file, m_indenter)); + } + + void write_array(const char* name, const Array& array) + { + const char* encoding = + m_options & ProjectFileWriter::AsciiArrays + ? "ascii" + : "z85"; + + fprintf( + m_file, + "%sarray %s %s 1 " FMT_SIZE_T " %s\n", + m_indenter.c_str(), + name, + array_type_to_string(array.type()), + array.size(), + encoding); + const auto block = begin_block(); + do_write_array(array); + } + + void write_keyframed_array(const char* name, const KeyFramedArray& array) + { + const char* encoding = + m_options & ProjectFileWriter::AsciiArrays + ? "ascii" + : "z85"; + + fprintf( + m_file, + "%sarray %s %s " FMT_SIZE_T " " FMT_SIZE_T " %s\n", + m_indenter.c_str(), + name, + array_type_to_string(array.type()), + array.get_key_count(), + array.get_key(0).size(), + encoding); + const auto block = begin_block(); + + for (size_t i = 0, e = array.get_key_count(); i < e; ++i) + do_write_array(array.get_key(i)); + } + + void write_dictionary(const Dictionary& dictionary) + { + for (const auto& item : dictionary.strings()) + { + fprintf( + m_file, + "%s%s %s\n", + m_indenter.c_str(), + item.key(), + item.value()); + } + + for (const auto& item : dictionary.dictionaries()) + { + fprintf(m_file, "%s%s\n", m_indenter.c_str(), item.key()); + const auto block = begin_block(); + write_dictionary(item.value()); + } + } + + void write_params(const Dictionary& params) + { + if (!params.empty()) + { + fprintf(m_file, "%sparams\n", m_indenter.c_str()); + const auto block = begin_block(); + + for (const auto& item : params.strings()) + { + fprintf( + m_file, + "%s%s %s\n", + m_indenter.c_str(), + item.key(), + item.value()); + } + + for (const auto& item : params.dictionaries()) + { + fprintf(m_file, "%s%s\n", m_indenter.c_str(), item.key()); + const auto block = begin_block(); + write_dictionary(item.value()); + } + } + } + + void write_matrix(const Matrix4d& matrix, const float* time = nullptr) + { + if (time) + fprintf(m_file, "%smatrix %f\n", m_indenter.c_str(), *time); + else + fprintf(m_file, "%smatrix\n", m_indenter.c_str()); + + const auto block = begin_block(); + write_vector( + m_file, + m_indenter, + matrix, + 16, + 4, + MatrixFormat); + } + + template + void write_transform(const Transform& transform) + { + if (transform.get_local_to_parent() == Matrix::identity()) + return; + + fprintf(m_file, "%stransform\n", m_indenter.c_str()); + const auto block = begin_block(); + write_matrix(transform.get_local_to_parent(), nullptr); + } + + void write_transform_sequence(const TransformSequence& transform_sequence) + { + if (transform_sequence.empty()) + return; + + if (transform_sequence.size() == 1) + { + float time; + Transformd transform; + transform_sequence.get_transform(0, time, transform); + + if (transform.get_local_to_parent() == Matrix4d::identity()) + return; + } + + fprintf(m_file, "%stransform_sequence\n", m_indenter.c_str()); + const auto block = begin_block(); + + for (size_t i = 0, e = transform_sequence.size(); i < e; ++i) + { + float time; + Transformd transform; + transform_sequence.get_transform(i, time, transform); + write_matrix(transform.get_local_to_parent(), &time); + } + } + + void write_value_array(const char* element_name, const ColorValueArray& values) + { + fprintf(m_file, "%s%s\n", m_indenter.c_str(), element_name); + const auto block = begin_block(); + write_vector( + m_file, + m_indenter, + values, + values.size(), + 8, + ColorValueFormat); + } + + template + void write_entity(const char* element_name, const Entity& entity) + { + fprintf( + m_file, + "%s%s %s %s\n", + m_indenter.c_str(), + element_name, + entity.get_model(), + entity.get_name()); + + const auto block = begin_block(); + write_params(entity.get_parameters()); + } + + template + void write_collection(const Collection& collection) + { + if (!collection.empty()) + { + for (const auto& entity : sorted(collection)) + write(entity); + } + } + + void write(const AOV& aov) + { + fprintf(m_file, "%saov %s\n", m_indenter.c_str(), aov.get_name()); + const auto block = begin_block(); + write_params(aov.get_parameters()); + } + + void write(const Assembly& assembly) + { + fprintf( + m_file, + "%sassembly %s %s\n", + m_indenter.c_str(), + assembly.get_model(), + assembly.get_name()); + + const auto block = begin_block(); + write_params(assembly.get_parameters()); + + // Don't write the content of the assembly if it was + // generated procedurally. + if (dynamic_cast(&assembly) == nullptr) + { + write_collection(assembly.colors()); + write_collection(assembly.textures()); + write_collection(assembly.texture_instances()); + write_collection(assembly.bsdfs()); + write_collection(assembly.bssrdfs()); + write_collection(assembly.edfs()); + write_collection(assembly.shader_groups()); + write_collection(assembly.surface_shaders()); + write_collection(assembly.materials()); + write_collection(assembly.lights()); + write_collection(assembly.objects()); + write_collection(assembly.object_instances()); + write_collection(assembly.volumes()); + write_collection(assembly.assemblies()); + write_collection(assembly.assembly_instances()); + } + } + + void write(const AssemblyInstance& assembly_instance) + { + fprintf( + m_file, + "%sassembly_instance %s\n", + m_indenter.c_str(), + assembly_instance.get_name()); + + const auto block = begin_block(); + fprintf( + m_file, + "%sassembly %s\n", + m_indenter.c_str(), + assembly_instance.get_assembly_name()); + + write_params(assembly_instance.get_parameters()); + write_transform_sequence(assembly_instance.transform_sequence()); + } + + void write_assign_material( + const std::string& slot, + const std::string& side, + const std::string& name) + { + fprintf( + m_file, + "%sassign_material %s %s %s\n", + m_indenter.c_str(), + slot.c_str(), + side.c_str(), + name.c_str()); + } + + void write_assign_materials( + const ObjectInstance::Side side, + const StringDictionary& material_mappings) + { + const std::string side_string = side == ObjectInstance::FrontSide ? "front" : "back"; + + for (const_each i = material_mappings; i; ++i) + write_assign_material(i->key(), side_string, i->value()); + } + + void write(const BSDF& bsdf) + { + write_entity("bsdf", bsdf); + } + + void write(const BSSRDF& bssrdf) + { + write_entity("bssrdf", bssrdf); + } + + void write(const Camera& camera) + { + fprintf( + m_file, + "%scamera %s %s\n", + m_indenter.c_str(), + camera.get_model(), + camera.get_name()); + + const auto block = begin_block(); + write_params(camera.get_parameters()); + + write_transform_sequence(camera.transform_sequence()); + } + + void write(const ColorEntity& color_entity) + { + fprintf( + m_file, + "%scolor %s\n", + m_indenter.c_str(), + color_entity.get_name()); + + const auto block = begin_block(); + write_params(color_entity.get_parameters()); + write_value_array("values", color_entity.get_values()); + write_value_array("alpha", color_entity.get_alpha()); + } + + void write_configuration(const Configuration& configuration) + { + fprintf( + m_file, + "%sconfiguration %s\n", + m_indenter.c_str(), + configuration.get_name()); + + const auto block = begin_block(); + + if (configuration.get_base()) + { + fprintf( + m_file, + "%sbase %s\n", + m_indenter.c_str(), + configuration.get_base()->get_name()); + } + + write_params(configuration.get_parameters()); + } + + void write_configurations(const Project& project) + { + fprintf(m_file, "%sconfigurations\n", m_indenter.c_str()); + const auto block = begin_block(); + + // Write configurations. + for (const Configuration& configuration : sorted(project.configurations())) + { + if (!BaseConfigurationFactory::is_base_configuration(configuration.get_name())) + write_configuration(configuration); + } + } + + void write(const Display& display) + { + fprintf(m_file, "%sdisplay %s\n", m_indenter.c_str(), display.get_name()); + const auto block = begin_block(); + write_params(display.get_parameters()); + } + + void write(const EDF& edf) + { + write_entity("edf", edf); + } + + void write(const Environment& environment) + { + write_entity("environment", environment); + } + + void write(const EnvironmentEDF& env_edf) + { + fprintf( + m_file, + "%senvironment_edf %s %s\n", + m_indenter.c_str(), + env_edf.get_model(), + env_edf.get_name()); + + const auto block = begin_block(); + write_params(env_edf.get_parameters()); + write_transform_sequence(env_edf.transform_sequence()); + } + + void write(const EnvironmentShader& env_shader) + { + write_entity("environment_shader", env_shader); + } + + void write_frame(const Frame& frame) + { + fprintf(m_file, "%sframe %s\n", m_indenter.c_str(), frame.get_name()); + const auto block = begin_block(); + write_params(frame.get_parameters()); + write_collection(frame.aovs()); + write_collection(frame.post_processing_stages()); + } + + void write(const Light& light) + { + fprintf( + m_file, + "%slight %s %s\n", + m_indenter.c_str(), + light.get_model(), + light.get_name()); + + const auto block = begin_block(); + write_params(light.get_parameters()); + write_transform(light.get_transform()); + } + + void write(const Material& material) + { + write_entity("material", material); + } + + void write(const Volume& material) + { + write_entity("volume", material); + } + + void write_mesh_geometry(const MeshObject& object) + { + const auto num_keys = object.get_motion_segment_count() + 1; + + // Write points. + { + KeyFramedArray array( + ArrayType::Vector3fType, + object.get_vertex_count(), + num_keys); + + ArrayRef points(array.get_key(0)); + for (size_t i = 0, ie = object.get_vertex_count(); i < ie; ++i) + points[i] = object.get_vertex(i); + + for (size_t k = 1; k < num_keys; ++k) + { + ArrayRef points(array.get_key(k)); + for (size_t i = 0, ie = object.get_vertex_count(); i < ie; ++i) + points[i] = object.get_vertex_pose(i, k - 1); + } + + write_keyframed_array("P", array); + } + + // Write UVs + if (object.get_tex_coords_count() != 0) + { + Array array(ArrayType::Vector2fType, object.get_tex_coords_count()); + ArrayRef uvs(array); + + for (size_t i = 0, e = object.get_tex_coords_count(); i < e; ++i) + uvs[i] = object.get_tex_coords(i); + + write_array("uv", array); + } + + // Write normals. + if (object.get_vertex_normal_count() != 0) + { + KeyFramedArray array( + ArrayType::Vector3fType, + object.get_vertex_normal_count(), + num_keys); + + ArrayRef N(array.get_key(0)); + for (size_t i = 0, ie = object.get_vertex_normal_count(); i < ie; ++i) + N[i] = object.get_vertex_normal(i); + + for (size_t k = 1; k < num_keys; ++k) + { + ArrayRef N(array.get_key(k)); + for (size_t i = 0, ie = object.get_vertex_normal_count(); i < ie; ++i) + N[i] = object.get_vertex_normal_pose(i, k - 1); + } + + write_keyframed_array("N", array); + } + + // Write tangents. + if (object.get_vertex_tangent_count() != 0) + { + KeyFramedArray array( + ArrayType::Vector3fType, + object.get_vertex_tangent_count(), + num_keys); + + ArrayRef T(array.get_key(0)); + for (size_t i = 0, ie = object.get_vertex_tangent_count(); i < ie; ++i) + T[i] = object.get_vertex_tangent(i); + + for (size_t k = 1; k < num_keys; ++k) + { + ArrayRef N(array.get_key(k)); + for (size_t i = 0, ie = object.get_vertex_tangent_count(); i < ie; ++i) + T[i] = object.get_vertex_tangent_pose(i, k - 1); + } + + write_keyframed_array("T", array); + } + + // Write indices. + const size_t num_triangles = object.get_triangle_count(); + + // Vertices per face. + { + Array array(ArrayType::UInt32Type); + ArrayRef nverts(array); + nverts.fill(num_triangles, 3); + write_array("nverts", array); + } + + // Vertex indices. + { + Array array(ArrayType::UInt32Type, num_triangles * 3); + ArrayRef vindx(array); + + for (size_t i = 0; i < num_triangles; ++i) + { + const auto& triangle = object.get_triangle(i); + vindx[3 * i + 0] = triangle.m_v0; + vindx[3 * i + 1] = triangle.m_v1; + vindx[3 * i + 2] = triangle.m_v2; + } + + write_array("vindx", array); + } + + // UV indices. + if (object.get_tex_coords_count() != 0) + { + Array array(ArrayType::UInt32Type, num_triangles * 3); + ArrayRef uvindx(array); + + for (size_t i = 0; i < num_triangles; ++i) + { + const auto& triangle = object.get_triangle(i); + uvindx[3 * i + 0] = triangle.m_a0; + uvindx[3 * i + 1] = triangle.m_a1; + uvindx[3 * i + 2] = triangle.m_a2; + } + + write_array("uvindx", array); + } + + // Normal indices. + if (object.get_vertex_normal_count() != 0) + { + Array array(ArrayType::UInt32Type, num_triangles * 3); + ArrayRef nindx(array); + + for (size_t i = 0; i < num_triangles; ++i) + { + const auto& triangle = object.get_triangle(i); + nindx[3 * i + 0] = triangle.m_n0; + nindx[3 * i + 1] = triangle.m_n1; + nindx[3 * i + 2] = triangle.m_n2; + } + + write_array("nindx", array); + } + + // Tangent indices. + if (object.get_vertex_tangent_count() != 0) + { + Array array(ArrayType::UInt32Type, num_triangles * 3); + ArrayRef tindx(array); + + for (size_t i = 0; i < num_triangles; ++i) + { + const auto& triangle = object.get_triangle(i); + tindx[3 * i + 0] = triangle.m_v0; + tindx[3 * i + 1] = triangle.m_v1; + tindx[3 * i + 2] = triangle.m_v2; + } + + write_array("tindx", array); + } + + // Write material indices. + if (object.get_material_slot_count() > 1) + { + Array array(ArrayType::UInt32Type, num_triangles); + ArrayRef mindx(array); + + for (size_t i = 0; i < num_triangles; ++i) + mindx[i] = object.get_triangle(i).m_pa; + + write_array("mindx", array); + } + } + + void write_curve_geometry(const CurveObject& /*object*/) + { + // TODO: implement me... + } + + void write(const Object& object) + { + fprintf( + m_file, + "%sobject %s %s\n", + m_indenter.c_str(), + object.get_model(), + object.get_name()); + + const auto block = begin_block(); + write_params(object.get_parameters()); + + if (strcmp(object.get_model(), MeshObjectFactory().get_model()) == 0) + write_mesh_geometry(static_cast(object)); + else if (strcmp(object.get_model(), CurveObjectFactory().get_model()) == 0) + write_curve_geometry(static_cast(object)); + + // Write material slots. + for (size_t i = 0, e = object.get_material_slot_count(); i < e; ++i) + { + fprintf( + m_file, + "%smaterial_slot %s\n", + m_indenter.c_str(), + object.get_material_slot(i)); + } + } + + void write(const ObjectInstance& object_instance) + { + fprintf( + m_file, + "%sobject_instance %s\n", + m_indenter.c_str(), + object_instance.get_name()); + + const auto block = begin_block(); + fprintf( + m_file, + "%sobject %s\n", + m_indenter.c_str(), + object_instance.get_object_name()); + + write_params(object_instance.get_parameters()); + write_transform(object_instance.get_transform()); + + write_assign_materials(ObjectInstance::FrontSide, object_instance.get_front_material_mappings()); + write_assign_materials(ObjectInstance::BackSide, object_instance.get_back_material_mappings()); + } + + void write(const PostProcessingStage& stage) + { + write_entity("post_processing_stage", stage); + } + + void write_scene(const Scene& scene) + { + fprintf(m_file, "%sscene\n", m_indenter.c_str()); + const auto block = begin_block(); + + write_params(scene.get_parameters()); + + write_collection(scene.cameras()); + write_collection(scene.colors()); + write_collection(scene.textures()); + write_collection(scene.texture_instances()); + write_collection(scene.environment_edfs()); + write_collection(scene.environment_shaders()); + + if (scene.get_environment()) + write(*scene.get_environment()); + + write_collection(scene.shader_groups()); + write_collection(scene.assemblies()); + write_collection(scene.assembly_instances()); + } + + void write_search_paths(const Project& project) + { + const SearchPaths& search_paths = project.search_paths(); + + if (search_paths.get_explicit_path_count() > 0) + { + fprintf(m_file, "%ssearch_paths\n", m_indenter.c_str()); + const auto block = begin_block(); + + for (size_t i = 0; i < search_paths.get_explicit_path_count(); ++i) + { + fprintf( + m_file, + "%s%s\n", + m_indenter.c_str(), + search_paths.get_explicit_path(i)); + } + } + } + + void write(const ShaderParam& param) + { + fprintf( + m_file, + "%s%s %s\n", + m_indenter.c_str(), + param.get_name(), + param.get_value_as_string().c_str()); + } + + void write_osl_code(const char* /*code*/) + { + fprintf(m_file, "%scode\n", m_indenter.c_str()); + const auto block = begin_block(); + // TODO: implement me... + } + + void write(const Shader& shader) + { + fprintf( + m_file, + "%sshader %s %s %s\n", + m_indenter.c_str(), + shader.get_type(), + shader.get_name(), + shader.get_layer()); + + const auto block = begin_block(); + write_collection(shader.shader_params()); + + if (shader.get_source_code()) + write_osl_code(shader.get_source_code()); + } + + void write(const ShaderConnection& connection) + { + fprintf( + m_file, + "%s%s %s -> %s %s\n", + m_indenter.c_str(), + connection.get_src_layer(), + connection.get_src_param(), + connection.get_dst_layer(), + connection.get_dst_param()); + } + + void write(const ShaderGroup& shader_group) + { + fprintf( + m_file, + "%sshader_group %s\n", + m_indenter.c_str(), + shader_group.get_name()); + + const auto block = begin_block(); + + // Write shaders in the original order. + for (const Shader& shader : shader_group.shaders()) + write(shader); + + // Write shader connections in the original order. + if (!shader_group.shader_connections().empty()) + { + fprintf( + m_file, + "%sconnections\n", + m_indenter.c_str()); + + const auto block = begin_block(); + + for (const ShaderConnection& shader_connection : shader_group.shader_connections()) + write(shader_connection); + } + } + + void write(const SurfaceShader& surface_shader) + { + write_entity("surface_shader", surface_shader); + } + + void write(const Texture& texture) + { + write_entity("texture", texture); + } + + void write(const TextureInstance& texture_instance) + { + fprintf( + m_file, + "%stexture_instance %s\n", + m_indenter.c_str(), + texture_instance.get_name()); + + const auto block = begin_block(); + write_params(texture_instance.get_parameters()); + write_transform(texture_instance.get_transform()); + } + }; +} + +bool AppleseedProjectFileWriter::write_project_file( + Project& project, + const char* filepath, + const int options) +{ + Stopwatch stopwatch; + stopwatch.start(); + + RENDERER_LOG_INFO("writing project file %s...", filepath); + + // Open the file for writing. + FILE* file = fopen(filepath, "wt"); + if (file == nullptr) + { + RENDERER_LOG_ERROR("failed to write project file %s: i/o error.", filepath); + return false; + } + + // Write the file header. + fprintf(file, "# File generated by %s\n", Appleseed::get_synthetic_version_string()); + fprintf(file, "\n"); + + // Write the project. + Writer writer(filepath, file, options); + writer.write_project(project); + + // Close the file. + fclose(file); + + stopwatch.measure(); + + RENDERER_LOG_INFO( + "wrote project file %s in %s.", + filepath, + pretty_time(stopwatch.get_seconds()).c_str()); + + return true; +} +} // namespace renderer diff --git a/src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.h b/src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.h new file mode 100644 index 0000000000..54c2efa5f7 --- /dev/null +++ b/src/appleseed/renderer/modeling/project/appleseedprojectfilewriter.h @@ -0,0 +1,50 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Esteban Tovagliari, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// Forward declarations. +namespace renderer { class Project; } + +namespace renderer +{ + +// +// Appleseed project file writer. +// + +class AppleseedProjectFileWriter +{ + public: + static bool write_project_file( + Project& project, + const char* filepath, + const int options); +}; + +} // namespace renderer diff --git a/src/appleseed/renderer/modeling/project/projectfilereader.cpp b/src/appleseed/renderer/modeling/project/projectfilereader.cpp index 51dcf9823e..cf7769bce6 100644 --- a/src/appleseed/renderer/modeling/project/projectfilereader.cpp +++ b/src/appleseed/renderer/modeling/project/projectfilereader.cpp @@ -33,6 +33,7 @@ // appleseed.renderer headers. #include "renderer/modeling/project-builtin/cornellboxproject.h" #include "renderer/modeling/project-builtin/defaultproject.h" +#include "renderer/modeling/project/appleseedprojectfilereader.h" #include "renderer/modeling/project/configuration.h" #include "renderer/modeling/project/eventcounters.h" #include "renderer/modeling/project/project.h" @@ -46,10 +47,15 @@ #include "foundation/string/string.h" #include "foundation/utility/stopwatch.h" +// Boost headers. +#include "boost/filesystem/path.hpp" +#include "boost/filesystem/operations.hpp" + // Standard headers. #include using namespace foundation; +namespace bf = boost::filesystem; namespace renderer { @@ -91,12 +97,25 @@ auto_release_ptr ProjectFileReader::read( EventCounters event_counters; - auto_release_ptr project = - XMLProjectFileReader::read( - project_filepath, - schema_filepath, - options, - event_counters); + const auto ext = lower_case(bf::path(project_filepath).extension().string()); + + auto_release_ptr project; + + if (ext == ".seed") + { + project = AppleseedProjectFileReader::read( + project_filepath, + options, + event_counters); + } + else + { + project = XMLProjectFileReader::read( + project_filepath, + schema_filepath, + options, + event_counters); + } if (project.get()) postprocess_project(project.ref(), event_counters, options); diff --git a/src/appleseed/renderer/modeling/project/projectfilewriter.cpp b/src/appleseed/renderer/modeling/project/projectfilewriter.cpp index 3949462936..a16c698a67 100644 --- a/src/appleseed/renderer/modeling/project/projectfilewriter.cpp +++ b/src/appleseed/renderer/modeling/project/projectfilewriter.cpp @@ -31,6 +31,7 @@ #include "projectfilewriter.h" // appleseed.renderer headers. +#include "renderer/modeling/project/appleseedprojectfilewriter.h" #include "renderer/modeling/project/xmlprojectfilewriter.h" // appleseed.foundation headers. @@ -57,6 +58,12 @@ bool ProjectFileWriter::write( { const auto ext = lower_case(bf::path(filepath).extension().string()); + if (ext == ".aseed") + return AppleseedProjectFileWriter::write_project_file( + project, + filepath, + options); + if (ext == ".appleseedz") return XMLProjectFileWriter::write_packed_project_file( project, diff --git a/src/appleseed/renderer/modeling/project/projectfilewriter.h b/src/appleseed/renderer/modeling/project/projectfilewriter.h index a47c397d51..eb73a66a95 100644 --- a/src/appleseed/renderer/modeling/project/projectfilewriter.h +++ b/src/appleseed/renderer/modeling/project/projectfilewriter.h @@ -51,7 +51,8 @@ class APPLESEED_DLLSYMBOL ProjectFileWriter OmitHeaderComment = 1UL << 0, // do not write the header comment OmitWritingGeometryFiles = 1UL << 1, // do not write geometry files to disk OmitHandlingAssetFiles = 1UL << 2, // do not change paths to asset files (such as texture files) - CopyAllAssets = 1UL << 3 // copy all asset files (by default copy asset files with relative paths only) + CopyAllAssets = 1UL << 3, // copy all asset files (by default copy asset files with relative paths only) + AsciiArrays = 1UL << 4 // use ascii encoding for arrays }; // Write a project to disk.