Skip to content

Instantly share code, notes, and snippets.

@dgud
Last active July 24, 2019 09:46
Show Gist options
  • Save dgud/bc4906d066435180ec89a2913271466f to your computer and use it in GitHub Desktop.
Save dgud/bc4906d066435180ec89a2913271466f to your computer and use it in GitHub Desktop.
wings_convert test
#!/usr/bin/env escript
%% -*- erlang -*-
%% Wings 3D File convertion.
%%
%% Copyright (c) 2010 Dan Gudmundsson
%%
%% See the file "license.terms" for information on usage and redistribution
%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
-mode(compile).
-compile([{nowarn_deprecated_function, {erlang,get_stacktrace,0}}]).
%% If moved outside of wings directory modify
-define(WINGS_DIR, "c:/src/wings/ebin").
-record(opts,
{dir = ".", %% Ouput to directory
out_module, %% Output format
verbose=false, %% Verbose output
in_format, %% In format (if unknown extension).
image_format, %% Image out format
in_formats=[], %% Scanned, all import formats
out_formats=[], %% Scanned, all export formats
modify=[] %% Convertion modifications
}).
-record(format,
{mod, %% Module
ext_type, %% Extension
str="", %% Description string
option=false %% Allows options
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
main(Args) ->
Wings_dir = setup_paths(),
ok = wings:start(),
Init = fun() ->
wxTopLevelWindow:iconize(wings_wm:get_value(top_frame)),
keep
end,
wings ! {action, Init},
IEDir = filename:join(Wings_dir, "plugins/import_export"),
code:add_patha(IEDir),
Opts0 = scan_format([IEDir]),
put(verbose, false),
case parse_args(Args, Opts0) of
{#opts{out_module=undefined},_} ->
io:format("**** Error: Out format not specified~n~n"),
usage(Opts0);
{Opts = #opts{}, Files} ->
convert(Files, Opts),
quit(0);
error ->
usage(Opts0)
end.
setup_paths() ->
Escript = filename:dirname(filename:absname(escript:script_name())),
EnvDir = os:getenv("WINGS_DIR"),
DefDir = ?WINGS_DIR,
case test_paths([Escript, EnvDir,DefDir]) of
{ok, Path} ->
code:add_patha(filename:join([Path, "ebin"])),
Path;
_ ->
io:format("**** Error: Compiled wings files not found~n~n"),
io:format(" use 'set WINGS_DIR=c:\PATH_TO_WINGS_INSTALL~n~n")
end.
test_paths([false|Rest]) -> test_paths(Rest);
test_paths([Path0|Rest]) ->
Path = strip_path(lists:reverse(Path0)),
case filelib:is_regular(filename:join([Path, "ebin", "wings.beam"])) of
true -> {ok, Path};
false -> test_paths(Rest)
end;
test_paths([]) -> not_found.
strip_path("nibe/" ++ Path) -> lists:reverse(Path);
strip_path("crs/" ++ Path) -> lists:reverse(Path);
strip_path(Path) -> lists:reverse(Path).
convert(Fs, Opts) ->
Convert = fun(File) ->
call_wings({file, confirmed_new}),
ok = import_file(File, Opts),
ok = do_mods(Opts),
Out = filename:rootname(filename:basename(File)),
export_file(Out, Opts)
end,
[Convert(File) || File <- Fs],
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
scan_format(Dir) ->
Files = filelib:wildcard(filename:join(Dir, "wpc_*.beam")),
Plugin = fun(File, {Type, Acc}) ->
Mod = list_to_atom(filename:rootname(filename:basename(File))),
case Mod:menu({file, Type}, []) of
[{Str, ExtT, Extra}] ->
F = #format{mod=Mod, ext_type=ExtT, str = strip(Str),
option = lists:member(option, Extra)
},
{Type,[F|Acc]};
[{Str, ExtT}] ->
F = #format{mod=Mod, ext_type=ExtT, str = strip(Str)},
{Type,[F|Acc]};
[] ->
{Type, Acc}
end
end,
Default = [#format{mod=nendo, ext_type=ndo, str="Nendo (.ndo)"},
#format{mod=wings, ext_type=wings, str="Wings (.wings)"}],
{_,Export} = lists:foldl(Plugin, {export, Default}, Files),
{_,Import} = lists:foldl(Plugin, {import, Default}, Files),
#opts{in_formats=Import, out_formats=Export}.
strip(Str) ->
strip_1(lists:reverse(Str)).
strip_1([$.|Rest]) ->
strip_1(Rest);
strip_1(Str) ->
lists:reverse(Str).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parse_args(["-o", Dir|Rest], Opts) ->
parse_args(Rest, Opts#opts{dir=Dir});
parse_args(["--outdir", Dir|Rest], Opts) ->
parse_args(Rest, Opts#opts{dir=Dir});
parse_args(["-v"|Rest], Opts) ->
put(verbose, true),
parse_args(Rest, Opts#opts{verbose=true});
parse_args(["--verbose"|Rest], Opts) ->
put(verbose, true),
parse_args(Rest, Opts#opts{verbose=true});
parse_args(["--subdiv", N0|Rest], Opts=#opts{modify=Mod}) ->
N = try
list_to_integer(N0)
catch _:_ ->
io:format("**** Error: Option --subdiv ~p Not an integer ~n~n", [N0]),
usage(Opts)
end,
parse_args(Rest, Opts#opts{modify=[{subdivisions, N}|Mod]});
parse_args(["--tess"++_, "tri"++_|Rest], Opts=#opts{modify=Mod}) ->
parse_args(Rest, Opts#opts{modify=[{tesselation,triangulate}|Mod]});
parse_args(["--tess"++_, "quad"++_|Rest], Opts=#opts{modify=Mod}) ->
parse_args(Rest, Opts#opts{modify=[{tesselation,quadrangulate}|Mod]});
parse_args(["--name", Name|Rest], Opts=#opts{modify=Mod}) ->
parse_args(Rest, Opts#opts{modify=[{exp_name,Name}|Mod]});
parse_args(["--informat", Format|Rest], Opts) ->
parse_args(Rest, Opts#opts{in_format=check_format(in, Format, Opts)});
parse_args(["-f", Format|Rest], Opts) ->
parse_args(Rest, Opts#opts{out_module=check_format(out, Format, Opts)});
parse_args([Opt=[$-|_]| _], Opts) ->
io:format("**** Error: Unknown option ~p~n~n", [Opt]),
usage(Opts);
parse_args(Files, Opts) ->
{Opts, Files}.
check_format(Dir, Ext = [A|_], Opts) when A =/= $. ->
check_format(Dir, [$.|Ext], Opts);
check_format(in, Ext, O=#opts{in_formats=In}) ->
case get_module(Ext, In) of
error ->
check_format_err(in, Ext, O);
Mod -> Mod
end;
check_format(out, Ext, O=#opts{out_formats=Out}) ->
case get_module(Ext, Out) of
error ->
check_format_err(out, Ext, O);
Mod -> Mod
end.
check_format_err(Dir, Format, Opts) ->
io:format("**** Error: Format ~p for ~pput is not supported ~n~n", [Format,Dir]),
usage(Opts).
usage(#opts{in_formats=In, out_formats=Out}) ->
io:format("Usage: wings_convert -f OutFormat [Opts] Files ~n"
" Converts between file formats. ~n"
" Output is written to the current directory by default.~n~n"
" Example: wings_convert -f obj --subdiv 1 --tess quad ../model.wings~n~n"
" Options:~n"
" -o, --outdir DIR Write converted files to DIR.~n"
" -v, --verbose Verbose output.~n"
" --informat FORMAT Ignore file extension and use FORMAT as input.~n"
" --subdiv N Subdivide object N times (default 0).~n"
" --tess TYPE Tesselate object none|tri|quad (default none)~n"
" --name Name Only Export Object with Name~n"
%% " --image Convert images"
"~n"
),
io:format("~nSupported import formats:~n",[]),
[io:format(" ~s~n", [Str]) || #format{str=Str} <- In],
io:format("~nSupported export formats:~n",[]),
[io:format(" ~s~n", [Str]) || #format{str=Str} <- Out],
io:nl(),
quit(1).
quit(Exit) ->
verbose("Script: exiting\n", []),
Ref = monitor(process, wings),
wings ! {action, {file, confirmed_quit}},
receive
{'DOWN',Ref,_,_,_Reason} ->
timer:sleep(200),
halt(Exit)
after 500 ->
halt(Exit)
end.
get_module(Ext, [F=#format{str=Str}|List]) ->
case string:str(Str,Ext) of
0 ->
get_module(Ext,List);
_ ->
F
end;
get_module(_, []) ->
error.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
call_wings(Cmd) ->
verbose("Script: ~p~n",[{action, Cmd}]),
wings ! {action, Cmd},
Me = self(),
Sync = fun(_St) ->
Me ! {cmd, sync},
keep
end,
wings ! {external, Sync},
receive {cmd, sync} -> ok end,
ok.
import_file(File, Opts) ->
verbose("~s => ~n", [File]),
wings ! {action, fun() -> put(wings_not_running, {import, File}), keep end},
try import_file_1(File,Opts) of
{error,Reason} ->
io:format("**** Import Failed: ~p On file: ~p~n~n", [Reason, File]),
quit(1);
ok ->
ok
catch
_:{command_error,Message} ->
io:format("**** Import Failed: ~s On file: ~p~n~n", [Message, File]),
quit(1);
_:Reason ->
io:format("**** Import crashed: ~p On file: ~p~n~n", [Reason, File]),
io:format("Debug info: ~p~n~n",[erlang:get_stacktrace()]),
quit(1)
end.
import_file_1(File, Opts=#opts{in_format=undefined}) ->
import_file_2(filename:extension(File),File,Opts);
import_file_1(File, Opts=#opts{in_format=InFormat}) ->
import_file_2(InFormat,File,Opts).
import_file_2(#format{mod=wings}, File, _) ->
call_wings({file, {confirmed_open, File}});
import_file_2(#format{mod=nendo}, File, _) ->
call_wings({file, {import, {ndo, File}}});
import_file_2(#format{ext_type=Type, option=false}, _File, _Opts) ->
call_wings({file,{import,Type}});
import_file_2(#format{ext_type=Type, option=true}, _File, _) ->
call_wings({file,{import,{Type,[]}}});
import_file_2(Str, File, Opts = #opts{in_formats=In}) ->
case get_module(Str, In) of
error ->
io:format("**** Error: Import Failed: ~p On file: ~p~n~n",
["Unknown import format", File]),
quit(1);
Mod -> import_file_2(Mod, File, Opts)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_mods(#opts{modify=Mods}) ->
modify_model(Mods).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
export_file(File, Opts=#opts{dir=Dir, out_module=F=#format{ext_type=Ext}}) ->
FileName = filename:join(Dir, File++"."++ atom_to_list(Ext)),
verbose("Export to: ~s~n", [FileName]),
wings ! {action, fun() -> put(wings_not_running, {export, FileName}), keep end},
try export_file_1(F, FileName, Opts) of
{error,Reason} ->
io:format("**** Export Failed: ~p On file: ~p~n~n", [Reason, FileName]),
quit(1);
ok ->
ok
catch
_:{command_error,Message} ->
io:format("**** Export Failed: ~s On file: ~p~n~n", [Message, File]),
quit(1);
_:Reason ->
io:format("**** Export crashed: ~p On file: ~p~n~n", [Reason, FileName]),
io:format("Debug info: ~p~n~n",[erlang:get_stacktrace()]),
quit(1)
end.
export_file_1(F, FileName, #opts{modify=Mods}) ->
case proplists:get_value(exp_name, Mods, false) of
false -> export_file_2(F, FileName, false);
_Name -> export_file_2(F, FileName, true)
end.
export_file_2(#format{mod=wings}, FileName, Selected) ->
call_wings({file, {exp_cmd(save_as, Selected), {FileName, ignore}}});
export_file_2(#format{mod=nendo}, FileName, Selected) ->
call_wings({file, {exp_cmd(export, Selected), {ndo, FileName}}});
export_file_2(#format{ext_type=Type, option=false}, _FN, Selected) ->
call_wings({file,{exp_cmd(export, Selected),Type}});
export_file_2(#format{ext_type=Type, option=true}, _F, Selected) ->
Mods = [{include_uvs, true}, {include_normals, true}],
call_wings({file,{exp_cmd(export, Selected),{Type, Mods}}}).
exp_cmd(export, false) -> export;
exp_cmd(export, true) -> export_selected;
exp_cmd(save_as, false) -> save_as;
exp_cmd(save_as, true) -> save_selected.
verbose(F,A) ->
get(verbose) andalso io:format(F,A).
modify_model([]) ->
ok;
modify_model(Ps) ->
SubDivs = proplists:get_value(subdivisions, Ps, 0),
Tess = proplists:get_value(tesselation, Ps, none),
call_wings({select, body}),
case proplists:get_value(exp_name, Ps, false) of
false -> call_wings({select, all});
Name -> call_wings({select, {by, {by_name_with, Name}}})
end,
sub_divide(SubDivs),
tesselate(Tess),
ok.
sub_divide(0) -> ok;
sub_divide(N) ->
call_wings({body, smooth}),
sub_divide(N-1).
tesselate(none) -> ok;
tesselate(triangulate) ->
call_wings({tools,{virtual_mirror, freeze}}),
call_wings({select, face}),
call_wings({face, {tesselate, triangulate}});
tesselate(quadrangulate) ->
call_wings({tools,{virtual_mirror, freeze}}),
call_wings({select, face}),
call_wings({face, {tesselate, quadrangulate}}).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment