18using namespace helios;
22static constexpr float MIN_TRIANGLE_AREA_THRESHOLD = 1e-8f;
25 std::string data_str = node_data.child_value();
27 if (!data_str.empty()) {
28 std::istringstream data_stream(data_str);
31 while (data_stream >> tmp_s) {
33 data.push_back(tmp_f);
46 std::string data_str = node_data.child_value();
48 if (!data_str.empty()) {
49 std::istringstream data_stream(data_str);
52 while (data_stream >> tmp_s) {
54 data.push_back(tmp_f);
67 std::string data_str = node_data.child_value();
69 if (!data_str.empty()) {
70 std::istringstream data_stream(data_str);
73 while (data_stream >> tmp_s) {
75 data.push_back(tmp_f);
88 std::string data_str = node_data.child_value();
90 if (!data_str.empty()) {
91 std::istringstream data_stream(data_str);
94 while (data_stream >> tmp_s) {
96 data.push_back(tmp_f);
109 std::string data_str = node_data.child_value();
111 if (!data_str.empty()) {
112 std::istringstream data_stream(data_str);
114 while (data_stream >> tmp_s) {
115 data.push_back(tmp_s);
125 std::string data_str = node_data.child_value();
127 if (!data_str.empty()) {
128 std::istringstream data_stream(data_str);
129 std::vector<std::string> tmp_s(2);
131 while (data_stream >> tmp_s[0]) {
132 data_stream >> tmp_s[1];
147 std::string data_str = node_data.child_value();
149 if (!data_str.empty()) {
150 std::istringstream data_stream(data_str);
151 std::vector<std::string> tmp_s(3);
153 while (data_stream >> tmp_s[0]) {
154 data_stream >> tmp_s[1];
155 data_stream >> tmp_s[2];
170 std::string data_str = node_data.child_value();
172 if (!data_str.empty()) {
173 std::istringstream data_stream(data_str);
174 std::vector<std::string> tmp_s(4);
176 while (data_stream >> tmp_s[0]) {
177 data_stream >> tmp_s[1];
178 data_stream >> tmp_s[2];
179 data_stream >> tmp_s[3];
194 std::string data_str = node_data.child_value();
196 if (!data_str.empty()) {
197 std::istringstream data_stream(data_str);
198 std::vector<std::string> tmp_s(2);
200 while (data_stream >> tmp_s[0]) {
201 data_stream >> tmp_s[1];
216 std::string data_str = node_data.child_value();
218 if (!data_str.empty()) {
219 std::istringstream data_stream(data_str);
220 std::vector<std::string> tmp_s(3);
222 while (data_stream >> tmp_s[0]) {
223 data_stream >> tmp_s[1];
224 data_stream >> tmp_s[2];
239 std::string data_str = node_data.child_value();
241 if (!data_str.empty()) {
242 std::istringstream data_stream(data_str);
243 std::vector<std::string> tmp_s(4);
245 while (data_stream >> tmp_s[0]) {
246 data_stream >> tmp_s[1];
247 data_stream >> tmp_s[2];
248 data_stream >> tmp_s[3];
263 pugi::xml_node objID_node = node.child(
"objID");
278 pugi::xml_node transform_node = node.child(
"transform");
280 std::string transform_str = transform_node.child_value();
281 if (transform_str.empty()) {
285 std::istringstream stream(transform_str);
289 while (stream >> tmp_s) {
305 pugi::xml_node texture_node = node.child(
"texture");
307 if (texfile.empty()) {
317 pugi::xml_node uv_node = node.child(
"textureUV");
318 std::string texUV = uv_node.child_value();
319 if (!texUV.empty()) {
320 std::istringstream uv_stream(texUV);
321 std::vector<std::string> tmp_s(2);
323 while (uv_stream >> tmp_s[0]) {
324 uv_stream >> tmp_s[1];
339 pugi::xml_node sfrac_node = node.child(
"solid_fraction");
341 if (!sfrac.empty()) {
354 pugi::xml_node vertices_node = node.child(
"vertices");
356 std::string vertices_str = vertices_node.child_value();
357 if (!vertices_str.empty()) {
358 std::istringstream stream(vertices_str);
362 while (stream >> tmp_s) {
366 vertices.at(i) = tmp;
381 pugi::xml_node subdiv_node = node.child(
"subdivisions");
383 if (!subdiv.empty()) {
394 pugi::xml_node subdiv_node = node.child(
"subdivisions");
396 if (!subdiv.empty()) {
397 std::istringstream data_stream(subdiv);
398 std::vector<std::string> tmp_s(2);
399 data_stream >> tmp_s[0];
400 data_stream >> tmp_s[1];
411 pugi::xml_node subdiv_node = node.child(
"subdivisions");
413 if (!subdiv.empty()) {
414 std::istringstream data_stream(subdiv);
415 std::vector<std::string> tmp_s(3);
416 data_stream >> tmp_s[0];
417 data_stream >> tmp_s[1];
418 data_stream >> tmp_s[2];
429 pugi::xml_node node_data = node.child(
"nodes");
430 std::string data_str = node_data.child_value();
432 if (!data_str.empty()) {
433 std::istringstream data_stream(data_str);
434 std::vector<std::string> tmp_s(3);
436 while (data_stream >> tmp_s[0]) {
437 data_stream >> tmp_s[1];
438 data_stream >> tmp_s[2];
442 nodes.push_back(tmp);
453 pugi::xml_node node_data = node.child(
"radius");
454 std::string data_str = node_data.child_value();
456 if (!data_str.empty()) {
457 std::istringstream data_stream(data_str);
460 while (data_stream >> tmp_s) {
462 radius.push_back(tmp_f);
474void Context::loadMaterialData(pugi::xml_node mat_node,
const std::string &material_label) {
476 for (pugi::xml_node data = mat_node.child(
"data_uint"); data; data = data.next_sibling(
"data_uint")) {
477 const char *label = data.attribute(
"label").value();
478 std::vector<uint> datav;
480 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_uint> with label " + std::string(label) +
" contained invalid data.");
482 if (datav.size() == 1) {
484 }
else if (datav.size() > 1) {
490 for (pugi::xml_node data = mat_node.child(
"data_int"); data; data = data.next_sibling(
"data_int")) {
491 const char *label = data.attribute(
"label").value();
492 std::vector<int> datav;
494 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int> with label " + std::string(label) +
" contained invalid data.");
496 if (datav.size() == 1) {
498 }
else if (datav.size() > 1) {
504 for (pugi::xml_node data = mat_node.child(
"data_float"); data; data = data.next_sibling(
"data_float")) {
505 const char *label = data.attribute(
"label").value();
506 std::vector<float> datav;
508 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_float> with label " + std::string(label) +
" contained invalid data.");
510 if (datav.size() == 1) {
512 }
else if (datav.size() > 1) {
518 for (pugi::xml_node data = mat_node.child(
"data_double"); data; data = data.next_sibling(
"data_double")) {
519 const char *label = data.attribute(
"label").value();
520 std::vector<double> datav;
522 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_double> with label " + std::string(label) +
" contained invalid data.");
524 if (datav.size() == 1) {
526 }
else if (datav.size() > 1) {
532 for (pugi::xml_node data = mat_node.child(
"data_vec2"); data; data = data.next_sibling(
"data_vec2")) {
533 const char *label = data.attribute(
"label").value();
534 std::vector<vec2> datav;
536 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_vec2> with label " + std::string(label) +
" contained invalid data.");
538 if (datav.size() == 1) {
540 }
else if (datav.size() > 1) {
546 for (pugi::xml_node data = mat_node.child(
"data_vec3"); data; data = data.next_sibling(
"data_vec3")) {
547 const char *label = data.attribute(
"label").value();
548 std::vector<vec3> datav;
550 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_vec3> with label " + std::string(label) +
" contained invalid data.");
552 if (datav.size() == 1) {
554 }
else if (datav.size() > 1) {
560 for (pugi::xml_node data = mat_node.child(
"data_vec4"); data; data = data.next_sibling(
"data_vec4")) {
561 const char *label = data.attribute(
"label").value();
562 std::vector<vec4> datav;
564 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_vec4> with label " + std::string(label) +
" contained invalid data.");
566 if (datav.size() == 1) {
568 }
else if (datav.size() > 1) {
574 for (pugi::xml_node data = mat_node.child(
"data_int2"); data; data = data.next_sibling(
"data_int2")) {
575 const char *label = data.attribute(
"label").value();
576 std::vector<int2> datav;
578 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int2> with label " + std::string(label) +
" contained invalid data.");
580 if (datav.size() == 1) {
582 }
else if (datav.size() > 1) {
588 for (pugi::xml_node data = mat_node.child(
"data_int3"); data; data = data.next_sibling(
"data_int3")) {
589 const char *label = data.attribute(
"label").value();
590 std::vector<int3> datav;
592 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int3> with label " + std::string(label) +
" contained invalid data.");
594 if (datav.size() == 1) {
596 }
else if (datav.size() > 1) {
602 for (pugi::xml_node data = mat_node.child(
"data_int4"); data; data = data.next_sibling(
"data_int4")) {
603 const char *label = data.attribute(
"label").value();
604 std::vector<int4> datav;
606 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int4> with label " + std::string(label) +
" contained invalid data.");
608 if (datav.size() == 1) {
610 }
else if (datav.size() > 1) {
616 for (pugi::xml_node data = mat_node.child(
"data_string"); data; data = data.next_sibling(
"data_string")) {
617 const char *label = data.attribute(
"label").value();
618 std::vector<std::string> datav;
620 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_string> with label " + std::string(label) +
" contained invalid data.");
622 if (datav.size() == 1) {
624 }
else if (datav.size() > 1) {
630void Context::loadPData(pugi::xml_node p,
uint UUID) {
631 for (pugi::xml_node data = p.child(
"data_int"); data; data = data.next_sibling(
"data_int")) {
632 const char *label = data.attribute(
"label").value();
634 std::vector<int> datav;
636 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int> with label " + std::string(label) +
" contained invalid data.");
639 if (datav.size() == 1) {
641 }
else if (datav.size() > 1) {
646 for (pugi::xml_node data = p.child(
"data_uint"); data; data = data.next_sibling(
"data_uint")) {
647 const char *label = data.attribute(
"label").value();
649 std::vector<uint> datav;
651 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_uint> with label " + std::string(label) +
" contained invalid data.");
654 if (datav.size() == 1) {
656 }
else if (datav.size() > 1) {
661 for (pugi::xml_node data = p.child(
"data_float"); data; data = data.next_sibling(
"data_float")) {
662 const char *label = data.attribute(
"label").value();
664 std::vector<float> datav;
666 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_float> with label " + std::string(label) +
" contained invalid data.");
669 if (datav.size() == 1) {
671 }
else if (datav.size() > 1) {
676 for (pugi::xml_node data = p.child(
"data_double"); data; data = data.next_sibling(
"data_double")) {
677 const char *label = data.attribute(
"label").value();
679 std::vector<double> datav;
681 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_double> with label " + std::string(label) +
" contained invalid data.");
684 if (datav.size() == 1) {
686 }
else if (datav.size() > 1) {
691 for (pugi::xml_node data = p.child(
"data_vec2"); data; data = data.next_sibling(
"data_vec2")) {
692 const char *label = data.attribute(
"label").value();
694 std::vector<vec2> datav;
696 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_vec2> with label " + std::string(label) +
" contained invalid data.");
699 if (datav.size() == 1) {
701 }
else if (datav.size() > 1) {
706 for (pugi::xml_node data = p.child(
"data_vec3"); data; data = data.next_sibling(
"data_vec3")) {
707 const char *label = data.attribute(
"label").value();
709 std::vector<vec3> datav;
711 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_vec3> with label " + std::string(label) +
" contained invalid data.");
714 if (datav.size() == 1) {
716 }
else if (datav.size() > 1) {
721 for (pugi::xml_node data = p.child(
"data_vec4"); data; data = data.next_sibling(
"data_vec4")) {
722 const char *label = data.attribute(
"label").value();
724 std::vector<vec4> datav;
726 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_vec4> with label " + std::string(label) +
" contained invalid data.");
729 if (datav.size() == 1) {
731 }
else if (datav.size() > 1) {
736 for (pugi::xml_node data = p.child(
"data_int2"); data; data = data.next_sibling(
"data_int2")) {
737 const char *label = data.attribute(
"label").value();
739 std::vector<int2> datav;
741 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int2> with label " + std::string(label) +
" contained invalid data.");
744 if (datav.size() == 1) {
746 }
else if (datav.size() > 1) {
751 for (pugi::xml_node data = p.child(
"data_int3"); data; data = data.next_sibling(
"data_int3")) {
752 const char *label = data.attribute(
"label").value();
754 std::vector<int3> datav;
756 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int3> with label " + std::string(label) +
" contained invalid data.");
759 if (datav.size() == 1) {
761 }
else if (datav.size() > 1) {
766 for (pugi::xml_node data = p.child(
"data_int4"); data; data = data.next_sibling(
"data_int4")) {
767 const char *label = data.attribute(
"label").value();
769 std::vector<int4> datav;
771 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int4> with label " + std::string(label) +
" contained invalid data.");
774 if (datav.size() == 1) {
776 }
else if (datav.size() > 1) {
781 for (pugi::xml_node data = p.child(
"data_string"); data; data = data.next_sibling(
"data_string")) {
782 const char *label = data.attribute(
"label").value();
784 std::vector<std::string> datav;
786 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_string> with label " + std::string(label) +
" contained invalid data.");
789 if (datav.size() == 1) {
791 }
else if (datav.size() > 1) {
797void Context::loadOData(pugi::xml_node p,
uint ID) {
800 for (pugi::xml_node data = p.child(
"data_int"); data; data = data.next_sibling(
"data_int")) {
801 const char *label = data.attribute(
"label").value();
803 std::vector<int> datav;
805 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int> with label " + std::string(label) +
" contained invalid data.");
808 if (datav.size() == 1) {
810 }
else if (datav.size() > 1) {
815 for (pugi::xml_node data = p.child(
"data_uint"); data; data = data.next_sibling(
"data_uint")) {
816 const char *label = data.attribute(
"label").value();
818 std::vector<uint> datav;
820 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_uint> with label " + std::string(label) +
" contained invalid data.");
823 if (datav.size() == 1) {
825 }
else if (datav.size() > 1) {
830 for (pugi::xml_node data = p.child(
"data_float"); data; data = data.next_sibling(
"data_float")) {
831 const char *label = data.attribute(
"label").value();
833 std::vector<float> datav;
835 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_float> with label " + std::string(label) +
" contained invalid data.");
838 if (datav.size() == 1) {
840 }
else if (datav.size() > 1) {
845 for (pugi::xml_node data = p.child(
"data_double"); data; data = data.next_sibling(
"data_double")) {
846 const char *label = data.attribute(
"label").value();
848 std::vector<double> datav;
850 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_double> with label " + std::string(label) +
" contained invalid data.");
853 if (datav.size() == 1) {
855 }
else if (datav.size() > 1) {
860 for (pugi::xml_node data = p.child(
"data_vec2"); data; data = data.next_sibling(
"data_vec2")) {
861 const char *label = data.attribute(
"label").value();
863 std::vector<vec2> datav;
865 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_vec2> with label " + std::string(label) +
" contained invalid data.");
868 if (datav.size() == 1) {
870 }
else if (datav.size() > 1) {
875 for (pugi::xml_node data = p.child(
"data_vec3"); data; data = data.next_sibling(
"data_vec3")) {
876 const char *label = data.attribute(
"label").value();
878 std::vector<vec3> datav;
880 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_vec3> with label " + std::string(label) +
" contained invalid data.");
883 if (datav.size() == 1) {
885 }
else if (datav.size() > 1) {
890 for (pugi::xml_node data = p.child(
"data_vec4"); data; data = data.next_sibling(
"data_vec4")) {
891 const char *label = data.attribute(
"label").value();
893 std::vector<vec4> datav;
895 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_vec4> with label " + std::string(label) +
" contained invalid data.");
898 if (datav.size() == 1) {
900 }
else if (datav.size() > 1) {
905 for (pugi::xml_node data = p.child(
"data_int2"); data; data = data.next_sibling(
"data_int2")) {
906 const char *label = data.attribute(
"label").value();
908 std::vector<int2> datav;
910 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int2> with label " + std::string(label) +
" contained invalid data.");
913 if (datav.size() == 1) {
915 }
else if (datav.size() > 1) {
920 for (pugi::xml_node data = p.child(
"data_int3"); data; data = data.next_sibling(
"data_int3")) {
921 const char *label = data.attribute(
"label").value();
923 std::vector<int3> datav;
925 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int3> with label " + std::string(label) +
" contained invalid data.");
928 if (datav.size() == 1) {
930 }
else if (datav.size() > 1) {
935 for (pugi::xml_node data = p.child(
"data_int4"); data; data = data.next_sibling(
"data_int4")) {
936 const char *label = data.attribute(
"label").value();
938 std::vector<int4> datav;
940 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int4> with label " + std::string(label) +
" contained invalid data.");
943 if (datav.size() == 1) {
945 }
else if (datav.size() > 1) {
950 for (pugi::xml_node data = p.child(
"data_string"); data; data = data.next_sibling(
"data_string")) {
951 const char *label = data.attribute(
"label").value();
953 std::vector<std::string> datav;
955 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_string> with label " + std::string(label) +
" contained invalid data.");
958 if (datav.size() == 1) {
960 }
else if (datav.size() > 1) {
966void Context::loadOsubPData(pugi::xml_node p,
uint ID) {
969 std::vector<uint> prim_UUIDs = getObjectPointer_private(ID)->
getPrimitiveUUIDs();
973 for (pugi::xml_node prim_data = p.child(
"primitive_data_int"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int")) {
974 const char *label = prim_data.attribute(
"label").value();
977 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
978 if (u >= prim_UUIDs.size()) {
979 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
980 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
984 std::vector<int> datav;
986 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int> with label " + std::string(label) +
" contained invalid data.");
990 if (datav.size() == 1) {
992 }
else if (datav.size() > 1) {
1000 for (pugi::xml_node prim_data = p.child(
"primitive_data_uint"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_uint")) {
1001 const char *label = prim_data.attribute(
"label").value();
1004 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1005 if (u >= prim_UUIDs.size()) {
1006 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1007 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1011 std::vector<uint> datav;
1013 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_uint> with label " + std::string(label) +
" contained invalid data.");
1017 if (datav.size() == 1) {
1019 }
else if (datav.size() > 1) {
1027 for (pugi::xml_node prim_data = p.child(
"primitive_data_float"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_float")) {
1028 const char *label = prim_data.attribute(
"label").value();
1031 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1032 if (u >= prim_UUIDs.size()) {
1033 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1034 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1038 std::vector<float> datav;
1040 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_float> with label " + std::string(label) +
" contained invalid data.");
1044 if (datav.size() == 1) {
1046 }
else if (datav.size() > 1) {
1054 for (pugi::xml_node prim_data = p.child(
"primitive_data_double"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_double")) {
1055 const char *label = prim_data.attribute(
"label").value();
1058 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1059 if (u >= prim_UUIDs.size()) {
1060 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1061 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1065 std::vector<double> datav;
1067 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_double> with label " + std::string(label) +
" contained invalid data.");
1071 if (datav.size() == 1) {
1073 }
else if (datav.size() > 1) {
1081 for (pugi::xml_node prim_data = p.child(
"primitive_data_vec2"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_vec2")) {
1082 const char *label = prim_data.attribute(
"label").value();
1085 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1086 if (u >= prim_UUIDs.size()) {
1087 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1088 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1092 std::vector<vec2> datav;
1094 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec2> with label " + std::string(label) +
" contained invalid data.");
1098 if (datav.size() == 1) {
1100 }
else if (datav.size() > 1) {
1108 for (pugi::xml_node prim_data = p.child(
"primitive_data_vec3"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_vec3")) {
1109 const char *label = prim_data.attribute(
"label").value();
1112 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1113 if (u >= prim_UUIDs.size()) {
1114 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1115 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1119 std::vector<vec3> datav;
1121 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec3> with label " + std::string(label) +
" contained invalid data.");
1125 if (datav.size() == 1) {
1127 }
else if (datav.size() > 1) {
1135 for (pugi::xml_node prim_data = p.child(
"primitive_data_vec4"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_vec4")) {
1136 const char *label = prim_data.attribute(
"label").value();
1139 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1140 if (u >= prim_UUIDs.size()) {
1141 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1142 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1146 std::vector<vec4> datav;
1148 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec4> with label " + std::string(label) +
" contained invalid data.");
1152 if (datav.size() == 1) {
1154 }
else if (datav.size() > 1) {
1162 for (pugi::xml_node prim_data = p.child(
"primitive_data_int2"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int2")) {
1163 const char *label = prim_data.attribute(
"label").value();
1166 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1167 if (u >= prim_UUIDs.size()) {
1168 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1169 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1173 std::vector<int2> datav;
1175 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int2> with label " + std::string(label) +
" contained invalid data.");
1179 if (datav.size() == 1) {
1181 }
else if (datav.size() > 1) {
1189 for (pugi::xml_node prim_data = p.child(
"primitive_data_int3"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int3")) {
1190 const char *label = prim_data.attribute(
"label").value();
1193 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1194 if (u >= prim_UUIDs.size()) {
1195 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1196 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1200 std::vector<int3> datav;
1202 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int3> with label " + std::string(label) +
" contained invalid data.");
1206 if (datav.size() == 1) {
1208 }
else if (datav.size() > 1) {
1216 for (pugi::xml_node prim_data = p.child(
"primitive_data_int4"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int4")) {
1217 const char *label = prim_data.attribute(
"label").value();
1220 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1221 if (u >= prim_UUIDs.size()) {
1222 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1223 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1227 std::vector<int4> datav;
1229 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int4> with label " + std::string(label) +
" contained invalid data.");
1233 if (datav.size() == 1) {
1235 }
else if (datav.size() > 1) {
1243 for (pugi::xml_node prim_data = p.child(
"primitive_data_string"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_string")) {
1244 const char *label = prim_data.attribute(
"label").value();
1247 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1248 if (u >= prim_UUIDs.size()) {
1249 std::cerr <<
"WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1250 <<
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1254 std::vector<std::string> datav;
1256 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_string> with label " + std::string(label) +
" contained invalid data.");
1260 if (datav.size() == 1) {
1262 }
else if (datav.size() > 1) {
1273 std::cout <<
"Loading XML file: " << filename <<
"..." << std::flush;
1276 std::string fn = filename;
1278 if (ext !=
".xml" && ext !=
".XML") {
1284 std::string resolved_filename = resolved_path.string();
1286 XMLfiles.emplace_back(resolved_filename);
1289 std::vector<uint> UUID;
1292 pugi::xml_document xmldoc;
1295 pugi::xml_parse_result load_result = xmldoc.load_file(resolved_filename.c_str());
1299 helios_runtime_error(
"failed.\n XML [" + std::string(filename) +
"] parsed with errors, attr value: [" + xmldoc.child(
"node").attribute(
"attr").value() +
"]\nError description: " + load_result.description() +
1300 "\nError offset: " + std::to_string(load_result.offset) +
" (error at [..." + (filename + load_result.offset) +
"]\n");
1303 pugi::xml_node helios = xmldoc.child(
"helios");
1305 if (helios.empty()) {
1307 std::cout <<
"failed." << std::endl;
1309 helios_runtime_error(
"ERROR (Context::loadXML): XML file must have tag '<helios> ... </helios>' bounding all other tags.");
1313 std::map<uint, std::vector<uint>> object_prim_UUIDs;
1317 for (pugi::xml_node p = helios.child(
"date"); p; p = p.next_sibling(
"date")) {
1318 pugi::xml_node year_node = p.child(
"year");
1319 const char *year_str = year_node.child_value();
1322 helios_runtime_error(
"ERROR (Context::loadXML): Year given in 'date' block must be an integer value.");
1325 pugi::xml_node month_node = p.child(
"month");
1326 const char *month_str = month_node.child_value();
1329 helios_runtime_error(
"ERROR (Context::loadXML): Month given in 'date' block must be an integer value.");
1332 pugi::xml_node day_node = p.child(
"day");
1333 const char *day_str = day_node.child_value();
1336 helios_runtime_error(
"ERROR (Context::loadXML): Day given in 'date' block must be an integer value.");
1342 for (pugi::xml_node p = helios.child(
"time"); p; p = p.next_sibling(
"time")) {
1343 pugi::xml_node hour_node = p.child(
"hour");
1344 const char *hour_str = hour_node.child_value();
1347 helios_runtime_error(
"ERROR (Context::loadXML): Hour given in 'time' block must be an integer value.");
1350 pugi::xml_node minute_node = p.child(
"minute");
1351 const char *minute_str = minute_node.child_value();
1354 helios_runtime_error(
"ERROR (Context::loadXML): Minute given in 'time' block must be an integer value.");
1357 pugi::xml_node second_node = p.child(
"second");
1358 const char *second_str = second_node.child_value();
1361 helios_runtime_error(
"ERROR (Context::loadXML): Second given in 'time' block must be an integer value.");
1364 setTime(second, minute, hour);
1369 std::map<uint, std::string> legacy_material_id_to_label;
1371 for (pugi::xml_node m = helios.child(
"materials"); m; m = m.next_sibling(
"materials")) {
1372 for (pugi::xml_node mat = m.child(
"material"); mat; mat = mat.next_sibling(
"material")) {
1373 std::string material_label;
1375 std::string texture_file;
1376 bool texture_override =
false;
1379 pugi::xml_attribute label_attr = mat.attribute(
"label");
1380 if (!label_attr.empty()) {
1381 material_label = label_attr.value();
1384 pugi::xml_attribute id_attr = mat.attribute(
"id");
1385 if (!id_attr.empty()) {
1387 const char *id_str = id_attr.value();
1389 helios_runtime_error(
"ERROR (Context::loadXML): Material ID must be an unsigned integer value.");
1392 material_label =
"__auto_material_" + std::to_string(matID);
1393 legacy_material_id_to_label[matID] = material_label;
1395 helios_runtime_error(
"ERROR (Context::loadXML): Material must have either a 'label' or 'id' attribute.");
1400 pugi::xml_node color_node = mat.child(
"color");
1401 if (!color_node.empty()) {
1402 const char *color_str = color_node.child_value();
1403 std::istringstream color_stream(color_str);
1404 std::vector<float> color_vec;
1406 while (color_stream >> tmp) {
1407 color_vec.push_back(tmp);
1409 if (color_vec.size() == 3) {
1410 color =
make_RGBAcolor(color_vec.at(0), color_vec.at(1), color_vec.at(2), 1.f);
1411 }
else if (color_vec.size() == 4) {
1412 color =
make_RGBAcolor(color_vec.at(0), color_vec.at(1), color_vec.at(2), color_vec.at(3));
1417 pugi::xml_node texture_node = mat.child(
"texture");
1418 if (!texture_node.empty()) {
1419 texture_file =
deblank(texture_node.child_value());
1420 if (!texture_file.empty()) {
1421 addTexture(texture_file.c_str());
1426 pugi::xml_node override_node = mat.child(
"texture_override");
1427 if (!override_node.empty()) {
1428 const char *override_str = override_node.child_value();
1430 if (
parse_int(override_str, override_val)) {
1431 texture_override = (override_val != 0);
1437 pugi::xml_node twosided_node = mat.child(
"twosided_flag");
1438 if (!twosided_node.empty()) {
1439 const char *twosided_str = twosided_node.child_value();
1441 if (
parse_int(twosided_str, twosided_val) && twosided_val >= 0) {
1442 twosided = (
uint) twosided_val;
1449 uint newID = currentMaterialID++;
1450 Material loaded_mat(newID, material_label, color, texture_file, texture_override, twosided);
1451 materials[newID] = loaded_mat;
1452 material_label_to_id[material_label] = newID;
1456 if (!texture_file.empty()) {
1464 loadMaterialData(mat, material_label);
1469 for (pugi::xml_node p = helios.child(
"patch"); p; p = p.next_sibling(
"patch")) {
1473 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'patch' block must be a non-negative integer value.");
1477 float transform[16];
1480 helios_runtime_error(
"ERROR (Context::loadXML): Patch <transform> node contains less than 16 data values.");
1481 }
else if (result == 2) {
1482 helios_runtime_error(
"ERROR (Context::loadXML): Patch <transform> node contains invalid data.");
1486 std::string texture_file;
1490 std::vector<vec2> uv;
1492 helios_runtime_error(
"ERROR (Context::loadXML): (u,v) coordinates given in 'patch' block contain invalid data.");
1496 float solid_fraction = -1;
1498 helios_runtime_error(
"ERROR (Context::loadXML): Solid fraction given in 'patch' block contains invalid data.");
1502 pugi::xml_node material_node = p.child(
"material");
1503 pugi::xml_node material_id_node = p.child(
"material_id");
1504 std::string material_label_from_xml;
1505 bool has_material =
false;
1507 if (!material_node.empty()) {
1509 material_label_from_xml =
deblank(material_node.child_value());
1510 if (!material_label_from_xml.empty() &&
doesMaterialExist(material_label_from_xml)) {
1511 has_material =
true;
1512 ID =
addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1),
make_SphericalCoord(0, 0),
make_RGBAcolor(0, 0, 0, 1));
1514 }
else if (!material_id_node.empty()) {
1516 uint materialID_from_xml = 0;
1517 const char *mat_id_str = material_id_node.child_value();
1518 if (
parse_uint(mat_id_str, materialID_from_xml)) {
1520 auto it = legacy_material_id_to_label.find(materialID_from_xml);
1521 if (it != legacy_material_id_to_label.end()) {
1522 material_label_from_xml = it->second;
1523 has_material =
true;
1524 ID =
addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1),
make_SphericalCoord(0, 0),
make_RGBAcolor(0, 0, 0, 1));
1529 if (!has_material) {
1532 pugi::xml_node color_node = p.child(
"color");
1534 const char *color_str = color_node.child_value();
1535 if (strlen(color_str) == 0) {
1542 if (strcmp(texture_file.c_str(),
"none") == 0) {
1545 std::string texture_file_copy;
1546 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1547 texture_file_copy = texture_file;
1548 texture_file =
"lib/images/solid.jpg";
1555 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1556 getPrimitivePointer_private(ID)->setTextureFile(texture_file_copy.c_str());
1557 addTexture(texture_file_copy.c_str());
1558 getPrimitivePointer_private(ID)->setSolidFraction(solid_fraction);
1563 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1566 if (has_material && !material_label_from_xml.empty()) {
1571 object_prim_UUIDs[objID].push_back(ID);
1586 for (pugi::xml_node tri = helios.child(
"triangle"); tri; tri = tri.next_sibling(
"triangle")) {
1590 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'triangle' block must be a non-negative integer value.");
1594 float transform[16];
1597 helios_runtime_error(
"ERROR (Context::loadXML): Triangle <transform> node contains less than 16 data values.");
1598 }
else if (result == 2) {
1599 helios_runtime_error(
"ERROR (Context::loadXML): Triangle <transform> node contains invalid data.");
1603 std::string texture_file;
1607 std::vector<vec2> uv;
1609 helios_runtime_error(
"ERROR (Context::loadXML): (u,v) coordinates given in 'triangle' block contain invalid data.");
1613 float solid_fraction = -1;
1615 helios_runtime_error(
"ERROR (Context::loadXML): Solid fraction given in 'triangle' block contains invalid data.");
1619 pugi::xml_node material_node_tri = tri.child(
"material");
1620 pugi::xml_node material_id_node_tri = tri.child(
"material_id");
1621 std::string material_label_from_xml_tri;
1622 bool has_material_tri =
false;
1624 if (!material_node_tri.empty()) {
1626 material_label_from_xml_tri =
deblank(material_node_tri.child_value());
1627 if (!material_label_from_xml_tri.empty() &&
doesMaterialExist(material_label_from_xml_tri)) {
1628 has_material_tri =
true;
1630 }
else if (!material_id_node_tri.empty()) {
1632 uint materialID_from_xml_tri = 0;
1633 const char *mat_id_str = material_id_node_tri.child_value();
1634 if (
parse_uint(mat_id_str, materialID_from_xml_tri)) {
1636 auto it = legacy_material_id_to_label.find(materialID_from_xml_tri);
1637 if (it != legacy_material_id_to_label.end()) {
1638 material_label_from_xml_tri = it->second;
1639 has_material_tri =
true;
1644 std::vector<vec3> vert_pos;
1646 vert_pos.at(0) =
make_vec3(0.f, 0.f, 0.f);
1647 vert_pos.at(1) =
make_vec3(0.f, 1.f, 0.f);
1648 vert_pos.at(2) =
make_vec3(1.f, 1.f, 0.f);
1650 if (has_material_tri) {
1656 pugi::xml_node color_node = tri.child(
"color");
1658 const char *color_str = color_node.child_value();
1659 if (strlen(color_str) == 0) {
1666 if (strcmp(texture_file.c_str(),
"none") == 0 || uv.empty()) {
1667 ID =
addTriangle(vert_pos.at(0), vert_pos.at(1), vert_pos.at(2), color);
1669 std::string texture_file_copy;
1670 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1671 texture_file_copy = texture_file;
1672 texture_file =
"lib/images/solid.jpg";
1674 ID =
addTriangle(vert_pos.at(0), vert_pos.at(1), vert_pos.at(2), texture_file.c_str(), uv.at(0), uv.at(1), uv.at(2));
1675 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1676 getPrimitivePointer_private(ID)->setTextureFile(texture_file_copy.c_str());
1677 addTexture(texture_file_copy.c_str());
1678 getPrimitivePointer_private(ID)->setSolidFraction(solid_fraction);
1683 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1686 if (has_material_tri && !material_label_from_xml_tri.empty()) {
1691 object_prim_UUIDs[objID].push_back(ID);
1704 for (pugi::xml_node p = helios.child(
"voxel"); p; p = p.next_sibling(
"voxel")) {
1708 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'voxel' block must be a non-negative integer value.");
1712 float transform[16];
1715 helios_runtime_error(
"ERROR (Context::loadXML): Voxel <transform> node contains less than 16 data values.");
1716 }
else if (result == 2) {
1717 helios_runtime_error(
"ERROR (Context::loadXML): Voxel <transform> node contains invalid data.");
1721 float solid_fraction = 1;
1723 helios_runtime_error(
"ERROR (Context::loadXML): Solid fraction given in 'voxel' block contains invalid data.");
1727 pugi::xml_node material_node_vox = p.child(
"material");
1728 pugi::xml_node material_id_node_vox = p.child(
"material_id");
1729 std::string material_label_from_xml_vox;
1730 bool has_material_vox =
false;
1732 if (!material_node_vox.empty()) {
1734 material_label_from_xml_vox =
deblank(material_node_vox.child_value());
1735 if (!material_label_from_xml_vox.empty() &&
doesMaterialExist(material_label_from_xml_vox)) {
1736 has_material_vox =
true;
1738 }
else if (!material_id_node_vox.empty()) {
1740 uint materialID_from_xml_vox = 0;
1741 const char *mat_id_str = material_id_node_vox.child_value();
1742 if (
parse_uint(mat_id_str, materialID_from_xml_vox)) {
1744 auto it = legacy_material_id_to_label.find(materialID_from_xml_vox);
1745 if (it != legacy_material_id_to_label.end()) {
1746 material_label_from_xml_vox = it->second;
1747 has_material_vox =
true;
1752 if (has_material_vox) {
1754 ID =
addVoxel(
make_vec3(0, 0, 0),
make_vec3(0, 0, 0), 0,
make_RGBAcolor(0, 0, 0, 1));
1758 pugi::xml_node color_node = p.child(
"color");
1760 const char *color_str = color_node.child_value();
1761 if (strlen(color_str) == 0) {
1771 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1774 if (has_material_vox && !material_label_from_xml_vox.empty()) {
1779 object_prim_UUIDs[objID].push_back(ID);
1794 for (pugi::xml_node p = helios.child(
"tile"); p; p = p.next_sibling(
"tile")) {
1798 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'tile' block must be a non-negative integer value.");
1802 float transform[16];
1805 helios_runtime_error(
"ERROR (Context::loadXML): Tile <transform> node contains less than 16 data values.");
1806 }
else if (result == 2) {
1807 helios_runtime_error(
"ERROR (Context::loadXML): Tile <transform> node contains invalid data.");
1811 std::string texture_file;
1815 std::vector<vec2> uv;
1817 helios_runtime_error(
"ERROR (Context::loadXML): (u,v) coordinates given in 'tile' block contain invalid data.");
1822 pugi::xml_node color_node = p.child(
"color");
1824 const char *color_str = color_node.child_value();
1825 if (strlen(color_str) != 0) {
1832 if (result_subdiv == 1) {
1833 std::cerr <<
"WARNING (Context::loadXML): Number of subdivisions for tile was not provided. Assuming 1x1." << std::endl;
1835 }
else if (result_subdiv == 2) {
1836 helios_runtime_error(
"ERROR (Context::loadXML): Tile <subdivisions> node contains invalid data. ");
1841 patch.setTransformationMatrix(transform);
1856 if (strcmp(texture_file.c_str(),
"none") == 0) {
1857 if (strlen(color_str) == 0) {
1871 if (objID > 0 && !object_prim_UUIDs.empty() && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1882 loadOsubPData(p, ID);
1889 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1893 for (pugi::xml_node p = helios.child(
"sphere"); p; p = p.next_sibling(
"sphere")) {
1897 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'sphere' block must be a non-negative integer value.");
1901 objID = currentObjectID;
1906 float transform[16];
1909 helios_runtime_error(
"ERROR (Context::loadXML): Sphere <transform> node contains less than 16 data values.");
1910 }
else if (result == 2) {
1911 helios_runtime_error(
"ERROR (Context::loadXML): Sphere <transform> node contains invalid data.");
1915 std::string texture_file;
1920 pugi::xml_node color_node = p.child(
"color");
1922 const char *color_str = color_node.child_value();
1923 if (strlen(color_str) != 0) {
1930 if (result_subdiv == 1) {
1931 std::cerr <<
"WARNING (Context::loadXML): Number of subdivisions for sphere was not provided. Assuming 1x1." << std::endl;
1933 }
else if (result_subdiv == 2) {
1934 helios_runtime_error(
"ERROR (Context::loadXML): Sphere <subdivisions> node contains invalid data. ");
1938 std::vector<uint> empty;
1939 Sphere sphere(0, empty, 3,
"",
this);
1943 if (strcmp(texture_file.c_str(),
"none") == 0) {
1944 if (strlen(color_str) == 0) {
1954 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1967 loadOsubPData(p, ID);
1974 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1978 for (pugi::xml_node p = helios.child(
"tube"); p; p = p.next_sibling(
"tube")) {
1982 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'tube' block must be a non-negative integer value.");
1986 objID = currentObjectID;
1991 float transform[16];
1994 helios_runtime_error(
"ERROR (Context::loadXML): Tube <transform> node contains less than 16 data values.");
1995 }
else if (result == 2) {
1996 helios_runtime_error(
"ERROR (Context::loadXML): Tube <transform> node contains invalid data.");
2000 std::string texture_file;
2006 if (result_subdiv == 1) {
2007 std::cerr <<
"WARNING (Context::loadXML): Number of subdivisions for tube was not provided. Assuming 1x1." << std::endl;
2009 }
else if (result_subdiv == 2) {
2010 helios_runtime_error(
"ERROR (Context::loadXML): Tube <subdivisions> node contains invalid data. ");
2014 std::vector<vec3> nodes;
2015 pugi::xml_node nodes_node = p.child(
"nodes");
2021 std::vector<float> radii;
2022 pugi::xml_node radii_node = p.child(
"radius");
2029 pugi::xml_node color_node = p.child(
"color");
2030 const char *color_str = color_node.child_value();
2032 std::vector<RGBcolor> colors;
2033 if (strlen(color_str) > 0) {
2034 std::istringstream data_stream(color_str);
2035 std::vector<float> tmp;
2038 while (data_stream >> tmp.at(c)) {
2041 colors.push_back(
make_RGBcolor(tmp.at(0), tmp.at(1), tmp.at(2)));
2048 if (texture_file ==
"none") {
2051 ID =
addTubeObject(subdiv, nodes, radii, texture_file.c_str());
2057 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2070 loadOsubPData(p, ID);
2077 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2081 for (pugi::xml_node p = helios.child(
"box"); p; p = p.next_sibling(
"box")) {
2085 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'box' block must be a non-negative integer value.");
2089 objID = currentObjectID;
2094 float transform[16];
2097 helios_runtime_error(
"ERROR (Context::loadXML): Box <transform> node contains less than 16 data values.");
2098 }
else if (result == 2) {
2103 std::string texture_file;
2108 pugi::xml_node color_node = p.child(
"color");
2110 const char *color_str = color_node.child_value();
2111 if (strlen(color_str) != 0) {
2118 if (result_subdiv == 1) {
2119 std::cerr <<
"WARNING (Context::loadXML): Number of subdivisions for box was not provided. Assuming 1x1." << std::endl;
2121 }
else if (result_subdiv == 2) {
2122 helios_runtime_error(
"ERROR (Context::loadXML): Box <subdivisions> node contains invalid data. ");
2126 std::vector<uint> empty;
2131 if (strcmp(texture_file.c_str(),
"none") == 0) {
2132 if (strlen(color_str) == 0) {
2142 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2155 loadOsubPData(p, ID);
2162 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2166 for (pugi::xml_node p = helios.child(
"disk"); p; p = p.next_sibling(
"disk")) {
2170 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'disk' block must be a non-negative integer value.");
2174 objID = currentObjectID;
2179 float transform[16];
2182 helios_runtime_error(
"ERROR (Context::loadXML): Disk <transform> node contains less than 16 data values.");
2183 }
else if (result == 2) {
2184 helios_runtime_error(
"ERROR (Context::loadXML): Disk <transform> node contains invalid data.");
2188 std::string texture_file;
2193 pugi::xml_node color_node = p.child(
"color");
2195 const char *color_str = color_node.child_value();
2196 if (strlen(color_str) != 0) {
2203 if (result_subdiv == 1) {
2204 std::cerr <<
"WARNING (Context::loadXML): Number of subdivisions for disk was not provided. Assuming 1x1." << std::endl;
2206 }
else if (result_subdiv == 2) {
2207 helios_runtime_error(
"ERROR (Context::loadXML): Disk <subdivisions> node contains invalid data. ");
2211 std::vector<uint> empty;
2216 if (strcmp(texture_file.c_str(),
"none") == 0) {
2217 if (strlen(color_str) == 0) {
2227 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2240 loadOsubPData(p, ID);
2247 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2251 for (pugi::xml_node p = helios.child(
"cone"); p; p = p.next_sibling(
"cone")) {
2255 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'cone' block must be a non-negative integer value.");
2259 objID = currentObjectID;
2264 float transform[16];
2267 helios_runtime_error(
"ERROR (Context::loadXML): Cone <transform> node contains less than 16 data values.");
2268 }
else if (result == 2) {
2269 helios_runtime_error(
"ERROR (Context::loadXML): Cone <transform> node contains invalid data.");
2273 std::string texture_file;
2278 pugi::xml_node color_node = p.child(
"color");
2280 const char *color_str = color_node.child_value();
2281 if (strlen(color_str) != 0) {
2288 if (result_subdiv == 1) {
2289 std::cerr <<
"WARNING (Context::loadXML): Number of subdivisions for cone was not provided. Assuming 1x1." << std::endl;
2291 }
else if (result_subdiv == 2) {
2292 helios_runtime_error(
"ERROR (Context::loadXML): Cone <subdivisions> node contains invalid data. ");
2296 std::vector<vec3> nodes;
2297 pugi::xml_node nodes_node = p.child(
"nodes");
2303 std::vector<float> radii;
2304 pugi::xml_node radii_node = p.child(
"radius");
2310 if (texture_file ==
"none") {
2313 ID =
addConeObject(subdiv, nodes.at(0), nodes.at(1), radii.at(0), radii.at(1), texture_file.c_str());
2319 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2332 loadOsubPData(p, ID);
2339 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2343 for (pugi::xml_node p = helios.child(
"polymesh"); p; p = p.next_sibling(
"polymesh")) {
2347 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'polymesh' block must be a non-negative integer value.");
2351 objID = currentObjectID;
2361 loadOsubPData(p, ID);
2367 std::vector<uint> childUUIDs = object_prim_UUIDs.at(objID);
2368 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2373 for (pugi::xml_node data = helios.child(
"globaldata_int"); data; data = data.next_sibling(
"globaldata_int")) {
2374 const char *label = data.attribute(
"label").value();
2376 std::vector<int> datav;
2378 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int> with label " + std::string(label) +
" contained invalid data.");
2381 if (datav.size() == 1) {
2383 }
else if (datav.size() > 1) {
2388 for (pugi::xml_node data = helios.child(
"globaldata_uint"); data; data = data.next_sibling(
"globaldata_uint")) {
2389 const char *label = data.attribute(
"label").value();
2391 std::vector<uint> datav;
2393 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_uint> with label " + std::string(label) +
" contained invalid data.");
2396 if (datav.size() == 1) {
2398 }
else if (datav.size() > 1) {
2403 for (pugi::xml_node data = helios.child(
"globaldata_float"); data; data = data.next_sibling(
"globaldata_float")) {
2404 const char *label = data.attribute(
"label").value();
2406 std::vector<float> datav;
2408 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_float> with label " + std::string(label) +
" contained invalid data.");
2411 if (datav.size() == 1) {
2413 }
else if (datav.size() > 1) {
2418 for (pugi::xml_node data = helios.child(
"globaldata_double"); data; data = data.next_sibling(
"globaldata_double")) {
2419 const char *label = data.attribute(
"label").value();
2421 std::vector<double> datav;
2423 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_double> with label " + std::string(label) +
" contained invalid data.");
2426 if (datav.size() == 1) {
2428 }
else if (datav.size() > 1) {
2433 for (pugi::xml_node data = helios.child(
"globaldata_vec2"); data; data = data.next_sibling(
"globaldata_vec2")) {
2434 const char *label = data.attribute(
"label").value();
2436 std::vector<vec2> datav;
2438 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_vec2> with label " + std::string(label) +
" contained invalid data.");
2441 if (datav.size() == 1) {
2443 }
else if (datav.size() > 1) {
2448 for (pugi::xml_node data = helios.child(
"globaldata_vec3"); data; data = data.next_sibling(
"globaldata_vec3")) {
2449 const char *label = data.attribute(
"label").value();
2451 std::vector<vec3> datav;
2453 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_vec3> with label " + std::string(label) +
" contained invalid data.");
2456 if (datav.size() == 1) {
2458 }
else if (datav.size() > 1) {
2463 for (pugi::xml_node data = helios.child(
"globaldata_vec4"); data; data = data.next_sibling(
"globaldata_vec4")) {
2464 const char *label = data.attribute(
"label").value();
2466 std::vector<vec4> datav;
2468 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_vec4> with label " + std::string(label) +
" contained invalid data.");
2471 if (datav.size() == 1) {
2473 }
else if (datav.size() > 1) {
2478 for (pugi::xml_node data = helios.child(
"globaldata_int2"); data; data = data.next_sibling(
"globaldata_int2")) {
2479 const char *label = data.attribute(
"label").value();
2481 std::vector<int2> datav;
2483 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int2> with label " + std::string(label) +
" contained invalid data.");
2486 if (datav.size() == 1) {
2488 }
else if (datav.size() > 1) {
2493 for (pugi::xml_node data = helios.child(
"globaldata_int3"); data; data = data.next_sibling(
"globaldata_int3")) {
2494 const char *label = data.attribute(
"label").value();
2496 std::vector<int3> datav;
2498 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int3> with label " + std::string(label) +
" contained invalid data.");
2501 if (datav.size() == 1) {
2503 }
else if (datav.size() > 1) {
2508 for (pugi::xml_node data = helios.child(
"globaldata_int4"); data; data = data.next_sibling(
"globaldata_int4")) {
2509 const char *label = data.attribute(
"label").value();
2511 std::vector<int4> datav;
2513 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int4> with label " + std::string(label) +
" contained invalid data.");
2516 if (datav.size() == 1) {
2518 }
else if (datav.size() > 1) {
2523 for (pugi::xml_node data = helios.child(
"globaldata_string"); data; data = data.next_sibling(
"globaldata_string")) {
2524 const char *label = data.attribute(
"label").value();
2526 std::vector<std::string> datav;
2528 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_string> with label " + std::string(label) +
" contained invalid data.");
2531 if (datav.size() == 1) {
2533 }
else if (datav.size() > 1) {
2539 for (pugi::xml_node p = helios.child(
"timeseries"); p; p = p.next_sibling(
"timeseries")) {
2540 const char *label = p.attribute(
"label").value();
2542 for (pugi::xml_node d = p.child(
"datapoint"); d; d = d.next_sibling(
"datapoint")) {
2544 pugi::xml_node time_node = d.child(
"time");
2545 const char *time_str = time_node.child_value();
2546 if (strlen(time_str) > 0) {
2548 if (time_.
x < 0 || time_.
x > 23) {
2549 helios_runtime_error(
"ERROR (Context::loadXML): Invalid hour of " + std::to_string(time_.
x) +
" given in timeseries. Hour must be positive and not greater than 23.");
2550 }
else if (time_.
y < 0 || time_.
y > 59) {
2551 helios_runtime_error(
"ERROR (Context::loadXML): Invalid minute of " + std::to_string(time_.
y) +
" given in timeseries. Minute must be positive and not greater than 59.");
2552 }
else if (time_.
z < 0 || time_.
z > 59) {
2553 helios_runtime_error(
"ERROR (Context::loadXML): Invalid second of " + std::to_string(time_.
z) +
" given in timeseries. Second must be positive and not greater than 59.");
2557 helios_runtime_error(
"ERROR (Context::loadXML): No time was specified for timeseries datapoint.");
2561 bool date_flag =
false;
2563 pugi::xml_node date_node = d.child(
"date");
2564 const char *date_str = date_node.child_value();
2565 if (strlen(date_str) > 0) {
2567 if (date_.
x < 1 || date_.
x > 31) {
2568 helios_runtime_error(
"ERROR (Context::loadXML): Invalid day of month " + std::to_string(date_.
x) +
" given in timeseries. Day must be greater than zero and not greater than 31.");
2569 }
else if (date_.
y < 1 || date_.
y > 12) {
2570 helios_runtime_error(
"ERROR (Context::loadXML): Invalid month of " + std::to_string(date_.
y) +
" given in timeseries. Month must be greater than zero and not greater than 12.");
2571 }
else if (date_.
z < 1000 || date_.
z > 10000) {
2572 helios_runtime_error(
"ERROR (Context::loadXML): Invalid year of " + std::to_string(date_.
z) +
" given in timeseries. Year should be in YYYY format.");
2578 pugi::xml_node Jdate_node = d.child(
"dateJulian");
2579 const char *Jdate_str = Jdate_node.child_value();
2580 if (strlen(Jdate_str) > 0) {
2582 if (date_.
x < 1 || date_.
x > 366) {
2583 helios_runtime_error(
"ERROR (Context::loadXML): Invalid Julian day of year " + std::to_string(date_.
x) +
" given in timeseries. Julian day must be greater than zero and not greater than 366.");
2584 }
else if (date_.
y < 1000 || date_.
y > 10000) {
2585 helios_runtime_error(
"ERROR (Context::loadXML): Invalid year of " + std::to_string(date_.
y) +
" given in timeseries. Year should be in YYYY format.");
2592 helios_runtime_error(
"ERROR (Context::loadXML): No date was specified for timeseries datapoint.");
2596 pugi::xml_node value_node = d.child(
"value");
2597 const char *value_str = value_node.child_value();
2598 if (strlen(value_str) > 0) {
2600 helios_runtime_error(
"ERROR (Context::loadXML): Datapoint value in 'timeseries' block must be a float value.");
2603 helios_runtime_error(
"ERROR (Context::loadXML): No value was specified for timeseries datapoint.");
2611 std::cout <<
"done." << std::endl;
2622 const std::string &fn = filename;
2624 if (ext !=
".xml" && ext !=
".XML") {
2629 pugi::xml_document xmldoc;
2632 pugi::xml_parse_result load_result = xmldoc.load_file(filename.c_str());
2636 helios_runtime_error(
"failed.\n XML [" + filename +
"] parsed with errors, attr value: [" + xmldoc.child(
"node").attribute(
"attr").value() +
"]\nError description: " + load_result.description() +
2637 "\nError offset: " + std::to_string(load_result.offset) +
" (error at [..." + (filename.c_str() + load_result.offset) +
"]\n");
2640 pugi::xml_node helios = xmldoc.child(
"helios");
2642 if (helios.empty()) {
2646 for (pugi::xml_node p = helios.child(tag.c_str()); p; p = p.next_sibling(tag.c_str())) {
2647 const char *labelquery = p.attribute(
"label").value();
2649 if (labelquery == label || label.empty()) {
2657void Context::writeDataToXMLstream(
const char *data_group,
const std::vector<std::string> &data_labels,
void *ptr, std::ofstream &outfile)
const {
2658 for (
const auto &label: data_labels) {
2661 if (strcmp(data_group,
"primitive") == 0) {
2662 dtype = ((Primitive *) ptr)->getPrimitiveDataType(label.c_str());
2663 }
else if (strcmp(data_group,
"object") == 0) {
2664 dtype = ((
CompoundObject *) ptr)->getObjectDataType(label.c_str());
2665 }
else if (strcmp(data_group,
"material") == 0) {
2666 dtype = ((
Material *) ptr)->getMaterialDataType(label.c_str());
2667 }
else if (strcmp(data_group,
"global") == 0) {
2670 helios_runtime_error(
"ERROR (Context::writeDataToXMLstream): unknown data group argument of " + std::string(data_group) +
". Must be one of primitive, object, material, or global.");
2673 if (dtype == HELIOS_TYPE_UINT) {
2674 outfile <<
"\t<data_uint label=\"" << label <<
"\">" << std::flush;
2675 std::vector<uint> data;
2676 if (strcmp(data_group,
"primitive") == 0) {
2677 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2678 }
else if (strcmp(data_group,
"object") == 0) {
2680 }
else if (strcmp(data_group,
"material") == 0) {
2681 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2685 for (
int j = 0; j < data.size(); j++) {
2686 outfile << data.at(j) << std::flush;
2687 if (j != data.size() - 1) {
2688 outfile <<
" " << std::flush;
2691 outfile <<
"</data_uint>" << std::endl;
2692 }
else if (dtype == HELIOS_TYPE_INT) {
2693 outfile <<
"\t<data_int label=\"" << label <<
"\">" << std::flush;
2694 std::vector<int> data;
2695 if (strcmp(data_group,
"primitive") == 0) {
2696 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2697 }
else if (strcmp(data_group,
"object") == 0) {
2699 }
else if (strcmp(data_group,
"material") == 0) {
2700 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2704 for (
int j = 0; j < data.size(); j++) {
2705 outfile << data.at(j) << std::flush;
2706 if (j != data.size() - 1) {
2707 outfile <<
" " << std::flush;
2710 outfile <<
"</data_int>" << std::endl;
2711 }
else if (dtype == HELIOS_TYPE_FLOAT) {
2712 outfile <<
"\t<data_float label=\"" << label <<
"\">" << std::flush;
2713 std::vector<float> data;
2714 if (strcmp(data_group,
"primitive") == 0) {
2715 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2716 }
else if (strcmp(data_group,
"object") == 0) {
2718 }
else if (strcmp(data_group,
"material") == 0) {
2719 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2723 for (
int j = 0; j < data.size(); j++) {
2724 outfile << data.at(j) << std::flush;
2725 if (j != data.size() - 1) {
2726 outfile <<
" " << std::flush;
2729 outfile <<
"</data_float>" << std::endl;
2730 }
else if (dtype == HELIOS_TYPE_DOUBLE) {
2731 outfile <<
"\t<data_double label=\"" << label <<
"\">" << std::flush;
2732 std::vector<double> data;
2733 if (strcmp(data_group,
"primitive") == 0) {
2734 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2735 }
else if (strcmp(data_group,
"object") == 0) {
2737 }
else if (strcmp(data_group,
"material") == 0) {
2738 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2742 for (
int j = 0; j < data.size(); j++) {
2743 outfile << data.at(j) << std::flush;
2744 if (j != data.size() - 1) {
2745 outfile <<
" " << std::flush;
2748 outfile <<
"</data_double>" << std::endl;
2749 }
else if (dtype == HELIOS_TYPE_VEC2) {
2750 outfile <<
"\t<data_vec2 label=\"" << label <<
"\">" << std::flush;
2751 std::vector<vec2> data;
2752 if (strcmp(data_group,
"primitive") == 0) {
2753 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2754 }
else if (strcmp(data_group,
"object") == 0) {
2756 }
else if (strcmp(data_group,
"material") == 0) {
2757 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2761 for (
int j = 0; j < data.size(); j++) {
2762 outfile << data.at(j).x <<
" " << data.at(j).y << std::flush;
2763 if (j != data.size() - 1) {
2764 outfile <<
" " << std::flush;
2767 outfile <<
"</data_vec2>" << std::endl;
2768 }
else if (dtype == HELIOS_TYPE_VEC3) {
2769 outfile <<
"\t<data_vec3 label=\"" << label <<
"\">" << std::flush;
2770 std::vector<vec3> data;
2771 if (strcmp(data_group,
"primitive") == 0) {
2772 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2773 }
else if (strcmp(data_group,
"object") == 0) {
2775 }
else if (strcmp(data_group,
"material") == 0) {
2776 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2780 for (
int j = 0; j < data.size(); j++) {
2781 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z << std::flush;
2782 if (j != data.size() - 1) {
2783 outfile <<
" " << std::flush;
2786 outfile <<
"</data_vec3>" << std::endl;
2787 }
else if (dtype == HELIOS_TYPE_VEC4) {
2788 outfile <<
"\t<data_vec4 label=\"" << label <<
"\">" << std::flush;
2789 std::vector<vec4> data;
2790 if (strcmp(data_group,
"primitive") == 0) {
2791 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2792 }
else if (strcmp(data_group,
"object") == 0) {
2794 }
else if (strcmp(data_group,
"material") == 0) {
2795 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2799 for (
int j = 0; j < data.size(); j++) {
2800 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z <<
" " << data.at(j).w << std::flush;
2801 if (j != data.size() - 1) {
2802 outfile <<
" " << std::flush;
2805 outfile <<
"</data_vec4>" << std::endl;
2806 }
else if (dtype == HELIOS_TYPE_INT2) {
2807 outfile <<
"\t<data_int2 label=\"" << label <<
"\">" << std::flush;
2808 std::vector<int2> data;
2809 if (strcmp(data_group,
"primitive") == 0) {
2810 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2811 }
else if (strcmp(data_group,
"object") == 0) {
2813 }
else if (strcmp(data_group,
"material") == 0) {
2814 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2818 for (
int j = 0; j < data.size(); j++) {
2819 outfile << data.at(j).x <<
" " << data.at(j).y << std::flush;
2820 if (j != data.size() - 1) {
2821 outfile <<
" " << std::flush;
2824 outfile <<
"</data_int2>" << std::endl;
2825 }
else if (dtype == HELIOS_TYPE_INT3) {
2826 outfile <<
"\t<data_int3 label=\"" << label <<
"\">" << std::flush;
2827 std::vector<int3> data;
2828 if (strcmp(data_group,
"primitive") == 0) {
2829 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2830 }
else if (strcmp(data_group,
"object") == 0) {
2832 }
else if (strcmp(data_group,
"material") == 0) {
2833 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2837 for (
int j = 0; j < data.size(); j++) {
2838 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z << std::flush;
2839 if (j != data.size() - 1) {
2840 outfile <<
" " << std::flush;
2843 outfile <<
"</data_int3>" << std::endl;
2844 }
else if (dtype == HELIOS_TYPE_INT4) {
2845 outfile <<
"\t<data_int3 label=\"" << label <<
"\">" << std::flush;
2846 std::vector<int4> data;
2847 if (strcmp(data_group,
"primitive") == 0) {
2848 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2849 }
else if (strcmp(data_group,
"object") == 0) {
2851 }
else if (strcmp(data_group,
"material") == 0) {
2852 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2856 for (
int j = 0; j < data.size(); j++) {
2857 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z <<
" " << data.at(j).w << std::flush;
2858 if (j != data.size() - 1) {
2859 outfile <<
" " << std::flush;
2862 outfile <<
"</data_int4>" << std::endl;
2863 }
else if (dtype == HELIOS_TYPE_STRING) {
2864 outfile <<
"\t<data_string label=\"" << label <<
"\">" << std::flush;
2865 std::vector<std::string> data;
2866 if (strcmp(data_group,
"primitive") == 0) {
2867 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2868 }
else if (strcmp(data_group,
"object") == 0) {
2870 }
else if (strcmp(data_group,
"material") == 0) {
2871 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2875 for (
int j = 0; j < data.size(); j++) {
2876 outfile << data.at(j) << std::flush;
2877 if (j != data.size() - 1) {
2878 outfile <<
" " << std::flush;
2881 outfile <<
"</data_string>" << std::endl;
2891 for (
uint objID: objIDs) {
2893 helios_runtime_error(
"ERROR (Context::writeXML_byobject): Object with ID of " + std::to_string(objID) +
" does not exist.");
2901 std::cout <<
"Writing XML file " << filename <<
"..." << std::flush;
2904 std::string xmlfilename = filename;
2911 helios_runtime_error(
"ERROR (Context::writeXML): Invalid output file " + xmlfilename +
". No file name was provided.");
2915 if (file_extension !=
".xml" && file_extension !=
".XML") {
2916 xmlfilename.append(
".xml");
2921 std::ofstream outfile;
2922 outfile.open(xmlfilename);
2924 outfile <<
"<?xml version=\"1.0\"?>\n\n";
2926 outfile <<
"<helios>\n\n";
2931 std::set<std::string> material_labels_used;
2932 for (
uint UUID: UUIDs) {
2934 uint matID = getPrimitivePointer_private(UUID)->materialID;
2935 if (materials.find(matID) != materials.end()) {
2936 material_labels_used.insert(materials.at(matID).label);
2941 if (!material_labels_used.empty()) {
2942 outfile <<
" <materials>" << std::endl;
2943 for (
const std::string &label: material_labels_used) {
2946 const Material &mat = materials.at(matID);
2947 outfile <<
"\t<material label=\"" << mat.
label <<
"\">" << std::endl;
2948 outfile <<
"\t\t<color>" << mat.
color.
r <<
" " << mat.
color.
g <<
" " << mat.
color.
b <<
" " << mat.
color.
a <<
"</color>" << std::endl;
2950 outfile <<
"\t\t<texture>" << mat.
texture_file <<
"</texture>" << std::endl;
2953 outfile <<
"\t\t<texture_override>1</texture_override>" << std::endl;
2956 outfile <<
"\t\t<twosided_flag>" << mat.
twosided_flag <<
"</twosided_flag>" << std::endl;
2960 if (!mdata.empty()) {
2961 writeDataToXMLstream(
"material", mdata,
const_cast<Material *
>(&mat), outfile);
2963 outfile <<
"\t</material>" << std::endl;
2966 outfile <<
" </materials>\n" << std::endl;
2973 outfile <<
" <date>" << std::endl;
2975 outfile <<
"\t<day>" << date.
day <<
"</day>" << std::endl;
2976 outfile <<
"\t<month>" << date.
month <<
"</month>" << std::endl;
2977 outfile <<
"\t<year>" << date.
year <<
"</year>" << std::endl;
2979 outfile <<
" </date>" << std::endl;
2983 outfile <<
" <time>" << std::endl;
2985 outfile <<
"\t<hour>" << time.
hour <<
"</hour>" << std::endl;
2986 outfile <<
"\t<minute>" << time.
minute <<
"</minute>" << std::endl;
2987 outfile <<
"\t<second>" << time.
second <<
"</second>" << std::endl;
2989 outfile <<
" </time>" << std::endl;
2993 for (
uint UUID: UUIDs) {
2998 helios_runtime_error(
"ERROR (Context::writeXML): Primitive with UUID of " + std::to_string(p) +
" does not exist. There is a compound object with this ID - did you mean to call Context::writeXML_byobject()?");
3000 helios_runtime_error(
"ERROR (Context::writeXML): Primitive with UUID of " + std::to_string(p) +
" does not exist.");
3004 Primitive *prim = getPrimitivePointer_private(p);
3006 uint parent_objID = prim->getParentObjectID();
3010 std::string texture_file = prim->getTextureFile();
3012 std::vector<std::string> pdata = prim->listPrimitiveData();
3021 outfile <<
" <patch>" << std::endl;
3023 outfile <<
" <triangle>" << std::endl;
3025 outfile <<
" <voxel>" << std::endl;
3028 outfile <<
"\t<UUID>" << p <<
"</UUID>" << std::endl;
3030 if (parent_objID > 0) {
3031 outfile <<
"\t<objID>" << parent_objID <<
"</objID>" << std::endl;
3035 if (materials.find(prim->materialID) != materials.end()) {
3036 outfile <<
"\t<material>" << materials.at(prim->materialID).label <<
"</material>" << std::endl;
3039 if (!pdata.empty()) {
3040 writeDataToXMLstream(
"primitive", pdata, prim, outfile);
3045 Patch *patch = getPatchPointer_private(p);
3046 float transform[16];
3047 prim->getTransformationMatrix(transform);
3049 outfile <<
"\t<transform>";
3050 for (
float i: transform) {
3051 outfile << i <<
" ";
3053 outfile <<
"</transform>" << std::endl;
3054 std::vector<vec2> uv = patch->getTextureUV();
3056 outfile <<
"\t<textureUV>" << std::flush;
3057 for (
int i = 0; i < uv.size(); i++) {
3058 outfile << uv.at(i).x <<
" " << uv.at(i).y << std::flush;
3059 if (i != uv.size() - 1) {
3060 outfile <<
" " << std::flush;
3063 outfile <<
"</textureUV>" << std::endl;
3068 outfile <<
" </patch>" << std::endl;
3072 float transform[16];
3073 prim->getTransformationMatrix(transform);
3075 outfile <<
"\t<transform>";
3076 for (
float i: transform) {
3077 outfile << i <<
" ";
3079 outfile <<
"</transform>" << std::endl;
3081 std::vector<vec2> uv = getTrianglePointer_private(p)->getTextureUV();
3083 outfile <<
"\t<textureUV>" << std::flush;
3084 for (
int i = 0; i < uv.size(); i++) {
3085 outfile << uv.at(i).x <<
" " << uv.at(i).y << std::flush;
3086 if (i != uv.size() - 1) {
3087 outfile <<
" " << std::flush;
3090 outfile <<
"</textureUV>" << std::endl;
3095 outfile <<
" </triangle>" << std::endl;
3099 float transform[16];
3100 prim->getTransformationMatrix(transform);
3102 outfile <<
"\t<transform>";
3103 for (
float i: transform) {
3104 outfile << i <<
" ";
3106 outfile <<
"</transform>" << std::endl;
3111 outfile <<
" </voxel>" << std::endl;
3117 for (
auto o: objectIDs) {
3120 std::string texture_file =
obj->getTextureFile();
3122 std::vector<std::string> odata =
obj->listObjectData();
3125 outfile <<
" <tile>" << std::endl;
3127 outfile <<
" <box>" << std::endl;
3129 outfile <<
" <cone>" << std::endl;
3131 outfile <<
" <disk>" << std::endl;
3133 outfile <<
" <sphere>" << std::endl;
3135 outfile <<
" <tube>" << std::endl;
3137 outfile <<
" <polymesh>" << std::endl;
3140 outfile <<
"\t<objID>" << o <<
"</objID>" << std::endl;
3141 if (
obj->hasTexture()) {
3142 outfile <<
"\t<texture>" << texture_file <<
"</texture>" << std::endl;
3145 if (!odata.empty()) {
3146 writeDataToXMLstream(
"object", odata,
obj, outfile);
3149 std::vector<std::string> pdata_labels;
3150 std::vector<HeliosDataType> pdata_types;
3151 std::vector<uint> primitiveUUIDs =
obj->getPrimitiveUUIDs();
3152 for (
uint UUID: primitiveUUIDs) {
3153 std::vector<std::string> labels = getPrimitivePointer_private(UUID)->listPrimitiveData();
3154 for (
const auto &label: labels) {
3155 if (find(pdata_labels.begin(), pdata_labels.end(), label) == pdata_labels.end()) {
3156 pdata_labels.push_back(label);
3161 for (
size_t l = 0; l < pdata_labels.size(); l++) {
3163 outfile <<
"\t<primitive_data_float " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3164 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3166 std::vector<float> data;
3168 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3169 for (
float i: data) {
3170 outfile << i << std::flush;
3172 outfile <<
" </data>" << std::endl;
3175 outfile <<
"\t</primitive_data_float>" << std::endl;
3177 outfile <<
"\t<primitive_data_double " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3178 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3180 std::vector<double> data;
3182 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3183 for (
double i: data) {
3184 outfile << i << std::flush;
3186 outfile <<
" </data>" << std::endl;
3189 outfile <<
"\t</primitive_data_double>" << std::endl;
3191 outfile <<
"\t<primitive_data_uint " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3192 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3194 std::vector<uint> data;
3196 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3197 for (
unsigned int i: data) {
3198 outfile << i << std::flush;
3200 outfile <<
" </data>" << std::endl;
3203 outfile <<
"\t</primitive_data_uint>" << std::endl;
3205 outfile <<
"\t<primitive_data_int " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3206 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3208 std::vector<int> data;
3210 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3212 outfile << i << std::flush;
3214 outfile <<
" </data>" << std::endl;
3217 outfile <<
"\t</primitive_data_int>" << std::endl;
3219 outfile <<
"\t<primitive_data_int2 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3220 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3222 std::vector<int2> data;
3224 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3225 for (
auto &i: data) {
3226 outfile << i.x <<
" " << i.y << std::flush;
3228 outfile <<
" </data>" << std::endl;
3231 outfile <<
"\t</primitive_data_int2>" << std::endl;
3233 outfile <<
"\t<primitive_data_int3 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3234 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3236 std::vector<int3> data;
3238 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3239 for (
auto &i: data) {
3240 outfile << i.x <<
" " << i.y <<
" " << i.z << std::flush;
3242 outfile <<
" </data>" << std::endl;
3245 outfile <<
"\t</primitive_data_int3>" << std::endl;
3247 outfile <<
"\t<primitive_data_int4 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3248 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3250 std::vector<int4> data;
3252 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3253 for (
auto &i: data) {
3254 outfile << i.x <<
" " << i.y <<
" " << i.z <<
" " << i.w << std::flush;
3256 outfile <<
" </data>" << std::endl;
3259 outfile <<
"\t</primitive_data_int4>" << std::endl;
3261 outfile <<
"\t<primitive_data_vec2 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3262 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3264 std::vector<vec2> data;
3266 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3267 for (
auto &i: data) {
3268 outfile << i.x <<
" " << i.y << std::flush;
3270 outfile <<
" </data>" << std::endl;
3273 outfile <<
"\t</primitive_data_vec2>" << std::endl;
3275 outfile <<
"\t<primitive_data_vec3 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3276 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3278 std::vector<vec3> data;
3280 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3281 for (
auto &i: data) {
3282 outfile << i.x <<
" " << i.y <<
" " << i.z << std::flush;
3284 outfile <<
" </data>" << std::endl;
3287 outfile <<
"\t</primitive_data_vec3>" << std::endl;
3289 outfile <<
"\t<primitive_data_vec4 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3290 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3292 std::vector<vec4> data;
3294 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3295 for (
auto &i: data) {
3296 outfile << i.x <<
" " << i.y <<
" " << i.z <<
" " << i.w << std::flush;
3298 outfile <<
" </data>" << std::endl;
3301 outfile <<
"\t</primitive_data_vec4>" << std::endl;
3303 outfile <<
"\t<primitive_data_string " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3304 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3306 std::vector<std::string> data;
3308 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3309 for (
const auto &i: data) {
3310 outfile << i << std::flush;
3312 outfile <<
" </data>" << std::endl;
3315 outfile <<
"\t</primitive_data_string>" << std::endl;
3321 Tile *tile = getTileObjectPointer_private(o);
3323 float transform[16];
3327 outfile <<
"\t<subdivisions>" << subdiv.
x <<
" " << subdiv.
y <<
"</subdivisions>" << std::endl;
3329 outfile <<
"\t<transform> ";
3330 for (
float i: transform) {
3331 outfile << i <<
" ";
3333 outfile <<
"</transform>" << std::endl;
3335 outfile <<
" </tile>" << std::endl;
3339 Sphere *sphere = getSphereObjectPointer_private(o);
3341 float transform[16];
3344 outfile <<
"\t<transform> ";
3345 for (
float i: transform) {
3346 outfile << i <<
" ";
3348 outfile <<
"</transform>" << std::endl;
3351 outfile <<
"\t<subdivisions> " << subdiv <<
" </subdivisions>" << std::endl;
3353 outfile <<
" </sphere>" << std::endl;
3357 Tube *tube = getTubeObjectPointer_private(o);
3359 float transform[16];
3362 outfile <<
"\t<transform> ";
3363 for (
float i: transform) {
3364 outfile << i <<
" ";
3366 outfile <<
"</transform>" << std::endl;
3369 outfile <<
"\t<subdivisions> " << subdiv <<
" </subdivisions>" << std::endl;
3371 std::vector<vec3> nodes = tube->
getNodes();
3374 assert(nodes.size() == radius.size());
3375 outfile <<
"\t<nodes> " << std::endl;
3376 for (
auto &node: nodes) {
3377 outfile <<
"\t\t" << node.x <<
" " << node.y <<
" " << node.z << std::endl;
3379 outfile <<
"\t</nodes> " << std::endl;
3380 outfile <<
"\t<radius> " << std::endl;
3381 for (
float radiu: radius) {
3382 outfile <<
"\t\t" << radiu << std::endl;
3384 outfile <<
"\t</radius> " << std::endl;
3386 if (texture_file.empty()) {
3389 outfile <<
"\t<color> " << std::endl;
3390 for (
auto &color: colors) {
3391 outfile <<
"\t\t" << color.r <<
" " << color.g <<
" " << color.b << std::endl;
3393 outfile <<
"\t</color> " << std::endl;
3396 outfile <<
" </tube>" << std::endl;
3400 Box *box = getBoxObjectPointer_private(o);
3402 float transform[16];
3405 outfile <<
"\t<transform> ";
3406 for (
float i: transform) {
3407 outfile << i <<
" ";
3409 outfile <<
"</transform>" << std::endl;
3412 outfile <<
"\t<subdivisions> " << subdiv.
x <<
" " << subdiv.
y <<
" " << subdiv.
z <<
" </subdivisions>" << std::endl;
3414 outfile <<
" </box>" << std::endl;
3418 Disk *disk = getDiskObjectPointer_private(o);
3420 float transform[16];
3423 outfile <<
"\t<transform> ";
3424 for (
float i: transform) {
3425 outfile << i <<
" ";
3427 outfile <<
"</transform>" << std::endl;
3430 outfile <<
"\t<subdivisions> " << subdiv.
x <<
" " << subdiv.
y <<
" </subdivisions>" << std::endl;
3432 outfile <<
" </disk>" << std::endl;
3436 Cone *cone = getConeObjectPointer_private(o);
3438 float transform[16];
3441 outfile <<
"\t<transform> ";
3442 for (
float i: transform) {
3443 outfile << i <<
" ";
3445 outfile <<
"</transform>" << std::endl;
3448 outfile <<
"\t<subdivisions> " << subdiv <<
" </subdivisions>" << std::endl;
3453 assert(nodes.size() == radius.size());
3454 outfile <<
"\t<nodes> " << std::endl;
3455 for (
auto &node: nodes) {
3456 outfile <<
"\t\t" << node.x <<
" " << node.y <<
" " << node.z << std::endl;
3458 outfile <<
"\t</nodes> " << std::endl;
3459 outfile <<
"\t<radius> " << std::endl;
3460 for (
float radiu: radius) {
3461 outfile <<
"\t\t" << radiu << std::endl;
3463 outfile <<
"\t</radius> " << std::endl;
3465 outfile <<
" </cone>" << std::endl;
3469 outfile <<
" </polymesh>" << std::endl;
3476 for (
const auto &iter: globaldata) {
3477 std::string label = iter.first;
3481 outfile <<
" <globaldata_uint label=\"" << label <<
"\">" << std::flush;
3482 for (
size_t i = 0; i < data.
size; i++) {
3484 if (i != data.
size - 1) {
3485 outfile <<
" " << std::flush;
3488 outfile <<
"</globaldata_uint>" << std::endl;
3490 outfile <<
" <globaldata_int label=\"" << label <<
"\">" << std::flush;
3491 for (
size_t i = 0; i < data.
size; i++) {
3493 if (i != data.
size - 1) {
3494 outfile <<
" " << std::flush;
3497 outfile <<
"</globaldata_int>" << std::endl;
3499 outfile <<
" <globaldata_float label=\"" << label <<
"\">" << std::flush;
3500 for (
size_t i = 0; i < data.
size; i++) {
3502 if (i != data.
size - 1) {
3503 outfile <<
" " << std::flush;
3506 outfile <<
"</globaldata_float>" << std::endl;
3508 outfile <<
" <globaldata_double label=\"" << label <<
"\">" << std::flush;
3509 for (
size_t i = 0; i < data.
size; i++) {
3511 if (i != data.
size - 1) {
3512 outfile <<
" " << std::flush;
3515 outfile <<
"</globaldata_double>" << std::endl;
3517 outfile <<
" <globaldata_vec2 label=\"" << label <<
"\">" << std::endl;
3518 for (
size_t i = 0; i < data.
size; i++) {
3521 outfile <<
" </globaldata_vec2>" << std::endl;
3523 outfile <<
" <globaldata_vec3 label=\"" << label <<
"\">" << std::endl;
3524 for (
size_t i = 0; i < data.
size; i++) {
3527 outfile <<
" </globaldata_vec3>" << std::endl;
3529 outfile <<
" <globaldata_vec4 label=\"" << label <<
"\">" << std::endl;
3530 for (
size_t i = 0; i < data.
size; i++) {
3533 outfile <<
" </globaldata_vec4>" << std::endl;
3535 outfile <<
" <globaldata_int2 label=\"" << label <<
"\">" << std::endl;
3536 for (
size_t i = 0; i < data.
size; i++) {
3539 outfile <<
" </globaldata_int2>" << std::endl;
3541 outfile <<
" <globaldata_int3 label=\"" << label <<
"\">" << std::endl;
3542 for (
size_t i = 0; i < data.
size; i++) {
3545 outfile <<
" </globaldata_int3>" << std::endl;
3547 outfile <<
" <globaldata_int4 label=\"" << label <<
"\">" << std::endl;
3548 for (
size_t i = 0; i < data.
size; i++) {
3551 outfile <<
" </globaldata_int4>" << std::endl;
3553 outfile <<
" <globaldata_string label=\"" << label <<
"\">" << std::flush;
3554 for (
size_t i = 0; i < data.
size; i++) {
3556 if (i != data.
size - 1) {
3557 outfile <<
" " << std::flush;
3560 outfile <<
"</globaldata_string>" << std::endl;
3566 for (
const auto &iter: timeseries_data) {
3567 std::string label = iter.first;
3569 std::vector<float> data = iter.second;
3570 std::vector<double> dateval = timeseries_datevalue.at(label);
3572 assert(data.size() == dateval.size());
3574 outfile <<
" <timeseries label=\"" << label <<
"\">" << std::endl;
3576 for (
size_t i = 0; i < data.size(); i++) {
3580 outfile <<
"\t<datapoint>" << std::endl;
3582 outfile <<
"\t <date>" << a_date.
day <<
" " << a_date.
month <<
" " << a_date.
year <<
"</date>" << std::endl;
3584 outfile <<
"\t <time>" << a_time.
hour <<
" " << a_time.
minute <<
" " << a_time.
second <<
"</time>" << std::endl;
3586 outfile <<
"\t <value>" << data.at(i) <<
"</value>" << std::endl;
3588 outfile <<
"\t</datapoint>" << std::endl;
3591 outfile <<
" </timeseries>" << std::endl;
3596 outfile <<
"\n</helios>\n";
3601 std::cout <<
"done." << std::endl;
3609std::vector<uint>
Context::loadPLY(
const char *filename,
const vec3 &origin,
float height,
const std::string &upaxis,
bool silent) {
3614 return loadPLY(filename, origin, height, rotation, RGB::blue, upaxis, silent);
3617std::vector<uint>
Context::loadPLY(
const char *filename,
const vec3 &origin,
float height,
const RGBcolor &default_color,
const std::string &upaxis,
bool silent) {
3623 std::cout <<
"Reading PLY file " << filename <<
"..." << std::flush;
3626 std::string fn = filename;
3628 if (ext !=
".ply" && ext !=
".PLY") {
3632 if (upaxis !=
"XUP" && upaxis !=
"YUP" && upaxis !=
"ZUP") {
3633 helios_runtime_error(
"ERROR (Context::loadPLY): " + upaxis +
" is not a valid up-axis. Please specify a value of XUP, YUP, or ZUP.");
3636 std::string line, prop;
3638 uint vertexCount = 0, faceCount = 0;
3640 std::vector<vec3> vertices;
3641 std::vector<std::vector<int>> faces;
3642 std::vector<RGBcolor> colors;
3643 std::vector<std::string> properties;
3645 bool ifColor =
false;
3649 std::string resolved_filename = resolved_path.string();
3651 std::ifstream inputPly;
3652 inputPly.open(resolved_filename);
3654 if (!inputPly.is_open()) {
3662 if (
"ply" != line) {
3663 helios_runtime_error(
"ERROR (Context::loadPLY): " + std::string(filename) +
" is not a PLY file.");
3668 if (
"format" != line) {
3669 helios_runtime_error(
"ERROR (Context::loadPLY): could not determine data format of " + std::string(filename));
3673 if (
"ascii" != line) {
3677 std::string temp_string;
3679 while (
"end_header" != line) {
3682 if (
"comment" == line) {
3683 getline(inputPly, line);
3684 }
else if (
"element" == line) {
3687 if (
"vertex" == line) {
3688 inputPly >> temp_string;
3690 helios_runtime_error(
"ERROR (Context::loadPLY): PLY file read failed. Vertex count value should be a non-negative integer.");
3692 }
else if (
"face" == line) {
3693 inputPly >> temp_string;
3695 helios_runtime_error(
"ERROR (Context::loadPLY): PLY file read failed. Face count value should be a non-negative integer.");
3698 }
else if (
"property" == line) {
3701 if (
"list" != line) {
3703 properties.push_back(prop);
3708 for (
auto &property: properties) {
3709 if (property ==
"red") {
3714 std::cout <<
"forming " << faceCount <<
" triangles..." << std::flush;
3717 vertices.resize(vertexCount);
3718 colors.resize(vertexCount);
3719 faces.resize(faceCount);
3724 for (
uint row = 0; row < vertexCount; row++) {
3725 for (
auto &property: properties) {
3726 if (property ==
"x") {
3727 inputPly >> temp_string;
3730 helios_runtime_error(
"ERROR (Context::loadPLY): X value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3732 if (upaxis ==
"XUP") {
3733 vertices.at(row).z = x;
3734 }
else if (upaxis ==
"YUP") {
3735 vertices.at(row).y = x;
3736 }
else if (upaxis ==
"ZUP") {
3737 vertices.at(row).x = x;
3739 }
else if (property ==
"y") {
3740 inputPly >> temp_string;
3743 helios_runtime_error(
"ERROR (Context::loadPLY): Y value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3745 if (upaxis ==
"XUP") {
3746 vertices.at(row).x = y;
3747 }
else if (upaxis ==
"YUP") {
3748 vertices.at(row).z = y;
3749 }
else if (upaxis ==
"ZUP") {
3750 vertices.at(row).y = y;
3752 }
else if (property ==
"z") {
3753 inputPly >> temp_string;
3756 helios_runtime_error(
"ERROR (Context::loadPLY): Z value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3758 if (upaxis ==
"XUP") {
3759 vertices.at(row).y = z;
3760 }
else if (upaxis ==
"YUP") {
3761 vertices.at(row).x = z;
3762 }
else if (upaxis ==
"ZUP") {
3763 vertices.at(row).z = z;
3765 }
else if (property ==
"red") {
3766 inputPly >> temp_string;
3767 if (!
parse_float(temp_string, colors.at(row).r)) {
3768 helios_runtime_error(
"ERROR (Context::loadPLY): red color value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3770 colors.at(row).r /= 255.f;
3771 }
else if (property ==
"green") {
3772 inputPly >> temp_string;
3773 if (!
parse_float(temp_string, colors.at(row).g)) {
3774 helios_runtime_error(
"ERROR (Context::loadPLY): green color value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3776 colors.at(row).g /= 255.f;
3777 }
else if (property ==
"blue") {
3778 inputPly >> temp_string;
3779 if (!
parse_float(temp_string, colors.at(row).b)) {
3780 helios_runtime_error(
"ERROR (Context::loadPLY): blue color value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3782 colors.at(row).b /= 255.f;
3788 if (inputPly.eof()) {
3789 helios_runtime_error(
"ERROR (Context::loadPLY): Read past end of file while reading vertices. Vertex count specified in header may be incorrect.");
3798 for (
uint row = 0; row < vertexCount; row++) {
3799 if (vertices.at(row).x < boxmin.
x) {
3800 boxmin.
x = vertices.at(row).x;
3802 if (vertices.at(row).y < boxmin.
y) {
3803 boxmin.
y = vertices.at(row).y;
3805 if (vertices.at(row).z < boxmin.
z) {
3806 boxmin.
z = vertices.at(row).z;
3809 if (vertices.at(row).x > boxmax.
x) {
3810 boxmax.
x = vertices.at(row).x;
3812 if (vertices.at(row).y > boxmax.
y) {
3813 boxmax.
y = vertices.at(row).y;
3815 if (vertices.at(row).z > boxmax.
z) {
3816 boxmax.
z = vertices.at(row).z;
3823 scl = height / (boxmax.
z - boxmin.
z);
3825 for (
uint row = 0; row < vertexCount; row++) {
3826 vertices.at(row).z -= boxmin.
z;
3828 vertices.at(row).x *= scl;
3829 vertices.at(row).y *= scl;
3830 vertices.at(row).z *= scl;
3832 vertices.at(row) =
rotatePoint(vertices.at(row), rotation) + origin;
3838 std::vector<uint> UUID;
3839 for (
uint row = 0; row < faceCount; row++) {
3840 inputPly >> temp_string;
3843 helios_runtime_error(
"ERROR (Context::loadPLY): Vertex count for face " + std::to_string(row) +
" should be a non-negative integer.");
3846 faces.at(row).resize(v);
3848 for (
uint i = 0; i < v; i++) {
3849 inputPly >> temp_string;
3850 if (!
parse_int(temp_string, faces.at(row).at(i))) {
3851 helios_runtime_error(
"ERROR (Context::loadPLY): Vertex index for face " + std::to_string(row) +
" is invalid and could not be read.");
3857 for (
uint t = 2; t < v; t++) {
3860 color = colors.at(faces.at(row).front());
3862 color = default_color;
3865 vec3 v0 = vertices.at(faces.at(row).front());
3866 vec3 v1 = vertices.at(faces.at(row).at(t - 1));
3867 vec3 v2 = vertices.at(faces.at(row).at(t));
3869 if ((v0 - v1).magnitude() < 1e-10f || (v0 - v2).magnitude() < 1e-10f || (v1 - v2).magnitude() < 1e-10f) {
3875 if (triangle_area < MIN_TRIANGLE_AREA_THRESHOLD) {
3884 if (inputPly.eof()) {
3885 helios_runtime_error(
"ERROR (Context::loadPLY): Read past end of file while reading faces. Face count specified in header may be incorrect.");
3890 std::cout <<
"done." << std::endl;
3902 std::string fname{filename ? filename :
""};
3904 const auto dotPos = fname.find_last_of(
'.');
3905 const std::string ext = (dotPos != std::string::npos) ? fname.substr(dotPos) :
"";
3907 auto ciEqual = [](
const char a,
const char b) {
return std::tolower(a) == std::tolower(b); };
3908 bool isPly = (ext.size() == 4) && ciEqual(ext[1],
'p') && ciEqual(ext[2],
'l') && ciEqual(ext[3],
'y');
3911 helios_runtime_error(
"ERROR (Context::writePLY) Invalid file extension for " + fname +
". Expected a file ending in '.ply'.");
3915 std::ofstream PLYfile;
3916 PLYfile.open(fname, std::ios::out | std::ios::trunc);
3918 if (!PLYfile.is_open()) {
3922 PLYfile <<
"ply" << std::endl <<
"format ascii 1.0" << std::endl <<
"comment Helios generated" << std::endl;
3924 std::vector<int3> faces;
3925 std::vector<vec3> verts;
3926 std::vector<RGBcolor> colors;
3928 size_t vertex_count = 0;
3930 for (
auto UUID: UUIDs) {
3931 std::vector<vec3> vertices = getPrimitivePointer_private(UUID)->getVertices();
3932 PrimitiveType type = getPrimitivePointer_private(UUID)->getType();
3933 RGBcolor C = getPrimitivePointer_private(UUID)->getColor();
3937 faces.push_back(
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2));
3938 for (
int i = 0; i < 3; i++) {
3939 verts.push_back(vertices.at(i));
3940 colors.push_back(C);
3944 faces.push_back(
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2));
3945 faces.push_back(
make_int3((
int) vertex_count, (
int) vertex_count + 2, (
int) vertex_count + 3));
3946 for (
int i = 0; i < 4; i++) {
3947 verts.push_back(vertices.at(i));
3948 colors.push_back(C);
3954 PLYfile <<
"element vertex " << verts.size() << std::endl;
3955 PLYfile <<
"property float x" << std::endl <<
"property float y" << std::endl <<
"property float z" << std::endl;
3956 PLYfile <<
"property uchar red" << std::endl <<
"property uchar green" << std::endl <<
"property uchar blue" << std::endl;
3957 PLYfile <<
"element face " << faces.size() << std::endl;
3958 PLYfile <<
"property list uchar int vertex_indices" << std::endl <<
"end_header" << std::endl;
3960 for (
size_t v = 0; v < verts.size(); v++) {
3961 PLYfile << verts.at(v).x <<
" " << verts.at(v).y <<
" " << verts.at(v).z <<
" " << round(colors.at(v).r) <<
" " << round(colors.at(v).g) <<
" " << round(colors.at(v).b) << std::endl;
3964 for (
auto &face: faces) {
3965 PLYfile <<
"3 " << face.x <<
" " << face.y <<
" " << face.z << std::endl;
3976 return loadOBJ(filename, origin,
make_vec3(0, 0, height), rotation, default_color,
"ZUP", silent);
3980 return loadOBJ(filename, origin,
make_vec3(0, 0, height), rotation, default_color, upaxis, silent);
3986 std::cout <<
"Reading OBJ file " << filename <<
"..." << std::flush;
3989 std::string fn = filename;
3991 if (ext !=
".obj" && ext !=
".OBJ") {
3995 if (strcmp(upaxis,
"XUP") != 0 && strcmp(upaxis,
"YUP") != 0 && strcmp(upaxis,
"ZUP") != 0) {
3996 helios_runtime_error(
"ERROR (Context::loadOBJ): Up axis of " + std::string(upaxis) +
" is not valid. Should be one of 'XUP', 'YUP', or 'ZUP'.");
3999 std::string line, prop;
4001 std::vector<vec3> vertices;
4002 std::vector<std::string> objects;
4003 std::vector<vec2> texture_uv;
4004 std::map<std::string, std::vector<std::vector<int>>> face_inds, texture_inds;
4006 std::map<std::string, OBJmaterial> materials;
4008 std::vector<uint> UUID;
4012 std::string resolved_filename = resolved_path.string();
4014 std::ifstream inputOBJ, inputMTL;
4015 inputOBJ.open(resolved_filename);
4017 if (!inputOBJ.is_open()) {
4022 std::string filebase =
getFilePath(resolved_filename);
4025 float boxmin = 100000;
4026 float boxmax = -100000;
4028 std::string current_material =
"none";
4029 std::string current_object =
"none";
4032 while (inputOBJ.good()) {
4039 getline(inputOBJ, line);
4042 }
else if (line ==
"mtllib") {
4043 getline(inputOBJ, line);
4045 materials = loadMTL(filebase, material_file, default_color);
4048 }
else if (line ==
"o") {
4049 getline(inputOBJ, line);
4053 }
else if (line ==
"v") {
4054 getline(inputOBJ, line);
4057 vertices.emplace_back(verts);
4058 objects.emplace_back(current_object);
4060 if (verts.
z < boxmin) {
4063 if (verts.
z > boxmax) {
4068 }
else if (line ==
"vt") {
4069 getline(inputOBJ, line);
4073 texture_uv.emplace_back(uv);
4076 }
else if (line ==
"usemtl") {
4077 getline(inputOBJ, line);
4081 }
else if (line ==
"f") {
4082 getline(inputOBJ, line);
4084 std::istringstream stream(line);
4085 std::string tmp, digitf, digitu;
4086 std::vector<int> f, u;
4087 while (stream.good()) {
4094 digitf.push_back(i);
4102 for (
int i = ic + 1; i < tmp.size(); i++) {
4103 if (isdigit(tmp[i])) {
4104 digitu.push_back(tmp[i]);
4110 if (!digitf.empty()) {
4113 helios_runtime_error(
"ERROR (Context::loadOBJ): Face index on line " + std::to_string(lineno) +
" must be a non-negative integer value.");
4116 if (face <= 0 || face > vertices.size()) {
4117 helios_runtime_error(
"ERROR (Context::loadOBJ): Face vertex index " + std::to_string(face) +
" on line " + std::to_string(lineno) +
" is out of range. Valid range is 1-" + std::to_string(vertices.size()) +
4118 ". Check that vertex indices in face definitions reference existing vertices.");
4122 if (!digitu.empty()) {
4125 helios_runtime_error(
"ERROR (Context::loadOBJ): u,v index on line " + std::to_string(lineno) +
" must be a non-negative integer value.");
4128 if (uv <= 0 || uv > texture_uv.size()) {
4129 helios_runtime_error(
"ERROR (Context::loadOBJ): Texture coordinate index " + std::to_string(uv) +
" on line " + std::to_string(lineno) +
" is out of range. Valid range is 1-" + std::to_string(texture_uv.size()) +
4130 ". Check that texture coordinate indices in face definitions reference existing texture coordinates.");
4135 face_inds[current_material].push_back(f);
4136 texture_inds[current_material].push_back(u);
4140 getline(inputOBJ, line);
4145 if (scl.
x == 0 && scl.
y == 0 && scl.
z > 0) {
4146 if (boxmax - boxmin > 1e-6f) {
4147 scl =
make_vec3(scale.
z / (boxmax - boxmin), scale.
z / (boxmax - boxmin), scale.
z / (boxmax - boxmin));
4153 if (scl.
x == 0 && (scl.
y != 0 || scl.
z != 0)) {
4154 std::cout <<
"WARNING (Context::loadOBJ): Scaling factor given for x-direction is zero. Setting scaling factor to 1" << std::endl;
4156 if (scl.
y == 0 && (scl.
x != 0 || scl.
z != 0)) {
4157 std::cout <<
"WARNING (Context::loadOBJ): Scaling factor given for y-direction is zero. Setting scaling factor to 1" << std::endl;
4159 if (scl.
z == 0 && (scl.
x != 0 || scl.
y != 0)) {
4160 std::cout <<
"WARNING (Context::loadOBJ): Scaling factor given for z-direction is zero. Setting scaling factor to 1" << std::endl;
4175 struct TriangleData {
4176 vec3 vert0, vert1, vert2;
4177 std::string texture;
4181 bool textureColorIsOverridden;
4182 std::string materialname;
4189 std::map<std::string, std::string> mtl_to_context_material;
4190 for (
const auto &mat_entry : materials) {
4191 const std::string &matname = mat_entry.first;
4192 const OBJmaterial &mat = mat_entry.second;
4193 std::string context_matname = matname;
4197 context_matname = matname +
"_" + std::to_string(suffix++);
4202 if (!mat.texture.empty()) {
4206 mtl_to_context_material[matname] = context_matname;
4209 std::vector<TriangleData> triangleDataList;
4212 for (
auto iter = face_inds.begin(); iter != face_inds.end(); ++iter) {
4213 std::string materialname = iter->first;
4215 std::string texture;
4217 bool textureColorIsOverridden =
false;
4218 bool textureHasTransparency =
false;
4220 if (materials.find(materialname) != materials.end()) {
4221 const OBJmaterial &mat = materials.at(materialname);
4223 texture = mat.texture;
4225 textureColorIsOverridden = mat.textureColorIsOverridden;
4226 textureHasTransparency = mat.textureHasTransparency;
4229 const auto &material_faces = face_inds.at(materialname);
4230 const auto &material_texture_inds = texture_inds.count(materialname) ? texture_inds.at(materialname) : std::vector<std::vector<int>>();
4233 std::string exception_message;
4234 bool exception_occurred =
false;
4237#pragma omp parallel for schedule(dynamic)
4239 for (
int i = 0; i < static_cast<int>(material_faces.size()); i++) {
4241 for (
uint t = 2; t < material_faces[i].size(); t++) {
4242 vec3 v0 = vertices.at(material_faces[i][0] - 1);
4243 vec3 v1 = vertices.at(material_faces[i][t - 1] - 1);
4244 vec3 v2 = vertices.at(material_faces[i][t] - 1);
4246 if ((v0 - v1).magnitude() == 0 || (v0 - v2).magnitude() == 0 || (v1 - v2).magnitude() == 0) {
4250 if (strcmp(upaxis,
"YUP") == 0) {
4268 if (triangle_area > MIN_TRIANGLE_AREA_THRESHOLD) {
4269 TriangleData triangleData;
4270 triangleData.vert0 = vert0;
4271 triangleData.vert1 = vert1;
4272 triangleData.vert2 = vert2;
4273 triangleData.texture = texture;
4274 triangleData.color = color;
4275 triangleData.textureColorIsOverridden = textureColorIsOverridden;
4276 triangleData.materialname = mtl_to_context_material.count(materialname) ? mtl_to_context_material.at(materialname) : materialname;
4277 triangleData.object = objects.at(material_faces[i][0] - 1);
4281 triangleData.hasTexture = !texture.empty();
4285 if (triangleData.hasTexture && i < material_texture_inds.size() && !material_texture_inds[i].empty() && t < material_texture_inds[i].size()) {
4287 int iuv0 = material_texture_inds[i][0] - 1;
4288 int iuv1 = material_texture_inds[i][t - 1] - 1;
4289 int iuv2 = material_texture_inds[i][t] - 1;
4291 if (iuv0 >= 0 && iuv0 < texture_uv.size() && iuv1 >= 0 && iuv1 < texture_uv.size() && iuv2 >= 0 && iuv2 < texture_uv.size()) {
4292 triangleData.uv0 = texture_uv.at(iuv0);
4293 triangleData.uv1 = texture_uv.at(iuv1);
4294 triangleData.uv2 = texture_uv.at(iuv2);
4296 helios_runtime_error(
"ERROR (Context::loadOBJ): Invalid texture coordinate indices in face for material '" + materialname +
"'. " +
"UV indices [" + std::to_string(iuv0 + 1) +
", " + std::to_string(iuv1 + 1) +
", " +
4297 std::to_string(iuv2 + 1) +
"] " +
"exceed available UV coordinates (1-" + std::to_string(texture_uv.size()) +
"). " +
4298 "Check that all face texture coordinate references in the OBJ file are valid.");
4300 }
else if (triangleData.hasTexture) {
4301 helios_runtime_error(
"ERROR (Context::loadOBJ): Material '" + materialname +
"' specifies texture file '" + texture +
"' " +
"but face has no texture coordinates. Either remove the texture from the material " +
4302 "or add texture coordinates (vt) and face texture indices (f v1/vt1 v2/vt2 v3/vt3) to the OBJ file.");
4309 triangleDataList.push_back(triangleData);
4313 }
catch (
const std::exception &e) {
4319 if (!exception_occurred) {
4320 exception_message = e.what();
4321 exception_occurred =
true;
4328 if (exception_occurred) {
4334 for (
const auto &triangleData: triangleDataList) {
4337 if (triangleData.hasTexture) {
4338 ID =
addTriangle(triangleData.vert0, triangleData.vert1, triangleData.vert2, triangleData.texture.c_str(), triangleData.uv0, triangleData.uv1, triangleData.uv2);
4340 if (triangleData.textureColorIsOverridden) {
4345 ID =
addTriangle(triangleData.vert0, triangleData.vert1, triangleData.vert2, triangleData.color);
4350 if (!triangleData.materialname.empty() &&
doesMaterialExist(triangleData.materialname)) {
4360 std::cout <<
"done." << std::endl;
4366std::map<std::string, Context::OBJmaterial> Context::loadMTL(
const std::string &filebase,
const std::string &material_file,
const RGBcolor &default_color) {
4367 std::ifstream inputMTL;
4369 std::string file = material_file;
4373 std::filesystem::path resolved_path;
4375 if (std::filesystem::path(file).is_absolute()) {
4380 std::filesystem::path mtl_path = std::filesystem::path(filebase) / file;
4384 std::string resolved_file = resolved_path.string();
4385 inputMTL.open(resolved_file.c_str());
4387 if (!inputMTL.is_open()) {
4388 helios_runtime_error(
"ERROR (Context::loadMTL): Could not open material file " + resolved_file +
" after successful path resolution.");
4391 std::map<std::string, OBJmaterial> materials;
4397 while (inputMTL.good()) {
4398 if (strcmp(
"#", line.c_str()) == 0) {
4399 getline(inputMTL, line);
4401 }
else if (line ==
"newmtl") {
4402 getline(inputMTL, line);
4404 OBJmaterial mat(default_color,
"", 0);
4405 materials.emplace(material_name, mat);
4407 std::string map_Kd, map_d;
4409 while (line !=
"newmtl" && inputMTL.good()) {
4412 if (line ==
"newmtl") {
4414 }
else if (line ==
"map_a" || line ==
"map_Ka" || line ==
"Ks" || line ==
"Ka" || line ==
"map_Ks") {
4415 getline(inputMTL, line);
4416 }
else if (line ==
"map_Kd" || line ==
"map_d") {
4417 std::string maptype = line;
4418 getline(inputMTL, line);
4420 std::istringstream stream(line);
4422 while (stream.good()) {
4425 if (ext ==
".png" || ext ==
".PNG" || ext ==
".jpg" || ext ==
".JPG" || ext ==
".jpeg" || ext ==
".JPEG") {
4426 std::string texturefile = tmp;
4429 std::filesystem::path texture_path = texturefile;
4430 bool texture_exists =
false;
4433 if (std::filesystem::exists(texture_path)) {
4434 texture_exists =
true;
4437 texture_path = std::filesystem::path(filebase) / tmp;
4438 texturefile = texture_path.string();
4439 if (std::filesystem::exists(texture_path)) {
4440 texture_exists =
true;
4444 if (!texture_exists) {
4445 helios_runtime_error(
"ERROR (Context::loadOBJ): Texture file '" + tmp +
"' referenced in .mtl file cannot be found. " +
"Searched in current directory and OBJ file directory (" + filebase +
"). " +
4446 "Ensure texture file exists or remove texture reference from material.");
4449 if (maptype ==
"map_d") {
4450 map_d = texturefile;
4452 map_Kd = texturefile;
4456 }
else if (line ==
"Kd") {
4457 getline(inputMTL, line);
4460 materials.at(material_name).color =
make_RGBcolor(color.
r, color.
g, color.
b);
4462 getline(inputMTL, line);
4466 if (!map_Kd.empty()) {
4467 materials.at(material_name).texture = map_Kd;
4468 if (!map_d.empty() && map_d != map_Kd) {
4469 materials.at(material_name).textureHasTransparency =
true;
4471 }
else if (!map_d.empty()) {
4472 materials.at(material_name).texture = map_d;
4473 materials.at(material_name).textureColorIsOverridden =
true;
4476 getline(inputMTL, line);
4488void Context::writeOBJ(
const std::string &filename,
const std::vector<uint> &UUIDs,
bool write_normals,
bool silent)
const {
4489 writeOBJ(filename, UUIDs, {}, write_normals, silent);
4492void Context::writeOBJ(
const std::string &filename,
const std::vector<uint> &UUIDs,
const std::vector<std::string> &primitive_dat_fields,
bool write_normals,
bool silent)
const {
4494 if (UUIDs.empty()) {
4495 std::cout <<
"WARNING (Context::writeOBJ): No primitives found to write - OBJ file " << filename <<
" will not be written." << std::endl;
4498 if (filename.empty()) {
4499 std::cout <<
"WARNING (Context::writeOBJ): Filename was empty - OBJ file " << filename <<
" will not be written." << std::endl;
4503 std::string objfilename = filename;
4504 std::string mtlfilename = filename;
4510 if (file_extension !=
".obj" && file_extension !=
".OBJ") {
4511 objfilename.append(
".obj");
4512 mtlfilename.append(
".mtl");
4514 if (!file_path.empty()) {
4515 std::filesystem::path mtl_path = std::filesystem::path(file_path) / (file_stem +
".mtl");
4516 mtlfilename = mtl_path.string();
4518 mtlfilename = file_stem +
".mtl";
4522 if (!file_path.empty() && !std::filesystem::exists(file_path)) {
4523 if (!std::filesystem::create_directory(file_path)) {
4524 std::cerr <<
"failed. Directory " << file_path <<
" does not exist and it could not be created - OBJ file will not be written." << std::endl;
4530 std::cout <<
"Writing OBJ file " << objfilename <<
"..." << std::flush;
4533 std::vector<OBJmaterial> materials;
4534 std::unordered_map<std::string, uint> material_cache;
4535 const size_t primitive_count = UUIDs.size();
4536 const size_t estimated_vertices = primitive_count * 4;
4538 std::vector<vec3> verts;
4539 verts.reserve(estimated_vertices);
4540 std::vector<vec3> normals;
4541 if (write_normals) {
4542 normals.reserve(primitive_count);
4544 std::vector<vec2> uv;
4545 uv.reserve(estimated_vertices);
4547 std::map<uint, std::vector<int3>> faces;
4548 std::map<uint, std::vector<int>> normal_inds;
4549 std::map<uint, std::vector<int3>> uv_inds;
4550 size_t vertex_count = 1;
4551 size_t normal_count = 0;
4552 size_t uv_count = 1;
4553 std::map<uint, std::vector<uint>> UUIDs_write;
4555 std::map<std::string, std::map<uint, std::vector<int3>>> object_faces;
4556 std::map<std::string, std::map<uint, std::vector<int>>> object_normal_inds;
4557 std::map<std::string, std::map<uint, std::vector<int3>>> object_uv_inds;
4558 std::vector<std::string> object_order;
4559 object_order.reserve(primitive_count / 10);
4560 bool object_groups_found =
false;
4562 for (
size_t p: UUIDs) {
4564 std::ostringstream err_stream;
4565 err_stream <<
"ERROR (Context::writeOBJ): Primitive with UUID " << p <<
" does not exist. "
4566 <<
"Ensure all UUIDs in the input vector correspond to valid primitives before calling writeOBJ.";
4570 const Primitive *prim_ptr = getPrimitivePointer_private(p);
4573 std::ostringstream err_stream;
4574 err_stream <<
"ERROR (Context::writeOBJ): Voxel primitives (UUID " << p <<
") cannot be written to OBJ format. "
4575 <<
"OBJ format only supports surface primitives (triangles, patches). "
4576 <<
"Filter out voxel primitives before calling writeOBJ.";
4580 std::vector<vec3> vertices = prim_ptr->getVertices();
4583 std::string texturefile = prim_ptr->getTextureFile();
4584 bool texture_color_overridden = prim_ptr->isTextureColorOverridden();
4586 std::string obj_label =
"default";
4589 object_groups_found =
true;
4591 if (object_faces.find(obj_label) == object_faces.end()) {
4592 object_faces[obj_label] = {};
4593 object_normal_inds[obj_label] = {};
4594 object_uv_inds[obj_label] = {};
4595 object_order.push_back(obj_label);
4598 std::string material_key = texturefile +
"|" + std::to_string(C.
r) +
"," + std::to_string(C.
g) +
"," + std::to_string(C.
b) +
"|" + std::to_string(texture_color_overridden);
4601 auto material_iter = material_cache.find(material_key);
4603 if (material_iter != material_cache.end()) {
4605 material_ID = material_iter->second;
4608 OBJmaterial mat(C, texturefile, materials.size());
4609 materials.emplace_back(mat);
4610 material_ID = mat.materialID;
4613 materials.back().textureHasTransparency =
true;
4615 if (texture_color_overridden) {
4616 materials.back().textureColorIsOverridden =
true;
4619 material_cache[material_key] = material_ID;
4622 if (!primitive_dat_fields.empty()) {
4623 UUIDs_write[material_ID].push_back(p);
4626 if (write_normals) {
4628 normals.push_back(normal);
4633 int3 ftmp =
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2);
4634 faces[material_ID].push_back(ftmp);
4635 object_faces[obj_label][material_ID].push_back(ftmp);
4636 for (
int i = 0; i < 3; i++) {
4637 verts.push_back(vertices.at(i));
4641 if (write_normals) {
4642 normal_inds[material_ID].push_back(
static_cast<int>(normal_count));
4643 object_normal_inds[obj_label][material_ID].push_back(
static_cast<int>(normal_count));
4646 std::vector<vec2> uv_v = getTrianglePointer_private(p)->getTextureUV();
4647 if (getTrianglePointer_private(p)->hasTexture()) {
4648 int3 tuv =
make_int3((
int) uv_count, (
int) uv_count + 1, (
int) uv_count + 2);
4649 uv_inds[material_ID].push_back(tuv);
4650 object_uv_inds[obj_label][material_ID].push_back(tuv);
4651 for (
int i = 0; i < 3; i++) {
4652 uv.push_back(uv_v.at(i));
4657 uv_inds[material_ID].push_back(tuv);
4658 object_uv_inds[obj_label][material_ID].push_back(tuv);
4661 int3 ftmp1 =
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2);
4662 int3 ftmp2 =
make_int3((
int) vertex_count, (
int) vertex_count + 2, (
int) vertex_count + 3);
4663 faces[material_ID].push_back(ftmp1);
4664 faces[material_ID].push_back(ftmp2);
4665 object_faces[obj_label][material_ID].push_back(ftmp1);
4666 object_faces[obj_label][material_ID].push_back(ftmp2);
4667 for (
int i = 0; i < 4; i++) {
4668 verts.push_back(vertices.at(i));
4671 std::vector<vec2> uv_v;
4672 uv_v = getPatchPointer_private(p)->getTextureUV();
4674 if (write_normals) {
4675 normal_inds[material_ID].push_back(
static_cast<int>(normal_count));
4676 normal_inds[material_ID].push_back(
static_cast<int>(normal_count));
4677 object_normal_inds[obj_label][material_ID].push_back(
static_cast<int>(normal_count));
4678 object_normal_inds[obj_label][material_ID].push_back(
static_cast<int>(normal_count));
4681 if (getPatchPointer_private(p)->hasTexture()) {
4682 int3 tuv1 =
make_int3((
int) uv_count, (
int) uv_count + 1, (
int) uv_count + 2);
4683 int3 tuv2 =
make_int3((
int) uv_count, (
int) uv_count + 2, (
int) uv_count + 3);
4684 uv_inds[material_ID].push_back(tuv1);
4685 uv_inds[material_ID].push_back(tuv2);
4686 object_uv_inds[obj_label][material_ID].push_back(tuv1);
4687 object_uv_inds[obj_label][material_ID].push_back(tuv2);
4695 for (
int i = 0; i < 4; i++) {
4696 uv.push_back(uv_v.at(i));
4702 uv_inds[material_ID].push_back(tuv);
4703 uv_inds[material_ID].push_back(tuv);
4704 object_uv_inds[obj_label][material_ID].push_back(tuv);
4705 object_uv_inds[obj_label][material_ID].push_back(tuv);
4711 assert(normal_inds.size() == faces.size());
4713 assert(uv_inds.size() == faces.size());
4714 for (
int i = 0; i < faces.size(); i++) {
4715 assert(uv_inds.at(i).size() == faces.at(i).size());
4719 std::filesystem::path output_path = std::filesystem::path(file_path);
4720 std::filesystem::path texture_dir = output_path.parent_path();
4723 if (texture_dir.empty()) {
4727 for (
auto &material: materials) {
4728 std::string texture = material.texture;
4729 if (!texture.empty()) {
4731 std::filesystem::path source_path = std::filesystem::absolute(texture, ec);
4735 source_path = std::filesystem::path(texture);
4738 if (!std::filesystem::exists(source_path)) {
4743 auto filename = source_path.filename();
4744 std::filesystem::path dest_path = texture_dir / filename;
4747 bool same_file =
false;
4749 same_file = std::filesystem::equivalent(source_path, dest_path, ec);
4757 material.texture = filename.string();
4763 std::filesystem::copy_file(source_path, dest_path, std::filesystem::copy_options::overwrite_existing, ec);
4765 material.texture = filename.string();
4774 std::ofstream objfstream;
4775 objfstream.open(objfilename);
4776 std::ofstream mtlfstream;
4777 mtlfstream.open(mtlfilename);
4779 objfstream <<
"# Helios auto-generated OBJ File" << std::endl;
4780 objfstream <<
"# baileylab.ucdavis.edu/software/helios" << std::endl;
4781 objfstream <<
"mtllib " <<
getFileName(mtlfilename) << std::endl;
4784 std::vector<std::string> vertex_chunks;
4785 const int num_threads = std::min(
static_cast<int>(verts.size() / 1000 + 1), std::max(1,
static_cast<int>(std::thread::hardware_concurrency())));
4786 vertex_chunks.resize(num_threads);
4789#pragma omp parallel num_threads(num_threads)
4794 tid = omp_get_thread_num();
4796 std::ostringstream vertex_stream;
4797 vertex_stream.precision(8);
4799 const size_t chunk_size = (verts.size() + num_threads - 1) / num_threads;
4800 const size_t start_idx = tid * chunk_size;
4801 const size_t end_idx = std::min(start_idx + chunk_size, verts.size());
4803 for (
size_t i = start_idx; i < end_idx; i++) {
4804 vertex_stream <<
"v " << verts[i].x <<
" " << verts[i].y <<
" " << verts[i].z <<
"\n";
4807 vertex_chunks[tid] = vertex_stream.str();
4810 for (
const auto &chunk: vertex_chunks) {
4811 objfstream << chunk;
4814 if (write_normals) {
4815 std::vector<std::string> normal_chunks;
4816 normal_chunks.resize(num_threads);
4819#pragma omp parallel num_threads(num_threads)
4824 tid = omp_get_thread_num();
4826 std::ostringstream normal_stream;
4827 normal_stream.precision(8);
4829 const size_t chunk_size = (normals.size() + num_threads - 1) / num_threads;
4830 const size_t start_idx = tid * chunk_size;
4831 const size_t end_idx = std::min(start_idx + chunk_size, normals.size());
4833 const float epsilon = 1e-7;
4834 for (
size_t i = start_idx; i < end_idx; i++) {
4835 vec3 n = normals[i];
4836 if (std::abs(n.
x) < epsilon)
4838 if (std::abs(n.
y) < epsilon)
4840 if (std::abs(n.
z) < epsilon)
4842 normal_stream <<
"vn " << n.
x <<
" " << n.
y <<
" " << n.
z <<
"\n";
4845 normal_chunks[tid] = normal_stream.str();
4848 for (
const auto &chunk: normal_chunks) {
4849 objfstream << chunk;
4854 std::vector<std::string> uv_chunks;
4855 uv_chunks.resize(num_threads);
4858#pragma omp parallel num_threads(num_threads)
4863 tid = omp_get_thread_num();
4865 std::ostringstream uv_stream;
4866 uv_stream.precision(8);
4868 const size_t chunk_size = (uv.size() + num_threads - 1) / num_threads;
4869 const size_t start_idx = tid * chunk_size;
4870 const size_t end_idx = std::min(start_idx + chunk_size, uv.size());
4872 for (
size_t i = start_idx; i < end_idx; i++) {
4873 uv_stream <<
"vt " << uv[i].x <<
" " << uv[i].y <<
"\n";
4876 uv_chunks[tid] = uv_stream.str();
4879 for (
const auto &chunk: uv_chunks) {
4880 objfstream << chunk;
4886 if (object_groups_found) {
4889 for (
const auto &obj_label: object_order) {
4890 objfstream <<
"o " << obj_label <<
"\n";
4892 for (
int mat = 0; mat < materials.size(); mat++) {
4893 auto fit = object_faces[obj_label].find(mat);
4894 if (fit == object_faces[obj_label].end())
4897 objfstream <<
"usemtl material" << mat <<
"\n";
4899 const auto ¤t_faces = fit->second;
4900 if (current_faces.size() > 100) {
4902 std::vector<std::string> face_chunks;
4903 face_chunks.resize(num_threads);
4906#pragma omp parallel num_threads(num_threads)
4911 tid = omp_get_thread_num();
4913 std::ostringstream face_stream;
4915 const size_t chunk_size = (current_faces.size() + num_threads - 1) / num_threads;
4916 const size_t start_idx = tid * chunk_size;
4917 const size_t end_idx = std::min(start_idx + chunk_size, current_faces.size());
4919 for (
size_t f = start_idx; f < end_idx; f++) {
4921 if (write_normals) {
4922 face_stream <<
"f " << current_faces[f].x <<
"//" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].y <<
"//" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].z <<
"//"
4923 << object_normal_inds[obj_label][mat][f] <<
"\n";
4925 face_stream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z <<
"\n";
4927 }
else if (object_uv_inds[obj_label][mat][f].x < 0) {
4928 face_stream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1\n";
4930 if (write_normals) {
4931 face_stream <<
"f " << current_faces[f].x <<
"/" << object_uv_inds[obj_label][mat][f].x <<
"/" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].y <<
"/" << object_uv_inds[obj_label][mat][f].y
4932 <<
"/" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].z <<
"/" << object_uv_inds[obj_label][mat][f].z <<
"/" << object_normal_inds[obj_label][mat][f] <<
"\n";
4934 face_stream <<
"f " << current_faces[f].x <<
"/" << object_uv_inds[obj_label][mat][f].x <<
" " << current_faces[f].y <<
"/" << object_uv_inds[obj_label][mat][f].y <<
" " << current_faces[f].z <<
"/"
4935 << object_uv_inds[obj_label][mat][f].z <<
"\n";
4940 face_chunks[tid] = face_stream.str();
4944 for (
const auto &chunk: face_chunks) {
4945 objfstream << chunk;
4949 for (
size_t f = 0; f < current_faces.size(); ++f) {
4951 if (write_normals) {
4952 objfstream <<
"f " << current_faces[f].x <<
"//" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].y <<
"//" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].z <<
"//"
4953 << object_normal_inds[obj_label][mat][f] << std::endl;
4955 objfstream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z << std::endl;
4957 }
else if (object_uv_inds[obj_label][mat][f].x < 0) {
4958 objfstream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1" << std::endl;
4960 if (write_normals) {
4961 objfstream <<
"f " << current_faces[f].x <<
"/" << object_uv_inds[obj_label][mat][f].x <<
"/" << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].y <<
"/" << object_uv_inds[obj_label][mat][f].y <<
"/"
4962 << object_normal_inds[obj_label][mat][f] <<
" " << current_faces[f].z <<
"/" << object_uv_inds[obj_label][mat][f].z <<
"/" << object_normal_inds[obj_label][mat][f] << std::endl;
4964 objfstream <<
"f " << current_faces[f].x <<
"/" << object_uv_inds[obj_label][mat][f].x <<
" " << current_faces[f].y <<
"/" << object_uv_inds[obj_label][mat][f].y <<
" " << current_faces[f].z <<
"/"
4965 << object_uv_inds[obj_label][mat][f].z << std::endl;
4974 for (
int mat = 0; mat < materials.size(); mat++) {
4975 assert(materials.at(mat).materialID == mat);
4976 objfstream <<
"usemtl material" << mat <<
"\n";
4978 const auto ¤t_faces = faces.at(mat);
4979 if (current_faces.size() > 100) {
4981 std::vector<std::string> face_chunks;
4982 face_chunks.resize(num_threads);
4985#pragma omp parallel num_threads(num_threads)
4990 tid = omp_get_thread_num();
4992 std::ostringstream face_stream;
4994 const size_t chunk_size = (current_faces.size() + num_threads - 1) / num_threads;
4995 const size_t start_idx = tid * chunk_size;
4996 const size_t end_idx = std::min(start_idx + chunk_size, current_faces.size());
4998 for (
size_t f = start_idx; f < end_idx; f++) {
5000 if (write_normals) {
5001 face_stream <<
"f " << current_faces[f].x <<
"//" << normal_inds.at(mat)[f] <<
" " << current_faces[f].y <<
"//" << normal_inds.at(mat)[f] <<
" " << current_faces[f].z <<
"//" << normal_inds.at(mat)[f] <<
"\n";
5003 face_stream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z <<
"\n";
5005 }
else if (uv_inds.at(mat)[f].x < 0) {
5006 face_stream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1\n";
5008 if (write_normals) {
5009 face_stream <<
"f " << current_faces[f].x <<
"/" << uv_inds.at(mat)[f].x <<
"/" << normal_inds.at(mat)[f] <<
" " << current_faces[f].y <<
"/" << uv_inds.at(mat)[f].y <<
"/" << normal_inds.at(mat)[f] <<
" "
5010 << current_faces[f].z <<
"/" << uv_inds.at(mat)[f].z <<
"/" << normal_inds.at(mat)[f] <<
"\n";
5012 face_stream <<
"f " << current_faces[f].x <<
"/" << uv_inds.at(mat)[f].x <<
" " << current_faces[f].y <<
"/" << uv_inds.at(mat)[f].y <<
" " << current_faces[f].z <<
"/" << uv_inds.at(mat)[f].z <<
"\n";
5017 face_chunks[tid] = face_stream.str();
5021 for (
const auto &chunk: face_chunks) {
5022 objfstream << chunk;
5026 for (
int f = 0; f < current_faces.size(); f++) {
5028 if (write_normals) {
5029 objfstream <<
"f " << current_faces[f].x <<
"//" << normal_inds.at(mat)[f] <<
" " << current_faces[f].y <<
"//" << normal_inds.at(mat)[f] <<
" " << current_faces[f].z <<
"//" << normal_inds.at(mat)[f] << std::endl;
5031 objfstream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z << std::endl;
5033 }
else if (uv_inds.at(mat)[f].x < 0) {
5034 objfstream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1" << std::endl;
5036 if (write_normals) {
5037 objfstream <<
"f " << current_faces[f].x <<
"/" << uv_inds.at(mat)[f].x <<
"/" << normal_inds.at(mat)[f] <<
" " << current_faces[f].y <<
"/" << uv_inds.at(mat)[f].y <<
"/" << normal_inds.at(mat)[f] <<
" "
5038 << current_faces[f].z <<
"/" << uv_inds.at(mat)[f].z <<
"/" << normal_inds.at(mat)[f] << std::endl;
5040 objfstream <<
"f " << current_faces[f].x <<
"/" << uv_inds.at(mat)[f].x <<
" " << current_faces[f].y <<
"/" << uv_inds.at(mat)[f].y <<
" " << current_faces[f].z <<
"/" << uv_inds.at(mat)[f].z << std::endl;
5048 for (
int mat = 0; mat < materials.size(); mat++) {
5049 if (materials.at(mat).texture.empty()) {
5050 RGBcolor current_color = materials.at(mat).color;
5051 mtlfstream <<
"newmtl material" << mat << std::endl;
5052 mtlfstream <<
"Ka " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5053 mtlfstream <<
"Kd " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5054 mtlfstream <<
"Ks 0.0 0.0 0.0" << std::endl;
5055 mtlfstream <<
"illum 2 " << std::endl;
5057 std::string current_texture = materials.at(mat).texture;
5058 mtlfstream <<
"newmtl material" << mat << std::endl;
5059 if (materials.at(mat).textureColorIsOverridden) {
5060 RGBcolor current_color = materials.at(mat).color;
5061 mtlfstream <<
"Ka " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5062 mtlfstream <<
"Kd " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5064 mtlfstream <<
"map_Kd " << current_texture << std::endl;
5066 if (materials.at(mat).textureHasTransparency) {
5067 mtlfstream <<
"map_d " << current_texture << std::endl;
5069 mtlfstream <<
"Ks 0.0 0.0 0.0" << std::endl;
5070 mtlfstream <<
"illum 2 " << std::endl;
5077 if (!primitive_dat_fields.empty()) {
5078 bool uuidexistswarning =
false;
5079 bool dataexistswarning =
false;
5080 bool datatypewarning =
false;
5082 for (
const std::string &label: primitive_dat_fields) {
5083 std::filesystem::path dat_path = std::filesystem::path(file_path) / (file_stem +
"_" + std::string(label) +
".dat");
5084 std::string datfilename = dat_path.string();
5085 std::ofstream datout(datfilename);
5087 for (
int mat = 0; mat < materials.size(); mat++) {
5088 for (
uint UUID: UUIDs_write.at(mat)) {
5090 uuidexistswarning =
true;
5101 dataexistswarning =
true;
5102 for (
int i = 0; i < Nprims; i++) {
5103 datout << 0 << std::endl;
5112 for (
int i = 0; i < Nprims; i++) {
5113 datout << data << std::endl;
5118 for (
int i = 0; i < Nprims; i++) {
5119 datout << data << std::endl;
5124 for (
int i = 0; i < Nprims; i++) {
5125 datout << data << std::endl;
5130 for (
int i = 0; i < Nprims; i++) {
5131 datout << data << std::endl;
5136 for (
int i = 0; i < Nprims; i++) {
5137 datout << data << std::endl;
5140 datatypewarning =
true;
5141 for (
int i = 0; i < Nprims; i++) {
5142 datout << 0 << std::endl;
5151 if (uuidexistswarning) {
5152 helios_runtime_error(
"Context::writeOBJ: One or more UUIDs do not exist in the Context. Cannot write OBJ file with invalid primitives.");
5154 if (dataexistswarning) {
5155 helios_runtime_error(
"Context::writeOBJ: Primitive data requested did not exist for one or more primitives. Cannot write incomplete data to OBJ file.");
5157 if (datatypewarning) {
5158 helios_runtime_error(
"Context::writeOBJ: Only scalar primitive data types (uint, int, float, double, and string) are supported for primitive data export.");
5167void Context::writePrimitiveData(
const std::string &filename,
const std::vector<std::string> &column_format,
const std::vector<uint> &UUIDs,
bool print_header)
const {
5168 std::ofstream file(filename);
5171 for (
const auto &label: column_format) {
5172 file << label <<
" ";
5174 file.seekp(-1, std::ios_base::end);
5178 bool uuidexistswarning =
false;
5179 bool dataexistswarning =
false;
5180 bool datatypewarning =
false;
5182 for (
uint UUID: UUIDs) {
5184 uuidexistswarning =
true;
5187 for (
const auto &label: column_format) {
5188 if (label ==
"UUID") {
5189 file << UUID <<
" ";
5193 dataexistswarning =
true;
5201 file << data <<
" ";
5205 file << data <<
" ";
5209 file << data <<
" ";
5213 file << data <<
" ";
5217 file << data <<
" ";
5219 datatypewarning =
true;
5223 file.seekp(-1, std::ios_base::end);
5227 if (uuidexistswarning) {
5228 std::cerr <<
"WARNING (Context::writePrimitiveData): Vector of UUIDs passed to writePrimitiveData() function contained UUIDs that do not exist, which were skipped." << std::endl;
5230 if (dataexistswarning) {
5231 std::cerr <<
"WARNING (Context::writePrimitiveData): Primitive data requested did not exist for one or more primitives. A default value of 0 was written in these cases." << std::endl;
5233 if (datatypewarning) {
5234 std::cerr <<
"WARNING (Context::writePrimitiveData): Only scalar primitive data types (uint, int, float, and double) are supported for this function. A column of 0's was written in these cases." << std::endl;
5243 Date parseDateString(
const std::string &datestr,
const std::string &date_string_format,
size_t row,
const std::string &data_file) {
5246 if (datestr.find(
'-') == std::string::npos && datestr.find(
'/') == std::string::npos) {
5247 if (datestr.size() == 8) {
5249 int year, month, day;
5250 if (date_string_format ==
"YYYYMMDD" || date_string_format ==
"YYYY-MM-DD") {
5251 year = std::stoi(datestr.substr(0, 4));
5252 month = std::stoi(datestr.substr(4, 2));
5253 day = std::stoi(datestr.substr(6, 2));
5254 }
else if (date_string_format ==
"DDMMYYYY" || date_string_format ==
"DD-MM-YYYY" || date_string_format ==
"DD/MM/YYYY") {
5255 day = std::stoi(datestr.substr(0, 2));
5256 month = std::stoi(datestr.substr(2, 2));
5257 year = std::stoi(datestr.substr(4, 4));
5258 }
else if (date_string_format ==
"MMDDYYYY" || date_string_format ==
"MM-DD-YYYY" || date_string_format ==
"MM/DD/YYYY") {
5259 month = std::stoi(datestr.substr(0, 2));
5260 day = std::stoi(datestr.substr(2, 2));
5261 year = std::stoi(datestr.substr(4, 4));
5262 }
else if (date_string_format ==
"YYYYDDMM") {
5263 year = std::stoi(datestr.substr(0, 4));
5264 day = std::stoi(datestr.substr(4, 2));
5265 month = std::stoi(datestr.substr(6, 2));
5267 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid date string format '" + date_string_format +
"' for compact date on line " + std::to_string(row) +
" of file " + data_file +
".");
5269 if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31) {
5270 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse compact date string on line " + std::to_string(row) +
" of file " + data_file +
".");
5274 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
5275 ". Expected a delimited date (e.g., YYYY-MM-DD) or an 8-digit compact date (e.g., 20260203).");
5280 if (thisdatestr.size() != 3) {
5283 if (thisdatestr.size() != 3) {
5284 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
5285 ". It should be in the format YYYY-MM-DD, delimited by either '-' or '/'.");
5288 std::vector<int> thisdate(3);
5289 for (
int i = 0; i < 3; i++) {
5290 if (!
parse_int(thisdatestr.at(i), thisdate.at(i))) {
5291 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
5292 ". It should be in the format YYYY-MM-DD, delimited by either '-' or '/'.");
5296 int year, month, day;
5297 if (date_string_format ==
"YYYYMMDD" || date_string_format ==
"YYYY-MM-DD") {
5298 year = thisdate.at(0);
5299 month = thisdate.at(1);
5300 day = thisdate.at(2);
5301 }
else if (date_string_format ==
"YYYYDDMM") {
5302 year = thisdate.at(0);
5303 month = thisdate.at(2);
5304 day = thisdate.at(1);
5305 }
else if (date_string_format ==
"DDMMYYYY" || date_string_format ==
"DD-MM-YYYY" || date_string_format ==
"DD/MM/YYYY") {
5306 year = thisdate.at(2);
5307 month = thisdate.at(1);
5308 day = thisdate.at(0);
5309 }
else if (date_string_format ==
"MMDDYYYY" || date_string_format ==
"MM-DD-YYYY" || date_string_format ==
"MM/DD/YYYY") {
5310 year = thisdate.at(2);
5311 month = thisdate.at(0);
5312 day = thisdate.at(1);
5314 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid date string format in file " + data_file +
": " + date_string_format +
5315 ". Must be one of YYYYMMDD, YYYYDDMM, DDMMYYYY, MMDDYYYY (or with - or / delimiters, e.g. YYYY-MM-DD, DD/MM/YYYY).");
5318 if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31) {
5319 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
".");
5327 Time parseTimeString(
const std::string ×tr,
size_t row,
const std::string &data_file) {
5331 int hour = 0, minute = 0, second = 0;
5333 if (parts.size() == 1) {
5336 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5341 hour = hr_min / 100;
5342 minute = hr_min - hour * 100;
5344 }
else if (parts.size() == 2) {
5346 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5348 }
else if (parts.size() == 3) {
5350 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5353 std::string sec_str = parts.at(2);
5354 size_t dot_pos = sec_str.find(
'.');
5355 if (dot_pos != std::string::npos) {
5356 sec_str = sec_str.substr(0, dot_pos);
5359 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5362 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5378 void parseISO8601(
const std::string &datetimestr,
Date &date,
Time &time,
float &utc_offset,
size_t row,
const std::string &data_file) {
5381 size_t t_pos = datetimestr.find(
'T');
5382 if (t_pos == std::string::npos) {
5383 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): ISO-8601 datetime string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
" does not contain 'T' separator.");
5387 std::string date_part = datetimestr.substr(0, t_pos);
5389 if (date_parts.size() != 3) {
5390 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date portion of ISO-8601 string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5392 int year, month, day;
5394 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date portion of ISO-8601 string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5399 std::string time_tz = datetimestr.substr(t_pos + 1);
5402 std::string time_part;
5403 if (time_tz.back() ==
'Z' || time_tz.back() ==
'z') {
5404 time_part = time_tz.substr(0, time_tz.size() - 1);
5409 size_t tz_pos = std::string::npos;
5410 for (
size_t i = 1; i < time_tz.size(); i++) {
5411 if (time_tz[i] ==
'+' || time_tz[i] ==
'-') {
5417 if (tz_pos != std::string::npos) {
5418 time_part = time_tz.substr(0, tz_pos);
5419 std::string tz_str = time_tz.substr(tz_pos);
5420 char tz_sign = tz_str[0];
5421 std::string tz_num = tz_str.substr(1);
5423 int tz_hours = 0, tz_minutes = 0;
5424 if (!tz_parts.empty())
parse_int(tz_parts.at(0), tz_hours);
5425 if (tz_parts.size() > 1)
parse_int(tz_parts.at(1), tz_minutes);
5426 float iso_offset_hours =
static_cast<float>(tz_hours) +
static_cast<float>(tz_minutes) / 60.0f;
5427 if (tz_sign ==
'-') iso_offset_hours = -iso_offset_hours;
5430 utc_offset = -iso_offset_hours;
5432 time_part = time_tz;
5437 size_t dot_pos = time_part.find(
'.');
5438 if (dot_pos != std::string::npos) {
5439 time_part = time_part.substr(0, dot_pos);
5443 time = parseTimeString(time_part, row, data_file);
5447 void parseDatetimeString(
const std::string &datetimestr,
const std::string &date_string_format,
5448 Date &date,
Time &time,
float &utc_offset,
size_t row,
const std::string &data_file) {
5451 if (date_string_format ==
"ISO8601") {
5452 parseISO8601(datetimestr, date, time, utc_offset, row, data_file);
5456 if (date_string_format ==
"YYYYMMDDHH") {
5457 if (datetimestr.size() < 10) {
5458 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): YYYYMMDDHH datetime string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
" is too short.");
5460 int year = std::stoi(datetimestr.substr(0, 4));
5461 int month = std::stoi(datetimestr.substr(4, 2));
5462 int day = std::stoi(datetimestr.substr(6, 2));
5463 int hour = std::stoi(datetimestr.substr(8, 2));
5469 if (date_string_format ==
"YYYYMMDDHHMM") {
5470 if (datetimestr.size() < 12) {
5471 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): YYYYMMDDHHMM datetime string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
" is too short.");
5473 int year = std::stoi(datetimestr.substr(0, 4));
5474 int month = std::stoi(datetimestr.substr(4, 2));
5475 int day = std::stoi(datetimestr.substr(6, 2));
5476 int hour = std::stoi(datetimestr.substr(8, 2));
5477 int minute = std::stoi(datetimestr.substr(10, 2));
5485 size_t space_pos = datetimestr.find(
' ');
5486 if (space_pos != std::string::npos) {
5487 std::string date_part = datetimestr.substr(0, space_pos);
5488 std::string time_part = datetimestr.substr(space_pos + 1);
5491 std::string date_format;
5492 size_t fmt_space = date_string_format.find(
' ');
5493 if (fmt_space != std::string::npos) {
5494 date_format = date_string_format.substr(0, fmt_space);
5496 date_format = date_string_format;
5501 date = parseDateString(date_part, date_format, row, data_file);
5502 time = parseTimeString(time_part, row, data_file);
5506 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse datetime string '" + datetimestr +
"' with format '" + date_string_format +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5510 bool datetimeFormatHasSpace(
const std::string &format) {
5511 return format.find(
' ') != std::string::npos;
5516void Context::loadTabularTimeseriesData(
const std::string &data_file,
const std::vector<std::string> &col_labels,
const std::string &a_delimeter,
const std::string &a_date_string_format,
uint headerlines) {
5519 std::string resolved_filename = resolved_path.string();
5521 std::ifstream datafile(resolved_filename);
5523 if (!datafile.is_open()) {
5524 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Weather data file '" + data_file +
"' does not exist.");
5529 int datestrcol = -1;
5530 int datetimecol = -1;
5535 std::map<std::string, int> datacols;
5537 size_t Ncolumns = 0;
5539 size_t row = headerlines;
5541 std::vector<std::string> column_labels = col_labels;
5542 std::string delimiter = a_delimeter;
5543 std::string date_string_format = a_date_string_format;
5546 if (col_labels.size() == 1 && (col_labels.front() ==
"CIMIS" || col_labels.front() ==
"cimis")) {
5548 "",
"",
"",
"date",
"hour",
"DOY",
"ETo",
"",
"precipitation",
"",
"net_radiation",
"",
"vapor_pressure",
"",
"air_temperature",
"",
"air_humidity",
"",
"dew_point",
"",
"wind_speed",
"",
"wind_direction",
"",
"soil_temperature",
""};
5551 date_string_format =
"MMDDYYYY";
5555 if (!column_labels.empty()) {
5557 for (
auto &label: column_labels) {
5558 if (label ==
"year" || label ==
"Year") {
5560 }
else if (label ==
"DOY" || label ==
"Jul") {
5562 }
else if (label ==
"date" || label ==
"Date") {
5564 }
else if (label ==
"datetime" || label ==
"Datetime" || label ==
"DateTime") {
5566 }
else if (label ==
"hour" || label ==
"Hour") {
5568 }
else if (label ==
"minute" || label ==
"Minute") {
5570 }
else if (label ==
"second" || label ==
"Second") {
5572 }
else if (label ==
"time" || label ==
"Time") {
5574 }
else if (!label.empty()) {
5575 if (datacols.find(label) == datacols.end()) {
5576 datacols[label] = col;
5578 datacols[label +
"_dup"] = col;
5585 Ncolumns = column_labels.size();
5589 if (headerlines == 0) {
5590 std::cerr <<
"WARNING (Context::loadTabularTimeseriesData): "
5592 " argument was specified as zero, and no column label information was given. Attempting to read the first line to see if it contains label information."
5598 if (std::getline(datafile, line)) {
5601 if (line_parsed.empty()) {
5602 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but it did not contain the specified delimiter.");
5605 Ncolumns = line_parsed.size();
5607 for (
int col = 0; col < Ncolumns; col++) {
5608 const std::string &label = line_parsed.at(col);
5610 if (label ==
"year" || label ==
"Year") {
5612 }
else if (label ==
"DOY" || label ==
"Jul") {
5614 }
else if (label ==
"date" || label ==
"Date") {
5616 }
else if (label ==
"datetime" || label ==
"Datetime" || label ==
"DateTime") {
5618 }
else if (label ==
"hour" || label ==
"Hour") {
5620 }
else if (label ==
"minute" || label ==
"Minute") {
5622 }
else if (label ==
"second" || label ==
"Second") {
5624 }
else if (label ==
"time" || label ==
"Time") {
5626 }
else if (!label.empty()) {
5627 if (datacols.find(label) == datacols.end()) {
5628 datacols[label] = col;
5630 datacols[label +
"_dup"] = col;
5637 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but read failed.");
5640 if (yearcol == -1 && DOYcol == -1 && datestrcol == -1 && datetimecol == -1) {
5641 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but could not find valid label information.");
5646 bool has_date = (datestrcol >= 0) || (yearcol >= 0 && DOYcol >= 0);
5647 bool has_time = (hourcol >= 0) || (timecol >= 0);
5648 bool has_datetime = (datetimecol >= 0);
5650 if (has_datetime && datestrcol >= 0) {
5651 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Cannot specify both 'datetime' and 'date' columns. Use 'datetime' for combined date+time, or 'date' + 'hour'/'time' for separate columns.");
5653 if (has_datetime && hourcol >= 0) {
5654 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Cannot specify both 'datetime' and 'hour' columns. Use 'datetime' for combined date+time, or 'date' + 'hour' for separate columns.");
5656 if (has_datetime && timecol >= 0) {
5657 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Cannot specify both 'datetime' and 'time' columns. The 'datetime' column already includes time information.");
5659 if (!has_datetime && !has_date) {
5660 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): The date must be specified by a column labeled 'datetime', 'date', or by two columns labeled 'year' and 'DOY'.");
5662 if (!has_datetime && !has_time) {
5663 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): The time must be specified by a column labeled 'datetime', 'hour', or 'time'.");
5665 if (datacols.empty()) {
5666 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): No columns were found containing data variables (e.g., temperature, humidity, wind speed).");
5670 bool datetime_format_has_space = has_datetime && datetimeFormatHasSpace(date_string_format);
5676 for (
int i = 0; i < headerlines; i++) {
5677 std::getline(datafile, line);
5680 bool utc_offset_set =
false;
5682 while (std::getline(datafile, line)) {
5695 if (datetime_format_has_space && datetimecol >= 0 && line_separated.size() == Ncolumns + 1 && datetimecol + 1 <
static_cast<int>(line_separated.size())) {
5696 line_separated[datetimecol] = line_separated[datetimecol] +
" " + line_separated[datetimecol + 1];
5697 line_separated.erase(line_separated.begin() + datetimecol + 1);
5700 if (line_separated.size() != Ncolumns) {
5701 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Line " + std::to_string(row) +
" had " + std::to_string(line_separated.size()) +
" columns, but was expecting " + std::to_string(Ncolumns));
5706 float parsed_utc_offset = NAN;
5708 if (datetimecol >= 0) {
5710 parseDatetimeString(line_separated.at(datetimecol), date_string_format,
5711 date, time, parsed_utc_offset, row, data_file);
5714 if (yearcol >= 0 && DOYcol >= 0) {
5716 parse_int(line_separated.at(DOYcol), DOY);
5717 if (DOY < 1 || DOY > 366) {
5718 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid date specified on line " + std::to_string(row) +
".");
5721 parse_int(line_separated.at(yearcol), year);
5723 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid year specified on line " + std::to_string(row) +
".");
5726 }
else if (datestrcol >= 0) {
5727 date = parseDateString(line_separated.at(datestrcol), date_string_format, row, data_file);
5731 time = parseTimeString(line_separated.at(timecol), row, data_file);
5732 }
else if (hourcol >= 0) {
5737 if (!
parse_int(line_separated.at(hourcol), hour)) {
5738 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse hour string on line " + std::to_string(row) +
" of file " + data_file +
".");
5740 if (hour > 24 && minutecol < 0 && secondcol < 0) {
5742 hour = hr_min / 100;
5743 minute = hr_min - hour * 100;
5749 if (minutecol >= 0) {
5750 if (!
parse_int(line_separated.at(minutecol), minute)) {
5752 std::cout <<
"WARNING (Context::loadTabularTimeseriesData): Could not parse minute string on line " << row <<
" of file " << data_file <<
". Setting minute equal to 0." << std::endl;
5755 if (secondcol >= 0) {
5756 if (!
parse_int(line_separated.at(secondcol), second)) {
5758 std::cout <<
"WARNING (Context::loadTabularTimeseriesData): Could not parse second string on line " << row <<
" of file " << data_file <<
". Setting second equal to 0." << std::endl;
5766 if (time.
hour == 24) {
5772 if (!std::isnan(parsed_utc_offset)) {
5773 if (!utc_offset_set) {
5777 utc_offset_set =
true;
5782 for (
auto &dat: datacols) {
5783 std::string label = dat.first;
5784 int col = dat.second;
5787 if (!
parse_float(line_separated.at(col), dataval)) {
5788 std::cout <<
"WARNING (Context::loadTabularTimeseriesData): Failed to parse data value as float on line "
5789 << row <<
", column " << col + 1 <<
" of file " << data_file <<
". Skipping this value..." << std::endl;
5793 if (label ==
"air_humidity" && col_labels.size() == 1 && (col_labels.front() ==
"CIMIS" || col_labels.front() ==
"cimis")) {
5794 dataval = dataval / 100.f;