-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathcastle_model_converter.dpr
257 lines (226 loc) · 10.6 KB
/
castle_model_converter.dpr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
{
Copyright 2003-2024 Michalis Kamburelis.
This file is part of "Castle Game Engine".
"Castle Game Engine" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Castle Game Engine" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
----------------------------------------------------------------------------
}
{ Converter of 3D and 2D model formats supported by Castle Game Engine.
See https://castle-engine.io/castle-model-converter
for usage docs.
See https://castle-engine.io/creating_data_model_formats.php
for the supported model formats.
Usage:
Provide 2 parameters on the command-line:
1. input URL (can be just a filename) to read,
2. output URL (to save to, can be just a filename).
The output URL (2nd) is optional.
If not provided, the stdout is assumed, which is equivalent to providing '-'
as the output URL.
Also, the output URL is not allowed when the --validate is used.
Use '-' as input URL on the command-line to read from the standard input
(consider using then also --stdin-url=xxx option, to determine the file type
and base to resolve relative URLs).
Analogously, use '-' as output URL on the command-line to write to the standard output.
Consider using --stdout-url=xxx option to determine the file type.
This tool can be used instead of "castle-model-viewer" conversions done from GUI.
}
program castle_model_converter;
uses SysUtils, Classes,
{$ifndef VER3_0} OpenSSLSockets, {$endif}
CastleUtils, CastleClassUtils, X3DNodes, X3DLoad, CastleParameters, CastleDownload,
CastleFilesUtils, CastleUriUtils, CastleApplicationProperties, CastleLog,
X3DLoadInternalUtils, X3DFields,
V3DSceneVersion;
var
Validate: Boolean = false;
StdInUrl: String = 'stdin.x3dv';
StdOutUrl: String = 'stdout.x3dv';
OutputX3dEncoding: String = '';
const
Options: array [0..9] of TOption =
(
(Short: 'h'; Long: 'help'; Argument: oaNone),
(Short: 'v'; Long: 'version'; Argument: oaNone),
(Short: #0; Long: 'encoding'; Argument: oaRequired),
(Short: #0; Long: 'debug-log'; Argument: oaNone),
(Short: #0; Long: 'no-x3d-extensions'; Argument: oaNone),
(Short: #0; Long: 'enable-downloads'; Argument: oaNone),
(Short: #0; Long: 'validate'; Argument: oaNone),
(Short: #0; Long: 'stdin-url'; Argument: oaRequired),
(Short: #0; Long: 'stdout-url'; Argument: oaRequired),
(Short: #0; Long: 'float-precision'; Argument: oaRequired)
);
procedure OptionProc(OptionNum: Integer; HasArgument: boolean;
const Argument: string; const SeparateArgs: TSeparateArgs; Data: Pointer);
var
FloatPrecision: Integer;
begin
case OptionNum of
0:begin
InfoWrite(
'castle-model-converter: converter for all 3D and 2D model formats' +NL+
'supported by Castle Game Engine (like glTF, X3D, Collada, MD3...).' +NL+
NL+
'Call with these parameters on the command-line:' +NL+
NL+
'1. The input filename. It can be any filename, URL or - (stdin).' +NL+
' Required.' +NL+
NL+
'2. The output filename. It can be any filename, URL or - (stdout).' +NL+
' This parameter is optional. If not provided, the output is stdout,' +NL+
' unless --validate is also used (then the output is not written anywhere).' +NL+
NL+
'Available options are:' +NL+
OptionDescription('-h / --help', 'Print this help message and exit.') + NL +
OptionDescription('-v / --version', 'Print the version number and exit.') + NL +
OptionDescription('--no-x3d-extensions', 'Do not use Castle Game Engine extensions. This will output file valid in all X3D browsers (but maybe with some CGE-specific features missing).') +NL+
OptionDescription('--enable-downloads', 'Enable (blocking) downloads from the net, e.g. to download a texture or Inlined model referenced by htt(s) protocol).') +NL+
OptionDescription('--validate', 'Only validate the input, without any output (the output URL is not allowed in this case). Moreover, if there will be any warning or error, we will exit with non-zero status (by default, only errors cause non-zero status).') +NL+
OptionDescription('--stdin-url', 'If input URL is "-", then we read file contents from the standard input. In this case, you can use this option to provide a "pretend" URL for the input. We will use it to resolve relative URLs inside the input (e.g. to glTF binary blobs) and to guess the input file type. Default is "stdin.x3dv" in current directory, so we assume it is X3D (classic encoded), and resolve with respect to the current directory.') +NL+
OptionDescription('--stdout-url', 'If output URL is "-", then we write file contents to the standard output. In this case, you can use this option to provide a "pretend" URL for the output. We will use it to determine the output file type, e.g. "out.x3d" to output X3D XML encoding or "out.x3dv" to output X3D classic encoding.') +NL+
OptionDescription('--float-precision DIGITS', 'Number of digits after the decimal point when writing floating-point numbers. Default is to write all possibly relevant digits. Specify any value >= 0 to use this number of digits.') +NL+
OptionDescription('--encoding classic|xml', 'DEPRECATED. Choose X3D encoding. Do not use -- the 2nd parameter should determine the output type, ".x3d" extension says to make X3D XML, ".x3dv" says to make X3D classic. Or use --stdout-url to provide fake URL in case output is to stdout.') + NL +
NL+
ApplicationProperties.Description);
Halt;
end;
1:begin
Writeln(Version);
Halt;
end;
2:OutputX3dEncoding := Argument;
3:begin
LogEnableStandardOutput := false;
InitializeLog;
Writeln(ErrOutput, ApplicationProperties.ApplicationName, ': Logging to ', LogOutput);
end;
4:CastleX3dExtensions := false;
5:EnableBlockingDownloads := true;
6:Validate := true;
7:StdInUrl := Argument;
8:StdOutUrl := Argument;
9:begin
if not TryStrToInt(Argument, FloatPrecision) then
raise EInvalidParams.CreateFmt('Invalid --float-precision argument "%s"', [Argument]);
if FloatPrecision < 0 then
FloatOutputFormat := '%g'
else
FloatOutputFormat := '%.' + IntToStr(FloatPrecision) + 'f';
end;
else raise EInternalError.Create('OptionProc');
end;
end;
type
TEventsHandler = class
WasWarning: Boolean;
procedure HandleWarning(const Category, Message: String);
end;
procedure TEventsHandler.HandleWarning(const Category, Message: String);
begin
ApplicationProperties.WriteWarningOnConsole(Category, Message);
WasWarning := true;
end;
function LoadNodeStandardInput: TX3DRootNode;
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
ReadGrowingStream(StdInStream, Stream, true);
Result := LoadNode(Stream,
{ BaseUrl. When this is only a filename, like default "stdin.x3dv",
then LoadNode will interpret it as file in current directory already,
because LoadNode does AbsoluteUri at start. } StdInUrl,
{ MimeType } UriMimeType(StdInUrl));
finally FreeAndNil(Stream) end;
end;
procedure Run;
var
InputUrl, OutputUrl, OutputMimeType: String;
Node: TX3DRootNode;
EventsHandler: TEventsHandler;
OutputStream: TStream;
begin
ApplicationProperties.ApplicationName := 'castle-model-converter';
ApplicationProperties.Version := Version;
{ parse command-line, calculating InputUrl and OutputUrl }
Parameters.Parse(Options, @OptionProc, nil);
Parameters.CheckHighAtLeast(1);
Parameters.CheckHighAtMost(2);
InputUrl := Parameters[1];
if Parameters.High = 2 then
OutputUrl := Parameters[2]
else
OutputUrl := '-';
{ Make basic check that output URL was not specified when --validate is used.
This over-uses the fact that "not providing output URL" is equivalent to
providing "-" as output URL. }
if Validate and (OutputUrl <> '-') then
raise EInvalidParams.Create('When --validate is used, the output URL is not allowed');
EventsHandler := TEventsHandler.Create;
try
ApplicationProperties.OnWarning.Add(@EventsHandler.HandleWarning);
// load the model, thus calculating Node
if InputUrl = '-' then
Node := LoadNodeStandardInput
else
Node := LoadNode(InputUrl);
// calculate OutputMimeType
if OutputUrl = '-' then
OutputMimeType := UriMimeType(StdOutUrl)
else
OutputMimeType := UriMimeType(OutputUrl);
// override OutputMimeType if we need to support deprecated --encoding
if OutputX3dEncoding <> '' then
begin
WritelnWarning('DEPRECATED: --encoding argument is deprecated. Use instead the 2nd parameter (output URL) to determine the output type, from extension. Or use --stdout-url to provide fake URL in case output is to stdout.');
if SameText(OutputX3dEncoding, 'classic') then
begin
if Node.HasForceVersion and (Node.ForceVersion.Major <= 2) then
OutputMimeType := 'model/vrml' // VRML <= 2.0
else
OutputMimeType := 'model/x3d+vrml'; // X3D in classic encoding
end else
if SameText(OutputX3dEncoding, 'xml') then
begin
OutputMimeType := 'model/x3d+xml'; // X3D in XML encoding
end else
raise EInvalidParams.CreateFmt('Invalid --encoding argument "%s"', [OutputX3dEncoding]);
end;
try
if not Validate then
begin
if OutputUrl <> '-' then
OutputStream := UrlSaveStream(OutputUrl)
else
OutputStream := StdOutStream;
SaveNode(Node, OutputStream, OutputMimeType,
{ generator (metadata) } 'castle-model-converter, https://castle-engine.io/castle-model-converter',
{ source (metadata) } ExtractURIName(InputUrl));
if OutputStream <> StdOutStream then
FreeAndNil(OutputStream);
end;
finally FreeAndNil(Node) end;
if Validate and EventsHandler.WasWarning then
raise Exception.Create('Validation failed (consult the warnings above), exiting with non-zero status');
finally FreeAndNil(EventsHandler) end;
end;
begin
try
Run;
except
on E: TObject do
begin
{ In case of exception, write nice message and exit with non-zero status,
without dumping any stack trace (because it's normal to
exit with exception in case of project/environment error, not a bug,
and the stack trace is mostly useless for end-users in -dRELEASE mode). }
Writeln(ErrOutput, ExceptMessage(E));
Halt(1);
end;
end;
end.