Wildmeshing Toolkit
Loading...
Searching...
No Matches
ComponentIO.hpp
1#pragma once
2#include <optional>
3#include <set>
4#include <wmtk/MeshData.hpp>
5#include <wmtk/Types.hpp>
6#include <wmtk/history/History.hpp>
7#include <wmtk/io/read_edge_mesh.hpp>
8#include <wmtk/io/read_triangle_mesh.hpp>
9#include <wmtk/utils/Logger.hpp>
10#include <wmtk/utils/resolve_path.hpp>
11
12namespace wmtk::io {
13// Per-call state; no global mesh store, temporary files, or thread-local dispatch.
15{
17 std::optional<std::vector<uint8_t>> recorded_history, recorded_volume_history;
18
19public:
20 const MeshMap* inputs = nullptr;
21 ComponentResult result{{}, nlohmann::json::object()};
22 explicit ComponentIO(const MeshMap* meshes = nullptr)
23 : logging(meshes != nullptr)
24 , inputs(meshes)
25 {}
26 bool memory() const { return inputs != nullptr; }
27 // A history the component recorded (wmtk/history; its history_output), of a surface or of
28 // curves. The file route saves it to `path`. The memory route, which writes no file, returns
29 // the bytes of that file in the report, as "history" -- a JSON binary value, bytes in Python
30 // (take_result()).
31 template <int N>
32 void save_history(const history::History<N>& h, const std::string& path)
33 {
34 if (!memory()) {
35 h.save(path);
36 logger().info("history: {} edits saved to {}", h.edits.size(), path);
37 return;
38 }
39 recorded_history = h.to_bytes();
40 logger().info(
41 "history: {} edits, {} bytes, returned in the report",
42 h.edits.size(),
43 recorded_history->size());
44 }
45 // The volume around a tet mesh's tracked surface, recorded with its surface history
46 // (volume_history_output), as the bytes of its file (history::VolumeRecorder::to_bytes). The
47 // file route writes them to `path`; the memory route returns them in the report, as
48 // "volume_history", next to "history".
49 void save_volume_history(std::vector<uint8_t> bytes, const std::string& path)
50 {
51 if (!memory()) {
52 history::write_history(path, bytes);
53 logger().info(
54 "volume history: {:.1f} MB saved to {}",
55 double(bytes.size()) / 1e6,
56 path);
57 return;
58 }
59 logger().info(
60 "volume history: {:.1f} MB, returned in the report",
61 double(bytes.size()) / 1e6);
62 recorded_volume_history = std::move(bytes);
63 }
64 // The metadata of a history the component records: its parameters, and the route it ran on,
65 // "file" or "memory". On the memory route the inputs the parameters name are meshes of the
66 // call, which no file holds.
67 std::string history_metadata(const nlohmann::json& params) const
68 {
69 nlohmann::json metadata = params;
70 metadata["route"] = memory() ? "memory" : "file";
71 return metadata.dump();
72 }
73 // What a memory call returns: the result, with the histories recorded, if any, in its
74 // report.
75 ComponentResult take_result()
76 {
77 if (recorded_history)
78 result.second["history"] = nlohmann::json::binary(std::move(*recorded_history));
79 if (recorded_volume_history) {
80 result.second["volume_history"] =
81 nlohmann::json::binary(std::move(*recorded_volume_history));
82 }
83 recorded_history.reset();
84 recorded_volume_history.reset();
85 return std::move(result);
86 }
87 const MeshData& mesh(const std::string& name) const
88 {
89 const auto it = inputs->find(name);
90 if (it == inputs->end()) throw std::runtime_error("Unknown input mesh: " + name);
91 it->second.validate();
92 return it->second;
93 }
94 // Fields io::read_mesh attaches to plain geometry files: MSH source IDs and OBJ per-corner
95 // UVs/normals. Geometry-only inputs carry no attributes through the algorithm, so these are
96 // ignored, as the file readers ignore them; any other attribute is still rejected.
97 inline static const std::set<std::string> reader_vertex_attributes = {"vid"};
98 inline static const std::set<std::string> reader_corner_attributes = {"uv", "normal"};
99 static void attributes(
100 const MeshAttributes& a,
101 const std::set<std::string>& allowed,
102 const std::set<std::string>& required = {})
103 {
104 for (const auto& name : required)
105 if (!a.count(name))
106 throw std::runtime_error("Missing required mesh attribute: " + name);
107 for (const auto& [name, field] : a)
108 if (!allowed.count(name)) throw std::runtime_error("Unknown mesh attribute: " + name);
109 }
110 std::string path(const std::filesystem::path& root, const nlohmann::json& selector) const
111 {
112 return memory() ? selector.get<std::string>()
113 : utils::resolve_path(root, selector.get<std::string>()).string();
114 }
115 std::vector<std::string> paths(
116 const std::filesystem::path& root,
117 const nlohmann::json& selectors) const
118 {
119 if (selectors.is_string()) return {path(root, selectors)};
120 std::vector<std::string> out;
121 for (const auto& v : selectors) out.push_back(path(root, v));
122 return out;
123 }
124 // One named input, uncleaned: the memory twin of reading one file. `vertex_attributes`: the
125 // ones it may carry, and that are ignored. `triangle_attributes`: the ones its triangles may
126 // carry besides the reader's per-corner ones, which the component reads itself
127 // (isotropic_remeshing's feature_fields).
128 void raw_triangles(
129 const std::string& name,
130 MatrixXd& V,
131 MatrixXi& F,
132 const std::set<std::string>& vertex_attributes = reader_vertex_attributes,
133 const std::set<std::string>& triangle_attributes = {}) const
134 {
135 const auto& data = mesh(name);
136 attributes(data.vertex_attributes, vertex_attributes);
137 std::set<std::string> allowed = reader_corner_attributes;
138 allowed.insert(triangle_attributes.begin(), triangle_attributes.end());
139 for (const auto& [type, c] : data.cells) {
140 if (type != CellType::Triangle)
141 throw std::runtime_error("Expected triangle input: " + name);
142 attributes(c.attributes, allowed);
143 }
144 V = data.V;
145 F = indices(data.cell(CellType::Triangle).connectivity);
146 if (!F.rows()) throw std::runtime_error("Empty triangle input: " + name);
147 }
148 void triangles(
149 const std::string& name,
150 MatrixXd& V,
151 MatrixXi& F,
152 double rel = -1,
153 double abs = -1) const
154 {
155 if (!memory()) {
156 read_triangle_mesh(name, V, F, rel, abs);
157 return;
158 }
159 raw_triangles(name, V, F);
160 clean_triangle_mesh(V, F, rel, abs);
161 if (!F.rows()) throw std::runtime_error("No faces after cleaning: " + name);
162 }
163 // Like read_triangle_mesh(paths): concatenate the raw inputs, then clean them once, so
164 // vertex merging sees the same vertices (isolated ones included) in the same order.
165 // `read`, as there: the inputs as given, concatenated, and where their vertices and faces
166 // went. `vertex_attributes` and `triangle_attributes`, as in raw_triangles: a component's own
167 // output fields, say, so that a result can be the next call's input.
168 void triangles(
169 const std::vector<std::string>& names,
170 MatrixXd& V,
171 MatrixXi& F,
172 double rel,
173 double abs,
174 std::vector<int>* provenance = nullptr,
175 ReadProvenance* read = nullptr,
176 const std::set<std::string>& vertex_attributes = reader_vertex_attributes,
177 const std::set<std::string>& triangle_attributes = {}) const
178 {
179 if (!memory()) {
180 read_triangle_mesh(names, V, F, rel, abs, provenance, read);
181 return;
182 }
183 V.resize(0, 3);
184 F.resize(0, 3);
185 if (provenance) provenance->clear();
186 for (size_t i = 0; i < names.size(); ++i) {
187 MatrixXd v;
188 MatrixXi f;
189 raw_triangles(names[i], v, f, vertex_attributes, triangle_attributes);
190 const auto nv = V.rows(), nf = F.rows();
191 f.array() += nv;
192 V.conservativeResize(nv + v.rows(), 3);
193 V.bottomRows(v.rows()) = v;
194 F.conservativeResize(nf + f.rows(), 3);
195 F.bottomRows(f.rows()) = f;
196 if (provenance) provenance->resize(F.rows(), int(i));
197 }
198 clean_triangle_mesh(V, F, rel, abs, provenance, read);
199 if (!F.rows()) throw std::runtime_error("No faces after cleaning");
200 }
201 // `read`, as read_edge_mesh's provenance: the input as given, and where its vertices and
202 // segments went -- filled by the cleaning, as for triangles.
203 void edges(
204 const std::string& name,
205 MatrixXd& V,
206 MatrixXi& E,
207 double rel = -1,
208 double abs = -1,
209 ReadProvenance* read = nullptr) const
210 {
211 if (!memory()) {
212 read_edge_mesh(name, V, E, rel, abs, read);
213 return;
214 }
215 const auto& data = mesh(name);
216 attributes(data.vertex_attributes, reader_vertex_attributes);
217 for (const auto& [type, c] : data.cells) {
218 if (type != CellType::Edge && type != CellType::Point)
219 throw std::runtime_error("Expected curves or points: " + name);
220 attributes(c.attributes, {});
221 }
222 V = data.V;
223 E.resize(0, 2);
224 auto it = data.cells.find(CellType::Edge);
225 if (it != data.cells.end()) E = indices(it->second.connectivity);
226 clean_edge_mesh(V, E, rel, abs, read);
227 }
228 static MatrixXi indices(const DataMatrix<int64_t>& c)
229 {
230 if (c.size() && (c.maxCoeff() > std::numeric_limits<int>::max() || c.minCoeff() < 0))
231 throw std::runtime_error("Connectivity exceeds internal index range");
232 return c.cast<int>();
233 }
234 void prepare(nlohmann::json& j, const std::string& component) const
235 {
236 if (!memory()) return;
237 if ((component == "simwild" || component == "tetwild" || component == "triwild") &&
238 j.contains("input") && j["input"].is_string())
239 j["input"] = nlohmann::json::array({j["input"]});
240 j["application"] = component;
241 j["input_dir"] = "";
242 j["output"] = "";
243 j["log_file"] = "";
244 j["report"] = "";
245 }
246};
247
248template <typename Mesh, typename Position>
249MeshData triangle_data(const Mesh& m, Position position)
250{
251 MeshData data;
252 const auto vs = m.get_vertices();
253 const auto fs = m.get_faces();
254 data.V.resize(vs.size(), 3);
255 std::map<size_t, int64_t> rows;
256 for (size_t i = 0; i < vs.size(); ++i) {
257 rows[vs[i].vid(m)] = i;
258 data.V.row(i) = position(vs[i].vid(m));
259 }
260 auto& c = data.cells[CellType::Triangle].connectivity;
261 c.resize(fs.size(), 3);
262 for (size_t i = 0; i < fs.size(); ++i) {
263 auto v = m.oriented_tri_vertices(fs[i]);
264 for (int j = 0; j < 3; ++j) c(i, j) = rows.at(v[j].vid(m));
265 }
266 data.validate();
267 return data;
268}
269} // namespace wmtk::io
Definition Logger.hpp:21
Definition ComponentIO.hpp:15
void write_history(const std::filesystem::path &path, const std::vector< uint8_t > &bytes)
Definition History.cpp:439
Definition MeshData.hpp:48
A complex and the edits it went through, in commit order.
Definition History.hpp:101
void save(const std::string &path) const
Definition History.cpp:323
std::vector< uint8_t > to_bytes() const
The bytes of a history file, in memory: what a component's memory route returns.
Definition History.cpp:341
What reading a mesh and cleaning it changed: what a history needs to start from the files as they hol...
Definition ReadProvenance.hpp:22