1
2//
3// This source file is part of appleseed.
4// Visit http://appleseedhq.net/ for additional information and resources.
5//
6// This software is released under the MIT license.
7//
8// Copyright (c) 2012-2013 Esteban Tovagliari, Jupiter Jazz Limited
9// Copyright (c) 2014-2017 Esteban Tovagliari, The appleseedhq Organization
10//
11// Permission is hereby granted, free of charge, to any person obtaining a copy
12// of this software and associated documentation files (the "Software"), to deal
13// in the Software without restriction, including without limitation the rights
14// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15// copies of the Software, and to permit persons to whom the Software is
16// furnished to do so, subject to the following conditions:
17//
18// The above copyright notice and this permission notice shall be included in
19// all copies or substantial portions of the Software.
20//
21// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27// THE SOFTWARE.
28//
29
30// appleseed.python headers.
31#include "pyseed.h" // has to be first, to avoid redefinition warnings
32#include "bindentitycontainers.h"
33#include "dict2dict.h"
34
35// appleseed.renderer headers.
36#include "renderer/api/display.h"
37#include "renderer/api/frame.h"
38#include "renderer/api/project.h"
39#include "renderer/api/scene.h"
40
41// appleseed.foundation headers.
42#include "foundation/utility/searchpaths.h"
43
44// Standard headers.
45#include <cstddef>
46#include <string>
47
48namespace bpy = boost::python;
49using namespace foundation;
50using namespace renderer;
51using namespace std;
52
53// Work around a regression in Visual Studio 2015 Update 3.
54#if defined(_MSC_VER) && _MSC_VER == 1900
55namespace boost
56{
57 template <> Configuration const volatile* get_pointer<Configuration const volatile>(Configuration const volatile* p) { return p; }
58 template <> Project const volatile* get_pointer<Project const volatile>(Project const volatile* p) { return p; }
59}
60#endif
61
62namespace
63{
64 auto_release_ptr<Project> create_project(const string& name)
65 {
66 return ProjectFactory::create(name.c_str());
67 }
68
69 bpy::object create_default_project()
70 {
71 auto_release_ptr<Project> project(DefaultProjectFactory::create());
72 return bpy::object(project);
73 }
74
75 bpy::object create_cornell_box_project()
76 {
77 auto_release_ptr<Project> project(CornellBoxProjectFactory::create());
78 return bpy::object(project);
79 }
80
81 bpy::list project_get_search_paths(const Project* project)
82 {
83 bpy::list paths;
84
85 const SearchPaths& search_paths = project->search_paths();
86
87 for (size_t i = 0; i < search_paths.size(); ++i)
88 paths.append(search_paths[i]);
89
90 return paths;
91 }
92
93 void project_set_search_paths(Project* project, const bpy::list& paths)
94 {
95 project->search_paths().clear();
96
97 for (bpy::ssize_t i = 0, e = bpy::len(paths); i < e; ++i)
98 {
99 const bpy::extract<const char*> extractor(paths[i]);
100 if (extractor.check())
101 project->search_paths().push_back(extractor());
102 else
103 {
104 PyErr_SetString(PyExc_TypeError, "Incompatible type. Only strings accepted.");
105 bpy::throw_error_already_set();
106 }
107 }
108 }
109
110 ConfigurationContainer* project_get_configs(Project* project)
111 {
112 return &(project->configurations());
113 }
114
115 bool write_project_default_opts(
116 const ProjectFileWriter* writer,
117 const Project* project,
118 const char* filepath)
119 {
120 return ProjectFileWriter::write(*project, filepath);
121 }
122
123 bool write_project_with_opts(
124 const ProjectFileWriter* writer,
125 const Project* project,
126 const char* filepath,
127 int opts)
128 {
129 return ProjectFileWriter::write(*project, filepath, opts);
130 }
131
132 auto_release_ptr<Configuration> create_config(const string& name)
133 {
134 return ConfigurationFactory::create(name.c_str());
135 }
136
137 auto_release_ptr<Configuration> create_config_with_params(
138 const string& name,
139 const bpy::dict& params)
140 {
141 return ConfigurationFactory::create(name.c_str(), bpy_dict_to_param_array(params));
142 }
143
144 bpy::object create_base_final_config()
145 {
146 auto_release_ptr<Configuration> config(BaseConfigurationFactory::create_base_final());
147 return bpy::object(config);
148 }
149
150 bpy::object create_base_interactive_config()
151 {
152 auto_release_ptr<Configuration> config(BaseConfigurationFactory::create_base_interactive());
153 return bpy::object(config);
154 }
155
156 bpy::dict config_get_inherited_parameters(const Configuration* config)
157 {
158 ParamArray params(config->get_inherited_parameters());
159 return param_array_to_bpy_dict(params);
160 }
161
162 void config_insert_path(Configuration* config, const char* path, const bpy::object& value)
163 {
164 {
165 bpy::extract<const char*> extractor(value);
166 if (extractor.check())
167 {
168 config->get_parameters().insert_path(path, extractor());
169 return;
170 }
171 }
172
173 if (PyBool_Check(value.ptr()))
174 {
175 bpy::extract<bool> extractor(value);
176 if (extractor.check())
177 {
178 config->get_parameters().insert_path(path, extractor());
179 return;
180 }
181 }
182
183#if PY_MAJOR_VERSION == 2
184 if (PyInt_Check(value.ptr()))
185 {
186 bpy::extract<int> extractor(value);
187 if (extractor.check())
188 {
189 config->get_parameters().insert_path(path, extractor());
190 return;
191 }
192 }
193#endif
194
195 if (PyLong_Check(value.ptr()))
196 {
197 bpy::extract<int> extractor(value);
198 if (extractor.check())
199 {
200 config->get_parameters().insert_path(path, extractor());
201 return;
202 }
203 }
204
205 if (PyFloat_Check(value.ptr()))
206 {
207 bpy::extract<double> extractor(value);
208 if (extractor.check())
209 {
210 config->get_parameters().insert_path(path, extractor());
211 return;
212 }
213 }
214
215 PyErr_SetString(PyExc_TypeError, "Unsupported value type.");
216 bpy::throw_error_already_set();
217 }
218
219 void config_remove_path(Configuration* config, const char* path)
220 {
221 config->get_parameters().remove_path(path);
222 }
223
224 bpy::dict config_get_metadata()
225 {
226 return dictionary_to_bpy_dict(Configuration::get_metadata());
227 }
228
229 bpy::object project_file_reader_read_default_opts(
230 ProjectFileReader* reader,
231 const char* project_filename,
232 const char* schema_filename)
233 {
234 auto_release_ptr<Project> project(reader->read(project_filename, schema_filename));
235 return bpy::object(project);
236 }
237
238 bpy::object project_file_reader_read_with_opts(
239 ProjectFileReader* reader,
240 const char* project_filename,
241 const char* schema_filename,
242 const ProjectFileReader::Options opts)
243 {
244 auto_release_ptr<Project> project(reader->read(project_filename, schema_filename, opts));
245 return bpy::object(project);
246 }
247
248 bpy::object project_file_reader_load_builtin(ProjectFileReader* reader, const char* project_name)
249 {
250 auto_release_ptr<Project> project(reader->load_builtin(project_name));
251 return bpy::object(project);
252 }
253}
254
255void bind_project()
256{
257 bpy::class_<Configuration, auto_release_ptr<Configuration>, bpy::bases<Entity>, boost::noncopyable>("Configuration", bpy::no_init)
258 .def("create_base_final", create_base_final_config).staticmethod("create_base_final")
259 .def("create_base_interactive", create_base_interactive_config).staticmethod("create_base_interactive")
260
261 .def("__init__", bpy::make_constructor(create_config))
262 .def("__init__", bpy::make_constructor(create_config_with_params))
263
264 .def("set_base", &Configuration::set_base)
265 .def("get_base", &Configuration::get_base, bpy::return_value_policy<bpy::reference_existing_object>())
266 .def("get_inherited_parameters", config_get_inherited_parameters)
267
268 .def("insert_path", config_insert_path)
269 .def("remove_path", config_remove_path)
270
271 .def("get_metadata", config_get_metadata).staticmethod("get_metadata");
272
273 bind_typed_entity_map<Configuration>("ConfigurationContainer");
274
275 bpy::class_<Project, auto_release_ptr<Project>, bpy::bases<Entity>, boost::noncopyable>("Project", bpy::no_init)
276 .def("create_default", create_default_project).staticmethod("create_default")
277 .def("create_cornell_box", create_cornell_box_project).staticmethod("create_cornell_box")
278
279 .def("__init__", bpy::make_constructor(create_project))
280
281 .def("add_default_configurations", &Project::add_default_configurations)
282
283 .def("has_path", &Project::has_path)
284 .def("set_path", &Project::set_path)
285 .def("get_path", &Project::get_path)
286
287 .def("get_search_paths", project_get_search_paths)
288 .def("set_search_paths", project_set_search_paths)
289
290 .def("set_scene", &Project::set_scene)
291 .def("get_scene", &Project::get_scene, bpy::return_value_policy<bpy::reference_existing_object>())
292
293 .def("set_frame", &Project::set_frame)
294 .def("get_frame", &Project::get_frame, bpy::return_value_policy<bpy::reference_existing_object>())
295
296 .def("get_display", &Project::get_display, bpy::return_value_policy<bpy::reference_existing_object>())
297 .def("set_display", &Project::set_display)
298
299 .def("get_active_camera", &Project::get_uncached_active_camera, bpy::return_value_policy<bpy::reference_existing_object>())
300
301 .def("configurations", project_get_configs, bpy::return_value_policy<bpy::reference_existing_object>())
302
303 .def("create_aov_images", &Project::create_aov_images);
304
305 bpy::enum_<ProjectFileReader::Options>("ProjectFileReaderOptions")
306 .value("Defaults", ProjectFileReader::Defaults)
307 .value("OmitReadingMeshFiles", ProjectFileReader::OmitReadingMeshFiles)
308 .value("OmitProjectFileUpdate", ProjectFileReader::OmitProjectFileUpdate)
309 .value("OmitSearchPaths", ProjectFileReader::OmitSearchPaths)
310 .value("OmitProjectSchemaValidation", ProjectFileReader::OmitProjectSchemaValidation);
311
312 bpy::class_<ProjectFileReader>("ProjectFileReader")
313 .def("read", &project_file_reader_read_default_opts)
314 .def("read", &project_file_reader_read_with_opts)
315 .def("load_builtin", &project_file_reader_load_builtin);
316
317 bpy::enum_<ProjectFileWriter::Options>("ProjectFileWriterOptions")
318 .value("Defaults", ProjectFileWriter::Defaults)
319 .value("OmitHeaderComment", ProjectFileWriter::OmitHeaderComment)
320 .value("OmitWritingGeometryFiles", ProjectFileWriter::OmitWritingGeometryFiles)
321 .value("OmitHandlingAssetFiles", ProjectFileWriter::OmitHandlingAssetFiles)
322 .value("CopyAllAssets", ProjectFileWriter::CopyAllAssets);
323
324 bpy::class_<ProjectFileWriter>("ProjectFileWriter")
325 // These methods are static but for symmetry with ProjectFileReader we're exposing them as non-static.
326 .def("write", write_project_default_opts)
327 .def("write", write_project_with_opts);
328}
329