aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/test/opaque_tests_SUITE_data/src/wings')
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings.hrl205
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_dissolve.erl375
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge.erl243
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge_cmd.erl91
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_face.erl127
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_facemat.erl299
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_intl.hrl15
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_io.erl37
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_sel.erl68
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_shape.erl69
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_util.erl39
-rw-r--r--lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_we.erl250
12 files changed, 1818 insertions, 0 deletions
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings.hrl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings.hrl
new file mode 100644
index 0000000000..b9339a8eb1
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings.hrl
@@ -0,0 +1,205 @@
+%%
+%% wings.hrl --
+%%
+%% Global record definition and defines.
+%%
+%% Copyright (c) 2001-2005 Bjorn Gustavsson
+%%
+%% See the file "license.terms" for information on usage and redistribution
+%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+%%
+%% $Id: wings.hrl,v 1.1 2009/01/25 18:55:33 kostis Exp $
+%%
+
+-include("wings_intl.hrl").
+
+-ifdef(NEED_ESDL).
+-include_lib("esdl/include/sdl.hrl").
+-include_lib("esdl/include/sdl_events.hrl").
+-include_lib("esdl/include/sdl_video.hrl").
+-include_lib("esdl/include/sdl_keyboard.hrl").
+-include_lib("esdl/include/sdl_mouse.hrl").
+-include_lib("esdl/src/sdl_util.hrl").
+-define(CTRL_BITS, ?KMOD_CTRL).
+-define(ALT_BITS, ?KMOD_ALT).
+-define(SHIFT_BITS, ?KMOD_SHIFT).
+-define(META_BITS, ?KMOD_META).
+-endif.
+
+-define(WINGS_VERSION, ?wings_version).
+
+-define(CHAR_HEIGHT, wings_text:height()).
+-define(CHAR_WIDTH, wings_text:width()).
+
+-define(LINE_HEIGHT, (?CHAR_HEIGHT+2)).
+-define(GROUND_GRID_SIZE, 1).
+-define(CAMERA_DIST, (8.0*?GROUND_GRID_SIZE)).
+-define(NORMAL_LINEWIDTH, 1.0).
+-define(DEGREE, 176). %Degree character.
+
+-define(HIT_BUF_SIZE, (1024*1024)).
+
+-define(PANE_COLOR, {0.52,0.52,0.52}).
+-define(BEVEL_HIGHLIGHT, {0.9,0.9,0.9}).
+-define(BEVEL_LOWLIGHT, {0.3,0.3,0.3}).
+-define(BEVEL_HIGHLIGHT_MIX, 0.5).
+-define(BEVEL_LOWLIGHT_MIX, 0.5).
+
+-define(SLOW(Cmd), begin wings_io:hourglass(), Cmd end).
+-define(TC(Cmd), wings_util:tc(fun() -> Cmd end, ?MODULE, ?LINE)).
+
+-ifdef(DEBUG).
+-define(ASSERT(E), case E of
+ true -> ok;
+ _ ->
+ erlang:error({assertion_failed,?MODULE,?LINE})
+ end).
+-define(CHECK_ERROR(), wings_gl:check_error(?MODULE, ?LINE)).
+-else.
+-define(ASSERT(E),ok).
+-define(CHECK_ERROR(), ok).
+-endif.
+
+%% Display lists per object.
+%% Important: Plain integers and integers in lists will be assumed to
+%% be display lists. Arbitrary integers must be stored inside a tuple
+%% or record to not be interpreted as a display list.
+-record(dlo,
+ {work=none, %Workmode faces.
+ smooth=none, %Smooth-shaded faces.
+ edges=none, %Edges and wire-frame.
+ vs=none, %Unselected vertices.
+ hard=none, %Hard edges.
+ sel=none, %Selected items.
+ orig_sel=none, %Original selection.
+ normals=none, %Normals.
+ pick=none, %For picking.
+ proxy_faces=none, %Smooth proxy faces.
+ proxy_edges=none, %Smooth proxy edges.
+
+ %% Miscellanous.
+ hilite=none, %Hilite display list.
+ mirror=none, %Virtual mirror data.
+ ns=none, %Normals/positions per face.
+
+ %% Source for display lists.
+ src_we=none, %Source object.
+ src_sel=none, %Source selection.
+ orig_mode=none, %Original selection mode.
+ split=none, %Split data.
+ drag=none, %For dragging.
+ transparent=false, %Object includes transparancy.
+ proxy_data=none, %Data for smooth proxy.
+ open=false, %Open (has hole).
+
+ %% List of display lists known to be needed only based
+ %% on display modes, not whether the lists themselves exist.
+ %% Example: [work,edges]
+ needed=[]
+ }).
+
+%% Main state record containing all objects and other important state.
+-record(st,
+ {shapes, %All visible shapes
+ selmode, %Selection mode:
+ % vertex, edge, face, body
+ sh=false, %Smart highlight active: true|false
+ sel=[], %Current sel: [{Id,GbSet}]
+ ssels=[], %Saved selections:
+ % [{Name,Mode,GbSet}]
+ temp_sel=none, %Selection only temporary?
+
+ mat, %Defined materials (GbTree).
+ pal=[], %Palette
+ file, %Current filename.
+ saved, %True if model has been saved.
+ onext, %Next object id to use.
+ bb=none, %Saved bounding box.
+ edge_loop=none, %Previous edge loop.
+ views={0,{}}, %{Current,TupleOfViews}
+ pst=gb_trees:empty(), %Plugin State Info
+ % gb_tree where key is plugin module
+
+ %% Previous commands.
+ repeatable, %Last repeatable command.
+ ask_args, %Ask arguments.
+ drag_args, %Drag arguments for command.
+ def, %Default operations.
+
+ %% Undo information.
+ top, %Top of stack.
+ bottom, %Bottom of stack.
+ next_is_undo, %State of undo/redo toggle.
+ undone %States that were undone.
+ }).
+
+%% The Winged-Edge data structure.
+%% See http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/model/winged-e.html
+-record(we,
+ {id, %Shape id.
+ perm=0, %Permissions:
+ % 0 - Everything allowed.
+ % 1 - Visible, can't select.
+ % [] or {Mode,GbSet} -
+ % Invisible, can't select.
+ % The GbSet contains the
+ % object's selection.
+ name, %Name.
+ es, %gb_tree containing edges
+ fs, %gb_tree containing faces
+ he, %gb_sets containing hard edges
+ vc, %Connection info (=incident edge)
+ % for vertices.
+ vp, %Vertex positions.
+ pst=gb_trees:empty(), %Plugin State Info,
+ % gb_tree where key is plugin module
+ mat=default, %Materials.
+ next_id, %Next free ID for vertices,
+ % edges, and faces.
+ % (Needed because we never re-use
+ % IDs.)
+ mode, %'vertex'/'material'/'uv'
+ mirror=none, %Mirror: none|Face
+ light=none, %Light data: none|Light
+ has_shape=true %true|false
+ }).
+
+-define(IS_VISIBLE(Perm), (Perm =< 1)).
+-define(IS_NOT_VISIBLE(Perm), (Perm > 1)).
+-define(IS_SELECTABLE(Perm), (Perm == 0)).
+-define(IS_NOT_SELECTABLE(Perm), (Perm =/= 0)).
+
+-define(IS_LIGHT(We), ((We#we.light =/= none) and (not We#we.has_shape))).
+-define(IS_ANY_LIGHT(We), (We#we.light =/= none)).
+-define(HAS_SHAPE(We), (We#we.has_shape)).
+%-define(IS_LIGHT(We), (We#we.light =/= none)).
+%-define(IS_NOT_LIGHT(We), (We#we.light =:= none)).
+
+%% Edge in a winged-edge shape.
+-record(edge,
+ {vs, %Start vertex for edge
+ ve, %End vertex for edge
+ a=none, %Color or UV coordinate.
+ b=none, %Color or UV coordinate.
+ lf, %Left face
+ rf, %Right face
+ ltpr, %Left traversal predecessor
+ ltsu, %Left traversal successor
+ rtpr, %Right traversal predecessor
+ rtsu %Right traversal successor
+ }).
+
+%% The current view/camera.
+-record(view,
+ {origin,
+ distance, % From origo.
+ azimuth,
+ elevation,
+ pan_x, %Panning in X direction.
+ pan_y, %Panning in Y direction.
+ along_axis=none, %Which axis viewed along.
+ fov, %Field of view.
+ hither, %Near clipping plane.
+ yon %Far clipping plane.
+ }).
+
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_dissolve.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_dissolve.erl
new file mode 100644
index 0000000000..d7af9bb1d3
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_dissolve.erl
@@ -0,0 +1,375 @@
+%%
+%% wings_dissolve.erl --
+%%
+%% This module implements dissolve of faces.
+%%
+
+-module(wings_dissolve).
+
+-export([faces/2, complement/2]).
+
+-include("wings.hrl").
+
+%% faces([Face], We) -> We'
+%% Dissolve the given faces.
+faces([], We) -> We;
+faces(Faces, #we{fs=Ftab0}=We) ->
+ case gb_sets:is_empty(Faces) of
+ true -> We;
+ false when is_list(Faces) ->
+ Complement = ordsets:subtract(gb_trees:keys(Ftab0),
+ ordsets:from_list(Faces)),
+ dissolve_1(Faces, Complement, We);
+ false ->
+ Complement = ordsets:subtract(gb_trees:keys(Ftab0),
+ gb_sets:to_list(Faces)),
+ dissolve_1(Faces, Complement, We)
+ end.
+
+faces([], _, We) -> We;
+faces(Faces,Complement,We) ->
+ case gb_sets:is_empty(Faces) of
+ true -> We;
+ false -> dissolve_1(Faces, Complement,We)
+ end.
+
+dissolve_1(Faces, Complement, We0) ->
+ We1 = optimistic_dissolve(Faces,Complement,We0#we{vc=undefined}),
+ NewFaces = wings_we:new_items_as_ordset(face, We0, We1),
+ We2 = wings_face:delete_bad_faces(NewFaces, We1),
+ We = wings_we:rebuild(We2),
+ case wings_we:is_consistent(We) of
+ true ->
+ We;
+ false ->
+ io:format("Dissolving would cause an inconsistent object structure.")
+ end.
+
+%% complement([Face], We) -> We'
+%% Dissolve all faces BUT the given faces. Also invalidate the
+%% mirror face if it existed and was dissolved.
+complement(Fs0, #we{fs=Ftab0}=We0) when is_list(Fs0) ->
+ Fs = ordsets:subtract(gb_trees:keys(Ftab0), ordsets:from_list(Fs0)),
+ case faces(Fs, Fs0, We0) of
+ #we{mirror=none}=We -> We;
+ #we{mirror=Face,fs=Ftab}=We ->
+ case gb_trees:is_defined(Face, Ftab) of
+ false -> We;
+ true -> We#we{mirror=none}
+ end
+ end;
+complement(Fs, We) -> complement(gb_sets:to_list(Fs), We).
+
+optimistic_dissolve(Faces0, Compl, We0) ->
+ %% Optimistically assume that we have a simple region without
+ %% any holes.
+ case outer_edge_loop(Faces0, We0) of
+ error ->
+ %% Assumption was wrong. We need to partition the selection
+ %% and dissolve each partition in turn.
+ Parts = wings_sel:face_regions(Faces0, We0),
+ complex_dissolve(Parts, We0);
+ [_|_]=Loop ->
+ %% Assumption was correct.
+ simple_dissolve(Faces0, Compl, Loop, We0)
+ end.
+
+%% simple_dissolve(Faces, Loop, We0) -> We
+%% Dissolve a region of faces with no holes and no
+%% repeated vertices in the outer edge loop.
+
+simple_dissolve(Faces0, Compl, Loop, We0) ->
+ Faces = to_gb_set(Faces0),
+ OldFace = gb_sets:smallest(Faces),
+ Mat = wings_facemat:face(OldFace, We0),
+ We1 = fix_materials(Faces, Compl, We0),
+ #we{es=Etab0,fs=Ftab0,he=Htab0} = We1,
+ {Ftab1,Etab1,Htab} = simple_del(Faces, Ftab0, Etab0, Htab0, We1),
+ {NewFace,We2} = wings_we:new_id(We1),
+ Ftab = gb_trees:insert(NewFace, hd(Loop), Ftab1),
+ Last = lists:last(Loop),
+ Etab = update_outer([Last|Loop], Loop, NewFace, Ftab, Etab1),
+ We = We2#we{es=Etab,fs=Ftab,he=Htab},
+ wings_facemat:assign(Mat, [NewFace], We).
+
+fix_materials(Del,Keep,We) ->
+ case gb_sets:size(Del) < length(Keep) of
+ true ->
+ wings_facemat:delete_faces(Del,We);
+ false ->
+ wings_facemat:keep_faces(Keep,We)
+ end.
+
+to_gb_set(List) when is_list(List) ->
+ gb_sets:from_list(List);
+to_gb_set(S) -> S.
+
+%% Delete faces and inner edges for a simple region.
+simple_del(Faces, Ftab0, Etab0, Htab0, We) ->
+ case {gb_trees:size(Ftab0),gb_sets:size(Faces)} of
+ {AllSz,FaceSz} when AllSz < 2*FaceSz ->
+ %% At least half of the faces are selected.
+ %% It is faster to find the edges for the
+ %% unselected faces.
+ UnselFaces = ordsets:subtract(gb_trees:keys(Ftab0),
+ gb_sets:to_list(Faces)),
+
+ UnselSet = sofs:from_external(UnselFaces, [face]),
+ Ftab1 = sofs:from_external(gb_trees:to_list(Ftab0),
+ [{face,edge}]),
+ Ftab2 = sofs:restriction(Ftab1, UnselSet),
+ Ftab = gb_trees:from_orddict(sofs:to_external(Ftab2)),
+
+ Keep0 = wings_face:to_edges(UnselFaces, We),
+ Keep = sofs:set(Keep0, [edge]),
+ Etab1 = sofs:from_external(gb_trees:to_list(Etab0),
+ [{edge,info}]),
+ Etab2 = sofs:restriction(Etab1, Keep),
+ Etab = gb_trees:from_orddict(sofs:to_external(Etab2)),
+
+ Htab = simple_del_hard(Htab0, sofs:to_external(Keep), undefined),
+ {Ftab,Etab,Htab};
+ {_,_} ->
+ Ftab = lists:foldl(fun(Face, Ft) ->
+ gb_trees:delete(Face, Ft)
+ end, Ftab0, gb_sets:to_list(Faces)),
+ Inner = wings_face:inner_edges(Faces, We),
+ Etab = lists:foldl(fun(Edge, Et) ->
+ gb_trees:delete(Edge, Et)
+ end, Etab0, Inner),
+ Htab = simple_del_hard(Htab0, undefined, Inner),
+ {Ftab,Etab,Htab}
+ end.
+
+simple_del_hard(Htab, Keep, Remove) ->
+ case gb_sets:is_empty(Htab) of
+ true -> Htab;
+ false -> simple_del_hard_1(Htab, Keep, Remove)
+ end.
+
+simple_del_hard_1(Htab, Keep, undefined) ->
+ gb_sets:intersection(Htab, gb_sets:from_ordset(Keep));
+simple_del_hard_1(Htab, undefined, Remove) ->
+ gb_sets:difference(Htab, gb_sets:from_ordset(Remove)).
+
+%% complex([Partition], We0) -> We0
+%% The general dissolve.
+
+complex_dissolve([Faces|T], We0) ->
+ Face = gb_sets:smallest(Faces),
+ Mat = wings_facemat:face(Face, We0),
+ We1 = wings_facemat:delete_faces(Faces, We0),
+ Parts = outer_edge_partition(Faces, We1),
+ We = do_dissolve(Faces, Parts, Mat, We0, We1),
+ complex_dissolve(T, We);
+complex_dissolve([], We) -> We.
+
+do_dissolve(Faces, Ess, Mat, WeOrig, We0) ->
+ We1 = do_dissolve_faces(Faces, We0),
+ Inner = wings_face:inner_edges(Faces, WeOrig),
+ We2 = delete_inner(Inner, We1),
+ #we{he=Htab0} = We = do_dissolve_1(Ess, Mat, We2),
+ Htab = gb_sets:difference(Htab0, gb_sets:from_list(Inner)),
+ We#we{he=Htab}.
+
+do_dissolve_1([EdgeList|Ess], Mat, #we{es=Etab0,fs=Ftab0}=We0) ->
+ {Face,We1} = wings_we:new_id(We0),
+ Ftab = gb_trees:insert(Face, hd(EdgeList), Ftab0),
+ Last = lists:last(EdgeList),
+ Etab = update_outer([Last|EdgeList], EdgeList, Face, Ftab, Etab0),
+ We2 = We1#we{es=Etab,fs=Ftab},
+ We = wings_facemat:assign(Mat, [Face], We2),
+ do_dissolve_1(Ess, Mat, We);
+do_dissolve_1([], _Mat, We) -> We.
+
+do_dissolve_faces(Faces, #we{fs=Ftab0}=We) ->
+ Ftab = lists:foldl(fun(Face, Ft) ->
+ gb_trees:delete(Face, Ft)
+ end, Ftab0, gb_sets:to_list(Faces)),
+ We#we{fs=Ftab}.
+
+delete_inner(Inner, #we{es=Etab0}=We) ->
+ Etab = lists:foldl(fun(Edge, Et) ->
+ gb_trees:delete(Edge, Et)
+ end, Etab0, Inner),
+ We#we{es=Etab}.
+
+update_outer([Pred|[Edge|Succ]=T], More, Face, Ftab, Etab0) ->
+ #edge{rf=Rf} = R0 = gb_trees:get(Edge, Etab0),
+ Rec = case gb_trees:is_defined(Rf, Ftab) of
+ true ->
+ ?ASSERT(false == gb_trees:is_defined(R0#edge.lf, Ftab)),
+ LS = succ(Succ, More),
+ R0#edge{lf=Face,ltpr=Pred,ltsu=LS};
+ false ->
+ ?ASSERT(true == gb_trees:is_defined(R0#edge.lf, Ftab)),
+ RS = succ(Succ, More),
+ R0#edge{rf=Face,rtpr=Pred,rtsu=RS}
+ end,
+ Etab = gb_trees:update(Edge, Rec, Etab0),
+ update_outer(T, More, Face, Ftab, Etab);
+update_outer([_], _More, _Face, _Ftab, Etab) -> Etab.
+
+succ([Succ|_], _More) -> Succ;
+succ([], [Succ|_]) -> Succ.
+
+%% outer_edge_loop(FaceSet,WingedEdge) -> [Edge] | error.
+%% Partition the outer edges of the FaceSet into a single closed loop.
+%% Return 'error' if the faces in FaceSet does not form a
+%% simple region without holes.
+%%
+%% Equvivalent to
+%% case outer_edge_partition(FaceSet,WingedEdge) of
+%% [Loop] -> Loop;
+%% [_|_] -> error
+%% end.
+%% but faster.
+
+outer_edge_loop(Faces, We) ->
+ case lists:sort(collect_outer_edges(Faces, We)) of
+ [] -> error;
+ [{Key,Val}|Es0] ->
+ case any_duplicates(Es0, Key) of
+ false ->
+ Es = gb_trees:from_orddict(Es0),
+ N = gb_trees:size(Es),
+ outer_edge_loop_1(Val, Es, Key, N, []);
+ true -> error
+ end
+ end.
+
+outer_edge_loop_1({Edge,V}, _, V, 0, Acc) ->
+ %% This edge completes the loop, and we have used all possible edges.
+ [Edge|Acc];
+outer_edge_loop_1({_,V}, _, V, _N, _) ->
+ %% Loop is complete, but we haven't used all edges.
+ error;
+outer_edge_loop_1({_,_}, _, _, 0, _) ->
+ %% We have used all possible edges, but somehow the loop
+ %% is not complete. I can't see how this is possible.
+ erlang:error(internal_error);
+outer_edge_loop_1({Edge,Vb}, Es, EndV, N, Acc0) ->
+ Acc = [Edge|Acc0],
+ outer_edge_loop_1(gb_trees:get(Vb, Es), Es, EndV, N-1, Acc).
+
+any_duplicates([{V,_}|_], V) -> true;
+any_duplicates([_], _) -> false;
+any_duplicates([{V,_}|Es], _) -> any_duplicates(Es, V).
+
+%% outer_edge_partition(FaceSet, WingedEdge) -> [[Edge]].
+%% Partition the outer edges of the FaceSet. Each partion
+%% of edges form a closed loop with no repeated vertices.
+%% Outer edges are edges that have one face in FaceSet
+%% and one outside.
+%% It is assumed that FaceSet consists of one region returned by
+%% wings_sel:face_regions/2.
+
+outer_edge_partition(Faces, We) ->
+ F0 = collect_outer_edges(Faces, We),
+ F = gb_trees:from_orddict(wings_util:rel2fam(F0)),
+ partition_edges(F, []).
+
+collect_outer_edges(Faces, We) when is_list(Faces) ->
+ collect_outer_edges_1(Faces, gb_sets:from_list(Faces), We);
+collect_outer_edges(Faces, We) ->
+ collect_outer_edges_1(gb_sets:to_list(Faces), Faces, We).
+
+collect_outer_edges_1(Fs0, Faces0, #we{fs=Ftab}=We) ->
+ case {gb_trees:size(Ftab),gb_sets:size(Faces0)} of
+ {AllSz,FaceSz} when AllSz < 2*FaceSz ->
+ Fs = ordsets:subtract(gb_trees:keys(Ftab), Fs0),
+ Faces = gb_sets:from_ordset(Fs),
+ Coll = collect_outer_edges_a(Faces),
+ wings_face:fold_faces(Coll, [], Fs, We);
+ {_,_} ->
+ Coll = collect_outer_edges_b(Faces0),
+ wings_face:fold_faces(Coll, [], Fs0, We)
+ end.
+
+collect_outer_edges_a(Faces) ->
+ fun(Face, _, Edge, #edge{ve=V,vs=OtherV,lf=Face,rf=Other}, Acc) ->
+ case gb_sets:is_member(Other, Faces) of
+ false -> [{V,{Edge,OtherV}}|Acc];
+ true -> Acc
+ end;
+ (Face, _, Edge, #edge{ve=OtherV,vs=V,rf=Face,lf=Other}, Acc) ->
+ case gb_sets:is_member(Other, Faces) of
+ false -> [{V,{Edge,OtherV}}|Acc];
+ true -> Acc
+ end
+ end.
+
+collect_outer_edges_b(Faces) ->
+ fun(Face, _, Edge, #edge{vs=V,ve=OtherV,lf=Face,rf=Other}, Acc) ->
+ case gb_sets:is_member(Other, Faces) of
+ false -> [{V,{Edge,OtherV}}|Acc];
+ true -> Acc
+ end;
+ (Face, _, Edge, #edge{vs=OtherV,ve=V,rf=Face,lf=Other}, Acc) ->
+ case gb_sets:is_member(Other, Faces) of
+ false -> [{V,{Edge,OtherV}}|Acc];
+ true -> Acc
+ end
+ end.
+
+partition_edges(Es0, Acc) ->
+ case gb_trees:is_empty(Es0) of
+ true -> Acc;
+ false ->
+ {Key,Val,Es1} = gb_trees:take_smallest(Es0),
+ {Cycle,Es} = part_collect_cycle(Key, Val, Es1, []),
+ partition_edges(Es, [Cycle|Acc])
+ end.
+
+%% part_collect_cycle(Vertex, VertexInfo, EdgeInfo, Acc0) ->
+%% none | {[Edge],EdgeInfo}
+%% Collect the cycle starting with Vertex.
+%%
+%% Note: This function can only return 'none' when called
+%% recursively.
+
+part_collect_cycle(_, repeated, _, _) ->
+ %% Repeated vertex - we are not allowed to go this way.
+ %% Can only happen if we were called recursively because
+ %% a fork was encountered.
+ none;
+part_collect_cycle(_Va, [{Edge,Vb}], Es0, Acc0) ->
+ %% Basic case. Only one way to go.
+ Acc = [Edge|Acc0],
+ case gb_trees:lookup(Vb, Es0) of
+ none ->
+ {Acc,Es0};
+ {value,Val} ->
+ Es = gb_trees:delete(Vb, Es0),
+ part_collect_cycle(Vb, Val, Es, Acc)
+ end;
+part_collect_cycle(Va, [Val|More], Es0, []) ->
+ %% No cycle started yet and we have multiple choice of
+ %% edges out from this vertex. It doesn't matter which
+ %% edge we follow, so we'll follow the first one.
+ {Cycle,Es} = part_collect_cycle(Va, [Val], Es0, []),
+ {Cycle,gb_trees:insert(Va, More, Es)};
+part_collect_cycle(Va, Edges, Es0, Acc) ->
+ %% We have a partially collected cycle and we have a
+ %% fork (multiple choice of edges). Here we must choose
+ %% an edge that closes the cycle without passing Va
+ %% again (because repeated vertices are not allowed).
+ Es = gb_trees:insert(Va, repeated, Es0),
+ part_fork(Va, Edges, Es, Acc, []).
+
+part_fork(Va, [Val|More], Es0, Acc, Tried) ->
+ %% Try to complete the cycle by following this edge.
+ case part_collect_cycle(Va, [Val], Es0, Acc) of
+ none ->
+ %% Failure - try the next edge.
+ part_fork(Va, More, Es0, Acc, [Val|Tried]);
+ {Cycle,Es} ->
+ %% Found a cycle. Update the vertex information
+ %% with all edges remaining.
+ {Cycle,gb_trees:update(Va, lists:reverse(Tried, More), Es)}
+ end;
+part_fork(_, [], _, _, _) ->
+ %% None of edges were possible. Can only happen if this function
+ %% was called recursively (i.e. if we hit another fork while
+ %% processing a fork).
+ none.
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge.erl
new file mode 100644
index 0000000000..3483acb711
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge.erl
@@ -0,0 +1,243 @@
+%%
+%% wings_edge.erl --
+%%
+%% This module contains most edge command and edge utility functions.
+%%
+%% Copyright (c) 2001-2008 Bjorn Gustavsson.
+%%
+%% See the file "license.terms" for information on usage and redistribution
+%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+%%
+%% $Id: wings_edge.erl,v 1.1 2009/01/25 18:55:33 kostis Exp $
+%%
+
+-module(wings_edge).
+
+-export([dissolve_edges/2]).
+
+-include("wings.hrl").
+
+%%%
+%%% Dissolve.
+%%%
+
+dissolve_edges(Edges0, We0) when is_list(Edges0) ->
+ #we{es=Etab} = We1 = lists:foldl(fun internal_dissolve_edge/2, We0, Edges0),
+ case [E || E <- Edges0, gb_trees:is_defined(E, Etab)] of
+ Edges0 ->
+ %% No edge was deleted in the last pass. We are done.
+ We = wings_we:rebuild(We0#we{vc=undefined}),
+ wings_we:validate_mirror(We);
+ Edges ->
+ dissolve_edges(Edges, We1)
+ end;
+dissolve_edges(Edges, We) ->
+ dissolve_edges(gb_sets:to_list(Edges), We).
+
+internal_dissolve_edge(Edge, #we{es=Etab}=We0) ->
+ case gb_trees:lookup(Edge, Etab) of
+ none -> We0;
+ {value,#edge{ltpr=Same,ltsu=Same,rtpr=Same,rtsu=Same}} ->
+ Empty = gb_trees:empty(),
+ We0#we{vc=Empty,vp=Empty,es=Empty,fs=Empty,he=gb_sets:empty()};
+ {value,#edge{rtpr=Back,ltsu=Back}=Rec} ->
+ merge_edges(backward, Edge, Rec, We0);
+ {value,#edge{rtsu=Forward,ltpr=Forward}=Rec} ->
+ merge_edges(forward, Edge, Rec, We0);
+ {value,Rec} ->
+ try dissolve_edge_1(Edge, Rec, We0) of
+ We -> We
+ catch
+ throw:hole -> We0
+ end
+ end.
+
+%% dissolve_edge_1(Edge, EdgeRecord, We) -> We
+%% Remove an edge and a face. If one of the faces is degenerated
+%% (only consists of two edges), remove that one. Otherwise, it
+%% doesn't matter which face we remove.
+dissolve_edge_1(Edge, #edge{lf=Remove,rf=Keep,ltpr=Same,ltsu=Same}=Rec, We) ->
+ dissolve_edge_2(Edge, Remove, Keep, Rec, We);
+dissolve_edge_1(Edge, #edge{lf=Keep,rf=Remove}=Rec, We) ->
+ dissolve_edge_2(Edge, Remove, Keep, Rec, We).
+
+dissolve_edge_2(Edge, FaceRemove, FaceKeep,
+ #edge{ltpr=LP,ltsu=LS,rtpr=RP,rtsu=RS},
+ #we{fs=Ftab0,es=Etab0,he=Htab0}=We0) ->
+ %% First change face for all edges surrounding the face we will remove.
+ Etab1 = wings_face:fold(
+ fun (_, E, _, IntEtab) when E =:= Edge -> IntEtab;
+ (_, E, R, IntEtab) ->
+ case R of
+ #edge{lf=FaceRemove,rf=FaceKeep} ->
+ throw(hole);
+ #edge{rf=FaceRemove,lf=FaceKeep} ->
+ throw(hole);
+ #edge{lf=FaceRemove} ->
+ gb_trees:update(E, R#edge{lf=FaceKeep}, IntEtab);
+ #edge{rf=FaceRemove} ->
+ gb_trees:update(E, R#edge{rf=FaceKeep}, IntEtab)
+ end
+ end, Etab0, FaceRemove, We0),
+
+ %% Patch all predecessors and successor of the edge we will remove.
+ Etab2 = patch_edge(LP, RS, Edge, Etab1),
+ Etab3 = patch_edge(LS, RP, Edge, Etab2),
+ Etab4 = patch_edge(RP, LS, Edge, Etab3),
+ Etab5 = patch_edge(RS, LP, Edge, Etab4),
+
+ %% Remove the edge.
+ Etab = gb_trees:delete(Edge, Etab5),
+ Htab = hardness(Edge, soft, Htab0),
+
+ %% Remove the face. Patch the face entry for the remaining face.
+ Ftab1 = gb_trees:delete(FaceRemove, Ftab0),
+ We1 = wings_facemat:delete_face(FaceRemove, We0),
+ Ftab = gb_trees:update(FaceKeep, LP, Ftab1),
+
+ %% Return result.
+ We = We1#we{es=Etab,fs=Ftab,vc=undefined,he=Htab},
+ AnEdge = gb_trees:get(FaceKeep, Ftab),
+ case gb_trees:get(AnEdge, Etab) of
+ #edge{lf=FaceKeep,ltpr=Same,ltsu=Same} ->
+ internal_dissolve_edge(AnEdge, We);
+ #edge{rf=FaceKeep,rtpr=Same,rtsu=Same} ->
+ internal_dissolve_edge(AnEdge, We);
+ _Other ->
+ case wings_we:is_face_consistent(FaceKeep, We) of
+ true ->
+ We;
+ false ->
+ io:format("Dissolving would cause a badly formed face.")
+ end
+ end.
+
+%%
+%% We like winged edges, but not winged vertices (a vertex with
+%% only two edges connected to it). We will remove the winged vertex
+%% by joining the two edges connected to it.
+%%
+
+merge_edges(Dir, Edge, Rec, #we{es=Etab}=We) ->
+ {Va,Vb,_,_,_,_,To,To} = half_edge(Dir, Rec),
+ case gb_trees:get(To, Etab) of
+ #edge{vs=Va,ve=Vb} ->
+ del_2edge_face(Dir, Edge, Rec, To, We);
+ #edge{vs=Vb,ve=Va} ->
+ del_2edge_face(Dir, Edge, Rec, To, We);
+ _Other ->
+ merge_1(Dir, Edge, Rec, To, We)
+ end.
+
+merge_1(Dir, Edge, Rec, To, #we{es=Etab0,fs=Ftab0,he=Htab0}=We) ->
+ OtherDir = reverse_dir(Dir),
+ {Vkeep,Vdelete,Lf,Rf,A,B,L,R} = half_edge(OtherDir, Rec),
+ Etab1 = patch_edge(L, To, Edge, Etab0),
+ Etab2 = patch_edge(R, To, Edge, Etab1),
+ Etab3 = patch_half_edge(To, Vkeep, Lf, A, L, Rf, B, R, Vdelete, Etab2),
+ Htab = hardness(Edge, soft, Htab0),
+ Etab = gb_trees:delete(Edge, Etab3),
+ #edge{lf=Lf,rf=Rf} = Rec,
+ Ftab1 = update_face(Lf, To, Edge, Ftab0),
+ Ftab = update_face(Rf, To, Edge, Ftab1),
+ merge_2(To, We#we{es=Etab,fs=Ftab,he=Htab,vc=undefined}).
+
+merge_2(Edge, #we{es=Etab}=We) ->
+ %% If the merged edge is part of a two-edge face, we must
+ %% remove that edge too.
+ case gb_trees:get(Edge, Etab) of
+ #edge{ltpr=Same,ltsu=Same} ->
+ internal_dissolve_edge(Edge, We);
+ #edge{rtpr=Same,rtsu=Same} ->
+ internal_dissolve_edge(Edge, We);
+ _Other -> We
+ end.
+
+update_face(Face, Edge, OldEdge, Ftab) ->
+ case gb_trees:get(Face, Ftab) of
+ OldEdge -> gb_trees:update(Face, Edge, Ftab);
+ _Other -> Ftab
+ end.
+
+del_2edge_face(Dir, EdgeA, RecA, EdgeB,
+ #we{es=Etab0,fs=Ftab0,he=Htab0}=We) ->
+ {_,_,Lf,Rf,_,_,_,_} = half_edge(reverse_dir(Dir), RecA),
+ RecB = gb_trees:get(EdgeB, Etab0),
+ Del = gb_sets:from_list([EdgeA,EdgeB]),
+ EdgeANear = stabile_neighbor(RecA, Del),
+ EdgeBNear = stabile_neighbor(RecB, Del),
+ Etab1 = patch_edge(EdgeANear, EdgeBNear, EdgeA, Etab0),
+ Etab2 = patch_edge(EdgeBNear, EdgeANear, EdgeB, Etab1),
+ Etab3 = gb_trees:delete(EdgeA, Etab2),
+ Etab = gb_trees:delete(EdgeB, Etab3),
+
+ %% Patch hardness table.
+ Htab1 = hardness(EdgeA, soft, Htab0),
+ Htab = hardness(EdgeB, soft, Htab1),
+
+ %% Patch the face table.
+ #edge{lf=Klf,rf=Krf} = gb_trees:get(EdgeANear, Etab),
+ KeepFaces = ordsets:from_list([Klf,Krf]),
+ EdgeAFaces = ordsets:from_list([Lf,Rf]),
+ [DelFace] = ordsets:subtract(EdgeAFaces, KeepFaces),
+ Ftab1 = gb_trees:delete(DelFace, Ftab0),
+ [KeepFace] = ordsets:intersection(KeepFaces, EdgeAFaces),
+ Ftab2 = update_face(KeepFace, EdgeANear, EdgeA, Ftab1),
+ Ftab = update_face(KeepFace, EdgeBNear, EdgeB, Ftab2),
+
+ %% Return result.
+ We#we{vc=undefined,es=Etab,fs=Ftab,he=Htab}.
+
+stabile_neighbor(#edge{ltpr=Ea,ltsu=Eb,rtpr=Ec,rtsu=Ed}, Del) ->
+ [Edge] = lists:foldl(fun(E, A) ->
+ case gb_sets:is_member(E, Del) of
+ true -> A;
+ false -> [E|A]
+ end
+ end, [], [Ea,Eb,Ec,Ed]),
+ Edge.
+
+%%%
+%%% Setting hard/soft edges.
+%%%
+
+hardness(Edge, soft, Htab) -> gb_sets:delete_any(Edge, Htab);
+hardness(Edge, hard, Htab) -> gb_sets:add(Edge, Htab).
+
+%%%
+%%% Utilities.
+%%%
+
+reverse_dir(forward) -> backward;
+reverse_dir(backward) -> forward.
+
+half_edge(backward, #edge{vs=Va,ve=Vb,lf=Lf,rf=Rf,a=A,b=B,ltsu=L,rtpr=R}) ->
+ {Va,Vb,Lf,Rf,A,B,L,R};
+half_edge(forward, #edge{ve=Va,vs=Vb,lf=Lf,rf=Rf,a=A,b=B,ltpr=L,rtsu=R}) ->
+ {Va,Vb,Lf,Rf,A,B,L,R}.
+
+patch_half_edge(Edge, V, FaceA, A, Ea, FaceB, B, Eb, OrigV, Etab) ->
+ New = case gb_trees:get(Edge, Etab) of
+ #edge{vs=OrigV,lf=FaceA,rf=FaceB}=Rec ->
+ Rec#edge{a=A,vs=V,ltsu=Ea,rtpr=Eb};
+ #edge{vs=OrigV,lf=FaceB,rf=FaceA}=Rec ->
+ Rec#edge{a=B,vs=V,ltsu=Eb,rtpr=Ea};
+ #edge{ve=OrigV,lf=FaceA,rf=FaceB}=Rec ->
+ Rec#edge{b=B,ve=V,ltpr=Ea,rtsu=Eb};
+ #edge{ve=OrigV,lf=FaceB,rf=FaceA}=Rec ->
+ Rec#edge{b=A,ve=V,ltpr=Eb,rtsu=Ea}
+ end,
+ gb_trees:update(Edge, New, Etab).
+
+patch_edge(Edge, ToEdge, OrigEdge, Etab) ->
+ New = case gb_trees:get(Edge, Etab) of
+ #edge{ltsu=OrigEdge}=R ->
+ R#edge{ltsu=ToEdge};
+ #edge{ltpr=OrigEdge}=R ->
+ R#edge{ltpr=ToEdge};
+ #edge{rtsu=OrigEdge}=R ->
+ R#edge{rtsu=ToEdge};
+ #edge{rtpr=OrigEdge}=R ->
+ R#edge{rtpr=ToEdge}
+ end,
+ gb_trees:update(Edge, New, Etab).
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge_cmd.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge_cmd.erl
new file mode 100644
index 0000000000..e478ec245b
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_edge_cmd.erl
@@ -0,0 +1,91 @@
+%%
+%% wings_edge.erl --
+%%
+%% This module contains most edge command and edge utility functions.
+%%
+
+-module(wings_edge_cmd).
+
+-export([loop_cut/1]).
+
+-include("wings.hrl").
+
+%%%
+%%% The Loop Cut command.
+%%%
+
+loop_cut(St0) ->
+ {Sel,St} = wings_sel:fold(fun loop_cut/3, {[],St0}, St0),
+ wings_sel:set(body, Sel, St).
+
+loop_cut(Edges, #we{name=Name,id=Id,fs=Ftab}=We0, {Sel,St0}) ->
+ AdjFaces = wings_face:from_edges(Edges, We0),
+ case loop_cut_partition(AdjFaces, Edges, We0, []) of
+ [_] ->
+ io:format("Edge loop doesn't divide ~p into two parts.", [Name]);
+ Parts0 ->
+ %% We arbitrarily decide that the largest part of the object
+ %% will be left unselected and will keep the name of the object.
+
+ Parts1 = [{gb_trees:size(P),P} || P <- Parts0],
+ Parts2 = lists:reverse(lists:sort(Parts1)),
+ [_|Parts] = [gb_sets:to_list(P) || {_,P} <- Parts2],
+
+ %% Also, this first part will also contain any sub-object
+ %% that was not reachable from any of the edges. Therefore,
+ %% we calculate the first part as the complement of the union
+ %% of all other parts.
+
+ FirstComplement = ordsets:union(Parts),
+ First = ordsets:subtract(gb_trees:keys(Ftab), FirstComplement),
+
+ We = wings_dissolve:complement(First, We0),
+ Shs = St0#st.shapes,
+ St = St0#st{shapes=gb_trees:update(Id, We, Shs)},
+ loop_cut_make_copies(Parts, We0, Sel, St)
+ end.
+
+loop_cut_make_copies([P|Parts], We0, Sel0, #st{onext=Id}=St0) ->
+ Sel = [{Id,gb_sets:singleton(0)}|Sel0],
+ We = wings_dissolve:complement(P, We0),
+ St = wings_shape:insert(We, cut, St0),
+ loop_cut_make_copies(Parts, We0, Sel, St);
+loop_cut_make_copies([], _, Sel, St) -> {Sel,St}.
+
+loop_cut_partition(Faces0, Edges, We, Acc) ->
+ case gb_sets:is_empty(Faces0) of
+ true -> Acc;
+ false ->
+ {AFace,Faces1} = gb_sets:take_smallest(Faces0),
+ Reachable = collect_faces(AFace, Edges, We),
+ Faces = gb_sets:difference(Faces1, Reachable),
+ loop_cut_partition(Faces, Edges, We, [Reachable|Acc])
+ end.
+
+collect_faces(Face, Edges, We) ->
+ collect_faces(gb_sets:singleton(Face), We, Edges, gb_sets:empty()).
+
+collect_faces(Work0, We, Edges, Acc0) ->
+ case gb_sets:is_empty(Work0) of
+ true -> Acc0;
+ false ->
+ {Face,Work1} = gb_sets:take_smallest(Work0),
+ Acc = gb_sets:insert(Face, Acc0),
+ Work = collect_maybe_add(Work1, Face, Edges, We, Acc),
+ collect_faces(Work, We, Edges, Acc)
+ end.
+
+collect_maybe_add(Work, Face, Edges, We, Res) ->
+ wings_face:fold(
+ fun(_, Edge, Rec, A) ->
+ case gb_sets:is_member(Edge, Edges) of
+ true -> A;
+ false ->
+ Of = wings_face:other(Face, Rec),
+ case gb_sets:is_member(Of, Res) of
+ true -> A;
+ false -> gb_sets:add(Of, A)
+ end
+ end
+ end, Work, Face, We).
+
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_face.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_face.erl
new file mode 100644
index 0000000000..487c05aa58
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_face.erl
@@ -0,0 +1,127 @@
+%%
+%% wings_face.erl --
+%%
+%% This module contains help routines for faces, such as fold functions
+%% face iterators.
+%%
+
+-module(wings_face).
+
+-export([delete_bad_faces/2, fold/4, fold_faces/4, from_edges/2,
+ inner_edges/2, to_edges/2, other/2]).
+
+-include("wings.hrl").
+
+from_edges(Es, #we{es=Etab}) when is_list(Es) ->
+ from_edges_1(Es, Etab, []);
+from_edges(Es, We) ->
+ from_edges(gb_sets:to_list(Es), We).
+
+from_edges_1([E|Es], Etab, Acc) ->
+ #edge{lf=Lf,rf=Rf} = gb_trees:get(E, Etab),
+ from_edges_1(Es, Etab, [Lf,Rf|Acc]);
+from_edges_1([], _, Acc) -> gb_sets:from_list(Acc).
+
+%% other(Face, EdgeRecord) -> OtherFace
+%% Pick up the "other face" from an edge record.
+other(Face, #edge{lf=Face,rf=Other}) -> Other;
+other(Face, #edge{rf=Face,lf=Other}) -> Other.
+
+%% to_edges(Faces, We) -> [Edge]
+%% Convert a set or list of faces to a list of edges.
+to_edges(Fs, We) ->
+ ordsets:from_list(to_edges_raw(Fs, We)).
+
+%% inner_edges(Faces, We) -> [Edge]
+%% Given a set of faces, return all inner edges.
+inner_edges(Faces, We) ->
+ S = to_edges_raw(Faces, We),
+ inner_edges_1(lists:sort(S), []).
+
+inner_edges_1([E,E|T], In) ->
+ inner_edges_1(T, [E|In]);
+inner_edges_1([_|T], In) ->
+ inner_edges_1(T, In);
+inner_edges_1([], In) -> lists:reverse(In).
+
+%% Fold over all edges surrounding a face.
+
+fold(F, Acc, Face, #we{es=Etab,fs=Ftab}) ->
+ Edge = gb_trees:get(Face, Ftab),
+ fold(Edge, Etab, F, Acc, Face, Edge, not_done).
+
+fold(LastEdge, _, _, Acc, _, LastEdge, done) -> Acc;
+fold(Edge, Etab, F, Acc0, Face, LastEdge, _) ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{ve=V,lf=Face,ltsu=NextEdge}=E ->
+ Acc = F(V, Edge, E, Acc0),
+ fold(NextEdge, Etab, F, Acc, Face, LastEdge, done);
+ #edge{vs=V,rf=Face,rtsu=NextEdge}=E ->
+ Acc = F(V, Edge, E, Acc0),
+ fold(NextEdge, Etab, F, Acc, Face, LastEdge, done)
+ end.
+
+%% Fold over a set of faces.
+
+fold_faces(F, Acc0, [Face|Faces], #we{es=Etab,fs=Ftab}=We) ->
+ Edge = gb_trees:get(Face, Ftab),
+ Acc = fold_faces_1(Edge, Etab, F, Acc0, Face, Edge, not_done),
+ fold_faces(F, Acc, Faces, We);
+fold_faces(_F, Acc, [], _We) -> Acc;
+fold_faces(F, Acc, Faces, We) ->
+ fold_faces(F, Acc, gb_sets:to_list(Faces), We).
+
+fold_faces_1(LastEdge, _, _, Acc, _, LastEdge, done) -> Acc;
+fold_faces_1(Edge, Etab, F, Acc0, Face, LastEdge, _) ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{ve=V,lf=Face,ltsu=NextEdge}=E ->
+ Acc = F(Face, V, Edge, E, Acc0),
+ fold_faces_1(NextEdge, Etab, F, Acc, Face, LastEdge, done);
+ #edge{vs=V,rf=Face,rtsu=NextEdge}=E ->
+ Acc = F(Face, V, Edge, E, Acc0),
+ fold_faces_1(NextEdge, Etab, F, Acc, Face, LastEdge, done)
+ end.
+
+%% Return an unsorted list of edges for the faces (with duplicates).
+
+to_edges_raw(Faces, #we{es=Etab,fs=Ftab}) when is_list(Faces) ->
+ to_edges_raw(Faces, Ftab, Etab, []);
+to_edges_raw(Faces, We) ->
+ to_edges_raw(gb_sets:to_list(Faces), We).
+
+to_edges_raw([Face|Faces], Ftab, Etab, Acc0) ->
+ Edge = gb_trees:get(Face, Ftab),
+ Acc = to_edges_raw_1(Edge, Etab, Acc0, Face, Edge, not_done),
+ to_edges_raw(Faces, Ftab, Etab, Acc);
+to_edges_raw([], _, _, Acc) -> Acc.
+
+to_edges_raw_1(LastEdge, _, Acc, _, LastEdge, done) -> Acc;
+to_edges_raw_1(Edge, Etab, Acc, Face, LastEdge, _) ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{lf=Face,ltsu=NextEdge} ->
+ to_edges_raw_1(NextEdge, Etab, [Edge|Acc], Face, LastEdge, done);
+ #edge{rf=Face,rtsu=NextEdge} ->
+ to_edges_raw_1(NextEdge, Etab, [Edge|Acc], Face, LastEdge, done)
+ end.
+
+delete_bad_faces(Fs, #we{fs=Ftab,es=Etab}=We) when is_list(Fs) ->
+ Es = bad_edges(Fs, Ftab, Etab, []),
+ wings_edge:dissolve_edges(Es, We);
+delete_bad_faces(Fs, We) ->
+ delete_bad_faces(gb_sets:to_list(Fs), We).
+
+bad_edges([F|Fs], Ftab, Etab, Acc) ->
+ case gb_trees:lookup(F, Ftab) of
+ {value,Edge} ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{ltpr=Same,ltsu=Same,rtpr=Same,rtsu=Same} ->
+ erlang:error({internal_error,one_edged_face,F});
+ #edge{ltpr=Same,ltsu=Same} ->
+ bad_edges(Fs, Ftab, Etab, [Edge|Acc]);
+ #edge{rtpr=Same,rtsu=Same} ->
+ bad_edges(Fs, Ftab, Etab, [Edge|Acc]);
+ _ -> bad_edges(Fs, Ftab, Etab, Acc)
+ end;
+ none -> bad_edges(Fs, Ftab, Etab, Acc)
+ end;
+bad_edges([], _, _, Acc) -> Acc.
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_facemat.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_facemat.erl
new file mode 100644
index 0000000000..6e018e49b5
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_facemat.erl
@@ -0,0 +1,299 @@
+%%
+%% wings_facemat.erl --
+%%
+%% This module keeps tracks of the mapping from a face number
+%% to its material name.
+%%
+%% Copyright (c) 2001-2005 Bjorn Gustavsson
+%%
+%% See the file "license.terms" for information on usage and redistribution
+%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+%%
+%% $Id: wings_facemat.erl,v 1.1 2009/01/25 18:55:33 kostis Exp $
+%%
+%%
+%%
+
+-module(wings_facemat).
+-export([all/1,face/2,used_materials/1,mat_faces/2,
+ assign/2,assign/3,
+ delete_face/2,delete_faces/2,keep_faces/2,
+ hide_faces/1,show_faces/1,
+ renumber/2,gc/1,merge/1]).
+
+-include("wings.hrl").
+-import(lists, [keysearch/3,reverse/1,reverse/2,sort/1]).
+
+%%%
+%%% API functions for retrieving information.
+%%%
+
+%% all(We) -> [{Face,MaterialName}]
+%% Return materials for all faces as an ordered list.
+all(#we{mat=M}=We) when is_atom(M) ->
+ Vis = visible_faces(We),
+ make_tab(Vis, M);
+all(#we{mat=L}) when is_list(L) ->
+ remove_invisible(L).
+
+%% face(Face, We) -> MaterialName
+%% Return the material for the face Face.
+face(_, #we{mat=M}) when is_atom(M) -> M;
+face(Face, #we{mat=Tab}) ->
+ {value,{_,Mat}} = keysearch(Face, 1, Tab),
+ Mat.
+
+%% used_materials(We) -> [MaterialName]
+%% Return an ordered list of all materials used in the We.
+used_materials(#we{mat=M}) when is_atom(M) -> [M];
+used_materials(#we{mat=L}) when is_list(L) ->
+ used_materials_1(L, []).
+
+%% mat_faces([{Face,Info}], We) -> [{Mat,[{Face,Info}]}]
+%% Group face tab into groups based on material.
+%% Used for displaying objects.
+mat_faces(Ftab, #we{mat=AtomMat}) when is_atom(AtomMat) ->
+ [{AtomMat,Ftab}];
+mat_faces(Ftab, #we{mat=MatTab}) ->
+ mat_faces_1(Ftab, remove_invisible(MatTab), []).
+
+%%%
+%%% API functions for updating material name mapping.
+%%%
+
+%% assign([{Face,MaterialName}], We) -> We'
+%% Assign materials.
+assign([], We) -> We;
+assign([{F,M}|_]=FaceMs, We) when is_atom(M), is_integer(F) ->
+ Tab = ordsets:from_list(FaceMs),
+ assign_face_ms(Tab, We).
+
+%% assign(MaterialName, Faces, We) -> We'
+%% Assign MaterialName to all faces Faces.
+assign(Mat, _, #we{mat=Mat}=We) when is_atom(Mat) -> We;
+assign(Mat, Fs, We) when is_atom(Mat), is_list(Fs) ->
+ assign_1(Mat, Fs, We);
+assign(Mat, Fs, We) when is_atom(Mat) ->
+ assign_1(Mat, gb_sets:to_list(Fs), We).
+
+%% delete_face(Face, We) -> We'
+%% Delete the material name mapping for the face Face.
+delete_face(_, #we{mat=AtomMat}=We) when is_atom(AtomMat) -> We;
+delete_face(Face, #we{mat=MatTab0}=We) ->
+ MatTab = orddict:erase(Face, MatTab0),
+ We#we{mat=MatTab}.
+
+%% delete_face(Faces, We) -> We'
+%% Delete the material name mapping for all faces Faces.
+delete_faces(_, #we{mat=AtomMat}=We) when is_atom(AtomMat) -> We;
+delete_faces(Faces0, #we{mat=MatTab0}=We) when is_list(Faces0) ->
+ Faces = sofs:from_external(Faces0, [face]),
+ MatTab1 = sofs:from_external(MatTab0, [{face,mat}]),
+ MatTab2 = sofs:drestriction(MatTab1, Faces),
+ MatTab = sofs:to_external(MatTab2),
+ We#we{mat=MatTab};
+delete_faces(Faces, We) ->
+ delete_faces(gb_sets:to_list(Faces), We).
+
+%% keep_faces(Faces, We) -> We'
+%% Delete all the other material names mapping for all faces other Faces.
+keep_faces(_, #we{mat=AtomMat}=We) when is_atom(AtomMat) -> We;
+keep_faces([Face], We) ->
+ Mat = face(Face,We),
+ We#we{mat=[{Face,Mat}]};
+keep_faces(Faces0, #we{mat=MatTab0}=We) when is_list(Faces0) ->
+ Faces = sofs:from_external(Faces0, [face]),
+ MatTab1 = sofs:from_external(MatTab0, [{face,mat}]),
+ MatTab2 = sofs:restriction(MatTab1, Faces),
+ MatTab = sofs:to_external(MatTab2),
+ We#we{mat=MatTab};
+keep_faces(Faces, We) ->
+ keep_faces(gb_sets:to_list(Faces), We).
+
+%% hide_faces(We) -> We'
+%% Update the material name mapping in the We to reflect
+%% the newly hidden faces in the face tab.
+hide_faces(#we{mat=M}=We) when is_atom(M) -> We;
+hide_faces(#we{mat=L0,fs=Ftab}=We) ->
+ L = hide_faces_1(L0, Ftab, []),
+ We#we{mat=L}.
+
+%% show_faces(We) -> We'
+%% Update the material name mapping in the We to reflect
+%% that all faces are again visible.
+show_faces(#we{mat=M}=We) when is_atom(M) -> We;
+show_faces(#we{mat=L0}=We) ->
+ L = show_faces_1(L0, []),
+ We#we{mat=L}.
+
+%% renumber(MaterialMapping, FaceOldToNew) -> MaterialMapping.
+%% Renumber face number in material name mapping.
+renumber(Mat, _) when is_atom(Mat) -> Mat;
+renumber(L, Fmap) when is_list(L) -> renumber_1(L, Fmap, []).
+
+%% gc(We) -> We'
+%% Garbage collect the material mapping information, removing
+%% the mapping for any face no longer present in the face table.
+gc(#we{mat=Mat}=We) when is_atom(Mat) -> We;
+gc(#we{mat=Tab0,fs=Ftab}=We) ->
+ Fs = sofs:from_external(gb_trees:keys(Ftab), [face]),
+ Tab1 = sofs:from_external(Tab0, [{face,material}]),
+ Tab2 = sofs:restriction(Tab1, Fs),
+ Tab = sofs:to_external(Tab2),
+ We#we{mat=compress(Tab)}.
+
+%% merge([We]) -> [{Face,MaterialName}] | MaterialName.
+%% Merge materials for several objects.
+merge([#we{mat=M}|Wes]=L) when is_atom(M) ->
+ case merge_all_same(Wes, M) of
+ true -> M;
+ false -> merge_1(L, [])
+ end;
+merge(L) -> merge_1(L, []).
+
+merge_1([#we{mat=M,es=Etab}|T], Acc) when is_atom(M) ->
+ FsM = merge_2(gb_trees:values(Etab), M, []),
+ merge_1(T, [FsM|Acc]);
+merge_1([#we{mat=FsMs}|T], Acc) ->
+ merge_1(T, [FsMs|Acc]);
+merge_1([], Acc) -> lists:merge(Acc).
+
+merge_2([#edge{lf=Lf,rf=Rf}|T], M, Acc) ->
+ merge_2(T, M, [{Lf,M},{Rf,M}|Acc]);
+merge_2([], _, Acc) -> ordsets:from_list(Acc).
+
+merge_all_same([#we{mat=M}|Wes], M) -> merge_all_same(Wes, M);
+merge_all_same([_|_], _) -> false;
+merge_all_same([], _) -> true.
+
+%%%
+%%% Local functions.
+%%%
+
+assign_1(Mat, Fs, #we{fs=Ftab}=We) ->
+ case length(Fs) =:= gb_trees:size(Ftab) of
+ true -> We#we{mat=Mat};
+ false -> assign_2(Mat, Fs, We)
+ end.
+
+assign_2(Mat, Fs0, #we{fs=Ftab,mat=Mat0}=We) when is_atom(Mat0) ->
+ Fs = ordsets:from_list(Fs0),
+ OtherFaces = ordsets:subtract(gb_trees:keys(Ftab), Fs),
+ Tab0 = make_tab(OtherFaces, Mat0),
+ Tab1 = make_tab(Fs, Mat),
+ Tab = lists:merge(Tab0, Tab1),
+ We#we{mat=Tab};
+assign_2(Mat, Fs0, #we{mat=Tab0}=We) when is_list(Tab0) ->
+ Fs = ordsets:from_list(Fs0),
+ Tab1 = make_tab(Fs, Mat),
+ Tab = mat_merge(Tab1, Tab0, []),
+ We#we{mat=Tab}.
+
+assign_face_ms(Tab, #we{fs=Ftab}=We) ->
+ case length(Tab) =:= gb_trees:size(Ftab) of
+ true -> We#we{mat=compress(Tab)};
+ false -> assign_face_ms_1(Tab, We)
+ end.
+
+assign_face_ms_1(Tab1, #we{fs=Ftab,mat=Mat0}=We) when is_atom(Mat0) ->
+ Tab0 = make_tab(gb_trees:keys(Ftab), Mat0),
+ Tab = mat_merge(Tab1, Tab0, []),
+ We#we{mat=Tab};
+assign_face_ms_1(Tab1, #we{mat=Tab0}=We) when is_list(Tab0) ->
+ Tab = mat_merge(Tab1, Tab0, []),
+ We#we{mat=Tab}.
+
+mat_merge([{Fn,_}|_]=Fns, [{Fo,_}=Fold|Fos], Acc) when Fo < Fn ->
+ mat_merge(Fns, Fos, [Fold|Acc]);
+mat_merge([{Fn,_}=Fnew|Fns], [{Fo,_}|_]=Fos, Acc) when Fo > Fn ->
+ mat_merge(Fns, Fos, [Fnew|Acc]);
+mat_merge([Fnew|Fns], [_|Fos], Acc) -> % Equality
+ mat_merge(Fns, Fos, [Fnew|Acc]);
+mat_merge([], Fos, Acc) ->
+ rev_compress(Acc, Fos);
+mat_merge(Fns, [], Acc) ->
+ rev_compress(Acc, Fns).
+
+make_tab(Fs, M) ->
+ make_tab_1(Fs, M, []).
+
+make_tab_1([F|Fs], M, Acc) ->
+ make_tab_1(Fs, M, [{F,M}|Acc]);
+make_tab_1([], _, Acc) -> reverse(Acc).
+
+
+visible_faces(#we{fs=Ftab}) ->
+ visible_faces_1(gb_trees:keys(Ftab)).
+
+visible_faces_1([F|Fs]) when F < 0 ->
+ visible_faces_1(Fs);
+visible_faces_1(Fs) -> Fs.
+
+remove_invisible([{F,_}|Fs]) when F < 0 ->
+ remove_invisible(Fs);
+remove_invisible(Fs) -> Fs.
+
+hide_faces_1([{F,_}=P|Fms], Ftab, Acc) when F < 0 ->
+ hide_faces_1(Fms, Ftab, [P|Acc]);
+hide_faces_1([{F,M}=P|Fms], Ftab, Acc) ->
+ case gb_trees:is_defined(F, Ftab) of
+ false -> hide_faces_1(Fms, Ftab, [{-F-1,M}|Acc]);
+ true -> hide_faces_1(Fms, Ftab, [P|Acc])
+ end;
+hide_faces_1([], _, Acc) -> sort(Acc).
+
+show_faces_1([{F,M}|Fms], Acc) when F < 0 ->
+ show_faces_1(Fms, [{-F-1,M}|Acc]);
+show_faces_1(Fs, Acc) -> sort(Acc++Fs).
+
+renumber_1([{F,M}|T], Fmap, Acc) ->
+ renumber_1(T, Fmap, [{gb_trees:get(F, Fmap),M}|Acc]);
+renumber_1([], _, Acc) -> sort(Acc).
+
+%% rev_compress([{Face,Mat}], [{Face,Mat}]) -> [{Face,Mat}] | Mat.
+%% Reverse just like lists:reverse/2, but if all materials
+%% turns out to be just the same, return that material.
+rev_compress(L, Acc) ->
+ case same_mat(Acc) of
+ [] -> reverse(L, Acc);
+ M -> rev_compress_1(L, M, Acc)
+ end.
+
+rev_compress_1([{_,M}=E|T], M, Acc) ->
+ %% Same material.
+ rev_compress_1(T, M, [E|Acc]);
+rev_compress_1([_|_]=L, _, Acc) ->
+ %% Another material. Finish by using reverse/2.
+ reverse(L, Acc);
+rev_compress_1([], M, _) ->
+ %% All materials turned out to be the same.
+ M.
+
+%% compress(MaterialTab) -> [{Face,Mat}] | Mat.
+%% Compress a face mapping if possible.
+compress(M) when is_atom(M) -> M;
+compress(L) when is_list(L) ->
+ case same_mat(L) of
+ [] -> L;
+ M -> M
+ end.
+
+same_mat([]) -> [];
+same_mat([{_,M}|T]) -> same_mat_1(T, M).
+
+same_mat_1([{_,M}|T], M) -> same_mat_1(T, M);
+same_mat_1([], M) -> M;
+same_mat_1(_, _) -> [].
+
+used_materials_1([{_,M}|T], [M|_]=Acc) ->
+ used_materials_1(T, Acc);
+used_materials_1([{_,M}|T], Acc) ->
+ used_materials_1(T, [M|Acc]);
+used_materials_1([], Acc) ->
+ ordsets:from_list(Acc).
+
+mat_faces_1([{F1,_}|_]=Fs, [{F2,_}|Ms], Acc) when F2 < F1 ->
+ mat_faces_1(Fs, Ms, Acc);
+mat_faces_1([{F,Info}|Fs], [{F,Mat}|Ms], Acc) ->
+ mat_faces_1(Fs, Ms, [{Mat,{F,Info}}|Acc]);
+mat_faces_1([], _, Acc) -> wings_util:rel2fam(Acc).
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_intl.hrl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_intl.hrl
new file mode 100644
index 0000000000..ebcb560f27
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_intl.hrl
@@ -0,0 +1,15 @@
+%%
+%% wings_intl.hrl --
+%%
+%% Defines for translations
+%%
+%% Copyright (c) 2001-2005 Bjorn Gustavsson
+%%
+%% See the file "license.terms" for information on usage and redistribution
+%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+%%
+%% $Id: wings_intl.hrl,v 1.1 2009/01/25 18:55:33 kostis Exp $
+%%
+
+-define(STR(A,B,Str), wings_lang:str({?MODULE,A,B},Str)).
+-define(__(Key,Str), wings_lang:str({?MODULE,Key},Str)).
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_io.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_io.erl
new file mode 100644
index 0000000000..39002c675d
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_io.erl
@@ -0,0 +1,37 @@
+%%
+%% wings_io.erl --
+%%
+%% This module contains most of the low-level GUI for Wings.
+%%
+
+-module(wings_io).
+
+-export([get_matching_events/1]).
+
+-define(EVENT_QUEUE, wings_io_event_queue).
+
+%%%
+%%% Input.
+%%%
+
+get_matching_events(Filter) ->
+ Eq = get(?EVENT_QUEUE),
+ get_matching_events_1(Filter, Eq, [], []).
+
+get_matching_events_1(Filter, Eq0, Match, NoMatch) ->
+ case queue:out(Eq0) of
+ {{value,Ev},Eq} ->
+ case Filter(Ev) of
+ false ->
+ get_matching_events_1(Filter, Eq, Match, [Ev|NoMatch]);
+ true ->
+ get_matching_events_1(Filter, Eq, [Ev|Match], NoMatch)
+ end;
+ {empty,{In,Out}} ->
+ case Match of
+ [] -> [];
+ _ ->
+ put(?EVENT_QUEUE, {In, lists:reverse(NoMatch, Out)}),
+ Match
+ end
+ end.
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_sel.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_sel.erl
new file mode 100644
index 0000000000..eef797027e
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_sel.erl
@@ -0,0 +1,68 @@
+%%
+%% wings_sel.erl --
+%%
+%% This module implements selection utilities.
+%%
+
+-module(wings_sel).
+
+-export([face_regions/2, fold/3, set/3]).
+
+-include("wings.hrl").
+
+set(Mode, Sel, St) ->
+ St#st{selmode=Mode, sel=lists:sort(Sel), sh=false}.
+
+%%%
+%%% Fold over the selection.
+%%%
+
+fold(F, Acc, #st{sel=Sel,shapes=Shapes}) ->
+ fold_1(F, Acc, Shapes, Sel).
+
+fold_1(F, Acc0, Shapes, [{Id,Items}|T]) ->
+ We = gb_trees:get(Id, Shapes),
+ ?ASSERT(We#we.id =:= Id),
+ fold_1(F, F(Items, We, Acc0), Shapes, T);
+fold_1(_F, Acc, _Shapes, []) -> Acc.
+
+%%%
+%%% Divide the face selection into regions where each face shares at least
+%%% one edge with another face in the same region. Two faces can share a
+%%% vertex without necessarily being in the same region.
+%%%
+
+face_regions(Faces, We) when is_list(Faces) ->
+ face_regions_1(gb_sets:from_list(Faces), We);
+face_regions(Faces, We) ->
+ face_regions_1(Faces, We).
+
+face_regions_1(Faces, We) ->
+ find_face_regions(Faces, We, fun collect_face_fun/5, []).
+
+find_face_regions(Faces0, We, Coll, Acc) ->
+ case gb_sets:is_empty(Faces0) of
+ true -> Acc;
+ false ->
+ {Face,Faces1} = gb_sets:take_smallest(Faces0),
+ Ws = [Face],
+ {Reg,Faces} = collect_face_region(Ws, We, Coll, [], Faces1),
+ find_face_regions(Faces, We, Coll, [Reg|Acc])
+ end.
+
+collect_face_region([_|_]=Ws0, We, Coll, Reg0, Faces0) ->
+ Reg = Ws0++Reg0,
+ {Ws,Faces} = wings_face:fold_faces(Coll, {[],Faces0}, Ws0, We),
+ collect_face_region(Ws, We, Coll, Reg, Faces);
+collect_face_region([], _, _, Reg, Faces) ->
+ {gb_sets:from_list(Reg),Faces}.
+
+collect_face_fun(Face, _, _, Rec, {Ws,Faces}=A) ->
+ Of = case Rec of
+ #edge{lf=Face,rf=Of0} -> Of0;
+ #edge{rf=Face,lf=Of0} -> Of0
+ end,
+ case gb_sets:is_member(Of, Faces) of
+ true -> {[Of|Ws],gb_sets:delete(Of, Faces)};
+ false -> A
+ end.
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_shape.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_shape.erl
new file mode 100644
index 0000000000..0df8ca68eb
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_shape.erl
@@ -0,0 +1,69 @@
+%%
+%% wings_shape.erl --
+%%
+%% Utilities for shape records.
+%%
+
+-module(wings_shape).
+
+-export([insert/3]).
+
+-include("wings.hrl").
+
+%%%
+%%% Exported functions.
+%%%
+
+%% new(We, Suffix, St0) -> St.
+%% Suffix = cut | clone | copy | extract | sep
+%%
+%% Create a new object based on an old object. The name
+%% will be created from the old name (with digits and known
+%% suffixes stripped) with the given Suffix and a number
+%% appended.
+insert(#we{name=OldName}=We0, Suffix, #st{shapes=Shapes0,onext=Oid}=St) ->
+ Name = new_name(OldName, Suffix, Oid),
+ We = We0#we{id=Oid,name=Name},
+ Shapes = gb_trees:insert(Oid, We, Shapes0),
+ St#st{shapes=Shapes,onext=Oid+1}.
+
+%%%
+%%% Local functions follow.
+%%%
+
+new_name(OldName, Suffix0, Id) ->
+ Suffix = suffix(Suffix0),
+ Base = base(lists:reverse(OldName)),
+ lists:reverse(Base, "_" ++ Suffix ++ integer_to_list(Id)).
+
+%% Note: Filename suffixes are intentionally not translated.
+%% If we are to translate them in the future, base/1 below
+%% must be updated to strip suffixes (both for the current language
+%% and for English).
+
+suffix(cut) -> "cut";
+suffix(clone) -> "clone";
+suffix(copy) -> "copy";
+suffix(extract) -> "extract";
+suffix(mirror) -> "mirror";
+suffix(sep) -> "sep".
+
+%% base_1(ReversedName) -> ReversedBaseName
+%% Given an object name, strip digits and known suffixes to
+%% create a base name. Returns the unchanged name if
+%% no known suffix could be stripped.
+
+base(OldName) ->
+ case base_1(OldName) of
+ error -> OldName;
+ Base -> Base
+ end.
+
+base_1([H|T]) when $0 =< H, H =< $9 -> base_1(T);
+base_1("tuc_"++Base) -> Base; %"_cut"
+base_1("enolc_"++Base) -> Base; %"_clone"
+base_1("ypoc_"++Base) -> Base; %"_copy"
+base_1("tcartxe_"++Base) -> Base; %"_extract"
+base_1("rorrim_"++Base) -> Base; %"_mirror"
+base_1("pes_"++Base) -> Base; %"_sep"
+base_1(_Base) -> error.
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_util.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_util.erl
new file mode 100644
index 0000000000..9572e19955
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_util.erl
@@ -0,0 +1,39 @@
+%%
+%% wings_util.erl --
+%%
+%% Various utility functions that not obviously fit somewhere else.
+%%
+
+-module(wings_util).
+
+-export([gb_trees_smallest_key/1, gb_trees_largest_key/1,
+ gb_trees_map/2, rel2fam/1]).
+
+-include("wings.hrl").
+
+rel2fam(Rel) ->
+ sofs:to_external(sofs:relation_to_family(sofs:relation(Rel))).
+
+%% a definition that does not violate the opaqueness of gb_tree()
+gb_trees_smallest_key(Tree) ->
+ {Key, _V} = gb_trees:smallest(Tree),
+ Key.
+
+%% a definition that violates the opaqueness of gb_tree()
+gb_trees_largest_key({_, Tree}) ->
+ largest_key1(Tree).
+
+largest_key1({Key, _Value, _Smaller, nil}) ->
+ Key;
+largest_key1({_Key, _Value, _Smaller, Larger}) ->
+ largest_key1(Larger).
+
+gb_trees_map(F, {Size,Tree}) ->
+ {Size,gb_trees_map_1(F, Tree)}.
+
+gb_trees_map_1(_, nil) -> nil;
+gb_trees_map_1(F, {K,V,Smaller,Larger}) ->
+ {K,F(K, V),
+ gb_trees_map_1(F, Smaller),
+ gb_trees_map_1(F, Larger)}.
+
diff --git a/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_we.erl b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_we.erl
new file mode 100644
index 0000000000..d782144def
--- /dev/null
+++ b/lib/dialyzer/test/opaque_tests_SUITE_data/src/wings/wings_we.erl
@@ -0,0 +1,250 @@
+%%
+%% wings_we.erl --
+%%
+%% This module contains functions to build and manipulate
+%% we records (winged-edged records, the central data structure
+%% in Wings 3D).
+
+-module(wings_we).
+
+-export([rebuild/1, is_consistent/1, is_face_consistent/2, new_id/1,
+ new_items_as_ordset/3, validate_mirror/1, visible/1, visible_edges/1]).
+
+-include("wings.hrl").
+
+%%%
+%%% API.
+%%%
+
+validate_mirror(#we{mirror=none}=We) -> We;
+validate_mirror(#we{fs=Ftab,mirror=Face}=We) ->
+ case gb_trees:is_defined(Face, Ftab) of
+ false -> We#we{mirror=none};
+ true -> We
+ end.
+
+%% rebuild(We) -> We'
+%% Rebuild any missing 'vc' and 'fs' tables. If there are
+%% fewer elements in the 'vc' table than in the 'vp' table,
+%% remove redundant entries in the 'vp' table. Updated id
+%% bounds.
+rebuild(#we{vc=undefined,fs=undefined,es=Etab0}=We0) ->
+ Etab = gb_trees:to_list(Etab0),
+ Ftab = rebuild_ftab(Etab),
+ VctList = rebuild_vct(Etab),
+ We = We0#we{vc=gb_trees:from_orddict(VctList),fs=Ftab},
+ rebuild_1(VctList, We);
+rebuild(#we{vc=undefined,es=Etab}=We) ->
+ VctList = rebuild_vct(gb_trees:to_list(Etab), []),
+ rebuild_1(VctList, We#we{vc=gb_trees:from_orddict(VctList)});
+rebuild(#we{fs=undefined,es=Etab}=We) ->
+ Ftab = rebuild_ftab(gb_trees:to_list(Etab)),
+ rebuild(We#we{fs=Ftab});
+rebuild(We) -> update_id_bounds(We).
+
+%%% Utilities for allocating IDs.
+
+new_id(#we{next_id=Id}=We) ->
+ {Id,We#we{next_id=Id+1}}.
+
+%%% Returns sets of newly created items.
+
+new_items_as_ordset(vertex, #we{next_id=Wid}, #we{next_id=NewWid,vp=Tab}) ->
+ new_items_as_ordset_1(Tab, Wid, NewWid);
+new_items_as_ordset(edge, #we{next_id=Wid}, #we{next_id=NewWid,es=Tab}) ->
+ new_items_as_ordset_1(Tab, Wid, NewWid);
+new_items_as_ordset(face, #we{next_id=Wid}, #we{next_id=NewWid,fs=Tab}) ->
+ new_items_as_ordset_1(Tab, Wid, NewWid).
+
+any_hidden(#we{fs=Ftab}) ->
+ not gb_trees:is_empty(Ftab) andalso
+ wings_util:gb_trees_smallest_key(Ftab) < 0.
+
+%%%
+%%% Local functions.
+%%%
+
+rebuild_1(VctList, #we{vc=Vct,vp=Vtab0}=We) ->
+ case {gb_trees:size(Vct),gb_trees:size(Vtab0)} of
+ {Same,Same} -> rebuild(We);
+ {Sz1,Sz2} when Sz1 < Sz2 ->
+ Vtab = vertex_gc_1(VctList, gb_trees:to_list(Vtab0), []),
+ rebuild(We#we{vp=Vtab})
+ end.
+
+rebuild_vct(Es) ->
+ rebuild_vct(Es, []).
+
+rebuild_vct([{Edge,#edge{vs=Va,ve=Vb}}|Es], Acc0) ->
+ Acc = rebuild_maybe_add(Va, Vb, Edge, Acc0),
+ rebuild_vct(Es, Acc);
+rebuild_vct([], VtoE) ->
+ build_incident_tab(VtoE).
+
+rebuild_ftab(Es) ->
+ rebuild_ftab_1(Es, []).
+
+rebuild_ftab_1([{Edge,#edge{lf=Lf,rf=Rf}}|Es], Acc0) ->
+ Acc = rebuild_maybe_add(Lf, Rf, Edge, Acc0),
+ rebuild_ftab_1(Es, Acc);
+rebuild_ftab_1([], FtoE) ->
+ gb_trees:from_orddict(build_incident_tab(FtoE)).
+
+rebuild_maybe_add(Ka, Kb, E, [_,{Ka,_}|_]=Acc) ->
+ [{Kb,E}|Acc];
+rebuild_maybe_add(Ka, Kb, E, [_,{Kb,_}|_]=Acc) ->
+ [{Ka,E}|Acc];
+rebuild_maybe_add(Ka, Kb, E, [{Ka,_}|_]=Acc) ->
+ [{Kb,E}|Acc];
+rebuild_maybe_add(Ka, Kb, E, [{Kb,_}|_]=Acc) ->
+ [{Ka,E}|Acc];
+rebuild_maybe_add(Ka, Kb, E, Acc) ->
+ [{Ka,E},{Kb,E}|Acc].
+
+vertex_gc_1([{V,_}|Vct], [{V,_}=Vtx|Vpos], Acc) ->
+ vertex_gc_1(Vct, Vpos, [Vtx|Acc]);
+vertex_gc_1([_|_]=Vct, [_|Vpos], Acc) ->
+ vertex_gc_1(Vct, Vpos, Acc);
+vertex_gc_1([], _, Acc) ->
+ gb_trees:from_orddict(lists:reverse(Acc)).
+
+%%%
+%%% Handling of hidden faces.
+%%%
+
+visible(#we{mirror=none,fs=Ftab}) ->
+ visible_2(gb_trees:keys(Ftab));
+visible(#we{mirror=Face,fs=Ftab}) ->
+ visible_2(gb_trees:keys(gb_trees:delete(Face, Ftab))).
+
+visible_2([F|Fs]) when F < 0 -> visible_2(Fs);
+visible_2(Fs) -> Fs.
+
+visible_edges(#we{es=Etab,mirror=Face}=We) ->
+ case any_hidden(We) of
+ false -> gb_trees:keys(Etab);
+ true -> visible_es_1(gb_trees:to_list(Etab), Face, [])
+ end.
+
+visible_es_1([{E,#edge{lf=Lf,rf=Rf}}|Es], Face, Acc) ->
+ if
+ Lf < 0 ->
+ %% Left face hidden.
+ if
+ Rf < 0; Rf =:= Face ->
+ %% Both faces invisible (in some way).
+ visible_es_1(Es, Face, Acc);
+ true ->
+ %% Right face is visible.
+ visible_es_1(Es, Face, [E|Acc])
+ end;
+ Lf =:= Face, Rf < 0 ->
+ %% Left face mirror, right face hidden.
+ visible_es_1(Es, Face, Acc);
+ true ->
+ %% At least one face visible.
+ visible_es_1(Es, Face, [E|Acc])
+ end;
+visible_es_1([], _, Acc) -> ordsets:from_list(Acc).
+
+update_id_bounds(#we{vp=Vtab,es=Etab,fs=Ftab}=We) ->
+ case gb_trees:is_empty(Etab) of
+ true -> We#we{next_id=0};
+ false ->
+ LastId = lists:max([wings_util:gb_trees_largest_key(Vtab),
+ wings_util:gb_trees_largest_key(Etab),
+ wings_util:gb_trees_largest_key(Ftab)]),
+ We#we{next_id=LastId+1}
+ end.
+
+%% build_incident_tab([{Elem,Edge}]) -> [{Elem,Edge}]
+%% Elem = Face or Vertex
+%% Build the table of incident edges for either faces or vertices.
+%% Returns an ordered list where each Elem is unique.
+
+build_incident_tab(ElemToEdgeRel) ->
+ T = ets:new(?MODULE, [ordered_set]),
+ ets:insert(T, ElemToEdgeRel),
+ R = ets:tab2list(T),
+ ets:delete(T),
+ R.
+
+%%%
+%%% Calculate normals.
+%%%
+
+new_items_as_ordset_1(Tab, Wid, NewWid) when NewWid-Wid < 32 ->
+ new_items_as_ordset_2(Wid, NewWid, Tab, []);
+new_items_as_ordset_1(Tab, Wid, _NewWid) ->
+ [Item || Item <- gb_trees:keys(Tab), Item >= Wid].
+
+new_items_as_ordset_2(Wid, NewWid, Tab, Acc) when Wid < NewWid ->
+ case gb_trees:is_defined(Wid, Tab) of
+ true -> new_items_as_ordset_2(Wid+1, NewWid, Tab, [Wid|Acc]);
+ false -> new_items_as_ordset_2(Wid+1, NewWid, Tab, Acc)
+ end;
+new_items_as_ordset_2(_Wid, _NewWid, _Tab, Acc) -> lists:reverse(Acc).
+
+%%%
+%%% Test the consistency of a #we{}.
+%%%
+
+is_consistent(#we{}=We) ->
+ try
+ validate_vertex_tab(We),
+ validate_faces(We)
+ catch error:_ -> false
+ end.
+
+is_face_consistent(Face, #we{fs=Ftab,es=Etab}) ->
+ Edge = gb_trees:get(Face, Ftab),
+ try validate_face(Face, Edge, Etab)
+ catch error:_ -> false
+ end.
+
+validate_faces(#we{fs=Ftab,es=Etab}) ->
+ validate_faces_1(gb_trees:to_list(Ftab), Etab).
+
+validate_faces_1([{Face,Edge}|Fs], Etab) ->
+ validate_face(Face, Edge, Etab),
+ validate_faces_1(Fs, Etab);
+validate_faces_1([], _) -> true.
+
+validate_face(Face, Edge, Etab) ->
+ Ccw = walk_face_ccw(Edge, Etab, Face, Edge, []),
+ Edge = walk_face_cw(Edge, Etab, Face, Ccw),
+ [V|Vs] = lists:sort(Ccw),
+ validate_face_vertices(Vs, V).
+
+validate_face_vertices([V|_], V) ->
+ erlang:error(repeated_vertex);
+validate_face_vertices([_], _) ->
+ true;
+validate_face_vertices([V|Vs], _) ->
+ validate_face_vertices(Vs, V).
+
+walk_face_ccw(LastEdge, _, _, LastEdge, [_|_]=Acc) -> Acc;
+walk_face_ccw(Edge, Etab, Face, LastEdge, Acc) ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{ve=V,lf=Face,ltpr=Next} ->
+ walk_face_ccw(Next, Etab, Face, LastEdge, [V|Acc]);
+ #edge{vs=V,rf=Face,rtpr=Next} ->
+ walk_face_ccw(Next, Etab, Face, LastEdge, [V|Acc])
+ end.
+
+walk_face_cw(Edge, _, _, []) -> Edge;
+walk_face_cw(Edge, Etab, Face, [V|Vs]) ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{vs=V,lf=Face,ltsu=Next} ->
+ walk_face_cw(Next, Etab, Face, Vs);
+ #edge{ve=V,rf=Face,rtsu=Next} ->
+ walk_face_cw(Next, Etab, Face, Vs)
+ end.
+
+validate_vertex_tab(#we{es=Etab,vc=Vct}) ->
+ lists:foreach(fun({V,Edge}) ->
+ case gb_trees:get(Edge, Etab) of
+ #edge{vs=V} -> ok;
+ #edge{ve=V} -> ok
+ end
+ end, gb_trees:to_list(Vct)).