Blender V4.5
usd_hook.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "usd_hook.hh"
6
7#include "usd.hh"
8#include "usd_asset_utils.hh"
9#include "usd_hash_types.hh"
10#include "usd_reader_prim.hh"
11#include "usd_reader_stage.hh"
13
14#include "BLI_map.hh"
15#include "BLI_utildefines.h"
16#include "BLI_vector.hh"
17
18#include "BKE_report.hh"
19
20#include "DNA_material_types.h"
22
23#include "RNA_access.hh"
24#include "RNA_prototypes.hh"
25#include "RNA_types.hh"
26#include "bpy_rna.hh"
27
28#include <list>
29#include <memory>
30#include <string>
31
32#if PXR_VERSION >= 2411
33# include <pxr/external/boost/python/call_method.hpp>
34# include <pxr/external/boost/python/class.hpp>
35# include <pxr/external/boost/python/dict.hpp>
36# include <pxr/external/boost/python/import.hpp>
37# include <pxr/external/boost/python/list.hpp>
38# include <pxr/external/boost/python/ref.hpp>
39# include <pxr/external/boost/python/return_value_policy.hpp>
40# include <pxr/external/boost/python/to_python_converter.hpp>
41# include <pxr/external/boost/python/tuple.hpp>
42# define PYTHON_NS pxr::pxr_boost::python
43# define REF pxr::pxr_boost::python::ref
44
45using namespace pxr::pxr_boost;
46#else
47# include <boost/python/call_method.hpp>
48# include <boost/python/class.hpp>
49# include <boost/python/import.hpp>
50# include <boost/python/return_value_policy.hpp>
51# include <boost/python/to_python_converter.hpp>
52# include <boost/python/tuple.hpp>
53# define PYTHON_NS boost::python
54# define REF boost::ref
55
56using namespace boost;
57#endif
58
59namespace blender::io::usd {
60
61using USDHookList = std::list<std::unique_ptr<USDHook>>;
63
64/* USD hook type declarations */
66{
67 static USDHookList hooks{};
68 return hooks;
69}
70
71void USD_register_hook(std::unique_ptr<USDHook> hook)
72{
73 if (USD_find_hook_name(hook->idname)) {
74 /* The hook is already in the list. */
75 return;
76 }
77
78 /* Add hook type to the list. */
79 hook_list().push_back(std::move(hook));
80}
81
83{
84 hook_list().remove_if(
85 [hook](const std::unique_ptr<USDHook> &item) { return item.get() == hook; });
86}
87
88USDHook *USD_find_hook_name(const char idname[])
89{
90 /* sanity checks */
91 if (hook_list().empty() || (idname == nullptr) || (idname[0] == 0)) {
92 return nullptr;
93 }
94
95 USDHookList::iterator hook_iter = std::find_if(
96 hook_list().begin(), hook_list().end(), [idname](const std::unique_ptr<USDHook> &item) {
97 return STREQ(item->idname, idname);
98 });
99
100 return (hook_iter == hook_list().end()) ? nullptr : hook_iter->get();
101}
102
103/* Convert PointerRNA to a PyObject*. */
105
106 /* We pass the argument by value because we need
107 * to obtain a non-const pointer to it. */
108 static PyObject *convert(PointerRNA ptr)
109 {
111 }
112};
113
114/* Encapsulate arguments for scene export. */
116
118
119 USDSceneExportContext(pxr::UsdStageRefPtr in_stage, Depsgraph *depsgraph) : stage(in_stage)
120 {
121 depsgraph_ptr = RNA_pointer_create_discrete(nullptr, &RNA_Depsgraph, depsgraph);
122 }
123
124 pxr::UsdStageRefPtr get_stage() const
125 {
126 return stage;
127 }
128
130 {
131 return depsgraph_ptr;
132 }
133
134 pxr::UsdStageRefPtr stage;
136};
137
138/* Encapsulate arguments for scene import. */
141
142 USDSceneImportContext(pxr::UsdStageRefPtr in_stage, const ImportedPrimMap &in_prim_map)
143 : stage(in_stage), prim_map(in_prim_map)
144 {
145 }
146
147 void release()
148 {
149 delete prim_map_dict;
150 }
151
152 pxr::UsdStageRefPtr get_stage() const
153 {
154 return stage;
155 }
156
157 PYTHON_NS::dict get_prim_map()
158 {
159 if (!prim_map_dict) {
160 prim_map_dict = new PYTHON_NS::dict;
161
162 prim_map.foreach_item([&](const pxr::SdfPath &path, const Vector<PointerRNA> &ids) {
163 if (!prim_map_dict->has_key(path)) {
164 (*prim_map_dict)[path] = PYTHON_NS::list();
165 }
166
167 PYTHON_NS::list list = PYTHON_NS::extract<PYTHON_NS::list>((*prim_map_dict)[path]);
168 for (const auto &ptr_rna : ids) {
169 list.append(ptr_rna);
170 }
171 });
172 }
173
174 return *prim_map_dict;
175 }
176
177 pxr::UsdStageRefPtr stage;
179 PYTHON_NS::dict *prim_map_dict = nullptr;
180};
181
182/* Encapsulate arguments for material export. */
185
186 USDMaterialExportContext(pxr::UsdStageRefPtr in_stage,
187 const USDExportParams &in_params,
188 ReportList *in_reports)
189 : stage(in_stage), params(in_params), reports(in_reports)
190 {
191 }
192
193 pxr::UsdStageRefPtr get_stage() const
194 {
195 return stage;
196 }
197
203 std::string export_texture(PYTHON_NS::object obj) const
204 {
205 ID *id;
206 if (!pyrna_id_FromPyObject(obj.ptr(), &id)) {
207 return "";
208 }
209
210 if (!id) {
211 return "";
212 }
213
214 if (GS(id->name) != ID_IM) {
215 return "";
216 }
217
218 Image *ima = reinterpret_cast<Image *>(id);
219
220 std::string asset_path = get_tex_image_asset_filepath(ima, stage, params);
221
222 if (params.export_textures) {
223 blender::io::usd::export_texture(ima, stage, params.overwrite_textures, reports);
224 }
225
226 return asset_path;
227 }
228
229 pxr::UsdStageRefPtr stage;
232};
233
234/* Encapsulate arguments for material import. */
237
238 USDMaterialImportContext(pxr::UsdStageRefPtr in_stage,
239 const USDImportParams &in_params,
240 ReportList *in_reports)
241 : stage(in_stage), params(in_params), reports(in_reports)
242 {
243 }
244
245 pxr::UsdStageRefPtr get_stage() const
246 {
247 return stage;
248 }
249
258 PYTHON_NS::tuple import_texture(const std::string &asset_path) const
259 {
260 if (!should_import_asset(asset_path)) {
261 /* This path does not need to be imported, so return it unchanged. */
262 return PYTHON_NS::make_tuple(asset_path, false);
263 }
264
265 const char *textures_dir = params.import_textures_mode == USD_TEX_IMPORT_PACK ?
267 params.import_textures_dir;
268
269 const eUSDTexNameCollisionMode name_collision_mode = params.import_textures_mode ==
272 params.tex_name_collision_mode;
273
274 std::string import_path = import_asset(
275 asset_path.c_str(), textures_dir, name_collision_mode, reports);
276
277 if (import_path == asset_path) {
278 /* Path is unchanged. */
279 return PYTHON_NS::make_tuple(asset_path, false);
280 }
281
282 const bool is_temporary = params.import_textures_mode == USD_TEX_IMPORT_PACK;
283 return PYTHON_NS::make_tuple(import_path, is_temporary);
284 }
285
286 pxr::UsdStageRefPtr stage;
289};
290
292{
293 static bool registered = false;
294
295 /* No need to register if there are no hooks. */
296 if (hook_list().empty()) {
297 return;
298 }
299
300 if (registered) {
301 return;
302 }
303
304 registered = true;
305
306 PyGILState_STATE gilstate = PyGILState_Ensure();
307
308 /* We must import these modules for the USD type converters to work. */
309 python::import("pxr.Usd");
310 python::import("pxr.UsdShade");
311
312 /* Register converter from PoinerRNA to a PyObject*. */
313 python::to_python_converter<PointerRNA, PointerRNAToPython>();
314
315 /* Register context class converters. */
316 python::class_<USDSceneExportContext>("USDSceneExportContext")
317 .def("get_stage", &USDSceneExportContext::get_stage)
318 .def("get_depsgraph",
320 python::return_value_policy<python::return_by_value>());
321
322 python::class_<USDMaterialExportContext>("USDMaterialExportContext")
323 .def("get_stage", &USDMaterialExportContext::get_stage)
324 .def("export_texture", &USDMaterialExportContext::export_texture);
325
326 python::class_<USDSceneImportContext>("USDSceneImportContext")
327 .def("get_stage", &USDSceneImportContext::get_stage)
328 .def("get_prim_map", &USDSceneImportContext::get_prim_map);
329
330 python::class_<USDMaterialImportContext>("USDMaterialImportContext")
331 .def("get_stage", &USDMaterialImportContext::get_stage)
332 .def("import_texture", &USDMaterialImportContext::import_texture);
333
334 PyGILState_Release(gilstate);
335}
336
337/* Retrieve and report the current Python error. */
339{
340 if (!PyErr_Occurred()) {
341 return;
342 }
343
344 PyErr_Print();
345
347 RPT_ERROR,
348 "An exception occurred invoking USD hook '%s'. Please see the console for details",
349 hook->name);
350}
351
352/* Abstract base class to facilitate calling a function with a given
353 * signature defined by the registered USDHook classes. Subclasses
354 * override virtual methods to specify the hook function name and to
355 * call the hook with the required arguments.
356 */
358 public:
360
361 /* Attempt to call the function, if defined by the registered hooks. */
362 void call()
363 {
364 if (hook_list().empty()) {
365 return;
366 }
367
368 PyGILState_STATE gilstate = PyGILState_Ensure();
369 init_in_gil();
370
371 /* Iterate over the hooks and invoke the hook function, if it's defined. */
372 USDHookList::const_iterator hook_iter = hook_list().begin();
373 while (hook_iter != hook_list().end()) {
374
375 /* XXX: Not sure if this is necessary:
376 * Advance the iterator before invoking the callback, to guard
377 * against the unlikely error where the hook is de-registered in
378 * the callback. This would prevent a crash due to the iterator
379 * getting invalidated. */
380 USDHook *hook = hook_iter->get();
381 ++hook_iter;
382
383 if (!hook->rna_ext.data) {
384 continue;
385 }
386
387 try {
388 PyObject *hook_obj = static_cast<PyObject *>(hook->rna_ext.data);
389
390 if (!PyObject_HasAttrString(hook_obj, function_name())) {
391 continue;
392 }
393
394 call_hook(hook_obj);
395 }
396 catch (python::error_already_set const &) {
398 }
399 catch (...) {
401 reports_, RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name);
402 }
403 }
404
406 PyGILState_Release(gilstate);
407 }
408
409 protected:
410 /* Override to specify the name of the function to be called. */
411 virtual const char *function_name() const = 0;
412 /* Override to call the function of the given object with the
413 * required arguments, e.g.,
414 *
415 * python::call_method<void>(hook_obj, function_name(), arg1, arg2); */
416 virtual void call_hook(PyObject *hook_obj) = 0;
417
418 virtual void init_in_gil(){};
419 virtual void release_in_gil(){};
420
421 /* Reports list provided when constructing the subclass, used by #call() to store reports. */
423};
424
426 private:
427 USDSceneExportContext hook_context_;
428
429 public:
430 OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
431 : USDHookInvoker(reports), hook_context_(stage, depsgraph)
432 {
433 }
434
435 protected:
436 const char *function_name() const override
437 {
438 return "on_export";
439 }
440
441 void call_hook(PyObject *hook_obj) override
442 {
443 python::call_method<bool>(hook_obj, function_name(), REF(hook_context_));
444 }
445};
446
448 private:
449 USDMaterialExportContext hook_context_;
450 pxr::UsdShadeMaterial usd_material_;
451 PointerRNA material_ptr_;
452
453 public:
454 OnMaterialExportInvoker(pxr::UsdStageRefPtr stage,
455 Material *material,
456 const pxr::UsdShadeMaterial &usd_material,
457 const USDExportParams &export_params,
460 hook_context_(stage, export_params, reports),
461 usd_material_(usd_material)
462 {
463 material_ptr_ = RNA_pointer_create_discrete(nullptr, &RNA_Material, material);
464 }
465
466 protected:
467 const char *function_name() const override
468 {
469 return "on_material_export";
470 }
471
472 void call_hook(PyObject *hook_obj) override
473 {
474 python::call_method<bool>(
475 hook_obj, function_name(), REF(hook_context_), material_ptr_, usd_material_);
476 }
477};
478
480 private:
481 USDSceneImportContext hook_context_;
482
483 public:
484 OnImportInvoker(pxr::UsdStageRefPtr stage, const ImportedPrimMap &prim_map, ReportList *reports)
485 : USDHookInvoker(reports), hook_context_(stage, prim_map)
486 {
487 }
488
489 protected:
490 const char *function_name() const override
491 {
492 return "on_import";
493 }
494
495 void call_hook(PyObject *hook_obj) override
496 {
497 python::call_method<bool>(hook_obj, function_name(), REF(hook_context_));
498 }
499
500 void release_in_gil() override
501 {
502 hook_context_.release();
503 }
504};
505
507 private:
508 USDMaterialImportContext hook_context_;
509 pxr::UsdShadeMaterial usd_material_;
510 bool result_ = false;
511
512 public:
513 MaterialImportPollInvoker(pxr::UsdStageRefPtr stage,
514 const pxr::UsdShadeMaterial &usd_material,
515 const USDImportParams &import_params,
518 hook_context_(stage, import_params, reports),
519 usd_material_(usd_material)
520 {
521 }
522
523 bool result() const
524 {
525 return result_;
526 }
527
528 protected:
529 const char *function_name() const override
530 {
531 return "material_import_poll";
532 }
533
534 void call_hook(PyObject *hook_obj) override
535 {
536 /* If we already know that one of the registered hook classes can import the material
537 * because it returned true in a previous invocation of the callback, we skip the call. */
538 if (!result_) {
539 result_ = python::call_method<bool>(
540 hook_obj, function_name(), REF(hook_context_), usd_material_);
541 }
542 }
543};
544
546 private:
547 USDMaterialImportContext hook_context_;
548 pxr::UsdShadeMaterial usd_material_;
549 PointerRNA material_ptr_;
550 bool result_ = false;
551
552 public:
553 OnMaterialImportInvoker(pxr::UsdStageRefPtr stage,
554 Material *material,
555 const pxr::UsdShadeMaterial &usd_material,
556 const USDImportParams &import_params,
559 hook_context_(stage, import_params, reports),
560 usd_material_(usd_material)
561 {
562 material_ptr_ = RNA_pointer_create_discrete(nullptr, &RNA_Material, material);
563 }
564
565 bool result() const
566 {
567 return result_;
568 }
569
570 protected:
571 const char *function_name() const override
572 {
573 return "on_material_import";
574 }
575
576 void call_hook(PyObject *hook_obj) override
577 {
578 result_ |= python::call_method<bool>(
579 hook_obj, function_name(), REF(hook_context_), material_ptr_, usd_material_);
580 }
581};
582
583void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
584{
585 if (hook_list().empty()) {
586 return;
587 }
588
589 OnExportInvoker on_export(stage, depsgraph, reports);
590 on_export.call();
591}
592
593void call_material_export_hooks(pxr::UsdStageRefPtr stage,
594 Material *material,
595 const pxr::UsdShadeMaterial &usd_material,
596 const USDExportParams &export_params,
598{
599 if (hook_list().empty()) {
600 return;
601 }
602
603 OnMaterialExportInvoker on_material_export(
604 stage, material, usd_material, export_params, reports);
605 on_material_export.call();
606}
607
609{
610 if (hook_list().empty()) {
611 return;
612 }
613
614 const Vector<USDPrimReader *> &readers = archive->readers();
615 const ImportSettings &settings = archive->settings();
616 ImportedPrimMap prim_map;
617
618 /* Resize based on the typical scenario where there will be both Object and Data entries
619 * in the map in addition to each material. */
620 prim_map.reserve((readers.size() * 2) + settings.usd_path_to_mat.size());
621
622 for (const USDPrimReader *reader : readers) {
623 if (!reader) {
624 continue;
625 }
626
627 Object *ob = reader->object();
628
629 prim_map.lookup_or_add_default(reader->object_prim_path())
631 if (ob->data) {
632 prim_map.lookup_or_add_default(reader->data_prim_path())
633 .append(RNA_id_pointer_create(static_cast<ID *>(ob->data)));
634 }
635 }
636
637 settings.usd_path_to_mat.foreach_item([&prim_map](const pxr::SdfPath &path, Material *mat) {
639 });
640
641 OnImportInvoker on_import(archive->stage(), prim_map, reports);
642 on_import.call();
643}
644
645bool have_material_import_hook(pxr::UsdStageRefPtr stage,
646 const pxr::UsdShadeMaterial &usd_material,
647 const USDImportParams &import_params,
649{
650 if (hook_list().empty()) {
651 return false;
652 }
653
654 MaterialImportPollInvoker poll(stage, usd_material, import_params, reports);
655 poll.call();
656
657 return poll.result();
658}
659
660bool call_material_import_hooks(pxr::UsdStageRefPtr stage,
661 Material *material,
662 const pxr::UsdShadeMaterial &usd_material,
663 const USDImportParams &import_params,
665{
666 if (hook_list().empty()) {
667 return false;
668 }
669
670 OnMaterialImportInvoker on_material_import(
671 stage, material, usd_material, import_params, reports);
672 on_material_import.call();
673 return on_material_import.result();
674}
675
676} // namespace blender::io::usd
677
678#undef REF
679#undef PYTHON_NS
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define STREQ(a, b)
@ ID_IM
ReportList * reports
Definition WM_types.hh:1025
iter begin(iter)
BPy_StructRNA * depsgraph
PyObject * pyrna_struct_CreatePyObject(PointerRNA *ptr)
Definition bpy_rna.cc:8384
void foreach_item(const FuncT &func) const
Definition BLI_map.hh:687
int64_t size() const
Definition BLI_map.hh:976
void append(const T &value)
Value & lookup_or_add_default(const Key &key)
Definition BLI_map.hh:639
void reserve(int64_t n)
Definition BLI_map.hh:1028
int64_t size() const
const char * function_name() const override
Definition usd_hook.cc:529
MaterialImportPollInvoker(pxr::UsdStageRefPtr stage, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:513
void call_hook(PyObject *hook_obj) override
Definition usd_hook.cc:534
void call_hook(PyObject *hook_obj) override
Definition usd_hook.cc:441
OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:430
const char * function_name() const override
Definition usd_hook.cc:436
void call_hook(PyObject *hook_obj) override
Definition usd_hook.cc:495
const char * function_name() const override
Definition usd_hook.cc:490
OnImportInvoker(pxr::UsdStageRefPtr stage, const ImportedPrimMap &prim_map, ReportList *reports)
Definition usd_hook.cc:484
const char * function_name() const override
Definition usd_hook.cc:467
void call_hook(PyObject *hook_obj) override
Definition usd_hook.cc:472
OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDExportParams &export_params, ReportList *reports)
Definition usd_hook.cc:454
OnMaterialImportInvoker(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:553
const char * function_name() const override
Definition usd_hook.cc:571
void call_hook(PyObject *hook_obj) override
Definition usd_hook.cc:576
USDHookInvoker(ReportList *reports)
Definition usd_hook.cc:359
virtual const char * function_name() const =0
virtual void call_hook(PyObject *hook_obj)=0
const ImportSettings & settings() const
const blender::Vector< USDPrimReader * > & readers() const
#define GS(a)
bool pyrna_id_FromPyObject(PyObject *obj, ID **id)
Definition bpy_rna.cc:8499
const char * temp_textures_dir()
bool should_import_asset(const std::string &path)
bool have_material_import_hook(pxr::UsdStageRefPtr stage, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:645
void USD_unregister_hook(USDHook *hook)
Definition usd_hook.cc:82
std::string import_asset(const char *src, const char *import_dir, eUSDTexNameCollisionMode name_collision_mode, ReportList *reports)
static void export_texture(const USDExporterContext &usd_export_context, bNode *node)
@ USD_TEX_IMPORT_PACK
Definition usd.hh:66
void USD_register_hook(std::unique_ptr< USDHook > hook)
Definition usd_hook.cc:71
static USDHookList & hook_list()
Definition usd_hook.cc:65
eUSDTexNameCollisionMode
Definition usd.hh:74
@ USD_TEX_NAME_COLLISION_OVERWRITE
Definition usd.hh:76
std::list< std::unique_ptr< USDHook > > USDHookList
Definition usd_hook.cc:61
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context, bNode *node)
void call_import_hooks(USDStageReader *archive, ReportList *reports)
Definition usd_hook.cc:608
Map< pxr::SdfPath, Vector< PointerRNA > > ImportedPrimMap
Definition usd_hook.cc:62
bool call_material_import_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:660
void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDExportParams &export_params, ReportList *reports)
Definition usd_hook.cc:593
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:583
USDHook * USD_find_hook_name(const char idname[])
Definition usd_hook.cc:88
static void handle_python_error(USDHook *hook, ReportList *reports)
Definition usd_hook.cc:338
void register_hook_converters()
Definition usd_hook.cc:291
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
PointerRNA RNA_id_pointer_create(ID *id)
Definition DNA_ID.h:404
char name[66]
Definition DNA_ID.h:415
blender::Map< pxr::SdfPath, Material * > usd_path_to_mat
static PyObject * convert(PointerRNA ptr)
Definition usd_hook.cc:108
ExtensionRNA rna_ext
Definition usd.hh:328
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:193
std::string export_texture(PYTHON_NS::object obj) const
Definition usd_hook.cc:203
USDMaterialExportContext(pxr::UsdStageRefPtr in_stage, const USDExportParams &in_params, ReportList *in_reports)
Definition usd_hook.cc:186
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:245
PYTHON_NS::tuple import_texture(const std::string &asset_path) const
Definition usd_hook.cc:258
USDMaterialImportContext(pxr::UsdStageRefPtr in_stage, const USDImportParams &in_params, ReportList *in_reports)
Definition usd_hook.cc:238
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:124
USDSceneExportContext(pxr::UsdStageRefPtr in_stage, Depsgraph *depsgraph)
Definition usd_hook.cc:119
const PointerRNA & get_depsgraph() const
Definition usd_hook.cc:129
USDSceneImportContext(pxr::UsdStageRefPtr in_stage, const ImportedPrimMap &in_prim_map)
Definition usd_hook.cc:142
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:152
#define REF
Definition usd_hook.cc:54
PointerRNA * ptr
Definition wm_files.cc:4226