%% %% wings_shape.erl -- %% %% Utilities for shape records. %% %% 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_shape.erl,v 1.78 2005/11/29 22:25:44 raimo_niskanen Exp $ %% -module(wings_shape). -export([new/3,insert/3,replace/3,window/1,window/4]). -export([all_selectable/1]). -export([show_all/1,unlock_all/1,permissions/3]). -define(NEED_ESDL, 1). -define(NEED_OPENGL, 1). -include("wings.hrl"). -import(lists, [map/2,foldl/3,reverse/1,reverse/2, keymember/3,keysearch/3,sort/1]). %%% %%% Exported functions. %%% %% new(Name, We, St0) -> St. %% Create a new object having the given name, %% converting all unknown materials to default. new(Name, We0, #st{shapes=Shapes0,onext=Oid,mat=Mat}=St) -> UsedMat = wings_facemat:used_materials(We0), We = case lists:filter( fun (M) -> not gb_trees:is_defined(M, Mat) end, UsedMat) of [] -> We0; XMat -> FMs = lists:filter(fun ({_,M}) -> lists:member(M, XMat) end, wings_facemat:all(We0)), wings_facemat:assign(default, [F||{F,_}<-FMs], We0) end, Shapes = gb_trees:insert(Oid, We#we{name=Name,id=Oid}, Shapes0), St#st{shapes=Shapes,onext=Oid+1}. %% 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}. replace(Id, We0, #st{shapes=Shapes0}=St) -> We = We0#we{id=Id}, Shapes = gb_trees:update(Id, We, Shapes0), St#st{shapes=Shapes}. permissions(We, Visible, Locked) -> P0 = case Visible of true -> 0; false -> 2 end, P = case Locked of true -> P0 bor 1; false -> P0 end, We#we{perm=P}. show_all(#st{shapes=Shs0,sel=Sel0}=St) -> Shs1 = gb_trees:values(Shs0), Shs2 = [{Id,show_we(We)} || #we{id=Id}=We <- Shs1], Shs = gb_trees:from_orddict(Shs2), Sel = sort(show_all_sel(Shs1, St, Sel0)), St#st{shapes=Shs,sel=Sel}. unlock_all(#st{shapes=Shs0}=St) -> Shs1 = gb_trees:values(Shs0), Shs2 = [{Id,maybe_unlock(We)} || #we{id=Id}=We <- Shs1], Shs = gb_trees:from_orddict(Shs2), St#st{shapes=Shs}. %% all_selectable(St) -> GbSet %% Return a GbSet containing IDs for all selectable objects (i.e. not locked). all_selectable(#st{shapes=Shs}) -> all_selectable_1(gb_trees:to_list(Shs), []). all_selectable_1([{Id,#we{perm=P}}|T], Acc) when ?IS_SELECTABLE(P) -> all_selectable_1(T, [Id|Acc]); all_selectable_1([_|T], Acc) -> all_selectable_1(T, Acc); all_selectable_1([], Acc) -> gb_sets:from_ordset(reverse(Acc)). %%% %%% Local functions follow. %%% new_name(OldName, Suffix0, Id) -> Suffix = suffix(Suffix0), Base = base(reverse(OldName)), 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. %%% %%% Object window. %%% -record(ost, {st, %Current St. n, %Number of objects. first, %First object to show. sel, %Current selection. os, %All objects. active, %Number of active object. lh, %Line height. op %Latest operation. }). window(St) -> Name = {object,wings_wm:this()}, case wings_wm:is_window(Name) of true -> wings_wm:raise(Name), keep; false -> {{_,DeskY},{DeskW,DeskH}} = wings_wm:win_rect(desktop), W = 28*?CHAR_WIDTH, Pos = {DeskW-5,DeskY+55}, Size = {W,DeskH div 2}, window(Name, Pos, Size, St), keep end. window({_,Client}=Name, Pos, Size, St) -> Title = title(Client), Ost = #ost{first=0,lh=18,active=-1}, Current = {current_state,St}, Op = {seq,push,event(Current, Ost)}, Props = [{display_lists,geom_display_lists}], wings_wm:toplevel(Name, Title, Pos, Size, [{sizeable,?PANE_COLOR},closable,vscroller, {anchor,ne},{properties,Props}], Op). title(geom) -> ?STR(title,1,"Geometry Graph"); title({geom,N}) -> ?STR(title,2,"Geometry Graph #") ++ integer_to_list(N). get_event(Ost) -> {replace,fun(Ev) -> event(Ev, Ost) end}. event(resized, Ost) -> update_scroller(Ost), keep; event(close, _) -> delete; event(redraw, Ost) -> wings_io:ortho_setup(), {W,H} = wings_wm:win_size(), wings_io:border(0, 0, W-1, H-1, ?PANE_COLOR), draw_objects(Ost), keep; event({current_state,St}, Ost0) -> Ost = update_state(St, Ost0), update_scroller(Ost), get_event(Ost); event(#mousemotion{x=X,y=Y}, Ost) -> Act = active_object(Y, Ost), help(Act, active_field(X)), keep; event(#mousebutton{button=4,state=?SDL_RELEASED}, Ost) -> zoom_step(-1*lines(Ost) div 4, Ost); event(#mousebutton{button=5,state=?SDL_RELEASED}, Ost) -> zoom_step(lines(Ost) div 4, Ost); event(#mousebutton{}=Ev, Ost) -> do_action(Ev, Ost); event(scroll_page_up, Ost) -> zoom_step(-lines(Ost), Ost); event(scroll_page_down, Ost) -> zoom_step(lines(Ost), Ost); event({set_knob_pos,Pos}, #ost{first=First0,n=N}=Ost0) -> case round(N*Pos) of First0 -> keep; First when First < N -> wings_wm:dirty(), Ost = Ost0#ost{first=First}, update_scroller(Ost), get_event(Ost); _ -> keep end; event({action,{objects,Cmd}}, Ost) -> command(Cmd, Ost); event(language_changed, _) -> {object,Geom} = This = wings_wm:this(), wings_wm:toplevel_title(This, title(Geom)), keep; event(Ev, Ost) -> case wings_hotkey:event(Ev) of {select,deselect} -> wings_wm:dirty(), get_event(Ost#ost{active=-1}); _ -> keep end. help(-1, _) -> wings_wm:message(""); help(_, name) -> wings_msg:button(?STR(help,1,"Select"), [],?STR(help,2,"Show menu")); help(_, visibility) -> help_1(?STR(help,3,"Toggle visibility of active object"), ?STR(help,4,"Toggle visibility of all other objects")); help(_, lock) -> help_1(?STR(help,5,"Lock/unlock active object"), ?STR(help,6,"Lock/unlock all objects")); help(_, selection) -> help_1(?STR(help,7,"Toggle selection for active object"), ?STR(help,8,"Toggle selection for all other objects")); help(_, wire) -> help_1(?STR(help,9,"Toggle shaded/wireframe for active object"), ?STR(help,10,"Toggle shaded/wireframe for all other objects")). help_1(OneMsg, ThreeMsg) -> wings_msg:button(OneMsg, [], ThreeMsg). command({delete_object,Id}, _) -> send_client({action,{body,{delete_object,[Id]}}}); command({duplicate_object,Id}, _) -> send_client({action,{body,{duplicate_object,[Id]}}}); command({rename_object,Id}, _) -> send_client({action,{body,{rename,[Id]}}}); command(Cmd, _) -> io:format("NYI: ~p\n", [Cmd]), keep. update_state(St, #ost{first=OldFirst}=Ost0) -> #ost{first=First0} = Ost = update_state_1(St, Ost0), case clamp(First0, Ost) of OldFirst -> Ost; First -> wings_wm:dirty(), Ost#ost{first=First} end. update_state_1(#st{sel=Sel,shapes=Shs}=St, #ost{st=#st{sel=Sel,shapes=Shs}}=Ost) -> Ost#ost{st=St}; update_state_1(#st{sel=Sel,shapes=Shs0}=St, #ost{st=#st{sel=Sel},os=Objs}=Ost) -> Shs = gb_trees:values(Shs0), case have_objects_really_changed(Shs, Objs) of false -> ok; true -> wings_wm:dirty() end, Ost#ost{st=St,sel=Sel,os=Shs,n=gb_trees:size(Shs0)}; update_state_1(#st{sel=Sel,shapes=Shs}=St, #ost{st=#st{sel=Sel0}}=Ost) -> case has_sel_really_changed(Sel, Sel0) of false -> ok; true -> wings_wm:dirty() end, Ost#ost{st=St,sel=Sel,os=gb_trees:values(Shs),n=gb_trees:size(Shs)}; update_state_1(#st{sel=Sel,shapes=Shs}=St, Ost) -> Ost#ost{st=St,sel=Sel,os=gb_trees:values(Shs),n=gb_trees:size(Shs)}. update_scroller(#ost{n=0}) -> Name = wings_wm:this(), wings_wm:set_knob(Name, 0.0, 1.0); update_scroller(#ost{first=First,n=N}=Ost) -> Name = wings_wm:this(), Lines = lines(Ost), wings_wm:set_knob(Name, First/N, Lines/N). has_sel_really_changed([{Id,_}|SelA], [{Id,_}|SelB]) -> has_sel_really_changed(SelA, SelB); has_sel_really_changed([], []) -> false; has_sel_really_changed(_, _) -> true. have_objects_really_changed([#we{id=Id,name=Name,perm=P}|WesA], [#we{id=Id,name=Name,perm=P}|WesB]) -> have_objects_really_changed(WesA, WesB); have_objects_really_changed([], []) -> false; have_objects_really_changed(_, _) -> true. zoom_step(Step, #ost{first=First0}=Ost0) -> case clamp(First0+Step, Ost0) of First0 -> keep; First -> wings_wm:dirty(), Ost = Ost0#ost{first=First}, update_scroller(Ost), get_event(Ost) end. clamp(F, #ost{n=N}=Ost) -> Max = case N-lines(Ost) of Neg when Neg < 0 -> 0; Other -> Other end, if F < 0 -> 0; F > Max -> Max; true -> F end. active_object(Y0, #ost{lh=Lh,first=First,n=N}) -> case Y0 of Y when Y < 0 -> -1; Y1 -> case Y1 div Lh of Y when First+Y < N -> First+Y; _ -> -1 end end. active_field(X) -> NamePos = name_pos(), EyePos = eye_pos(), LockPos = lock_pos(), WirePos = wire_pos(), if X < NamePos -> selection; X < EyePos -> name; X < LockPos -> visibility; X < WirePos -> lock; true -> wire end. do_action(#mousebutton{button=B}, _) when B > 3 -> keep; do_action(#mousebutton{x=X,y=Y,button=B,state=S}, #ost{active=Act0,os=Objs}=Ost) -> Act = active_object(Y, Ost), case active_field(X) of name when B =:= 1, S =:= ?SDL_PRESSED -> if Act =:= Act0 -> keep; true -> wings_wm:dirty(), get_event(Ost#ost{active=Act}) end; name when B =:= 3, S =:= ?SDL_RELEASED -> {GlobX,GlobY} = wings_wm:local2global(X, Y), do_menu(Act, GlobX, GlobY, Ost); Field when S =:= ?SDL_PRESSED -> if Act =:= -1 -> keep; true -> We = lists:nth(Act+1, Objs), do_action_1(Field, B, We, Ost) end; _ -> keep end. do_action_1(visibility, 1, We, Ost) -> toggle_visibility(We, Ost); do_action_1(visibility, 3, We, Ost) -> toggle_visibility_all(We, Ost); do_action_1(lock, 1, We, Ost) -> toggle_lock(We, Ost); do_action_1(lock, 3, We, Ost) -> toggle_lock_all(We, Ost); do_action_1(selection, 1, We, Ost) -> toggle_sel(We, Ost); do_action_1(selection, 3, We, Ost) -> toggle_sel_all(We, Ost); do_action_1(wire, 1, We, Ost) -> toggle_wire(We, Ost); do_action_1(wire, 3, We, Ost) -> toggle_wire_all(We, Ost); do_action_1(_, _, _, Ost) -> get_event(Ost). toggle_visibility(#we{id=Id,perm=Perm}, #ost{st=St0}=Ost) -> {Op,St} = if ?IS_VISIBLE(Perm) -> {hide,hide_object(Id, St0)}; true -> {show,show_object(Id, St0)} end, send_client({new_state,St}), get_event(Ost#ost{op=Op}). toggle_visibility_all(#we{id=Id}, #ost{os=Objs,st=St0}=Ost) -> St = case are_all_visible(Objs, Id) of false -> show_all(St0); true -> hide_others(Id, St0) end, send_client({new_state,St}), get_event(Ost#ost{op=none}). are_all_visible([#we{id=Id}|T], Id) -> are_all_visible(T, Id); are_all_visible([#we{perm=P}|T], Id) -> case ?IS_VISIBLE(P) of false -> false; true -> are_all_visible(T, Id) end; are_all_visible([], _) -> true. toggle_lock(#we{perm=Perm}, _) when ?IS_NOT_VISIBLE(Perm) -> keep; toggle_lock(#we{id=Id,perm=Perm}, #ost{st=St0}=Ost) when ?IS_SELECTABLE(Perm) -> send_client({new_state,lock_object(Id, St0)}), get_event(Ost#ost{op=lock}); toggle_lock(#we{id=Id}, #ost{st=St0}=Ost) -> send_client({new_state,unlock_object(Id, St0)}), get_event(Ost#ost{op=unlock}). toggle_lock_all(#we{id=Id}, #ost{st=St0,os=Objs}=Ost) -> St = case are_all_visible_locked(Objs, Id) of true -> unlock_all(St0); false -> lock_others(Id, St0) end, send_client({new_state,St}), get_event(Ost#ost{op=none}). are_all_visible_locked([#we{id=Id}|T], Id) -> are_all_visible_locked(T, Id); are_all_visible_locked([#we{perm=P}|T], Id) -> case ?IS_VISIBLE(P) of false -> are_all_visible_locked(T, Id); true when ?IS_NOT_SELECTABLE(P) -> are_all_visible_locked(T, Id); true -> false end; are_all_visible_locked([], _) -> true. toggle_sel(#we{id=Id,perm=P}, #ost{st=St0,sel=Sel}=Ost) -> case keymember(Id, 1, Sel) of false when ?IS_SELECTABLE(P) -> St = wings_sel:select_object(Id, St0), send_client({new_state,St}), get_event(Ost#ost{op=select}); true -> St = wings_sel:deselect_object(Id, St0), send_client({new_state,St}), get_event(Ost#ost{op=deselect}); false -> get_event(Ost#ost{op=none}) end. toggle_sel_all(We, Ost) -> toggle_sel_all_1(We, Ost), get_event(Ost#ost{op=none}). toggle_sel_all_1(_, #ost{sel=[],st=St0}) -> St = wings_sel_cmd:select_all(St0), send_client({new_state,St}); toggle_sel_all_1(#we{id=Id}, #ost{sel=[{Id,_}],st=St0}) -> St = wings_sel_cmd:select_all(St0#st{sel=[]}), send_client({new_state,St}); toggle_sel_all_1(#we{id=Id,perm=P}, #ost{st=St}) when ?IS_SELECTABLE(P) -> send_client({new_state,wings_sel:select_object(Id, St#st{sel=[]})}); toggle_sel_all_1(_, _) -> ok. toggle_wire(#we{id=Id}, #ost{st=St}) -> {_,Client} = wings_wm:this(), W0 = wings_wm:get_prop(Client, wireframed_objects), W = case gb_sets:is_member(Id, W0) of false -> gb_sets:insert(Id, W0); true -> gb_sets:delete(Id, W0) end, wings_wm:set_prop(Client, wireframed_objects, W), wings_draw:refresh_dlists(St), wings_wm:dirty(). toggle_wire_all(#we{id=Id}, #ost{st=St}) -> All = all_selectable(St), {_,Client} = wings_wm:this(), W0 = wings_wm:get_prop(Client, wireframed_objects), W1 = case gb_sets:is_empty(gb_sets:delete_any(Id, W0)) of true -> All; false -> gb_sets:difference(W0, All) end, W = case gb_sets:is_member(Id, W0) of false -> gb_sets:delete_any(Id, W1); true -> gb_sets:add(Id, W1) end, wings_wm:set_prop(Client, wireframed_objects, W), wings_draw:refresh_dlists(St), wings_wm:dirty(). %%% %%% Popup menus. %%% do_menu(-1, _, _, _) -> keep; do_menu(Act, X, Y, #ost{os=Objs}) -> Menu = case lists:nth(Act+1, Objs) of #we{id=Id} -> [{?STR(do_menu,1,"Duplicate"),menu_cmd(duplicate_object, Id), ?STR(do_menu,2,"Duplicate selected objects")}, {?STR(do_menu,3,"Delete"),menu_cmd(delete_object, Id), ?STR(do_menu,4,"Delete selected objects")}, {?STR(do_menu,5,"Rename"),menu_cmd(rename_object, Id), ?STR(do_menu,6,"Rename selected objects")}] end, wings_menu:popup_menu(X, Y, objects, Menu). menu_cmd(Cmd, Id) -> {'VALUE',{Cmd,Id}}. %%% %%% Draw the object window. %%% draw_objects(#ost{os=Objs0,first=First,lh=Lh,active=Active,n=N0}=Ost) -> Objs = lists:nthtail(First, Objs0), R = right_pos(), Lines = lines(Ost), N = case N0-First of N1 when N1 < Lines -> N1; _ -> Lines end, draw_icons(N, Objs, Ost, R, Active-First, Lh-2), draw_objects_1(N, Objs, Ost, R, Active-First, Lh-2). draw_objects_1(0, _, _, _, _, _) -> ok; draw_objects_1(N, [#we{name=Name}|Wes], #ost{lh=Lh}=Ost, R, Active, Y) -> if Active == 0 -> gl:color3f(0, 0, 0.5), gl:recti(name_pos()-2, Y-?CHAR_HEIGHT, R-2, Y+4), gl:color3f(1, 1, 1); true -> ok end, wings_io:text_at(name_pos(), Y, Name), gl:color3b(0, 0, 0), draw_objects_1(N-1, Wes, Ost, R, Active-1, Y+Lh). draw_icons(N, Objs, Ost, R, I, Y) -> {_,Client} = wings_wm:this(), Wires = wings_wm:get_prop(Client, wireframed_objects), DrawData = {N,Ost,R,I,Y,Wires}, wings_io:draw_icons(fun() -> foldl(fun draw_icons_1/2, DrawData, Objs) end). draw_icons_1(_, done) -> done; draw_icons_1(_, {0,_,_,_,_,_}) -> done; draw_icons_1(#we{id=Id,perm=Perm}=We, {N,#ost{sel=Sel,lh=Lh}=Ost, R,Active,Y,Wires}) -> EyePos = eye_pos(), LockPos = lock_pos(), SelPos = sel_pos(), WirePos = wire_pos(), IconY = Y - 14, Wire = gb_sets:is_member(Id, Wires), if Perm =:= 1; Perm =:= 3 -> wings_io:draw_icon(LockPos, IconY, small_locked); true -> wings_io:draw_icon(LockPos, IconY, small_unlocked) end, if ?IS_VISIBLE(Perm) -> wings_io:draw_icon(EyePos, IconY, small_eye), case Wire of false -> wings_io:draw_icon(WirePos, IconY, small_object); true -> wings_io:draw_icon(WirePos, IconY, small_wire) end; true -> wings_io:draw_icon(EyePos, IconY, small_closed_eye) end, case keymember(Id, 1, Sel) of false when ?IS_ANY_LIGHT(We) -> wings_io:draw_icon(SelPos, IconY, small_light); false -> wings_io:draw_icon(SelPos, IconY, small_object); true when ?IS_ANY_LIGHT(We) -> wings_io:draw_icon(SelPos, IconY, small_sel_light); true -> wings_io:draw_icon(SelPos, IconY, small_sel) end, {N-1,Ost,R,Active-1,Y+Lh,Wires}; draw_icons_1(_, Acc) -> Acc. sel_pos() -> 2. name_pos() -> 22. eye_pos() -> right_pos(). lock_pos() -> right_pos()+16+2. wire_pos() -> right_pos()+32+4. right_pos() -> {W,_} = wings_wm:win_size(), W-3*(16+2). lines(#ost{lh=Lh}) -> {_,_,_,H} = wings_wm:viewport(), H div Lh. %%% %%% Utilities. %%% hide_object(Id, #st{shapes=Shs0}=St) -> We0 = gb_trees:get(Id, Shs0), We = hide_we(We0, St), Shs = gb_trees:update(Id, We, Shs0), wings_sel:deselect_object(Id, St#st{shapes=Shs}). hide_we(#we{id=Id,perm=Perm0}=We, St) -> Perm = case get_sel(Id, St) of [] -> Perm0 bor 2; Other -> Other end, We#we{perm=Perm}. show_object(Id, #st{shapes=Shs0}=St) -> We0 = gb_trees:get(Id, Shs0), Sel = update_sel(We0, St), We = show_we(We0), Shs = gb_trees:update(Id, We, Shs0), St#st{sel=Sel,shapes=Shs}. show_we(#we{perm=Perm0}=We) -> Perm = case Perm0 of 3 -> 1; 1 -> 1; _ -> 0 end, We#we{perm=Perm}. lock_object(Id, St0) -> St = wings_sel:deselect_object(Id, St0), update_permission(1, Id, St). unlock_object(Id, St) -> update_permission(0, Id, St). update_permission(Perm, Id, #st{shapes=Shs0}=St) -> We = gb_trees:get(Id, Shs0), Shs = gb_trees:update(Id, We#we{perm=Perm}, Shs0), Sel = update_sel(We, St), St#st{sel=Sel,shapes=Shs}. hide_others(ThisId, #st{shapes=Shs0,sel=Sel0}=St) -> Shs1 = map(fun(#we{id=Id}=We) when ThisId =:= Id -> {Id,We}; (#we{id=Id}=We) -> {Id,hide_we(We, St)} end, gb_trees:values(Shs0)), Shs = gb_trees:from_orddict(Shs1), Sel = [This || {Id,_}=This <- Sel0, Id =:= ThisId], St#st{shapes=Shs,sel=Sel}. show_all_sel([#we{id=Id,perm={Mode,Set}}|T], #st{selmode=Mode}=St, Acc) -> show_all_sel(T, St, [{Id,Set}|Acc]); show_all_sel([#we{id=Id,perm={SMode,Set0}}|T], #st{selmode=Mode}=St, Acc) -> StTemp = St#st{selmode=SMode,sel=[{Id,Set0}]}, #st{sel=[{Id,Set}]} = wings_sel_conv:mode(Mode, StTemp), show_all_sel(T, St, [{Id,Set}|Acc]); show_all_sel([_|T], St, Acc) -> show_all_sel(T, St, Acc); show_all_sel([], _St, Acc) -> Acc. lock_others(ThisId, #st{shapes=Shs0,sel=Sel0}=St) -> Shs1 = map(fun(#we{id=Id}=We) when ThisId =:= Id -> {Id,We}; (#we{id=Id,perm=P}=We) when ?IS_VISIBLE(P) -> {Id,We#we{perm=1}}; (#we{id=Id}=We) -> {Id,We} end, gb_trees:values(Shs0)), Shs = gb_trees:from_orddict(Shs1), Sel = [This || {Id,_}=This <- Sel0, Id =:= ThisId], St#st{shapes=Shs,sel=Sel}. maybe_unlock(#we{perm=P}=We) when ?IS_VISIBLE(P) -> We#we{perm=0}; maybe_unlock(We) -> We. get_sel(Id, #st{selmode=Mode,sel=Sel}) -> case keysearch(Id, 1, Sel) of false -> []; {value,{Id,Set}} -> {Mode,Set} end. update_sel(#we{id=Id,perm={Mode,Set}}, #st{selmode=Mode,sel=Sel}) -> sort([{Id,Set}|Sel]); update_sel(#we{id=Id,perm={SMode,Elems0}}, #st{selmode=Mode,sel=Sel}=St) -> StTemp = St#st{selmode=SMode,sel=[{Id,Elems0}]}, #st{sel=[{Id,Elems}]} = wings_sel_conv:mode(Mode, StTemp), sort([{Id,Elems}|Sel]); update_sel(_, #st{sel=Sel}) -> Sel. send_client(Message) -> {_,Client} = wings_wm:this(), wings_wm:send(Client, Message).