1%% 2%% wings_sel.erl -- 3%% 4%% This module implements selection utilities. 5%% 6%% Copyright (c) 2001-2011 Bjorn Gustavsson 7%% 8%% See the file "license.terms" for information on usage and redistribution 9%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. 10%% 11%% $Id$ 12%% 13 14-module(wings_sel). 15 16-export([clear/1,reset/1,set/2,set/3, 17 conditional_reset/1, 18 selected_ids/1,unselected_ids/1, 19 map/2,map_obj/2, 20 map_update_sel/2,map_update_sel/3, 21 update_sel/2,update_sel/3,update_sel_all/2, 22 fold_obj/3,fold/3,dfold/4,mapfold/3, 23 new_sel/3,make/3,valid_sel/1,valid_sel/3,valid_sel_groups/3, 24 clone/2,clone/3,combine/2,combine/3,merge/2, 25 center/1,center_vs/1, 26 bbox_center/1,bounding_box/1, 27 face_regions/2,strict_face_regions/2,edge_regions/2, 28 select_object/2,deselect_object/2, 29 get_all_items/2,get_all_items/3, 30 inverse_items/3,to_vertices/3]). 31 32-export_type([mode/0, 33 edge_set/0,face_set/0,item_id/0,item_set/0,vertex_set/0]). 34 35-include("wings.hrl"). 36-include_lib("e3d/e3d.hrl"). 37 38-import(lists, [foldl/3,reverse/1,reverse/2,sort/1,keydelete/3,keymember/3]). 39 40-type mode() :: 'vertex' | 'edge' | 'face' | 'body'. 41 42-type vertex_num() :: wings_vertex:vertex_num(). 43-type edge_num() :: wings_edge:edge_num(). 44-type visible_face_num() :: wings_face:visible_face_num(). 45 46-type vertex_set() :: gb_sets:set(vertex_num()). 47-type edge_set() :: gb_sets:set(edge_num()). 48-type face_set() :: gb_sets:set(visible_face_num()). 49 50-type item_id() :: visible_face_num() | edge_num() | vertex_num() | 0. 51-type item_set() :: gb_sets:set(item_id()). 52 53 54-type obj_id() :: non_neg_integer(). 55 56-spec clear(#st{}) -> #st{}. 57 58clear(St) -> 59 St#st{sel=[],sh=false}. 60 61-spec reset(#st{}) -> #st{}. 62 63reset(#st{selmode=Mode}=St) -> 64 case Mode of 65 body -> St#st{selmode=face,sel=[],sh=true}; 66 _ -> St#st{sel=[],sh=true} 67 end. 68 69-spec conditional_reset(#st{}) -> #st{}. 70 71conditional_reset(#st{sel=[]}=St) -> 72 reset(St); 73conditional_reset(St) -> 74 St#st{sel=[],sh=false}. 75 76-spec set(Sel, #st{}) -> #st{} when 77 Sel :: [{item_id(),item_set()}]. 78 79set([], St) -> 80 clear(St); 81set(Sel, St) -> 82 St#st{sel=sort(Sel),sh=false}. 83 84-spec set(Mode, Sel, #st{}) -> #st{} when 85 Mode :: mode(), 86 Sel :: [{item_id(),item_set()}]. 87 88set(Mode, [], St) -> 89 clear(St#st{selmode=Mode}); 90set(Mode, Sel, St) -> 91 St#st{selmode=Mode,sel=sort(Sel),sh=false}. 92 93 94%% 95%% Return the Ids for all selected objects. 96%% 97 98-spec selected_ids(#st{}) -> [non_neg_integer()]. 99 100selected_ids(#st{sel=Sel}) -> 101 [Id || {Id,_} <- Sel]. 102 103%% 104%% Return the Ids for all selected objects. 105%% 106 107-spec unselected_ids(#st{}) -> [non_neg_integer()]. 108 109unselected_ids(#st{sel=Sel,shapes=Shs}) -> 110 SelIds = [Id || {Id,_} <- Sel], 111 AllIds = gb_trees:keys(Shs), 112 ordsets:subtract(AllIds, SelIds). 113 114%%% 115%%% Map over the selection, modifying the selected #we{} 116%%% records. 117%%% 118 119-spec map(Fun, #st{}) -> #st{} when 120 Fun :: fun((Items, #we{}) -> #we{}), 121 Items :: item_set(). 122 123map(F, #st{shapes=Shs0,sel=Sel}=St) -> 124 Shs1 = gb_trees:to_list(Shs0), 125 Shs = map_1(F, Sel, Shs1, St, []), 126 St#st{shapes=Shs}. 127 128 129%%% 130%%% Map over the selection, modifying the object maps. 131%%% 132 133-spec map_obj(F, #st{}) -> #st{} when 134 F :: fun((InObj) -> OutObj), 135 InObj :: wings_obj:obj(), 136 OutObj :: wings_obj:obj(). 137 138map_obj(F, #st{sel=Sel}=St) -> 139 SF = fun(#{id:=Id}=Obj) -> 140 case keymember(Id, 1, Sel) of 141 false -> Obj; 142 true -> F(Obj) 143 end 144 end, 145 wings_obj:map(SF, St). 146 147%% 148%% Map over the selection, modifying the objects and the selection. 149%% 150 151-spec map_update_sel(Fun, Mode, #st{}) -> #st{} when 152 Fun :: fun((InItems, #we{}) -> {#we{},OutItems}), 153 Mode :: mode(), 154 InItems :: item_set(), 155 OutItems :: item_set(). 156 157map_update_sel(F, Mode, St0) when is_function(F, 2) -> 158 St = map_update_sel(F, St0), 159 St#st{selmode=Mode}. 160 161-spec map_update_sel(Fun, #st{}) -> #st{} when 162 Fun :: fun((InItems, #we{}) -> {#we{},OutItems}), 163 InItems :: item_set(), 164 OutItems :: item_set(). 165 166map_update_sel(F, St0) when is_function(F, 2) -> 167 MF = fun(Sel0, #we{id=Id}=We0, Acc) -> 168 {We,Sel} = F(Sel0, We0), 169 case gb_sets:is_empty(Sel) of 170 true -> {We,Acc}; 171 false -> {We,[{Id,Sel}|Acc]} 172 end 173 end, 174 {St,Sel} = mapfold(MF, [], St0), 175 set(Sel, St). 176 177%% 178%% Map over the selection, modifying the selection. 179%% 180 181-spec update_sel(Fun, Mode, #st{}) -> #st{} when 182 Fun :: fun((InItems, #we{}) -> OutItems), 183 Mode :: mode(), 184 InItems :: item_set(), 185 OutItems :: item_set(). 186 187update_sel(F, Mode, St0) when is_function(F, 2) -> 188 St = update_sel(F, St0), 189 St#st{selmode=Mode}. 190 191-spec update_sel(Fun, #st{}) -> #st{} when 192 Fun :: fun((Items, #we{}) -> Items), 193 Items :: gb_sets:set(item_id()). 194 195update_sel(F, #st{sel=Sel0,shapes=Shapes}=St) when is_function(F, 2) -> 196 Sel = update_sel_1(Sel0, F, Shapes), 197 set(Sel, St). 198 199%% 200%% Map over all objects, modifying the selection. 201%% 202 203-spec update_sel_all(Fun, #st{}) -> #st{} when 204 Fun :: fun((Items, #we{}) -> Items), 205 Items :: gb_sets:set(item_id()). 206 207update_sel_all(F, #st{sel=Sel0,shapes=Shapes}=St) when is_function(F, 2) -> 208 Sel = update_sel_all_1(gb_trees:values(Shapes), Sel0, F), 209 set(Sel, St). 210 211%%% 212%%% Distributed fold over the selection. The Map function 213%%% will be called in process holding the #we{}. The 214%%% Reduce function will be called in the main Wings 215%%% process. 216%%% 217 218-spec dfold(Map, Reduce, Acc0, #st{}) -> Acc1 when 219 Map :: fun((InItems, #we{}) -> Int), 220 Reduce :: fun((Int, AccIn) -> AccOut), 221 InItems :: item_set(), 222 Acc0 :: term(), 223 Acc1 :: term(), 224 AccIn :: term(), 225 AccOut :: term(). 226 227dfold(Map, Reduce, Acc0, St) when is_function(Map, 2), 228 is_function(Reduce, 2) -> 229 #st{sel=Sel,shapes=Shapes} = St, 230 dfold_1(Sel, Map, Reduce, Shapes, Acc0). 231 232dfold_1([{Id,Items}|T], Map, Reduce, Shapes, Acc0) -> 233 We = gb_trees:get(Id, Shapes), 234 ?ASSERT(We#we.id =:= Id), 235 Int = Map(Items, We), 236 Acc = Reduce(Int, Acc0), 237 dfold_1(T, Map, Reduce, Shapes, Acc); 238dfold_1([], _, _, _, Acc) -> Acc. 239 240%%% 241%%% Fold over the selection of objects (not #we{} records). 242%%% 243 244-spec fold_obj(Fun, Acc0, #st{}) -> Acc1 when 245 Fun :: fun((wings_obj:obj(), AccIn) -> AccOut), 246 Acc0 :: term(), 247 Acc1 :: term(), 248 AccIn :: term(), 249 AccOut :: term(). 250 251fold_obj(F, Acc0, #st{sel=Sel}=St) -> 252 FF = fun(#{id:=Id}=Obj, A) -> 253 case keymember(Id, 1, Sel) of 254 false -> A; 255 true -> F(Obj, A) 256 end 257 end, 258 wings_obj:fold(FF, Acc0, St). 259 260%%% 261%%% Fold over the selection. 262%%% 263 264-spec fold(Fun, Acc0, #st{}) -> Acc1 when 265 Fun :: fun((Items, #we{}, AccIn) -> AccOut), 266 Items :: item_set(), 267 Acc0 :: term(), 268 Acc1 :: term(), 269 AccIn :: term(), 270 AccOut :: term(). 271 272fold(F, Acc, #st{sel=Sel,shapes=Shapes}) -> 273 fold_1(F, Acc, Shapes, Sel). 274 275%%% 276%%% Map and fold over the selection. 277%%% 278 279-spec mapfold(Fun, Acc0, #st{}) -> {#st{},Acc1} when 280 Fun :: fun((Items, #we{}, AccIn) -> {#we{},AccOut}), 281 Items :: item_set(), 282 Acc0 :: term(), 283 Acc1 :: term(), 284 AccIn :: term(), 285 AccOut :: term(). 286 287mapfold(F, Acc0, #st{shapes=Shs0,sel=Sel}=St) -> 288 Shs1 = gb_trees:to_list(Shs0), 289 {Shs,Acc} = mapfold_1(F, Acc0, Sel, Shs1, St, []), 290 {St#st{shapes=Shs},Acc}. 291 292%% 293%% Construct a new selection based on all visible geometry. 294%% 295%% Light can only be selected in body mode. 296%% 297 298-spec new_sel(Fun, Mode, #st{}) -> #st{} when 299 Fun :: fun((InItems, #we{}) -> OutItems), 300 Mode :: mode(), 301 InItems :: item_set(), 302 OutItems :: item_set(). 303 304new_sel(F, Mode, #st{shapes=Shapes}=St) when is_function(F, 2) -> 305 Sel0 = gb_trees:values(Shapes), 306 Sel = new_sel_1(Sel0, F, Mode), 307 St#st{selmode=Mode,sel=Sel}. 308 309%% make(Filter, Mode, St) -> [{Id,ItemGbSet}]. 310%% Mode = body|face|edge|vertex 311%% Filter(Item, We) -> true|false 312%% Item = face() | edge() | vertex() | 0 313%% Construct a selection by calling Filter(Item, We) for each 314%% item in each object (where Item is either a face number, 315%% edge number, vertex number, or 0 depending on Mode). 316%% 317 318-spec make(Fun, Mode, #st{}) -> #st{} when 319 Fun :: fun((Items, #we{}) -> boolean()), 320 Mode :: mode(), 321 Items :: item_set(). 322 323make(Filter, Mode, St) when is_function(Filter, 2) -> 324 new_sel(fun(Sel, We) -> 325 gb_sets:filter(fun(Item) -> Filter(Item, We) end, Sel) 326 end, Mode, St). 327 328 329%% 330%% Clone the selection. 331%% 332 333-type suffix() :: 'cut' | 'clone' | 'copy' | 'extract' | 'mirror' | 'sep'. 334-type clone_item() :: {#we{},item_set(),suffix()}. 335-type clone_out() :: {#we{},item_set(),[clone_item()]}. 336-type clone_fun() :: fun((item_set(), #we{}) -> clone_out()). 337 338-spec clone(Fun, #st{}) -> #st{} when 339 Fun :: clone_fun(). 340 341clone(F, St0) -> 342 MF = fun(Items, We, Acc) -> 343 clone_fun(F, Items, We, Acc) 344 end, 345 #st{sel=Sel} = St = fold(MF, St0#st{sel=[]}, St0), 346 St#st{sel=sort(Sel)}. 347 348-spec clone(Fun, Mode, #st{}) -> #st{} when 349 Fun :: clone_fun(), 350 Mode :: mode(). 351 352clone(Fun, Mode, St0) -> 353 St = clone(Fun, St0), 354 St#st{selmode=Mode}. 355 356%% 357%% Combine selected objects to one. 358%% 359 360-spec combine(Fun, #st{}) -> #st{} when 361 Fun :: fun((item_set(), #we{}) -> {#we{},item_set()}). 362 363combine(F, St) -> 364 MF = fun(Wes, Sel, Mode) -> 365 Zipped = combine_zip(Wes, Sel, Mode), 366 {We,Items0} = wings_we:merge_root_set(Zipped), 367 Items1 = combine_items(Mode, Items0), 368 Items = gb_sets:from_ordset(Items1), 369 F(Items, We) 370 end, 371 comb_merge(MF, St). 372 373-spec combine(Fun, Mode, #st{}) -> #st{} when 374 Fun :: fun((item_set(), #we{}) -> {#we{},item_set()}), 375 Mode :: mode(). 376 377combine(F, Mode, St0) -> 378 St = combine(F, St0), 379 St#st{selmode=Mode}. 380 381-spec merge(Fun, #st{}) -> #st{} when 382 Fun :: fun(({#we{},item_set()}) -> {#we{},item_set()}). 383 384merge(F, St) when is_function(F, 1) -> 385 MF = fun(Wes, Sel, _Mode) -> 386 Zipped = merge_zip(Wes, Sel), 387 F(Zipped) 388 end, 389 comb_merge(MF, St). 390 391%%% 392%%% Calculate the center for all selected objects. 393%%% 394 395-spec center(#st{}) -> e3d_vec:vector(). 396 397center(#st{selmode=Mode}=St) -> 398 MF = fun(Items, We) -> 399 Vs = to_vertices(Mode, Items, We), 400 wings_vertex:center(Vs, We) 401 end, 402 RF = fun(V, {N,Acc}) -> {N+1,e3d_vec:add(V, Acc)} end, 403 {N,Sum} = dfold(MF, RF, {0,e3d_vec:zero()}, St), 404 e3d_vec:divide(Sum, N). 405 406 407%%% 408%%% Calculate the center for all selected vertices. 409%%% 410 411-spec center_vs(#st{}) -> e3d_vec:vector(). 412 413center_vs(#st{selmode=Mode}=St) -> 414 MF = fun(Items, We) -> 415 Vs = to_vertices(Mode, Items, We), 416 N = length(Vs), 417 Center = wings_vertex:center(Vs, We), 418 {N,e3d_vec:mul(Center, float(N))} 419 end, 420 RF = fun({N0,C0}, {N1,C1}) -> {N0+N1,e3d_vec:add(C0, C1)} end, 421 {N,Sum} = dfold(MF, RF, {0,e3d_vec:zero()}, St), 422 e3d_vec:divide(Sum, N). 423 424%% Calculate center of bounding box. 425 426-spec bbox_center(#st{}) -> e3d_vec:vector(). 427 428bbox_center(St) -> 429 BBox = bounding_box(St), 430 e3d_vec:average(BBox). 431 432%%% 433%%% Calculate the bounding-box for the selection. 434%%% 435 436-spec bounding_box(#st{}) -> [e3d_vec:vector()] | 'none'. 437 438bounding_box(#st{selmode=Mode}=St) -> 439 MF = fun(Items, We) -> 440 Vs = to_vertices(Mode, Items, We), 441 wings_vertex:bounding_box(Vs, We) 442 end, 443 RF = fun(Box, none) -> Box; 444 (Box, Acc) -> e3d_vec:bounding_box(Box++Acc) 445 end, 446 dfold(MF, RF, none, St). 447 448%%% 449%%% Divide the face selection into regions where each face shares at least 450%%% one edge with another face in the same region. Two faces can share a 451%%% vertex without necessarily being in the same region. 452%%% 453 454-spec face_regions(Faces, #we{}) -> [face_set()] when 455 Faces :: face_set() | [visible_face_num()]. 456 457face_regions(Faces, We) when is_list(Faces) -> 458 face_regions_1(gb_sets:from_list(Faces), We); 459face_regions(Faces, We) -> 460 face_regions_1(Faces, We). 461 462%%% 463%%% Divide the face selection into regions where each face shares at least 464%%% one vertex with another face in the same region. 465%%% 466 467-spec strict_face_regions(Faces, #we{}) -> [face_set()] when 468 Faces :: face_set() | [visible_face_num()]. 469 470strict_face_regions(Faces, We) when is_list(Faces) -> 471 find_strict_face_regions(gb_sets:from_list(Faces), We, []); 472strict_face_regions(Faces, We) -> 473 find_strict_face_regions(Faces, We, []). 474 475%%% 476%%% Here we want to divide the selection into regions of connected edges. 477%%% We use a standard working-set algorithm. 478%%% 479 480-spec edge_regions(Edges, #we{}) -> [edge_set()] when 481 Edges :: edge_set() | [edge_num()]. 482 483edge_regions(Edges, We) when is_list(Edges) -> 484 find_edge_regions(gb_sets:from_list(Edges), We, []); 485edge_regions(Edges, We) -> 486 find_edge_regions(Edges, We, []). 487 488%%% 489%%% Validate selection. 490%%% 491 492-spec valid_sel(#st{}) -> #st{}. 493 494valid_sel(#st{sel=Sel,selmode=Mode}=St) -> 495 St#st{sel=valid_sel(Sel, Mode, St)}. 496 497-spec valid_sel(SelIn, Mode, #st{}) -> SelOut when 498 Mode :: mode(), 499 SelIn :: [{item_id(),item_set()}], 500 SelOut :: [{item_id(),item_set()}]. 501 502valid_sel(Sel0, Mode, #st{shapes=Shapes}) -> 503 Sel = foldl( 504 fun({Id,Items0}, A) -> 505 case gb_trees:lookup(Id, Shapes) of 506 none -> A; 507 {value,#we{perm=Perm}} when ?IS_NOT_SELECTABLE(Perm) -> 508 A; 509 {value,We} -> 510 Items = validate_items(Items0, Mode, We), 511 case gb_sets:is_empty(Items) of 512 false -> [{Id,Items}|A]; 513 true -> A 514 end 515 end 516 end, [], Sel0), 517 reverse(Sel). 518 519-spec valid_sel_groups(SelIn, Mode, #st{}) -> SelOut when 520 Mode :: mode(), 521 SelIn :: [{item_id(),item_set()}], 522 SelOut :: [{item_id(),item_set()}]. 523 524valid_sel_groups(Sel0, Mode, #st{shapes=Shapes}) -> % allow wings to save any selection groups 525 Sel = foldl( 526 fun({Id,Items0}, A) -> 527 case gb_trees:lookup(Id, Shapes) of 528 none -> A; 529 {value,We} -> 530 Items = validate_items(Items0, Mode, We), 531 case gb_sets:is_empty(Items) of 532 false -> [{Id,Items}|A]; 533 true -> A 534 end 535 end 536 end, [], Sel0), 537 reverse(Sel). 538 539-spec select_object(obj_id(), #st{}) -> #st{}. 540 541select_object(Id, #st{selmode=Mode,sel=Sel0}=St) -> 542 case keymember(Id, 1, Sel0) of 543 true -> St; 544 false -> 545 Sel = sort([{Id,get_all_items(Mode, Id, St)}|Sel0]), 546 St#st{sel=Sel} 547 end. 548 549-spec deselect_object(obj_id(), #st{}) -> #st{}. 550 551deselect_object(Id, #st{sel=Sel0}=St) -> 552 Sel = keydelete(Id, 1, Sel0), 553 St#st{sel=Sel}. 554 555-spec inverse_items(Mode, InItems, #we{}) -> OutItems when 556 Mode :: mode(), 557 InItems :: item_set(), 558 OutItems :: item_set(). 559 560inverse_items(Mode, Elems, We) -> 561 gb_sets:difference(get_all_items(Mode, We), Elems). 562 563-spec get_all_items(mode(), obj_id(), #st{}) -> item_set(). 564 565get_all_items(Mode, Id, #st{shapes=Shapes}) -> 566 We = gb_trees:get(Id, Shapes), 567 get_all_items(Mode, We). 568 569-spec to_vertices(Mode, Items, #we{}) -> Vertices when 570 Mode :: mode(), 571 Items :: item_set(), 572 Vertices :: [vertex_num()]. 573 574to_vertices(vertex, Vs, _) -> 575 gb_sets:to_list(Vs); 576to_vertices(face, Faces, We) -> 577 wings_face:to_vertices(Faces, We); 578to_vertices(edge, Edges, We) -> 579 wings_edge:to_vertices(Edges, We); 580to_vertices(body, _, #we{vp=Vtab}) -> 581 wings_util:array_keys(Vtab). 582 583%%% 584%%% Local functions. 585%%% 586 587map_1(F, [{Id,Items}|Sel], [{Id,We0}|Shs], St, Acc) -> 588 ?ASSERT(We0#we.id =:= Id), 589 #we{es=Etab} = We = F(Items, We0), 590 case wings_util:array_is_empty(Etab) of 591 true -> map_1(F, Sel, Shs, St, Acc); 592 false -> map_1(F, Sel, Shs, St, [{Id,We}|Acc]) 593 end; 594map_1(F, [_|_]=Sel, [Pair|Shs], St, Acc) -> 595 map_1(F, Sel, Shs, St, [Pair|Acc]); 596map_1(_F, [], Shs, _St, Acc) -> 597 gb_trees:from_orddict(reverse(Acc, Shs)). 598 599update_sel_1([{Id,Sel0}|T], F, Shapes) -> 600 We = gb_trees:get(Id, Shapes), 601 ?ASSERT(We#we.id =:= Id), 602 Sel = F(Sel0, We), 603 case gb_sets:is_empty(Sel) of 604 false -> 605 [{Id,Sel}|update_sel_1(T, F, Shapes)]; 606 true -> 607 update_sel_1(T, F, Shapes) 608 end; 609update_sel_1([], _, _) -> []. 610 611update_sel_all_1([#we{id=Id}=We|Wes], [{Id,Items0}|Sel], F) -> 612 Items = F(Items0, We), 613 case gb_sets:is_empty(Items) of 614 false -> 615 [{Id,Items}|update_sel_all_1(Wes, Sel, F)]; 616 true -> 617 update_sel_all_1(Wes, Sel, F) 618 end; 619update_sel_all_1([#we{id=Id,perm=P}|Wes]=Wes0, Sel, F) -> 620 if 621 ?IS_SELECTABLE(P) -> 622 update_sel_all_1(Wes0, [{Id,gb_sets:empty()}|Sel], F); 623 true -> 624 update_sel_all_1(Wes, Sel, F) 625 end; 626update_sel_all_1([], _, _) -> []. 627 628fold_1(F, Acc0, Shapes, [{Id,Items}|T]) -> 629 We = gb_trees:get(Id, Shapes), 630 ?ASSERT(We#we.id =:= Id), 631 fold_1(F, F(Items, We, Acc0), Shapes, T); 632fold_1(_F, Acc, _Shapes, []) -> Acc. 633 634mapfold_1(F, Acc0, [{Id,Items}|Sel], [{Id,We0}|Shs], St, ShsAcc) -> 635 ?ASSERT(We0#we.id =:= Id), 636 {#we{es=Etab}=We,Acc} = F(Items, We0, Acc0), 637 case wings_util:array_is_empty(Etab) of 638 true -> mapfold_1(F, Acc0, Sel, Shs, St, ShsAcc); 639 false -> mapfold_1(F, Acc, Sel, Shs, St, [{Id,We}|ShsAcc]) 640 end; 641mapfold_1(F, Acc, [_|_]=Sel, [Pair|Shs], St, ShsAcc) -> 642 mapfold_1(F, Acc, Sel, Shs, St, [Pair|ShsAcc]); 643mapfold_1(_F, Acc, [], Shs, _St, ShsAcc) -> 644 {gb_trees:from_orddict(reverse(ShsAcc, Shs)),Acc}. 645 646new_sel_1([#we{perm=Perm}|Shs], F, Mode) when ?IS_NOT_SELECTABLE(Perm) -> 647 new_sel_1(Shs, F, Mode); 648new_sel_1([We|Shs], F, Mode) when ?IS_LIGHT(We), Mode =/= body -> 649 new_sel_1(Shs, F, Mode); 650new_sel_1([#we{id=Id}=We|Shs], F, Mode) -> 651 Sel0 = get_all_items(Mode, We), 652 Sel = F(Sel0, We), 653 case gb_sets:is_empty(Sel) of 654 false -> 655 [{Id,Sel}|new_sel_1(Shs, F, Mode)]; 656 true -> 657 new_sel_1(Shs, F, Mode) 658 end; 659new_sel_1([], _, _) -> []. 660 661clone_fun(F, Items0, #we{id=Id}=We0, #st{shapes=Shs0}=St0) -> 662 {We,Items,New} = F(Items0, We0), 663 Shs = gb_trees:update(Id, We, Shs0), 664 St1 = St0#st{shapes=Shs}, 665 St = clone_add_sel(Items, Id, St1), 666 clone_fun_add(New, St). 667 668clone_fun_add([{#we{name=Name0}=We0,Items,Suffix}|T], 669 #st{onext=Id,shapes=Shs0}=St0) -> 670 Name = new_name(Name0, Suffix, Id), 671 We = We0#we{id=Id,name=Name}, 672 Shs = gb_trees:insert(Id, We, Shs0), 673 St1 = St0#st{shapes=Shs,onext=Id+1}, 674 St = clone_add_sel(Items, Id, St1), 675 clone_fun_add(T, St); 676clone_fun_add([], St) -> St. 677 678clone_add_sel(Items, Id, #st{sel=Sel}=St) -> 679 case gb_sets:is_empty(Items) of 680 false -> St#st{sel=[{Id,Items}|Sel]}; 681 true -> St 682 end. 683 684new_name(OldName, Suffix0, Id) -> 685 Suffix = suffix(Suffix0), 686 Base = base(reverse(OldName)), 687 reverse(Base, "_" ++ Suffix ++ integer_to_list(Id)). 688 689%% Note: Filename suffixes are intentionally not translated. 690%% If we are to translate them in the future, base/1 below 691%% must be updated to strip suffixes (both for the current language 692%% and for English). 693 694suffix(cut) -> "cut"; 695suffix(clone) -> "clone"; 696suffix(copy) -> "copy"; 697suffix(extract) -> "extract"; 698suffix(mirror) -> "mirror"; 699suffix(sep) -> "sep". 700 701%% base_1(ReversedName) -> ReversedBaseName 702%% Given an object name, strip digits and known suffixes to 703%% create a base name. Returns the unchanged name if 704%% no known suffix could be stripped. 705 706base(OldName) -> 707 case base_1(OldName) of 708 error -> OldName; 709 Base -> Base 710 end. 711 712base_1([H|T]) when $0 =< H, H =< $9 -> base_1(T); 713base_1("tuc_"++Base) -> Base; %"_cut" 714base_1("enolc_"++Base) -> Base; %"_clone" 715base_1("ypoc_"++Base) -> Base; %"_copy" 716base_1("tcartxe_"++Base) -> Base; %"_extract" 717base_1("rorrim_"++Base) -> Base; %"_mirror" 718base_1("pes_"++Base) -> Base; %"_sep" 719base_1(_Base) -> error. 720 721comb_merge(MF, #st{shapes=Shs0,selmode=Mode,sel=[{Id,_}|_]=Sel0}=St) -> 722 Shs1 = sofs:from_external(gb_trees:to_list(Shs0), [{id,object}]), 723 Sel1 = sofs:from_external(Sel0, [{id,dummy}]), 724 Sel2 = sofs:domain(Sel1), 725 {Wes0,Shs2} = sofs:partition(1, Shs1, Sel2), 726 Wes = sofs:to_external(sofs:range(Wes0)), 727 {We0,Items} = MF(Wes, Sel0, Mode), 728 We = case lists:usort([wings_obj:get_folder(We) || We <- Wes]) of 729 [Folder] -> wings_obj:set_folder(Folder, We0); 730 _ -> We0 %% From different folders add to root folder 731 end, 732 Shs = gb_trees:from_orddict(sort([{Id,We}|sofs:to_external(Shs2)])), 733 Sel = case gb_sets:is_empty(Items) of 734 true -> []; 735 false -> [{Id,Items}] 736 end, 737 St#st{shapes=Shs,sel=Sel}. 738 739combine_zip([#we{id=Id}=We|Wes], [{Id,Items0}|Sel], Mode) -> 740 RootSet = combine_root_set(Mode, Items0), 741 [{We,RootSet}|combine_zip(Wes, Sel, Mode)]; 742combine_zip([], [], _) -> []. 743 744combine_root_set(body, _Items) -> 745 []; 746combine_root_set(Mode, Items) -> 747 [{Mode,Item} || Item <- gb_sets:to_list(Items)]. 748 749combine_items(body, _RootSet) -> 750 [0]; 751combine_items(_, RootSet) -> 752 ordsets:from_list([Item || {_,Item} <- RootSet]). 753 754merge_zip([#we{id=Id}=We|Wes], [{Id,Items}|Sel]) -> 755 [{We,Items}|merge_zip(Wes, Sel)]; 756merge_zip([], []) -> []. 757 758 759face_regions_1(Faces, We) -> 760 find_face_regions(Faces, We, fun collect_face_fun/5, []). 761 762find_face_regions(Faces0, We, Coll, Acc) -> 763 case gb_sets:is_empty(Faces0) of 764 true -> Acc; 765 false -> 766 {Face,Faces1} = gb_sets:take_smallest(Faces0), 767 Ws = [Face], 768 {Reg,Faces} = collect_face_region(Ws, We, Coll, [], Faces1), 769 find_face_regions(Faces, We, Coll, [Reg|Acc]) 770 end. 771 772collect_face_region([_|_]=Ws0, We, Coll, Reg0, Faces0) -> 773 Reg = Ws0++Reg0, 774 {Ws,Faces} = wings_face:fold_faces(Coll, {[],Faces0}, Ws0, We), 775 collect_face_region(Ws, We, Coll, Reg, Faces); 776collect_face_region([], _, _, Reg, Faces) -> 777 {gb_sets:from_list(Reg),Faces}. 778 779collect_face_fun(Face, _, _, Rec, {Ws,Faces}=A) -> 780 Of = case Rec of 781 #edge{lf=Face,rf=Of0} -> Of0; 782 #edge{rf=Face,lf=Of0} -> Of0 783 end, 784 case gb_sets:is_member(Of, Faces) of 785 true -> {[Of|Ws],gb_sets:delete(Of, Faces)}; 786 false -> A 787 end. 788 789find_strict_face_regions(Faces0, We, Acc) -> 790 case gb_sets:is_empty(Faces0) of 791 true -> Acc; 792 false -> 793 {Face,Faces1} = gb_sets:take_smallest(Faces0), 794 Ws = gb_sets:singleton(Face), 795 {Reg,Faces} = collect_strict_face_region(Ws, We, [], Faces1), 796 find_strict_face_regions(Faces, We, [Reg|Acc]) 797 end. 798 799collect_strict_face_region(Ws0, We, Reg0, Faces0) -> 800 case gb_sets:is_empty(Ws0) of 801 true -> {gb_sets:from_list(Reg0),Faces0}; 802 false -> 803 {Face,Ws1} = gb_sets:take_smallest(Ws0), 804 Reg = [Face|Reg0], 805 {Ws,Faces} = collect_strict_adj_sel(Face, We, Ws1, Faces0), 806 collect_strict_face_region(Ws, We, Reg, Faces) 807 end. 808 809collect_strict_adj_sel(Face, We, Ws0, Faces0) -> 810 wings_face:fold( 811 fun(V, _, _, A0) -> 812 wings_vertex:fold( 813 fun(_, Of, _, {W0,F0}=A1) -> 814 case gb_sets:is_member(Of, F0) of 815 true -> {gb_sets:insert(Of, W0),gb_sets:delete(Of, F0)}; 816 false -> A1 817 end 818 end, A0, V, We) 819 end, {Ws0,Faces0}, Face, We). 820 821find_edge_regions(Edges0, We, Acc) -> 822 case gb_sets:is_empty(Edges0) of 823 true -> Acc; 824 false -> 825 {Edge,Edges1} = gb_sets:take_smallest(Edges0), 826 Ws = gb_sets:singleton(Edge), 827 {Reg,Edges} = find_all_adj_edges(Ws, We, [], Edges1), 828 find_edge_regions(Edges, We, [Reg|Acc]) 829 end. 830 831find_all_adj_edges(Ws0, #we{es=Etab}=We, Reg0, Edges0) -> 832 case gb_sets:is_empty(Ws0) of 833 true -> {gb_sets:from_list(Reg0),Edges0}; 834 false -> 835 {Edge,Ws1} = gb_sets:take_smallest(Ws0), 836 Reg = [Edge|Reg0], 837 #edge{vs=Va,ve=Vb} = array:get(Edge, Etab), 838 Adj0 = add_adjacent_edges(Va, We, []), 839 Adj1 = add_adjacent_edges(Vb, We, Adj0), 840 Adj = gb_sets:from_list(Adj1), 841 AdjSel = gb_sets:intersection(Adj, Edges0), 842 Ws = gb_sets:union(Ws1, AdjSel), 843 Edges = gb_sets:difference(Edges0, AdjSel), 844 find_all_adj_edges(Ws, We, Reg, Edges) 845 end. 846 847add_adjacent_edges(V, We, Acc) -> 848 wings_vertex:fold(fun(Edge, _, _, A) -> [Edge|A] end, Acc, V, We). 849 850validate_items(Items, body, _We) -> Items; 851validate_items(Items, Mode, We) -> 852 gb_sets:intersection(Items, get_all_items(Mode, We)). 853 854get_all_items(vertex, We) -> 855 gb_sets:from_ordset(wings_we:visible_vs(We)); 856get_all_items(edge, We) -> 857 gb_sets:from_ordset(wings_we:visible_edges(We)); 858get_all_items(face, We) -> 859 gb_sets:from_ordset(wings_we:visible(We)); 860get_all_items(body, _) -> 861 gb_sets:singleton(0). 862