Skip to content

Commit 90bdf2d

Browse files
committed
Support processing multiple files at once.
1 parent 08607f9 commit 90bdf2d

File tree

2 files changed

+148
-126
lines changed

2 files changed

+148
-126
lines changed

src/args.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Args {
3636
}
3737
}
3838
} else if (arg[0] != '-') {
39-
globalopts.insert(arg);
39+
globalopts.push_back(arg);
4040
allopts.push_back(arg);
4141
}
4242
}
@@ -64,7 +64,7 @@ class Args {
6464
return default_arg;
6565
}
6666

67-
const std::set<std::string>& orphans() const { return globalopts; }
67+
const std::vector<std::string>& orphans() const { return globalopts; }
6868

6969
std::string app() const { return app_name; }
7070

@@ -73,7 +73,7 @@ class Args {
7373
std::vector<std::string> allopts;
7474
std::set<char> shortopts;
7575
std::set<std::string> longopts;
76-
std::set<std::string> globalopts;
76+
std::vector<std::string> globalopts;
7777
};
7878

7979

src/obj-magic.cpp

Lines changed: 145 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ int main(int argc, char* argv[]) {
4040
return EXIT_SUCCESS;
4141
}
4242
if (args.opt('h', "help") || argc < 3) {
43-
std::cerr << "Usage: " << args.app() << " PARAM [PARAM...] FILE" << std::endl;
43+
std::cerr << "Usage: " << args.app() << " PARAM [PARAM...] FILE [FILE...]" << std::endl;
4444
std::cerr << "Parameters:" << std::endl;
4545
std::cerr << " -h --help print this help and exit" << std::endl;
4646
std::cerr << " -v --version print version and exit" << std::endl;
47-
std::cerr << " -o --out FILE put output to FILE instead of stdout" << std::endl;
47+
std::cerr << " -o --out FILE put output to FILE instead of stdout (if 1 input given)" << std::endl;
4848
std::cerr << " -O --overwrite edit input file directly, overwriting it" << std::endl;
4949
std::cerr << " -i --info print info about the object and exit" << std::endl;
5050
std::cerr << " -n --normalize-normals renormalize all normals" << std::endl;
@@ -59,6 +59,7 @@ int main(int argc, char* argv[]) {
5959
std::cerr << " --fit[xyz] AMOUNT uniformly scale to fit AMOUNT in dimension" << std::endl;
6060
std::cerr << " --resize[xyz] AMOUNT non-uniformly scale to fit AMOUNT in dimension" << std::endl;
6161
std::cerr << std::endl;
62+
std::cerr << "Multiple input files will force --overwrite mode." << std::endl;
6263
std::cerr << "[xyz] - long option suffixed with x, y or z operates only on that axis." << std::endl;
6364
std::cerr << "No suffix (or short form) assumes all axes." << std::endl;
6465
std::cerr << "Example: " << args.app() << " --scale 0.5 model.obj" << std::endl;
@@ -71,12 +72,23 @@ int main(int argc, char* argv[]) {
7172
vec3 normal_scale = args.opt(' ', "invert-normals") ? vec3(-1.0f) : vec3(1.0);
7273

7374
// Output stream handling
74-
std::string infile = argv[argc-1];
75+
std::vector<std::string> files = args.orphans();
76+
auto removed_files_it = std::remove_if(files.begin(), files.end(), [](const std::string& file) { return file.find(".obj") == std::string::npos; });
77+
files.erase(removed_files_it, files.end());
78+
if (files.empty()) {
79+
std::cerr << "Need at least one input file!" << std::endl;
80+
return EXIT_FAILURE;
81+
}
7582
std::string outfile = args.arg<std::string>('o', "out");
7683
std::ofstream fout;
77-
std::stringstream sout;
7884
bool inPlaceOutput = false;
79-
if (outfile == infile || args.opt('O', "overwrite")) { // In-place
85+
if (files.size() > 1) {
86+
inPlaceOutput = !info;
87+
if (!outfile.empty()) {
88+
std::cerr << "Can't use -o / --out option with multiple input files." << std::endl;
89+
return EXIT_FAILURE;
90+
}
91+
} else if (outfile == files[0] || args.opt('O', "overwrite")) { // In-place
8092
inPlaceOutput = true;
8193
} else if (!outfile.empty()) {
8294
fout.open(outfile.c_str());
@@ -85,7 +97,6 @@ int main(int argc, char* argv[]) {
8597
return EXIT_FAILURE;
8698
}
8799
}
88-
std::ostream& out = inPlaceOutput ? sout : (outfile.empty() ? std::cout : fout);
89100

90101
vec3 scale(args.arg('s', "scale", 1.0f));
91102
scale.x *= args.arg(' ', "scalex", 1.0f);
@@ -140,134 +151,145 @@ int main(int argc, char* argv[]) {
140151
if (rotangles.z != 0.0f) temprot = rotate(temprot, rotangles.z, vec3(0,0,1));
141152
mat3 rotation(temprot);
142153

143-
std::ifstream file(infile.c_str(), std::ios::binary);
144-
if (!file.is_open()) {
145-
std::cerr << "Failed to open file " << infile << std::endl;
146-
return EXIT_FAILURE;
147-
}
154+
bool infoHeaderDone = false;
155+
for (const std::string& infile : files) {
156+
std::stringstream sout;
157+
std::ifstream file(infile.c_str(), std::ios::binary);
158+
std::ostream& out = inPlaceOutput ? sout : (outfile.empty() ? std::cout : fout);
159+
160+
if (!file.is_open()) {
161+
std::cerr << "Failed to open file " << infile << std::endl;
162+
return EXIT_FAILURE;
163+
}
164+
165+
std::string row;
166+
// Analyzing pass
167+
bool analyze = info || (center.length() > 0.0f) || (fit.length() > 0.0f) || (resize.length() > 0.0f);
168+
if (analyze) {
169+
vec3 lbound(std::numeric_limits<float>::max());
170+
vec3 ubound(-std::numeric_limits<float>::max());
171+
std::map<std::string, unsigned> materials;
172+
unsigned long long v_count = 0, vt_count = 0, vn_count = 0, f_count = 0, p_count = 0, l_count = 0, o_count = 0;
173+
while (getline(file, row)) {
174+
std::istringstream srow(row);
175+
vec3 in;
176+
std::string tempst;
177+
if (row.substr(0,2) == "v ") { // Vertices
178+
srow >> tempst >> in.x >> in.y >> in.z;
179+
lbound = min(in, lbound);
180+
ubound = max(in, ubound);
181+
++v_count;
182+
}
183+
else if (row.substr(0,3) == "vt ") ++vt_count;
184+
else if (row.substr(0,3) == "vn ") ++vn_count;
185+
else if (row.substr(0,2) == "p ") ++p_count;
186+
else if (row.substr(0,2) == "l ") ++l_count;
187+
else if (row.substr(0,2) == "f ") ++f_count;
188+
else if (row.substr(0,2) == "o ") ++o_count;
189+
else if (row.substr(0,7) == "usemtl ") materials[row.substr(7)]++;
190+
}
191+
center *= (lbound + ubound) * 0.5f;
192+
// Output info?
193+
if (info) {
194+
if (!infoHeaderDone) {
195+
out << APPNAME << " " << VERSION << std::endl;
196+
infoHeaderDone = true;
197+
} else out << std::endl;
198+
out << std::endl;
199+
out << "Filename: " << infile << std::endl;
200+
out << "Vertices: " << v_count << std::endl;
201+
out << "TexCoords: " << vt_count << std::endl;
202+
out << "Normals: " << vn_count << std::endl;
203+
out << "Faces: " << f_count << std::endl;
204+
out << "Points: " << p_count << std::endl;
205+
out << "Lines: " << l_count << std::endl;
206+
out << "Named objects: " << o_count << std::endl;
207+
out << "Materials: " << materials.size() << std::endl;
208+
out << " " << std::right << std::setw(W) << "x" << std::setw(W) << "y" << std::setw(W) << "z" << std::endl;
209+
out << "Center: " << toString((lbound + ubound) * 0.5f) << std::endl;
210+
out << "Size: " << toString(ubound - lbound) << std::endl;
211+
out << "Lower bounds: " << toString(lbound) << std::endl;
212+
out << "Upper bounds: " << toString(ubound) << std::endl;
213+
continue;
214+
}
215+
if (fit.length()) {
216+
vec3 size = ubound - lbound;
217+
float fitScale = 1.f;
218+
if (args.arg(' ', "fit", 0.f)) fitScale = args.arg(' ', "fit", 0.f) / compMax(size);
219+
else if (fit.x) fitScale = fit.x / size.x;
220+
else if (fit.y) fitScale = fit.y / size.y;
221+
else if (fit.z) fitScale = fit.z / size.z;
222+
scale *= fitScale;
223+
}
224+
if (resize.length()) {
225+
vec3 size = ubound - lbound;
226+
vec3 resizeScale(1, 1, 1);
227+
if (resize.x) resizeScale.x = resize.x / size.x;
228+
if (resize.y) resizeScale.y = resize.y / size.y;
229+
if (resize.z) resizeScale.z = resize.z / size.z;
230+
scale *= resizeScale;
231+
}
232+
}
233+
234+
auto outputUnmodifiedRow = [](std::ostream& out, const std::string& row) {
235+
// getline stops at \n, so there might be \r hiding in there if we are reading CRLF files
236+
int last = row.size() - 1;
237+
if (last >= 0 && row[last] == '\r')
238+
out << row.substr(0, last) << std::endl;
239+
else out << row << std::endl;
240+
};
148241

149-
std::string row;
150-
// Analyzing pass
151-
bool analyze = info || (center.length() > 0.0f) || (fit.length() > 0.0f) || (resize.length() > 0.0f);
152-
if (analyze) {
153-
vec3 lbound(std::numeric_limits<float>::max());
154-
vec3 ubound(-std::numeric_limits<float>::max());
155-
std::map<std::string, unsigned> materials;
156-
unsigned long long v_count = 0, vt_count = 0, vn_count = 0, f_count = 0, p_count = 0, l_count = 0, o_count = 0;
242+
// Output pass
243+
file.clear();
244+
file.seekg(0, std::ios::beg);
157245
while (getline(file, row)) {
158246
std::istringstream srow(row);
159247
vec3 in;
160248
std::string tempst;
161249
if (row.substr(0,2) == "v ") { // Vertices
162250
srow >> tempst >> in.x >> in.y >> in.z;
163-
lbound = min(in, lbound);
164-
ubound = max(in, ubound);
165-
++v_count;
251+
vec3 old = in;
252+
in -= center;
253+
in *= mirror;
254+
in *= scale;
255+
in = rotation * in;
256+
in += translate;
257+
if (old != in)
258+
out << "v " << in.x << " " << in.y << " " << in.z << std::endl;
259+
else outputUnmodifiedRow(out, row);
260+
} else if (row.substr(0,3) == "vt ") { // Tex coords
261+
srow >> tempst >> in.x >> in.y;
262+
vec3 old = in;
263+
if (flipUvX) in.x = 1.0f - in.x;
264+
if (flipUvY) in.y = 1.0f - in.y;
265+
in.x *= scaleUv.x;
266+
in.y *= scaleUv.y;
267+
if (old != in)
268+
out << "vt " << in.x << " " << in.y << std::endl;
269+
else outputUnmodifiedRow(out, row);
270+
} else if (row.substr(0,3) == "vn ") { // Normals
271+
srow >> tempst >> in.x >> in.y >> in.z;
272+
vec3 old = in;
273+
in *= normal_scale;
274+
if (normalize_normals) in = normalize(in);
275+
if (old != in)
276+
out << "vn " << in.x << " " << in.y << " " << in.z << std::endl;
277+
else outputUnmodifiedRow(out, row);
278+
} else {
279+
outputUnmodifiedRow(out, row);
166280
}
167-
else if (row.substr(0,3) == "vt ") ++vt_count;
168-
else if (row.substr(0,3) == "vn ") ++vn_count;
169-
else if (row.substr(0,2) == "p ") ++p_count;
170-
else if (row.substr(0,2) == "l ") ++l_count;
171-
else if (row.substr(0,2) == "f ") ++f_count;
172-
else if (row.substr(0,2) == "o ") ++o_count;
173-
else if (row.substr(0,7) == "usemtl ") materials[row.substr(7)]++;
174-
}
175-
center *= (lbound + ubound) * 0.5f;
176-
// Output info?
177-
if (info) {
178-
out << APPNAME << " " << VERSION << std::endl;
179-
out << "Filename: " << infile << std::endl;
180-
out << "Vertices: " << v_count << std::endl;
181-
out << "TexCoords: " << vt_count << std::endl;
182-
out << "Normals: " << vn_count << std::endl;
183-
out << "Faces: " << f_count << std::endl;
184-
out << "Points: " << p_count << std::endl;
185-
out << "Lines: " << l_count << std::endl;
186-
out << "Named objects: " << o_count << std::endl;
187-
out << "Materials: " << materials.size() << std::endl;
188-
out << " " << std::right << std::setw(W) << "x" << std::setw(W) << "y" << std::setw(W) << "z" << std::endl;
189-
out << "Center: " << toString((lbound + ubound) * 0.5f) << std::endl;
190-
out << "Size: " << toString(ubound - lbound) << std::endl;
191-
out << "Lower bounds: " << toString(lbound) << std::endl;
192-
out << "Upper bounds: " << toString(ubound) << std::endl;
193-
return EXIT_SUCCESS;
194-
}
195-
if (fit.length()) {
196-
vec3 size = ubound - lbound;
197-
float fitScale = 1.f;
198-
if (args.arg(' ', "fit", 0.f)) fitScale = args.arg(' ', "fit", 0.f) / compMax(size);
199-
else if (fit.x) fitScale = fit.x / size.x;
200-
else if (fit.y) fitScale = fit.y / size.y;
201-
else if (fit.z) fitScale = fit.z / size.z;
202-
scale *= fitScale;
203-
}
204-
if (resize.length()) {
205-
vec3 size = ubound - lbound;
206-
vec3 resizeScale(1, 1, 1);
207-
if (resize.x) resizeScale.x = resize.x / size.x;
208-
if (resize.y) resizeScale.y = resize.y / size.y;
209-
if (resize.z) resizeScale.z = resize.z / size.z;
210-
scale *= resizeScale;
211-
}
212-
}
213-
214-
auto outputUnmodifiedRow = [](std::ostream& out, const std::string& row) {
215-
// getline stops at \n, so there might be \r hiding in there if we are reading CRLF files
216-
int last = row.size() - 1;
217-
if (last >= 0 && row[last] == '\r')
218-
out << row.substr(0, last) << std::endl;
219-
else out << row << std::endl;
220-
};
221-
222-
// Output pass
223-
file.clear();
224-
file.seekg(0, std::ios::beg);
225-
while (getline(file, row)) {
226-
std::istringstream srow(row);
227-
vec3 in;
228-
std::string tempst;
229-
if (row.substr(0,2) == "v ") { // Vertices
230-
srow >> tempst >> in.x >> in.y >> in.z;
231-
vec3 old = in;
232-
in -= center;
233-
in *= mirror;
234-
in *= scale;
235-
in = rotation * in;
236-
in += translate;
237-
if (old != in)
238-
out << "v " << in.x << " " << in.y << " " << in.z << std::endl;
239-
else outputUnmodifiedRow(out, row);
240-
} else if (row.substr(0,3) == "vt ") { // Tex coords
241-
srow >> tempst >> in.x >> in.y;
242-
vec3 old = in;
243-
if (flipUvX) in.x = 1.0f - in.x;
244-
if (flipUvY) in.y = 1.0f - in.y;
245-
in.x *= scaleUv.x;
246-
in.y *= scaleUv.y;
247-
if (old != in)
248-
out << "vt " << in.x << " " << in.y << std::endl;
249-
else outputUnmodifiedRow(out, row);
250-
} else if (row.substr(0,3) == "vn ") { // Normals
251-
srow >> tempst >> in.x >> in.y >> in.z;
252-
vec3 old = in;
253-
in *= normal_scale;
254-
if (normalize_normals) in = normalize(in);
255-
if (old != in)
256-
out << "vn " << in.x << " " << in.y << " " << in.z << std::endl;
257-
else outputUnmodifiedRow(out, row);
258-
} else {
259-
outputUnmodifiedRow(out, row);
260281
}
261-
}
262282

263-
if (inPlaceOutput) {
264-
file.close();
265-
fout.open(infile.c_str());
266-
if (fout.fail()) {
267-
std::cerr << "Failed to open file " << infile << " for output" << std::endl;
268-
return EXIT_FAILURE;
283+
if (inPlaceOutput) {
284+
file.close();
285+
std::ofstream finplaceout;
286+
finplaceout.open(infile.c_str());
287+
if (finplaceout.fail()) {
288+
std::cerr << "Failed to open file " << infile << " for output" << std::endl;
289+
return EXIT_FAILURE;
290+
}
291+
finplaceout << sout.rdbuf();
269292
}
270-
fout << sout.rdbuf();
271293
}
272294

273295
return EXIT_SUCCESS;

0 commit comments

Comments
 (0)