1%% 2%% wings_tweak.erl -- 3%% 4%% A rewrite of wpc_tweak.erl to add Tweak into the Wings core. 5%% 6%% Copyright (c) 2009-2011 Richard Jones 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%% 12 13-module(wings_tweak). 14 15-export([init/0,command/2, help_msg/0]). 16-export([tweak_event/2,menu/2,tweak_keys_info/0,tweak_disabled_msg/0, 17 tweak_info_line/0,tweak_magnet_help/0,statusbar/0]). 18 19-export([toggle_draw/1,point_center/3]). 20-export([update_dlist/3,draw/5,get_data/3]). 21 22-export([tweak_keys/0, menu/0, tweak_magnet_menu/0, constraints_menu/0]). %% For wings_tweak_win only 23 24-export_type([drag/0]). 25 26-define(NEED_OPENGL, 1). 27-define(NEED_ESDL, 1). 28 29-define(L_ALT, 307). 30-define(R_ALT, 308). 31 32-include("wings.hrl"). 33-include_lib("e3d/e3d.hrl"). 34 35-import(lists,[member/2,foldl/3]). 36 37%%% 38%%% Main Tweak Records 39%%% 40 41-record(tweak, 42 {mode, % current tweak tool 43 magnet, % true|false 44 mag_type, % magnet type: Type 45 mag_rad, % magnet influence radius 46 id, % {Id,Elem} mouse was over when tweak began 47 sym, % current magnet radius adjustment hotkey 48 ox,oy, % original X,Y 49 x,y, % current X,Y 50 cx,cy, % Calculated X,Y 51 warp, % true or size limits 52 clk=none, % click selection/deselection 53 st}). % wings st record (working) 54 55-record(drag, 56 {vs, 57 pos0, % Original position. 58 pos, % Current position. 59 pst=none, % Any data that a specific tweak tool needs stored 60 % temporarily 61 mag, % mag record 62 mm}). % original|mirror 63 64-record(mag, 65 {orig, % Orig centre of the selection being moved 66 vs, % [{V,Pos,Distance,Influence}] 67 vtab=[]}). % [{V,Pos}] (latest) 68 69-type drag() :: #drag{}. 70 71%%% 72%%% Set Default Tweak prefs 73%%% 74 75init() -> 76 set_default_tweak_keys(), 77 TweakMagnet = {true,dome,1.0}, %{magnet on, magnet type, magnet radius} 78 wings_pref:set_default(tweak_active,false), 79 wings_pref:set_default(tweak_magnet, TweakMagnet), 80 wings_pref:set_default(tweak_xyz, [false,false,false]), 81 wings_pref:set_default(tweak_axis, screen), 82 wings_pref:set_default(tweak_point, none), 83 wings_pref:set_default(tweak_click_speed, 300000), 84 wings_pref:set_default(tweak_mag_adj_sensitivity, 0.01), 85 wings_pref:set_default(tweak_magnet_color, {0.0, 0.0, 1.0, 0.06}), 86 wings_pref:set_default(tweak_geo_point, none), 87 wings_pref:set_default(tweak_radial, false), 88 wings_pref:set_default(tweak_vector_size, 0.5), 89 wings_pref:set_default(tweak_vector_width, 2.0), 90 wings_pref:set_default(tweak_vector_color, {1.0,0.5,0.0}), 91 wings_pref:set_default(tweak_speed, 0.5), %% control vs speed setting 92 wings_pref:set_default(tweak_axis_toggle, []), 93 wings_pref:set_default(tweak_magnet_influence, true), 94 95 %% Delete Old Prefs 96 wings_pref:delete_value(tweak_draw), 97 wings_pref:delete_value(tweak_help), 98 wings_pref:delete_value(tweak_magnet_colour), 99 wings_pref:delete_value(tweak_sb_clears_constraints), 100 wings_pref:delete_value(tweak_single_click), 101 true. 102 103%%% 104%%% Default Tweak Keys 105%%% 106 107set_default_tweak_keys() -> 108 Cam = wings_pref:get_value(camera_mode), 109 %% Set Default tweak keys according to the camera mode 110 case wings_pref:get_value(tweak_prefs) of 111 {Cam,Prefs0} -> 112 case check_tweak_prefs(Prefs0) of 113 [] -> set_tweak_keys(Cam); 114 Prefs -> 115 wings_pref:set_value(tweak_prefs,{Cam,Prefs}) 116 end; 117 _ -> 118 set_tweak_keys(Cam) 119 end. 120 121set_tweak_keys(Cam) -> 122 %% Set Default tweak keys according to the camera mode 123 TweakKeys = default_tweak_keys(), 124 wings_pref:set_value(tweak_prefs,{Cam,TweakKeys}), 125 wings_wm:dirty(), 126 wings_wm:send({tweak,tweak_palette}, update_palette), 127 TweakKeys. 128 129default_tweak_keys() -> 130 %% This is the format {{MouseButton, {Crtl, Shift, Alt}}, TweakMode} 131 F = false, 132 D = [{{1,{F,F,F}}, move}], 133 orddict:from_list(D). 134 135tweak_keys() -> 136 Cam = wings_pref:get_value(camera_mode), 137 case wings_pref:get_value(tweak_prefs) of 138 {Cam,Keys} -> Keys; 139 _ -> set_tweak_keys(Cam) 140 end. 141 142check_tweak_prefs([{{N,{A,B,C}},Mode}=P|Prefs]) -> 143 Check1 = is_integer(N), 144 Check2 = is_atom(A) andalso is_atom(B) andalso is_atom(C), 145 Check3 = member(Mode, [move,move_normal,scale,scale_uniform,relax,slide]), 146 case Check1 andalso Check2 andalso Check3 of 147 true -> [P|check_tweak_prefs(Prefs)]; 148 false -> check_tweak_prefs(Prefs) 149 end; 150check_tweak_prefs([]) -> []. 151 152%%% 153%%% Check for Tweak Events 154%%% 155 156tweak_event(Ev, St) -> 157 case wings_pref:get_value(tweak_active) of 158 true -> tweak_event_handler(Ev, St); 159 false -> next 160 end. 161 162%%% Mouse Buttons 163tweak_event_handler(#mousebutton{button=B,x=X,y=Y,mod=Mod,state=?SDL_PRESSED}, St) 164 when B < 4 -> 165 Cam = wings_pref:get_value(camera_mode), 166 case wings_pref:get_value(tweak_prefs) of 167 {Cam,TweakKeys} -> 168 Ctrl = Mod band ?CTRL_BITS =/= 0, 169 Shift = Mod band ?SHIFT_BITS =/= 0, 170 Alt = Mod band ?ALT_BITS =/= 0, 171 case orddict:find({B,{Ctrl,Shift,Alt}}, TweakKeys) of 172 {ok, Mode} -> 173 {Mag,MagType,MagR} = wings_pref:get_value(tweak_magnet), 174 Warp = case wings_pref:get_value(no_warp, false) of 175 false -> true; 176 true -> wings_wm:win_size() 177 end, 178 T = #tweak{mode=Mode,warp=Warp,ox=X,oy=Y,x=X,y=Y, 179 magnet=Mag,mag_type=MagType,mag_rad=MagR,st=St}, 180 handle_tweak_event_1(T); 181 error -> next 182 end; 183 _ -> 184 set_tweak_keys(Cam), 185 next 186 end; 187 188%%% Keyboard hits 189tweak_event_handler(#keyboard{sym=Sym,mod=Mod,state=?SDL_PRESSED}=Ev,St) -> 190 {Mag,MagType,MagR} = wings_pref:get_value(tweak_magnet), 191 case wings_hotkey:event(Ev,St#st{sel=[]}) of 192 {tweak,{tweak_magnet,mag_adjust}} when Mag -> 193 T = #tweak{magnet=Mag,mag_type=MagType,mag_rad=MagR,sym=Sym,st=St}, 194 magnet_adjust(T); 195 {tweak,{axis_constraint,Axis}} -> 196 Pressed = wings_pref:get_value(tweak_axis_toggle), 197 case lists:keymember(Sym, 1, Pressed) of 198 true -> keep; 199 false -> 200 ReturnAxis = toggle_data(Axis), 201 wings_pref:set_value(tweak_axis_toggle,[{Sym,ReturnAxis,os:timestamp()}|Pressed]), 202 wings_io:change_event_handler(?SDL_KEYUP, true), 203 toggle_axis(Axis), 204 wings_wm:dirty(), 205 wings_wm:send({tweak,axis_constraint}, update_palette), 206 keep 207 end; 208 next when Mag -> 209 case magnet_has_hotkey() of 210 true -> next; 211 false -> 212 case is_altkey_magnet_event(Sym,Mod) of 213 true -> 214 T = #tweak{magnet=Mag,mag_type=MagType,mag_rad=MagR,sym=Sym,st=St}, 215 magnet_adjust(T); 216 false -> next 217 end 218 end; 219 _ -> next 220 end; 221tweak_event_handler(#keyboard{sym=Sym,state=?SDL_RELEASED},_St) -> 222 Pressed0 = wings_pref:get_value(tweak_axis_toggle), 223 case lists:keytake(Sym,1,Pressed0) of 224 {value,{Sym,Axis,PressTime},Pressed} -> 225 ClickSpeed = wings_pref:get_value(tweak_click_speed), 226 case timer:now_diff(os:timestamp(), PressTime) > ClickSpeed of 227 true -> 228 toggle_axis(Axis), 229 wings_wm:dirty(), 230 wings_wm:send({tweak,axis_constraint}, update_palette); 231 false -> ok 232 end, 233 wings_pref:set_value(tweak_axis_toggle,Pressed), 234 case Pressed of 235 [] -> 236 wings_io:change_event_handler(?SDL_KEYUP, false), 237 keep; 238 _ -> keep 239 end; 240 false -> keep 241 end; 242 243tweak_event_handler(lost_focus,_) -> 244 wings_pref:set_value(tweak_axis_toggle,[]), 245 wings_io:change_event_handler(?SDL_KEYUP, false), 246 next; 247tweak_event_handler(_,_) -> 248 next. 249 250%%% 251%%% Start Tweak 252%%% 253 254handle_tweak_event_1(#tweak{x=X,y=Y, st=#st{sel=Sel}=St0}=T) -> 255 case wings_pick:do_pick(X,Y,St0) of 256 {add, What, St} when Sel =:= [] -> 257 from_element_point(X,Y,St0), 258 tweak_handler_setup(add, What, St, T); 259 {add, What, St} -> 260 from_element_point(X,Y,St), 261 tweak_handler_setup(add, What, St, T); 262 {delete, What, _} -> 263 from_element_point(X,Y,St0), 264 tweak_handler_setup(delete, What, St0, T); 265 none -> 266 next 267 end. 268 269tweak_handler_setup(Action, {Id,Elem,_}=What, St, T0) -> 270 IdElem = {Id,[Elem]}, 271 T = T0#tweak{id={Action,IdElem},cx=0,cy=0}, 272 {seq,push,initiate_tweak_handler(What, St, T)}. 273 274 275%%% 276%%% Initial Event Handler 277%%% 278 279%% Basically we want to wait for a mouse button release which sygnifies 280%% a Pick Event. If anything else happens go into the actual tweak handler. 281initiate_tweak_handler(What, St, T) -> 282 {replace,fun(Ev) -> 283 handle_initial_event(Ev, What, St, T) end}. 284 285handle_initial_event(redraw, What, St, T) -> 286 wings_draw:refresh_dlists(St), 287 wings:redraw(St), 288 initiate_tweak_handler(What, St, T); 289handle_initial_event(#mousebutton{button=1,state=?SDL_RELEASED}, What, #st{shapes=Shs,sel=Sel0}=St0, 290 #tweak{id={Action,{Id,[Elem]}},clk=none,x=X,y=Y}=T) -> 291 case wings_io:is_grabbed() of 292 false -> ok; 293 true -> wings_io:ungrab(X,Y) 294 end, 295 St = case Action of 296 add -> St0; 297 delete -> 298 We = gb_trees:get(Id, Shs), 299 case orddict:find(Id, Sel0) of 300 _ when ?IS_LIGHT(We) -> 301 Sel = orddict:erase(Id, Sel0), 302 St0#st{sel=Sel}; 303 {ok,Sel1} -> 304 case gb_sets:size(Sel1) of 305 1 -> 306 Sel = orddict:erase(Id, Sel0), 307 St0#st{sel=Sel}; 308 _ -> 309 Sel2 = gb_sets:delete(Elem, Sel1), 310 Sel = orddict:store(Id, Sel2, Sel0), 311 St0#st{sel=Sel} 312 end; 313 error -> 314 Sel = orddict:store(gb_sets:singleton(Elem), Id, Sel0), 315 St0#st{sel=Sel} 316 end 317 end, 318 wings_wm:current_state(St), 319 wings_wm:dirty(), 320 initiate_tweak_handler(What, St, T#tweak{clk={one,os:timestamp()}}); 321handle_initial_event(#mousebutton{button=1,x=X0,y=Y0,state=?SDL_PRESSED}=Ev, 322 _What, St, #tweak{clk={one,Clk},x=X,y=Y,st=TweakSt}) -> 323 case timer:now_diff(os:timestamp(),Clk) < wings_pref:get_value(tweak_click_speed) of 324 true -> 325 wings_pick:paint_pick(X0, Y0, TweakSt); 326 false -> 327 Window = wings_wm:this(), 328 wings_wm:send_after_redraw(Window, Ev#mousebutton{x=X,y=Y}), 329 wings_wm:later({new_state,St}), 330 pop 331 end; 332handle_initial_event({new_state,St}, _, _, _) -> 333 %% this is the exiting event from wings_pick after paint_pick/3 334 wings_wm:later({new_state,St}), 335 pop; 336handle_initial_event(#mousemotion{x=X,y=Y}=Ev, What, St, 337 #tweak{x=OX,y=OY,cx=CX,cy=CY,clk=Clk}=T0) -> 338 DX = X-OX, %since last move X 339 DY = Y-OY, %since last move Y 340 DxOrg = DX+CX, %total X 341 DyOrg = DY+CY, %total Y 342 Total = math:sqrt(DxOrg * DxOrg + DyOrg * DyOrg), 343 T = mouse_warp(X,Y,T0), 344 case Total > 3 of 345 true when Clk =:= none -> 346 enter_tweak_handler(Ev, What, St, T); 347 true -> 348 wings_wm:later({new_state,St}), 349 pop; 350 false -> 351 initiate_tweak_handler(What, St, T#tweak{cx=DxOrg,cy=DyOrg}) 352 end; 353handle_initial_event(Ev, _, St, #tweak{clk={one,_}}) -> 354 wings_wm:send_after_redraw(geom,Ev), 355 wings_wm:later({new_state,St}), 356 pop; 357handle_initial_event(#keyboard{sym=Sym,mod=Mod}=Ev, What, St, #tweak{x=X,y=Y}=T) 358 when Mod band (?ALT_BITS bor ?SHIFT_BITS bor ?CTRL_BITS) =:= 0 -> 359 %% Activate Tweak Camera 360 case wings_camera:tweak_camera_event(Sym, X, Y, St) of 361 next -> 362 enter_tweak_handler(Ev, What, St, T); 363 Other -> 364 case wings_io:is_grabbed() of 365 false -> wings_io:grab(); 366 true -> ok 367 end, 368 Other 369 end; 370handle_initial_event(Ev, What, St, T) -> 371 enter_tweak_handler(Ev, What, St, T). 372 373enter_tweak_handler(Ev, What, St, #tweak{id={Action,_},st=#st{sel=Sel}=St0}=T) -> 374 wings_io:change_event_handler(?SDL_KEYUP, true), 375 wings_wm:grab_focus(), 376 case wings_io:is_grabbed() of 377 true -> ok; 378 false -> wings_io:grab() 379 end, 380 St1 = case wings_pref:get_value(tweak_point) of 381 _ when Sel =:= [] -> St; 382 from_element -> St0; 383 from_cursor -> St0; 384 _other -> 385 case wings_pref:get_value(tweak_axis) of 386 element_normal -> St0; 387 element_normal_edge -> St0; 388 _ when Action =:= delete -> St0; 389 _ -> St 390 end 391 end, 392 begin_drag(What, St1, T), 393 do_tweak_0(0, 0, 0, 0, {move,screen}), 394 {replace,fun(Event) -> handle_tweak_drag_event_0(Event, T) end, Ev}. 395 396%%% 397%%% Tweak Event Handlers 398%%% 399 400update_tweak_handler(T) -> 401 case wings_pref:get_value(hide_sel_while_dragging) of 402 true -> ok; 403 false -> wings_draw:update_sel_dlist() 404 end, 405 wings_wm:dirty(), 406 tweak_drag_no_redraw(T). 407 408tweak_drag_no_redraw(T) -> 409 {replace,fun(Ev) -> handle_tweak_drag_event_0(Ev, T) end}. 410 411handle_tweak_drag_event_0(grab_lost, T) -> 412 end_drag(T); 413handle_tweak_drag_event_0(redraw, #tweak{mode=Mode,st=St}=T) -> 414 redraw(St), 415 tweak_keys_info(), 416 info_line(Mode), 417 case statusbar() of 418 [] -> ok; 419 TweakInfo -> wings_io:info(TweakInfo) 420 end, 421 tweak_drag_no_redraw(T); 422 423%%% 424%%% MouseMotion Events 425%%% 426 427handle_tweak_drag_event_0(#mousemotion{}=Ev, #tweak{mode={TwkMode,_}}=T0) -> 428 %% Tweak Modes that can be modified by xyz constraints 429 handle_tweak_drag_event_0(Ev, T0#tweak{mode=TwkMode}); 430handle_tweak_drag_event_0(#mousemotion{x=X,y=Y}, 431 #tweak{mode=TweakMode,x=OX,y=OY,cx=CX,cy=CY}=T0) -> 432 Mode = 433 case TweakMode of 434 move -> actual_mode(TweakMode); 435 scale -> actual_mode(TweakMode); 436 move_normal -> actual_mode(TweakMode); 437 scale_uniform -> actual_mode(TweakMode); 438 _ -> TweakMode 439 end, 440 DX = X-OX, %since last move X 441 DY = Y-OY, %since last move Y 442 DxOrg = DX+CX, %total X 443 DyOrg = DY+CY, %total Y 444 T1 = mouse_warp(X,Y,T0), 445 do_tweak_0(DX,DY,DxOrg,DyOrg,Mode), 446 T = T1#tweak{mode=Mode,cx=DxOrg,cy=DyOrg}, 447 update_tweak_handler(T); 448 449%%% 450%%% Keyboard Events 451%%% 452 453handle_tweak_drag_event_0(#keyboard{sym=Sym,state=?SDL_RELEASED},T) -> 454 Pressed0 = wings_pref:get_value(tweak_axis_toggle), 455 case lists:keytake(Sym,1,Pressed0) of 456 {value,{Sym,Axis,PressTime},Pressed} -> 457 ClickSpeed = wings_pref:get_value(tweak_click_speed), 458 case timer:now_diff(os:timestamp(), PressTime) > ClickSpeed of 459 true -> 460 toggle_axis(Axis), 461 wings_wm:send({tweak,axis_constraint}, update_palette); 462 false -> ok 463 end, 464 wings_pref:set_value(tweak_axis_toggle,Pressed), 465 update_tweak_handler(T); 466 false -> keep 467 end; 468handle_tweak_drag_event_0(#keyboard{sym=Sym,mod=Mod}=Ev, #tweak{x=OX,y=OY,st=St}=T) 469 when Mod band (?ALT_BITS bor ?SHIFT_BITS bor ?CTRL_BITS) =:= 0 -> 470 %% Activate Tweak Camera 471 case wings_camera:tweak_camera_event(Sym, OX, OY, St) of 472 next -> handle_tweak_drag_event_1(Ev, T); 473 Other -> Other 474 end; 475handle_tweak_drag_event_0(Ev,T) -> 476 handle_tweak_drag_event_1(Ev,T). 477 478handle_tweak_drag_event_1(#keyboard{sym=Sym,mod=Mod}=Ev, #tweak{magnet=Mag,st=St}=T) -> 479 case wings_hotkey:event(Ev, St) of 480 next -> 481 case magnet_has_hotkey() of 482 true -> 483 is_tweak_combo(T); 484 false when Mag-> 485 case is_altkey_magnet_event(Sym,Mod) of 486 true -> tweak_drag_mag_adjust(T#tweak{sym=Sym}); 487 false -> 488 is_tweak_combo(T) 489 end; 490 false -> 491 is_tweak_combo(T) 492 end; 493 {tweak,{tweak_magnet,mag_adjust}} -> 494 if Mag -> 495 tweak_drag_mag_adjust(T#tweak{sym=Sym}); 496 true -> keep 497 end; 498 Action -> 499 is_tweak_hotkey(Action, T#tweak{sym=Sym}) 500 end; 501 502handle_tweak_drag_event_1(Ev,T) -> 503 handle_tweak_drag_event_2(Ev,T). 504 505%%% 506%%% Mouse Button Events 507%%% 508 509handle_tweak_drag_event_2(#mousewheel{}=Ev, #tweak{st=St}) -> 510 case wings_camera:event(Ev, St) of 511 next -> keep; 512 Other -> Other 513 end; 514%% Mouse Button released, so end drag sequence. 515handle_tweak_drag_event_2(#mousebutton{button=B,state=?SDL_RELEASED}, T) when B < 4 -> 516 case wings_io:get_mouse_state() of 517 {0,_,_} -> 518 case wings_pref:get_value(tweak_axis_toggle) of 519 [] -> wings_io:change_event_handler(?SDL_KEYUP, false); 520 _ -> ok 521 end, 522 end_drag(T); 523 _buttons_still_pressed -> keep 524 end; 525handle_tweak_drag_event_2(_,_) -> 526 keep. 527 528%%% 529%%% Adjust Magnet Radius 530%%% 531 532magnet_adjust(#tweak{st=#st{selmode=body}}) -> next; 533magnet_adjust(#tweak{st=St0}=T0) -> 534 {_,X,Y} = wings_wm:local_mouse_state(), 535 case wings_pick:do_pick(X,Y,St0) of 536 {add, What, St} -> 537 magnet_handler_setup(What, X, Y, St, T0); 538 {delete, What, _} -> 539 magnet_handler_setup(What, X, Y, St0, T0); 540 none -> next 541 end. 542 543magnet_handler_setup({Id,Elem,_}=What, X, Y, St, T0) -> 544 wings_io:change_event_handler(?SDL_KEYUP, true), 545 IdElem = {Id,[Elem]}, 546 wings_wm:grab_focus(), 547 wings_io:grab(), 548 begin_magnet_adjustment(What, St), 549 tweak_magnet_radius_help(true), 550 T = T0#tweak{id=IdElem,ox=X,oy=Y,x=X,y=Y,cx=0,cy=0}, 551 {seq,push,update_magnet_handler(T)}. 552 553%%% 554%%% Magnet Handler 555%%% 556 557update_magnet_handler(T) -> 558 case wings_pref:get_value(hide_sel_while_dragging) of 559 true -> ok; 560 false -> wings_draw:update_sel_dlist() 561 end, 562 wings_wm:dirty(), 563 {replace,fun(Ev) -> handle_magnet_event(Ev, T)end}. 564 565handle_magnet_event(redraw, #tweak{st=St}=T) -> 566 redraw(St), 567 draw_magnet(T), 568 update_magnet_handler(T); 569handle_magnet_event({new_state,St}, T) -> 570 end_magnet_event(T#tweak{st=St}); 571handle_magnet_event(#mousemotion{x=X,y=Y},#tweak{x=OX}=T0) -> 572 DX = X-OX, %since last move X 573 T1 = mouse_warp(X,Y,T0), 574 T = adjust_magnet_radius(DX,T1), 575 update_magnet_handler(T); 576%% If something is pressed during magnet radius adjustment, save changes 577%% and begin new event. 578handle_magnet_event(#keyboard{sym=Sym,state=?SDL_RELEASED},#tweak{sym=Sym}=T) -> 579 end_magnet_event(T); 580handle_magnet_event(#keyboard{sym=Sym},#tweak{sym=Sym}) -> 581 keep; 582handle_magnet_event(#keyboard{}=Ev,T) -> 583 end_magnet_event(Ev,T); 584handle_magnet_event(#mousebutton{}=Ev,#tweak{ox=X,oy=Y}=T) -> 585 end_magnet_event(Ev#mousebutton{x=X,y=Y},T); 586handle_magnet_event(#mousemotion{},T) -> 587 end_magnet_event(T); 588handle_magnet_event(Ev,T) -> 589 end_magnet_event(Ev,T). 590 591 592%%% 593%%% Handeler for In-Drag Magnet Radius Adjustments 594%%% 595 596tweak_drag_mag_adjust(#tweak{st=#st{selmode=body}}) -> keep; 597tweak_drag_mag_adjust(#tweak{magnet=false}) -> keep; 598tweak_drag_mag_adjust(#tweak{mode=Mode,cx=CX,cy=CY,x=OX,y=OY}=T0) -> 599 {_,X,Y} = wings_wm:local_mouse_state(), 600 DX = X-OX, %since last move X 601 DY = Y-OY, %since last move Y 602 DxOrg = DX+CX, %total X 603 DyOrg = DY+CY, %total Y 604 T1 = mouse_warp(X, Y, T0), 605 do_tweak_0(DX,DY,DxOrg,DyOrg,Mode), 606 T = T1#tweak{cx=DxOrg,cy=DyOrg}, 607 update_in_drag_radius_handler(T). 608 609update_in_drag_radius_handler(T) -> 610 case wings_pref:get_value(hide_sel_while_dragging) of 611 true -> ok; 612 false -> wings_draw:update_sel_dlist() 613 end, 614 wings_wm:dirty(), 615 in_drag_radius_no_redraw(T). 616 617in_drag_radius_no_redraw(T) -> 618 {replace,fun(Ev) -> 619 handle_in_drag_magnet_ev(Ev, T)end}. 620 621handle_in_drag_magnet_ev(redraw, #tweak{magnet=Mag,st=St}=T) -> 622 redraw(St), 623 tweak_keys_info(), 624 tweak_magnet_radius_help(Mag), 625 draw_magnet(T), 626 in_drag_radius_no_redraw(T); 627handle_in_drag_magnet_ev(#mousemotion{x=X, y=Y},#tweak{x=OX}=T0) -> 628 DX = X-OX, %since last move X 629 T1 = mouse_warp(X,Y,T0), 630 T = in_drag_adjust_magnet_radius(DX,T1), 631 update_in_drag_radius_handler(T); 632handle_in_drag_magnet_ev(#keyboard{sym=Sym,state=?SDL_RELEASED}, #tweak{sym=Sym}=T) -> 633 end_in_drag_mag_event(redraw,T); 634handle_in_drag_magnet_ev(#keyboard{sym=Sym}, #tweak{sym=Sym}) -> 635 keep; 636handle_in_drag_magnet_ev(Ev,T) -> 637 end_in_drag_mag_event(Ev, T). 638 639end_in_drag_mag_event(Ev,#tweak{magnet=Mag, mag_type=MagType, mag_rad=MagR}=T) -> 640 wings_pref:set_value(tweak_magnet, {Mag, MagType, MagR}), 641 handle_tweak_drag_event_0(Ev, T). 642 643%%% 644%%% End Magnet Events 645%%% 646 647end_magnet_event(#tweak{st=St}=T) -> 648 end_magnet_event({new_state,St},T). 649 650end_magnet_event(Ev,#tweak{id=Id}=T) -> 651 wings_io:change_event_handler(?SDL_KEYUP, false), 652 save_magnet_prefs(T), 653 end_magnet_adjust(Id), 654 wings_wm:later(Ev), 655 pop. 656 657%%% 658%%% End of event handlers 659%%% 660 661mouse_warp(_X,_Y,#tweak{warp=true, x=OX,y=OY}=T) -> 662 wings_io:warp(OX,OY), 663 T; 664mouse_warp(X,Y,#tweak{warp={W,H}}=T) 665 when X < 10 orelse Y < 10 orelse X > (W-10) orelse Y > (H-10) -> 666 %% Warp at the window edges 667 Cx = W div 2, Cy = H div 2, 668 wings_io:warp(Cx, Cy), 669 T#tweak{x=Cx,y=Cy}; 670mouse_warp(X,Y, T) -> 671 T#tweak{x=X,y=Y}. 672 673redraw(St) -> 674 Render = 675 fun() -> 676 wings_wm:clear_background(), 677 wings_render:render(St) 678 end, 679 wings_io:batch(Render). 680 681%%% 682%%% Magnet Radius Adjustments 683%%% 684 685begin_magnet_adjustment(SelElem, St) -> 686 wings_draw:refresh_dlists(St), 687 wings_dl:map(fun(D, _) -> 688 begin_magnet_adjustment_fun(D, SelElem) 689 end, []). 690 691begin_magnet_adjustment_fun(#dlo{src_sel={Mode,Els},src_we=We}=D, SelElem) -> 692 Vs0 = sel_to_vs(Mode, gb_sets:to_list(Els), We), 693 case Vs0 of 694 [] -> D; 695 _ -> 696 Center = wings_vertex:center(Vs0, We), 697 MM = case {We,SelElem} of 698 {#we{id=Id},{Id,_,MM0}} -> MM0; 699 {_,_} -> original 700 end, 701 D#dlo{drag=#drag{pos=Center,mm=MM}} 702 end; 703begin_magnet_adjustment_fun(D, _) -> D. 704 705adjust_magnet_radius(MouseMovement, #tweak{mag_rad=Falloff0}=T0) -> 706 case Falloff0 + MouseMovement * wings_pref:get_value(tweak_mag_adj_sensitivity) of 707 Falloff when Falloff > 0 -> 708 T0#tweak{mag_rad=Falloff}; 709 _otherwise -> T0 710 end. 711 712in_drag_adjust_magnet_radius(MouseMovement, #tweak{mag_rad=Falloff0}=T) -> 713 case Falloff0 + MouseMovement * wings_pref:get_value(tweak_mag_adj_sensitivity) of 714 Falloff when Falloff > 0 -> 715 setup_magnet(T#tweak{mag_rad=Falloff}); 716 _otherwise -> T 717 end. 718 719end_magnet_adjust({OrigId,El}) -> 720 wings_dl:map(fun(#dlo{src_we=#we{id=Id}}=D, _) -> 721 if OrigId =:= Id -> show_cursor(El,D); true -> ok end, 722 D#dlo{vs=none,sel=none,drag=none} 723 end, []). 724 725%%% 726%%% Begin Drag 727%%% 728 729begin_drag(SelElem, St, T) -> 730 wings_draw:refresh_dlists(St), 731 wings_dl:map(fun(D, _) -> 732 begin_drag_fun(D, SelElem, St, T) 733 end, []). 734 735begin_drag_fun(#dlo{src_sel={body,_},src_we=#we{vp=Vtab}=We}=D, _, _, _) -> 736 Vs = wings_util:array_keys(Vtab), 737 Center = wings_vertex:center(Vs, We), 738 Id = e3d_mat:identity(), 739 D#dlo{drag={matrix,Center,Id,e3d_mat:expand(Id)}}; 740begin_drag_fun(#dlo{src_sel={Mode,Els},src_we=We}=D0, SelElem, #st{sel=Sel}=St, T) -> 741 Vs0 = sel_to_vs(Mode, gb_sets:to_list(Els), We), 742 case Vs0 of 743 [] -> D0; 744 _ -> 745 Center = wings_vertex:center(Vs0, We), 746 {Vs,Magnet,VsDyn} = begin_magnet(T, Vs0, Center, We), 747 #dlo{src_we=We0}= D = wings_draw:split(D0, Vs, St), 748 749 L = length(Sel) > 1, 750 MM = case {We,SelElem} of 751 {#we{id=Id},{Id,_,_}} when L -> original; %% so at least the shapes 752 %% drag in the same direction.. if the mirrors are pointed the same too. 753 {#we{id=Id},{Id,_,MM0}} -> MM0; 754 {_,_} -> original 755 end, 756 NewPst = set_edge_influence(Vs,VsDyn,We0), 757 D#dlo{src_we=We0#we{pst=NewPst},drag=#drag{vs=Vs0,pos0=Center,pos=Center,mag=Magnet,mm=MM}} 758 end; 759begin_drag_fun(D, _, _, _) -> D. 760 761end_drag(#tweak{mode=Mode,id={_,{OrigId,El}},st=St0}) -> 762 St = wings_dl:map(fun (#dlo{src_we=#we{id=Id}}=D, St1) -> 763 if OrigId =:= Id -> show_cursor(El,D); true -> ok end, 764 end_drag(Mode, D, St1) 765 end, St0), 766 wings_wm:later({new_state,St}), 767 pop. 768 769 770%%% 771%%% End Drag (end tweak event) 772%%% 773 774%% update 775end_drag(update, #dlo{src_sel={Mode,Sel}, src_we=#we{id=Id},drag={matrix,_,Matrix,_}}=D, 776 #st{shapes=Shs0}=St0) -> 777 We0 = gb_trees:get(Id, Shs0), 778 We = wings_we:transform_vs(Matrix, We0), 779 Shs = gb_trees:update(Id, We, Shs0), 780 St = St0#st{shapes=Shs}, 781 {D,St#st{selmode=Mode,sel=[{Id,Sel}]}}; 782end_drag(update, #dlo{src_sel={Mode,Sel},src_we=#we{id=Id}}=D0, #st{shapes=Shs0}=St0) -> 783 #dlo{src_we=We} = wings_draw:join(D0), 784 Shs = gb_trees:update(Id, We, Shs0), 785 St = St0#st{shapes=Shs}, 786 {D0,St#st{selmode=Mode,sel=[{Id,Sel}]}}; 787%% tweak modes 788end_drag(_, #dlo{src_we=#we{id=Id},drag={matrix,_,Matrix,_}}=D, 789 #st{shapes=Shs0}=St0) -> 790 We0 = gb_trees:get(Id, Shs0), 791 We = wings_we:transform_vs(Matrix, We0), 792 Shs = gb_trees:update(Id, We, Shs0), 793 St = St0#st{shapes=Shs}, 794 D1 = D#dlo{src_we=We}, 795 D2 = wings_draw:changed_we(D1, D), 796 {D2#dlo{vs=none,sel=none,drag=none},St}; 797end_drag(Mode, #dlo{src_sel={_,_},src_we=#we{id=Id}}=D0, #st{shapes=Shs0}=St0) -> 798 case Mode of 799 slide -> 800 case wings_io:is_key_pressed(?SDLK_F1) of 801 false -> 802 #dlo{src_we=We}=D = wings_draw:join(D0), 803 Shs = gb_trees:update(Id, We, Shs0), 804 St = St0#st{shapes=Shs}, 805 {D#dlo{vs=none,sel=none,drag=none},St}; 806 true -> 807 #dlo{src_we=We} = D = wings_draw:join(D0), 808 St = case collapse_short_edges(0.0001,We) of 809 {delete, _} -> 810 Shs = gb_trees:delete(Id,Shs0), 811 St0#st{shapes=Shs,sel=[]}; 812 {true, We1} -> 813 Shs = gb_trees:update(Id, We1, Shs0), 814 St0#st{shapes=Shs}; 815 {false, We1} -> 816 Shs = gb_trees:update(Id, We1, Shs0), 817 St0#st{shapes=Shs, sel=[]} 818 end, 819 {D#dlo{vs=none,sel=none,drag=none},St} 820 end; 821 _ -> 822 #dlo{src_we=#we{pst=Pst}=We}=D = wings_draw:join(D0), 823 We0=We#we{pst=remove_pst(Pst)}, 824 Shs = gb_trees:update(Id, We0, Shs0), 825 St = St0#st{shapes=Shs}, 826 {D#dlo{plugins=[],vs=none,sel=none,drag=none,src_we=We0},St} 827 end; 828end_drag(_, D, St) -> {D, St}. 829 830%%% 831%%% Do Tweak 832%%% 833 834do_tweak_0(DX0, DY0, DxOrg, DyOrg, Mode) -> 835 TweakSpeed = wings_pref:get_value(tweak_speed), 836 DX = DX0 * TweakSpeed, 837 DY = DY0 * TweakSpeed, 838 wings_dl:map(fun 839 (#dlo{src_we=We}=D, _) when ?IS_LIGHT(We) -> 840 case Mode of 841 {move,Dir} when Dir =:= normal; Dir =:= element_normal; 842 Dir =:= default; Dir =:= element_normal_edge -> 843 do_tweak(D, DX, DY, DxOrg, DyOrg, {move,screen}); 844 {move,_} -> 845 do_tweak(D, DX, DY, DxOrg, DyOrg, Mode); 846 _ -> 847 do_tweak(D, DX, DY, DxOrg, DyOrg, {move,screen}) 848 end; 849 (D, _) -> 850 do_tweak(D, DX, DY, DxOrg, DyOrg, Mode) 851 end, []). 852 853do_tweak(#dlo{drag={matrix,Pos0,Matrix0,_},src_we=#we{id=Id}}=D0, 854 DX,DY,_,_,Mode) -> 855 Matrices = wings_u:get_matrices(Id, original), 856 {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0), 857 TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}), 858 {Tx,Ty,Tz} = TweakPos, 859 {Px,Py,Pz} = Pos0, 860 Rad = wings_pref:get_value(tweak_radial), 861 Pos = case Mode of 862 {move,x} -> if Rad -> {Px,Ty,Tz}; true -> {Tx,Py,Pz} end; 863 {move,y} -> if Rad -> {Tx,Py,Tz}; true -> {Px,Ty,Pz} end; 864 {move,z} -> if Rad -> {Tx,Ty,Pz}; true -> {Px,Py,Tz} end; 865 {move,xy} -> if Rad -> {Px,Py,Tz}; true -> {Tx,Ty,Pz} end; 866 {move,yz} -> if Rad -> {Tx,Py,Pz}; true -> {Px,Ty,Tz} end; 867 {move,zx} -> if Rad -> {Px,Ty,Pz}; true -> {Tx,Py,Tz} end; 868 _Other -> TweakPos 869 end, 870 Move = e3d_vec:sub(Pos, Pos0), 871 Matrix = e3d_mat:mul(e3d_mat:translate(Move), Matrix0), 872 D0#dlo{drag={matrix,Pos,Matrix,e3d_mat:expand(Matrix)}}; 873 874do_tweak(#dlo{drag=#drag{vs=Vs,pos=Pos0,pos0=Orig,pst=none, %% pst =:= none 875 mag=Mag0,mm=MM}=Drag, src_we=#we{id=Id,mirror=Mir}}=D0, 876 DX, DY, _DxOrg, _DyOrg, {Scale,Type}) 877 when Scale =:= scale; Scale =:= scale_uniform -> 878 %% This is the first time through for Scale ops. 879 %% For default Scaling, figure out the axis of scaling by determining the 880 %% direction of the user's initial mouse motion. Save this PrimeVector to the 881 %% pst field in the #drag record. 882 Matrices = case Mir of 883 none -> wings_u:get_matrices(Id, original); 884 _ -> wings_u:get_matrices(Id, MM) 885 end, 886 {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0), 887 TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}), 888 TweakPointOpType = wings_pref:get_value(tweak_point), 889 {{Axis,ENorm,Point},_} = wings_pref:get_value(tweak_geo_point), 890 Radial = wings_pref:get_value(tweak_radial), 891 {Dir,Pos} = case Type of 892 x -> {axis,{1.0,0.0,0.0}}; 893 y -> {axis,{0.0,1.0,0.0}}; 894 z -> {axis,{0.0,0.0,1.0}}; 895 xy -> {radial,{0.0,0.0,1.0}}; 896 yz -> {radial,{1.0,0.0,0.0}}; 897 zx -> {radial,{0.0,1.0,0.0}}; 898 normal -> {dir, sel_normal_0(Vs,D0)}; 899 default_axis -> 900 {_,Normal} = wings_pref:get_value(default_axis), 901 {axis,Normal}; 902 element_normal -> {element_normal, Axis}; 903 element_normal_edge -> {element_normal_edge, ENorm}; 904 screen -> 905 case Radial of 906 true -> {dir, sel_normal_0(Vs,D0)}; 907 false when Scale =:= scale -> 908 {user,TweakPos}; %% This is for Default Scaling 909 false when Scale =:= scale_uniform -> 910 {uniform,TweakPos} 911 end 912 end, 913 {_,MSX,MSY} = wings_wm:local_mouse_state(), %% Mouse Position 914 {_,YY0} = wings_wm:win_size(wings_wm:this()), %% Window Size global 915 916 %% Cursor Position according to the model coordinates 917 CursorPos = screen_to_obj(Matrices,{float(MSX), float(YY0 - MSY), Zs}), 918 %% vector from where the user starts drag to bbox sel center 919 V1 = e3d_vec:norm_sub(CursorPos,Orig), 920 921 %% scale axis 922 V2 = case e3d_vec:norm_sub(Orig,Pos) of 923 {0.0,0.0,0.0} -> Pos; 924 Other -> Other 925 end, 926 927 %% Flip scale vec depending on the direction 928 %% the user is dragging. To or From the selection center. 929 Dot = e3d_vec:dot(V1, V2), 930 PVec = case Dot < 0.0 of 931 true -> e3d_vec:neg(V2); 932 false -> V2 933 end, 934 935 PrimeVec = case Dir of 936 % user when TweakPointOpType =:= from_element -> Axis; 937 user -> PVec; 938 _other -> Pos 939 end, 940 941 %% Check for active Point ops 942 VecData = {DistVec,AxisPoint} = case TweakPointOpType of 943 none -> {PVec, {PrimeVec, Orig}}; 944 from_cursor -> {e3d_vec:neg(PVec), {PrimeVec, CursorPos}}; 945 from_element -> 946 {e3d_vec:neg(PVec), {PrimeVec, Point}}; 947 from_default -> 948 {DefPoint,_} = wings_pref:get_value(default_axis), 949 {e3d_vec:neg(PVec), {PrimeVec, DefPoint}} 950 end, 951 952 Dist = dist_along_vector(Orig, TweakPos, DistVec)/2, 953 954 {Vtab,Mag} = case Dir of 955 radial -> tweak_scale_radial(Dist, AxisPoint, Mag0); 956 _ -> 957 case Radial of 958 true -> tweak_scale_radial(Dist, AxisPoint, Mag0); 959 false -> tweak_scale(Dist, AxisPoint, Mag0) 960 end 961 end, 962 963 Pst = {Type,Dir,VecData}, 964 D = D0#dlo{sel=none,drag=Drag#drag{pos=TweakPos,pst=Pst,mag=Mag}}, 965 wings_draw:update_dynamic(D, Vtab); 966 967do_tweak(#dlo{drag=#drag{pos=Pos0,pos0=Orig,pst={Type,Dir,PrimeVec}, 968 mag=Mag0,mm=MM}=Drag, src_we=#we{id=Id,mirror=Mir}}=D0, 969 DX, DY, _DxOrg, _DyOrg, {Scale,Type}) 970 when Scale =:= scale; Scale =:= scale_uniform -> 971 Matrices = case Mir of 972 none -> wings_u:get_matrices(Id, original); 973 _ -> wings_u:get_matrices(Id, MM) 974 end, 975 {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0), 976 TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}), 977 {DistVec,AxisPoint} = PrimeVec, 978 Dist = dist_along_vector(Orig, TweakPos, DistVec)/2, 979 {Vtab,Mag} = case Dir of 980 uniform -> tweak_scale_uniform(Dist, AxisPoint, Mag0); 981 radial -> tweak_scale_radial(Dist, AxisPoint, Mag0); 982 _ -> 983 case wings_pref:get_value(tweak_radial) of 984 true -> tweak_scale_radial(Dist, AxisPoint, Mag0); 985 false -> tweak_scale(Dist, AxisPoint, Mag0) 986 end 987 end, 988 D = D0#dlo{sel=none,drag=Drag#drag{pos=TweakPos,mag=Mag}}, 989 wings_draw:update_dynamic(D, Vtab); 990do_tweak(#dlo{drag=#drag{vs=Vs,pos=Pos0,mag=Mag0,mm=MM}=Drag, 991 src_we=#we{id=Id,mirror=Mir}}=D0, DX, DY, _DxOrg, _DyOrg, 992 {Move,Type}) when Move =:= move; Move =:= move_normal -> 993 Matrices = case Mir of 994 none -> wings_u:get_matrices(Id, original); 995 _ -> wings_u:get_matrices(Id, MM) 996 end, 997 Rad = wings_pref:get_value(tweak_radial), 998 {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0), 999 TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}), 1000 {Tx,Ty,Tz} = TweakPos, 1001 {Px,Py,Pz} = Pos0, 1002 {Vtab,Mag} = case Type of 1003 x -> 1004 Pos = if Rad -> {Px,Ty,Tz}; true -> {Tx,Py,Pz} end, 1005 magnet_tweak(Mag0, Pos); 1006 y -> 1007 Pos = if Rad -> {Tx,Py,Tz}; true -> {Px,Ty,Pz} end, 1008 magnet_tweak(Mag0, Pos); 1009 z -> 1010 Pos = if Rad -> {Tx,Ty,Pz}; true -> {Px,Py,Tz} end, 1011 magnet_tweak(Mag0, Pos); 1012 xy -> 1013 Pos = if Rad -> {Px,Py,Tz}; true -> {Tx,Ty,Pz} end, 1014 magnet_tweak(Mag0, Pos); 1015 yz -> 1016 Pos = if Rad -> {Tx,Py,Pz}; true -> {Px,Ty,Tz} end, 1017 magnet_tweak(Mag0, Pos); 1018 zx -> 1019 Pos = if Rad -> {Px,Ty,Pz}; true -> {Tx,Py,Tz} end, 1020 magnet_tweak(Mag0, Pos); 1021 normal -> 1022 Normal = sel_normal_0(Vs, D0), 1023 Pos = tweak_along_axis(Rad, Normal, Pos0, TweakPos), 1024 magnet_tweak(Mag0, Pos); 1025 default_axis -> 1026 {_,Axis} = wings_pref:get_value(default_axis), 1027 Pos = tweak_along_axis(Rad, Axis, Pos0, TweakPos), 1028 magnet_tweak(Mag0, Pos); 1029 element_normal -> 1030 {{Axis,_,_},_} = wings_pref:get_value(tweak_geo_point), 1031 Pos = tweak_along_axis(Rad, Axis, Pos0, TweakPos), 1032 magnet_tweak(Mag0, Pos); 1033 element_normal_edge -> 1034 {{_,Axis,_},_} = wings_pref:get_value(tweak_geo_point), 1035 Pos = tweak_along_axis(Rad, Axis, Pos0, TweakPos), 1036 magnet_tweak(Mag0, Pos); 1037 screen -> 1038 if Rad; Move =:= move_normal -> 1039 Normal = sel_normal_0(Vs, D0), 1040 Pos = tweak_along_axis(Rad, Normal, Pos0, TweakPos), 1041 magnet_tweak(Mag0, Pos); 1042 true -> 1043 Pos = TweakPos, 1044 magnet_tweak(Mag0, Pos) 1045 end 1046 end, 1047 D = D0#dlo{sel=none,drag=Drag#drag{pos=Pos,pst=none,mag=Mag}}, 1048 wings_draw:update_dynamic(D, Vtab); 1049do_tweak(#dlo{drag=#drag{pos=Pos0,pos0=Orig,mag=Mag0,mm=MM}=Drag, 1050 src_we=#we{id=Id,mirror=Mir}=We}=D0, DX, DY, DxOrg, _DyOrg, Mode) 1051 when Mode =:= relax; Mode =:= slide -> 1052 Matrices = case Mir of 1053 none -> wings_u:get_matrices(Id, original); 1054 _ -> wings_u:get_matrices(Id, MM) 1055 end, 1056 {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0), 1057 TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}), 1058 {Vtab,Mag} = 1059 case Mode of 1060 relax -> 1061 Len0 = abs(DxOrg) / 600, 1062 Len = case Len0 > 1 of 1063 true -> 1.0; 1064 false -> Len0 1065 end, 1066 relax_magnet_tweak_fn(Mag0, We, Len); 1067 slide -> 1068 magnet_tweak_slide_fn(Mag0, We, Orig, TweakPos) 1069 end, 1070 D = D0#dlo{sel=none,drag=Drag#drag{pos=TweakPos,pst=none,mag=Mag}}, 1071 wings_draw:update_dynamic(D, Vtab); 1072do_tweak(#dlo{drag=#drag{}=Drag}=D, _, _, _, _, _) -> 1073 D#dlo{drag=Drag#drag{pst=none}}; 1074do_tweak(D, _, _, _, _, _) -> D. 1075 1076%%% 1077%%% Tweak Tool Calculations 1078%%% 1079 1080%%% Scale 1081tweak_scale(Dist, {PVec, Point}, #mag{vs=Vs}=Mag) -> 1082 Vtab = foldl(fun({V, Pos0, Plane, _, Inf}, A) -> 1083 D = dist_along_vector(Point, Pos0, PVec), 1084 Pos1 = e3d_vec:add_prod(Pos0, PVec, Inf*D*Dist), 1085 Pos = mirror_constrain(Plane, Pos1), 1086 [{V,Pos}|A] 1087 end, [], Vs), 1088 {Vtab,Mag#mag{vtab=Vtab}}. 1089 1090tweak_scale_radial(Dist, {Norm,Point}, #mag{vs=Vs}=Mag) -> 1091 Vtab = foldl(fun({V, Pos0, Plane, _, Inf}, A) -> 1092 V1 = e3d_vec:norm_sub(Point, Pos0), 1093 V2 = e3d_vec:cross(V1,Norm), 1094 Vec = e3d_vec:norm(e3d_vec:cross(V2,Norm)), 1095 D = dist_along_vector(Point, Pos0, Vec), 1096 Pos1 = e3d_vec:add_prod(Pos0, Vec, Inf*D*Dist), 1097 Pos = mirror_constrain(Plane, Pos1), 1098 [{V,Pos}|A] 1099 end, [], Vs), 1100 {Vtab,Mag#mag{vtab=Vtab}}. 1101 1102tweak_scale_uniform(Dist, {_, Point}, #mag{vs=Vs}=Mag) -> 1103 Vtab = foldl(fun({V, Pos0, Plane, _, Inf}, A) -> 1104 Vec = e3d_vec:sub(Point, Pos0), 1105 Pos1 = e3d_vec:add_prod(Pos0, Vec, Inf*Dist), 1106 Pos = mirror_constrain(Plane, Pos1), 1107 [{V,Pos}|A] 1108 end, [], Vs), 1109 {Vtab,Mag#mag{vtab=Vtab}}. 1110 1111dist_along_vector(PosA,PosB,Vector) -> 1112 %% Return Distance between PosA and PosB along Vector 1113 {Xa,Ya,Za} = PosA, 1114 {Xb,Yb,Zb} = PosB, 1115 {Vx,Vy,Vz} = e3d_vec:norm(Vector), 1116 Vx*(Xa-Xb)+Vy*(Ya-Yb)+Vz*(Za-Zb). 1117 1118%%% Relax 1119relax_magnet_tweak_fn(#mag{vs=Vs}=Mag,We,Weight) -> 1120 Vtab = foldl(fun({V,P0,Plane,_,1.0}, A) -> 1121 P1=relax_vec_fn(V,We,P0,Weight), 1122 P = mirror_constrain(Plane, P1), 1123 [{V,P}|A]; 1124 ({V,P0,Plane,_,Inf}, A) -> 1125 P1=relax_vec_fn(V,We,P0,Weight*Inf), 1126 P = mirror_constrain(Plane, P1), 1127 [{V,P}|A] 1128 end, [], Vs), 1129 {Vtab,Mag#mag{vtab=Vtab}}. 1130 1131relax_vec_fn(V, #we{}=We,Pos0,Weight) -> 1132 Vec = relax_vec(V,We), 1133 D = e3d_vec:sub(Vec,Pos0), 1134 e3d_vec:add_prod(Pos0, D, Weight). 1135 1136relax_vec(V, We) -> 1137 case collect_neib_verts_coor(V, We) of 1138 [] -> 1139 %% Because of hidden faces there may be no neighbouring vertices, 1140 %% so we default to the position of the vertex itself. 1141 wings_vertex:pos(V, We); 1142 Cs0 -> 1143 Cs = [C || C <- Cs0, C =/= undefined], 1144 if Cs =:= [] -> wings_vertex:pos(V, We); 1145 true -> e3d_vec:average([wings_vertex:pos(V, We)|Cs]) 1146 end 1147 end. 1148 1149collect_neib_verts_coor(V,We)-> 1150 VertList = wings_vertex:fold(fun(_,_,ERec,Acc) -> 1151 [wings_vertex:other(V,ERec)|Acc] 1152 end,[],V,We), 1153 foldl(fun(Vert,A) -> [wings_vertex:pos(Vert,We)|A] end,[],VertList). 1154 1155%%% Slide 1156magnet_tweak_slide_fn(#mag{vs=Vs}=Mag, We,Orig,TweakPos) -> 1157 Vtab = foldl(fun({V,P0,Plane,_,Inf}, A) -> 1158 P1=slide_vec_w(V,P0,Orig,TweakPos,We,Inf,Vs), 1159 P = mirror_constrain(Plane, P1), 1160 [{V,P}|A] 1161 end, [], Vs), 1162 {Vtab,Mag#mag{vtab=Vtab}}. 1163 1164slide_vec_w(V, Vpos, VposS, TweakPosS, We, W,Vs) -> 1165 Dv = e3d_vec:sub(VposS,Vpos), 1166 TweakPos = e3d_vec:sub(TweakPosS, Dv), 1167 Cs0 = collect_neib_verts_coor_vs(V, We, Vs), 1168 Cs1 = [C || C <- Cs0, C =/= undefined], 1169 Cs = sub_pos_from_list(Cs1, Vpos), 1170 TweakPos2=e3d_vec:add(Vpos, e3d_vec:mul(e3d_vec:sub(TweakPos, Vpos), W)), 1171 slide_one_vec(Vpos, TweakPos2, Cs). 1172 1173slide_one_vec(Vpos, TweakPos, PosList) -> 1174 Dpos=e3d_vec:sub(TweakPos,Vpos), 1175 {Dp,_} = foldl(fun 1176 ({0.0,0.0,0.0},VPW) -> VPW; 1177 (Vec, {VP,W}) -> 1178 Vn = e3d_vec:norm(Vec), 1179 Dotp = e3d_vec:dot(Vn,Dpos), 1180 if 1181 Dotp > W, Dotp > 0 -> 1182 Len = e3d_vec:len(Vec), 1183 Dotp2 = if 1184 Dotp > Len -> Len; 1185 true -> Dotp 1186 end, 1187 {e3d_vec:mul(Vn, Dotp2),Dotp}; 1188 true -> {VP,W} 1189 end 1190 end,{{0,0,0},0},PosList), 1191 e3d_vec:add(Vpos,Dp). 1192 1193sub_pos_from_list(List,Pos) -> 1194 foldl(fun 1195 (E,B) -> [e3d_vec:sub(E,Pos)|B] end,[],List). 1196 1197collect_neib_verts_coor_vs(V,We,Vs)-> 1198 VertList = wings_vertex:fold(fun(_,_,ERec,Acc) -> 1199 [wings_vertex:other(V,ERec)|Acc] 1200 end,[],V,We), 1201 foldl(fun(E,B) -> [get_orig_pos(E,We,Vs)|B] end,[],VertList). 1202 1203get_orig_pos(V,We,Vs)-> 1204 Pos=foldl( 1205 fun({Vert,Coor,_,_,_},P) -> 1206 if V =:= Vert -> Coor; true-> P end 1207 end,none,Vs), 1208 case Pos of 1209 none -> wings_vertex:pos(V,We); 1210 _ -> Pos 1211 end. 1212 1213%% scanning over the mesh to collapse short edges 1214collapse_short_edges(Tolerance, #we{es=Etab,vp=Vtab}=We) -> 1215 Short = array:sparse_foldl( 1216 fun(Edge, #edge{vs=Va,ve=Vb}, A) -> 1217 case array:get(Va,Vtab) of 1218 undefined -> A; 1219 VaPos -> 1220 case array:get(Vb,Vtab) of 1221 undefined -> A; 1222 VbPos -> 1223 case abs(e3d_vec:dist(VaPos, VbPos)) of 1224 Dist when Dist < Tolerance -> [Edge|A]; 1225 _Dist -> A 1226 end 1227 end 1228 end 1229 end, [], Etab), 1230 NothingCollapsed = Short =:= [], 1231 case catch wings_collapse:collapse_edges(Short,We) of 1232 #we{}=We1 -> 1233 {NothingCollapsed, We1}; 1234 _ -> 1235 {delete, #we{}} 1236 end. 1237 1238%%% Along Average Normal 1239sel_normal_0(Vs, D) -> 1240 Normals = sel_normal(Vs,D), 1241 e3d_vec:norm(e3d_vec:add(Normals)). 1242 1243sel_normal( _, #dlo{src_we=#we{}=We,src_sel={face,Sel0}}) -> 1244 Faces = gb_sets:to_list(Sel0), 1245 face_normals(Faces,We,[]); 1246sel_normal(Vs,D) -> 1247 [vertex_normal(V, D) || V <- Vs]. 1248 1249%%% Radial of Default Axis 1250tweak_along_axis(true, Axis, Pos0, TweakPos) -> 1251 %% constraining by the plane 1252 Dot = e3d_vec:dot(Axis, Axis), 1253 if 1254 Dot =:= 0.0 -> Pos0; 1255 true -> 1256 T = - e3d_vec:dot(Axis, e3d_vec:sub(TweakPos, Pos0)) / Dot, 1257 e3d_vec:add_prod(TweakPos, Axis, T) 1258 end; 1259 1260%%% Tweak Along a non-standard Axis 1261tweak_along_axis(false, Axis, Pos0, TweakPos) -> 1262 %% Return the point along the normal closest to TweakPos. 1263 Dot = e3d_vec:dot(Axis, Axis), 1264 if 1265 Dot =:= 0.0 -> Pos0; 1266 true -> 1267 T = e3d_vec:dot(Axis, e3d_vec:sub(TweakPos, Pos0)) / Dot, 1268 e3d_vec:add_prod(Pos0, Axis, T) 1269 end. 1270 1271%%% 1272%%% Screen to Object Coordinates 1273%%% 1274 1275obj_to_screen({MVM,PM,VP}, Point) -> 1276 e3d_transform:project(Point, MVM, PM, VP). 1277 1278screen_to_obj({MVM,PM,VP}, Point) -> 1279 e3d_transform:unproject(Point, MVM, PM, VP). 1280 1281sel_to_vs(vertex, Vs, _) -> Vs; 1282sel_to_vs(edge, Es, We) -> wings_vertex:from_edges(Es, We); 1283sel_to_vs(face, Fs, We) -> wings_face:to_vertices(Fs, We). 1284 1285%%% 1286%%% Some Utilities 1287%%% 1288 1289%% vertex_normal(Vertex, DLO) -> UnormalizedNormal 1290%% Calculate the vertex normal. Will also work for vertices surrounded 1291%% by one or more hidden faces. 1292vertex_normal(V, D) -> 1293 OrigWe = wings_draw:original_we(D), 1294 FaceNs = [face_normal(F, D) || F <- wings_face:from_vs([V], OrigWe)], 1295 e3d_vec:add(FaceNs). 1296 1297%% face_normal(Face, DLO) -> Normal 1298%% Calculate the face normal. Will also work for faces that 1299%% are hidden (including the virtual mirror face). 1300face_normal(Face, #dlo{src_we=#we{vp=Vtab}}=D) -> 1301 #we{vp=OrigVtab} = OrigWe = wings_draw:original_we(D), 1302 Vs = wings_face:vertices_ccw(Face, OrigWe), 1303 VsPos = [vertex_pos(V, Vtab, OrigVtab) || V <- Vs], 1304 e3d_vec:normal(VsPos). 1305 1306face_normals([Face|Fs], We, Normals) -> 1307 N = wings_face:normal(Face, We), 1308 face_normals(Fs, We, [N|Normals]); 1309face_normals([], _We, Normals) -> 1310 Normals. 1311 1312vertex_pos(V, Vtab, OrigVtab) -> 1313 case array:get(V, Vtab) of 1314 undefined -> array:get(V, OrigVtab); 1315 Pos -> Pos 1316 end. 1317 1318magnet_tweak(#mag{orig=Orig,vs=Vs}=Mag, Pos) -> 1319 Vec = e3d_vec:sub(Pos, Orig), 1320 Vtab = foldl(fun({V,P0,Plane,_,1.0}, A) -> 1321 P1 = e3d_vec:add(P0, Vec), 1322 P = mirror_constrain(Plane, P1), 1323 [{V,P}|A]; 1324 ({V,P0,Plane,_,Inf}, A) -> 1325 P1 = e3d_vec:add_prod(P0, Vec, Inf), 1326 P = mirror_constrain(Plane, P1), 1327 [{V,P}|A] 1328 end, [], Vs), 1329 {Vtab,Mag#mag{vtab=Vtab}}. 1330 1331%% Get center point from closest element to cursor and store it for 1332%% From Element constraints. 1333from_element_point(X ,Y, #st{shapes=Shs}=St0) -> 1334 Stp = St0#st{selmode=face,sel=[],sh=true}, % smart highlight mode 1335 GeomPoint = wings_pick:raw_pick(X,Y,Stp), 1336 {Selmode, _, {IdP, ElemP}} = GeomPoint, 1337 We = gb_trees:get(IdP, Shs), 1338 AxisPoint = point_center(Selmode, ElemP, We), 1339 wings_pref:set_value(tweak_geo_point, {AxisPoint,GeomPoint}). 1340 1341point_center(vertex, V, #we{vp=Vtab,mirror=Mir}=We) -> 1342 {MirN,Ns} = wings_vertex:fold(fun 1343 (_, Face, _, {_,A}) when Face =:= Mir -> 1344 {wings_face:normal(Face, We),A}; 1345 (_, Face, _, {Mb,A}) -> 1346 {Mb,[wings_face:normal(Face, We)|A]} 1347 end, {none,[]}, V, We), 1348 Norm = e3d_vec:norm(e3d_vec:add(Ns)), 1349 Normal = case MirN =:= none of 1350 true -> Norm; 1351 false -> 1352 N = e3d_vec:cross(MirN, Norm), 1353 e3d_vec:cross(N,MirN) 1354 end, 1355 Pos = array:get(V, Vtab), 1356 {Normal,Normal,Pos}; 1357point_center(edge, E, #we{es=Etab,vp=Vtab,mirror=Mir}=We) -> 1358 #edge{vs=Va,ve=Vb,lf=Lf,rf=Rf} = array:get(E, Etab), 1359 PosA = array:get(Va, Vtab), 1360 PosB = array:get(Vb, Vtab), 1361 Pos = e3d_vec:average(PosA, PosB), 1362 EdgeNorm = e3d_vec:norm_sub(PosA,PosB), 1363 FaceNormL = wings_face:normal(Lf, We), 1364 FaceNormR = wings_face:normal(Rf, We), 1365 Normal = case Mir of 1366 Lf -> N = e3d_vec:cross(FaceNormL,FaceNormR), 1367 e3d_vec:cross(N,FaceNormL); 1368 Rf -> N = e3d_vec:cross(FaceNormR,FaceNormL), 1369 e3d_vec:cross(N,FaceNormR); 1370 _ -> e3d_vec:average(FaceNormL, FaceNormR) 1371 end, 1372 {Normal,EdgeNorm,Pos}; 1373point_center(face, F, We) -> 1374 Pos = wings_face:center(F, We), 1375 Normal = wings_face:normal(F, We), 1376 {Normal,Normal,Pos}. 1377 1378%%% 1379%%% Magnet Calculations 1380%%% 1381 1382%% Setup magnet in the middle of a tweak op 1383setup_magnet(#tweak{mode=TwkMode, cx=X, cy=Y}=T) 1384 when TwkMode =:= scale; TwkMode =:= scale_uniform; TwkMode =:= move_normal; TwkMode =:= move -> 1385 wings_dl:map(fun(D, _) -> 1386 setup_magnet_fun(D, T) 1387 end, []), 1388 Mode = actual_mode(TwkMode), 1389 do_tweak_0(0, 0, X, Y, Mode), 1390 T; 1391setup_magnet(#tweak{mode=Mode, cx=X, cy=Y}=T) -> 1392 wings_dl:map(fun(D, _) -> 1393 setup_magnet_fun(D, T) 1394 end, []), 1395 do_tweak_0(0, 0, X, Y, Mode), 1396 T. 1397 1398setup_magnet_fun(#dlo{src_sel={_,_},drag=#drag{vs=Vs0,pos0=Center}=Drag}=Dl0, 1399 #tweak{st=St}=T) -> 1400 We = wings_draw:original_we(Dl0), 1401 {Vs,Mag,VsDyn} = begin_magnet(T, Vs0, Center, We), 1402 #dlo{src_we=We0} = Dl = wings_draw:split(Dl0, Vs, St), 1403 NewPst = set_edge_influence(Vs,VsDyn,We0), 1404 Dl#dlo{src_we=We0#we{pst=NewPst},drag=Drag#drag{mag=Mag}}; 1405setup_magnet_fun(Dl, _) -> Dl. 1406 1407begin_magnet(#tweak{magnet=false}=T, Vs, Center, We) -> 1408 Mirror = mirror_info(We), 1409 Near = near(Center, Vs, [], Mirror, T, We), 1410 Mag = #mag{orig=Center,vs=Near}, 1411 {[Va || {Va,_,_,_,_} <- Near],Mag,[]}; 1412begin_magnet(#tweak{magnet=true}=T, Vs, Center, #we{vp=Vtab0}=We) -> 1413 Mirror = mirror_info(We), 1414 Vtab1 = sofs:from_external(array:sparse_to_orddict(Vtab0), [{vertex,info}]), 1415 Vtab2 = sofs:drestriction(Vtab1, sofs:set(Vs, [vertex])), 1416 Vtab = sofs:to_external(Vtab2), 1417 Near = near(Center, Vs, Vtab, Mirror, T, We), 1418 Mag = #mag{orig=Center,vs=Near}, 1419 {[Va || {Va,_,_,_,_} <- Near],Mag,[{Va,Inf} || {Va,_,_,_,Inf} <- Near]}. 1420 1421near(Center, Vs, MagVs0, Mirror, #tweak{mag_rad=R,mag_type=Type}, We) -> 1422 RSqr = R*R, 1423 MagVs = minus_locked_vs(MagVs0, We), 1424 M = foldl(fun({V,Pos}, A) -> 1425 case e3d_vec:dist_sqr(Pos, Center) of 1426 DSqr when DSqr =< RSqr -> 1427 D = math:sqrt(DSqr), 1428 Inf = magnet_type_calc(Type, D, R), 1429 Matrix = mirror_matrix(V, Mirror), 1430 [{V,Pos,Matrix,D,Inf}|A]; 1431 _ -> A 1432 end; 1433 (_, A) -> A 1434 end, [], MagVs), 1435 foldl(fun(V, A) -> 1436 Matrix = mirror_matrix(V, Mirror), 1437 Pos = wpa:vertex_pos(V, We), 1438 [{V,Pos,Matrix,0.0,1.0}|A] 1439 end, M, Vs). 1440 1441%%% Magnet Mask 1442minus_locked_vs(MagVs, #we{pst=Pst}) -> 1443 Mask = wings_pref:get_value(magnet_mask_on), 1444 case gb_trees:is_defined(wpc_magnet_mask,Pst) of 1445 true when Mask -> 1446 LockedVs = gb_sets:to_list(wpc_magnet_mask:get_locked_vs(Pst)), 1447 remove_masked(LockedVs, MagVs); 1448 _otherwise -> 1449 MagVs 1450 end. 1451 1452remove_masked([V|LockedVs],MagVs) -> 1453 remove_masked(LockedVs,lists:keydelete(V,1,MagVs)); 1454remove_masked([],MagVs) -> MagVs. 1455 1456magnet_type_calc(dome, D, R) when is_float(R) -> 1457 math:sin((R-D)/R*math:pi()/2); 1458magnet_type_calc(straight, D, R) when is_float(R) -> 1459 (R-D)/R; 1460magnet_type_calc(spike, D0, R) when is_float(R) -> 1461 D = (R-D0)/R, 1462 D*D. 1463 1464%%% 1465%%% Draw Magnet 1466%%% 1467 1468draw_magnet(#tweak{st=#st{selmode=body}}) -> ok; 1469draw_magnet(#tweak{magnet=true, mag_rad=R}) -> 1470 wings_dl:fold(fun(D, _) -> 1471 gl:pushAttrib(?GL_ALL_ATTRIB_BITS), 1472 gl:disable(?GL_DEPTH_TEST), 1473 gl:enable(?GL_BLEND), 1474 gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA), 1475 wings_view:load_matrices(false), 1476 {CR,CG,CB,CA}=wings_pref:get_value(tweak_magnet_color), 1477 wings_io:set_color({CR,CG,CB,CA}), 1478 draw_magnet_1(D, R), 1479 gl:popAttrib() 1480 end, []); 1481draw_magnet(_) -> ok. 1482 1483draw_magnet_1(#dlo{src_sel={Mode,Els},src_we=We,mirror=Mtx,drag=#drag{mm=Side}}, R) -> 1484 case Side of 1485 mirror -> gl:multMatrixf(Mtx); 1486 original -> ok 1487 end, 1488 Vs = sel_to_vs(Mode, gb_sets:to_list(Els), We), 1489 {X,Y,Z} = wings_vertex:center(Vs, We), 1490 gl:translatef(X, Y, Z), 1491 #{size:=Size, tris:=Tris} = wings_shapes:tri_sphere(#{subd=>4, scale=> R, binary => true}), 1492 wings_vbo:draw(fun(_) -> gl:drawArrays(?GL_TRIANGLES, 0, Size*3) end, Tris); 1493draw_magnet_1(_, _) -> []. 1494 1495mirror_info(#we{mirror=none}) -> {[],identity}; 1496mirror_info(#we{mirror=Face}=We) -> 1497 FaceVs = wings_face:vertices_ccw(Face, We), 1498 Flatten = wings_we:mirror_projection(We), 1499 {FaceVs,Flatten}. 1500 1501mirror_matrix(V, {MirrorVs,Flatten}) -> 1502 case member(V, MirrorVs) of 1503 false -> identity; 1504 true -> Flatten 1505 end. 1506 1507mirror_constrain(Matrix, Pos) -> e3d_mat:mul_point(Matrix, Pos). 1508 1509%%% 1510%%% Hotkey and Combo Checking 1511%%% 1512 1513magnet_has_hotkey() -> 1514 Hotkeys = wings_hotkey:matching([tweak_magnet,tweak]), 1515 lists:keymember(mag_adjust,1,Hotkeys). 1516 1517is_altkey_magnet_event(Sym,Mod) -> 1518 case Sym of 1519 ?L_ALT -> Mod band (?SHIFT_BITS bor ?CTRL_BITS) =:= 0; 1520 ?R_ALT -> Mod band (?SHIFT_BITS bor ?CTRL_BITS) =:= 0; 1521 _ -> false 1522 end. 1523 1524toggle_data(Axis) -> 1525 Tp = wings_pref:get_value(tweak_point), 1526 Ta = wings_pref:get_value(tweak_axis), 1527 Txyz = wings_pref:get_value(tweak_xyz), 1528 Which = Ta =:= screen, 1529 case Axis of 1530 radial -> radial; 1531 from_default -> Tp; 1532 from_cursor -> Tp; 1533 from_element -> Tp; 1534 Axis when Axis =:= x; Axis =:= y; Axis =:= z -> 1535 if Which -> Axis; true -> Ta end; 1536 Axis -> 1537 if Which -> Txyz; true -> Ta end 1538 end. 1539 1540is_tweak_hotkey({tweak,Cmd}, #tweak{magnet=Magnet,sym=Sym,st=St0}=T0) -> 1541 case Cmd of 1542 {axis_constraint, Axis} -> 1543 ReturnAxis = toggle_data(Axis), 1544 Pressed = wings_pref:get_value(tweak_axis_toggle), 1545 case lists:keymember(Sym, 1, Pressed) of 1546 true -> keep; 1547 false -> 1548 Data = [{Sym,ReturnAxis,os:timestamp()}|Pressed], 1549 wings_pref:set_value(tweak_axis_toggle, Data), 1550 toggle_axis(Axis), 1551 wings_wm:send({tweak,axis_constraint}, update_palette), 1552 St = wings_dl:map(fun (D, _) -> 1553 update_drag(D, T0) % used to find mid tweak model data.. 1554 end, St0), 1555 do_tweak_0(0, 0, 0, 0, {move,screen}), 1556 update_tweak_handler(T0#tweak{st=St}) 1557 end; 1558 {tweak_magnet, toggle_magnet} -> 1559 toggle_magnet(), 1560 {Mag, MagType, _} = wings_pref:get_value(tweak_magnet), 1561 T = T0#tweak{magnet=Mag, mag_type=MagType}, 1562 wings_wm:send({tweak,tweak_magnet}, update_palette), 1563 tweak_magnet_help(), 1564 setup_magnet(T), 1565 update_tweak_handler(T); 1566 {tweak_magnet,reset_radius} when Magnet-> 1567 Pref = wings_pref:get_value(tweak_magnet), 1568 wings_pref:set_value(tweak_magnet,setelement(3,Pref,1.0)), 1569 update_tweak_handler(T0#tweak{mag_rad=1.0}); 1570 {tweak_magnet, cycle_magnet} -> 1571 NewMag = cycle_magnet(), 1572 set_magnet_type(NewMag), 1573 T = if NewMag =:= off -> T0#tweak{magnet=false}; 1574 true -> T0#tweak{magnet=true,mag_type=NewMag} 1575 end, 1576 wings_wm:send({tweak,tweak_magnet}, update_palette), 1577 tweak_magnet_help(), 1578 setup_magnet(T), 1579 update_tweak_handler(T); 1580 {tweak_magnet, MagType} -> 1581 set_magnet_type(MagType), 1582 T = T0#tweak{magnet=true, mag_type=MagType}, 1583 wings_wm:send({tweak,tweak_magnet}, update_palette), 1584 tweak_magnet_help(), 1585 setup_magnet(T), 1586 update_tweak_handler(T); 1587 {Mode,1} when Mode =:= move; Mode =:= move_normal; Mode =:= slide; 1588 Mode =:= scale; Mode =:= scale_uniform; Mode =:= relax -> 1589 set_tweak_pref(Mode, 1, {false, false, false}), 1590 wings_wm:send({tweak,tweak_palette}, update_palette), 1591 is_tweak_combo(T0); 1592 _ -> 1593 keep 1594 end; 1595 1596is_tweak_hotkey({view,Cmd}, #tweak{st=St0}) when Cmd =/= quick_preview -> 1597 St = wings_dl:map(fun (D, St1) -> 1598 end_drag(update, D, St1) % used to find mid tweak model data 1599 end, St0), 1600 wings_view:command(Cmd, St), 1601 wings_wm:dirty(), 1602 keep; 1603is_tweak_hotkey(_, T) -> 1604 case wings_io:is_key_pressed(?SDLK_SPACE) of 1605 true -> is_tweak_combo(T); 1606 false -> keep 1607 end. 1608 1609is_tweak_combo(#tweak{st=#st{selmode=body}}) -> 1610 keep; % Still haven't add Scale 1611is_tweak_combo(#tweak{mode=Mode,st=St0}=T) -> 1612 {B,X0,Y0} = wings_io:get_mouse_state(), 1613 Ctrl = wings_io:is_modkey_pressed(?CTRL_BITS), 1614 Shift = wings_io:is_modkey_pressed(?SHIFT_BITS), 1615 Alt = wings_io:is_modkey_pressed(?ALT_BITS), 1616 {_,TweakKeys} = wings_pref:get_value(tweak_prefs), 1617 case orddict:find({B,{Ctrl,Shift,Alt}},TweakKeys) of 1618 {ok, Mode} -> keep; 1619 {ok, NewMode} when element(1,Mode) =:= NewMode -> keep; 1620 {ok, NewMode} -> 1621 St = wings_dl:map(fun (D, _) -> 1622 update_drag(D,T) % used to find mid tweak model data 1623 end, St0), 1624 do_tweak_0(0, 0, 0, 0, {move,screen}), 1625 {X,Y} = wings_wm:screen2local({X0,Y0}), 1626 update_tweak_handler(T#tweak{mode=NewMode,st=St,ox=X,oy=Y,x=X,y=Y,cx=0,cy=0}); 1627 _ -> keep 1628 end. 1629 1630update_drag(#dlo{src_sel={Mode,Els},src_we=#we{id=Id},drag=#drag{mm=MM}}=D0, 1631 #tweak{st=#st{shapes=Shs0}=St0}=T) -> 1632 #dlo{src_we=We}=D1 = wings_draw:join(D0), 1633 Shs = gb_trees:update(Id, We, Shs0), 1634 St = St0#st{shapes=Shs}, 1635 Vs0 = sel_to_vs(Mode, gb_sets:to_list(Els), We), 1636 Center = wings_vertex:center(Vs0, We), 1637 {Vs,Magnet,VsDyn} = begin_magnet(T#tweak{st=St}, Vs0, Center, We), 1638 #dlo{src_we=We0}= D = wings_draw:split(D1, Vs, St), 1639 NewPst = set_edge_influence(Vs,VsDyn,We0), 1640 {D#dlo{src_we=We0#we{pst=NewPst},drag=#drag{vs=Vs0,pos0=Center,pos=Center,mag=Magnet,mm=MM}},St}; 1641update_drag(D,#tweak{st=St}) -> {D,St}. 1642 1643%%% 1644%%% XYZ Tweak Constraints 1645%%% 1646 1647actual_mode(Mode) -> 1648 axis_constraints(Mode). 1649 1650axis_constraints(Mode) -> %% Mode =:= move; Mode =:= scale 1651 case wings_pref:get_value(tweak_axis) of 1652 screen -> 1653 TKeys = wings_pref:get_value(tweak_xyz), % Toggled xyz constraints 1654 case TKeys of 1655 [true,false,false] -> {Mode, x}; 1656 [false,true,false] -> {Mode, y}; 1657 [false,false,true] -> {Mode, z}; 1658 [true,true,false] -> {Mode, xy}; 1659 [false,true,true] -> {Mode, yz}; 1660 [true,false,true] -> {Mode, zx}; 1661 [_,_,_] -> {Mode,screen} 1662 end; 1663 Other -> {Mode,Other} 1664 end. 1665 1666%%% 1667%%% Show Cursor 1668%%% 1669 1670%% After releasing lmb to conclude drag, unhide the cursor and make sure its 1671%% inside the window at the centre of the selection if possible. 1672show_cursor(_, #dlo{src_we=#we{id=Id}, drag={matrix,Pos,_,_}}) -> 1673 Matrices = wings_u:get_matrices(Id, original), 1674 {X0,Y0,_} = obj_to_screen(Matrices, Pos), 1675 show_cursor_1(X0,Y0); 1676show_cursor(El, #dlo{src_sel={Mode,_},src_we=#we{id=Id}=We,drag=#drag{mm=MM}}) -> 1677 Vs0 = case catch sel_to_vs(Mode, El, We) of 1678 VsList when is_list(VsList) -> VsList; 1679 _ -> crash_the_next_check_too 1680 end, 1681 Center = case catch wings_vertex:center(Vs0, We) of 1682 {X,Y,Z}=C when C =:= {float(X), float(Y), float(Z)} -> C; 1683 _ -> 1684 {{_,_,C},_} = wings_pref:get_value(tweak_geo_point), 1685 C 1686 end, 1687 Matrices = wings_u:get_matrices(Id, MM), 1688 {X0,Y0,_} = obj_to_screen(Matrices, Center), 1689 show_cursor_1(X0,Y0); 1690show_cursor(_,_) -> 1691 {{_,_,Center},{_,Mir,{Id,_}}} = wings_pref:get_value(tweak_geo_point), 1692 Matrices = wings_u:get_matrices(Id, Mir), 1693 {X0,Y0,_} = obj_to_screen(Matrices, Center), 1694 show_cursor_1(X0,Y0). 1695 1696show_cursor_1(X0,Y0) -> 1697 {W,H} = wings_wm:win_size(), 1698 {X1,Y1} = {trunc(X0), H - trunc(Y0)}, 1699 X = if X1 < 0 -> 20; 1700 X1 > W -> W-20; 1701 true -> X1 1702 end, 1703 Y = if Y1 < 0 -> 20; 1704 Y1 > H -> H-20; 1705 true -> Y1 1706 end, 1707 wings_wm:release_focus(), 1708 wings_io:ungrab(X,Y). 1709 1710%%% 1711%%% Main Tweak Menu 1712%%% 1713 1714menu(X, Y) -> 1715 Menu = menu(), 1716 wings_menu:popup_menu(X, Y, tweak, Menu). 1717 1718menu() -> 1719 ToggleHelp = ?__(1,"Tweak is a collection of tools for quickly adjusting a mesh."), 1720 Toggle = case wings_pref:get_value(tweak_active) of 1721 false -> ?__(2,"Enable Tweak"); 1722 true -> ?__(3,"Disable Tweak") 1723 end, 1724 [{Toggle,toggle_tweak,ToggleHelp}, 1725 separator, 1726 {?__(4,"Magnets"),{tweak_magnet, tweak_magnet_menu()}}, 1727 {?__(5,"Axis Constraints"),{axis_constraint, constraints_menu()}}, 1728 separator, 1729 tweak_menu_item(move, 1730 ?__(6,"Move selection relative to screen, or constrained to an axis.")), 1731 tweak_menu_item(move_normal, 1732 ?__(7,"Move selection along average normal.")), 1733 tweak_menu_item(scale, 1734 ?__(8,"Scale selection relative to screen, or constrained to an axis.")), 1735 tweak_menu_item(scale_uniform, 1736 ?__(9,"Scale elements uniformly from the selection center.")), 1737 tweak_menu_item(relax, 1738 ?__(10,"Relax selection toward the average plane of neighbors.")), 1739 tweak_menu_item(slide, 1740 ?__(11,"Slide elements along adjacent edges.")), 1741 separator, 1742 {?__(13,"Tweak Preferences"),tweak_preferences, 1743 ?__(14,"Preferences for Tweak")}]. 1744 1745tweak_menu_item(Mode, Help0) -> 1746 Set = {bold,?__(2,"Set: ")}, 1747 Swap = {bold,?__(3,"Swap: ")}, 1748 SetHelp = [Set, ?__(4,"Hold modifiers and/or press any mouse button.")], 1749 SwapHelp = [Swap,?__(5,"Press another tool's key binding.")], 1750 DelHelp = [{bold, ?__(6,"Unbind: ")}, button(3)], 1751 Help = wings_msg:join([Help0, SetHelp, SwapHelp, DelHelp]), 1752 {mode(Mode),tweak_menu_fun(Mode),Help, keys_combo(Mode)}. 1753 1754mode({move,_}) -> 1755 ?__(1,"Move"); 1756mode(move) -> 1757 ?__(1,"Move"); 1758mode({move_normal,_}) -> 1759 ?__(2,"Move Normal"); 1760mode(move_normal) -> 1761 ?__(2,"Move Normal"); 1762mode({scale,_}) -> 1763 ?__(3,"Scale"); 1764mode(scale) -> 1765 ?__(3,"Scale"); 1766mode({scale_uniform,_}) -> 1767 ?__(4,"Scale Uniform"); 1768mode(scale_uniform) -> 1769 ?__(4,"Scale Uniform"); 1770mode(relax) -> 1771 ?__(5,"Relax"); 1772mode(slide) -> 1773 ?__(6,"Slide"); 1774mode(_) -> 1775 init(). 1776 1777tweak_menu_fun(Mode) -> 1778 fun 1779 (B,_) when B < 4 -> {tweak,{Mode,B}}; 1780 (_,_) -> ignore 1781 end. 1782 1783keys_combo(Key) -> 1784 TweakKeys = tweak_keys(), 1785 case lists:keyfind(Key,2,TweakKeys) of 1786 false -> []; 1787 {{Button,Modifiers},_} -> 1788 B = button(Button), 1789 Mod = modifier(Modifiers), 1790 [{hotkey, [Mod,B]}] 1791 end. 1792 1793%%% 1794%%% Constraints Menu 1795%%% 1796 1797constraints_menu() -> 1798 [Fx,Fy,Fz] = wings_pref:get_value(tweak_xyz), 1799 TwAx = wings_pref:get_value(tweak_axis), 1800 TwPt = wings_pref:get_value(tweak_point), 1801 1802 N = normal, 1803 D = default_axis, 1804 1805 NHelp = ?__(1,"Locks movement to the selection's Normal."), 1806 DaHelp = ?__(2,"Locks movement to the Default Axis."), 1807 RHelp = ?__(3,"Locks movement to the Radial of the current Tweak Axis."), 1808 X = wings_s:dir(x), 1809 Y = wings_s:dir(y), 1810 Z = wings_s:dir(z), 1811 Help = ?__(5,"Locks movement to the ~s axis."), 1812 [{X,x,wings_util:format(Help, [X]),crossmark(Fx)}, 1813 {Y,y,wings_util:format(Help, [Y]),crossmark(Fy)}, 1814 {Z,z,wings_util:format(Help, [Z]),crossmark(Fz)}, 1815 separator, 1816 {wings_s:dir(N), N, NHelp, crossmark(TwAx =:= N)}, 1817 {axis_string(D), D, DaHelp, crossmark(TwAx =:= D)}, 1818 {axis_string(element_normal),element_normal, 1819 ?__(6,"Constrained movement to normal of element marked by Tweak Vector."), 1820 crossmark(TwAx =:= element_normal)}, 1821 {axis_string(element_normal_edge),element_normal_edge, 1822 ?__(6,"Constrained movement to normal of element marked by Tweak Vector."), 1823 crossmark(TwAx =:= element_normal_edge)}, 1824 separator, 1825 {axis_string(radial), radial, RHelp, crossmark(wings_pref:get_value(tweak_radial))}, 1826 separator, 1827 {axis_string(from_cursor),from_cursor,?__(7,"Scale from mouse cursor."), 1828 crossmark(TwPt =:= from_cursor)}, 1829 {axis_string(from_element),from_element,?__(8,"Scale from element marked by Tweak Vector."), 1830 crossmark(TwPt =:= from_element)}, 1831 {axis_string(from_default),from_default,?__(9,"Scale from Default Point."), 1832 crossmark(TwPt =:= from_default)}, 1833 separator, 1834 {?__(10,"Clear Constraints"),clear,?__(11,"Clear all locked axes.")}]. 1835 1836%%% 1837%%% Magnet Menu 1838%%% 1839 1840tweak_magnet_menu() -> 1841 {Mag, MagType, _} = wings_pref:get_value(tweak_magnet), 1842 Help = ?__(3,"Tweak magnets are similar to 'soft selection'."), 1843 Toggle = if 1844 Mag -> ?__(1,"Disable Magnet"); 1845 true -> ?__(2,"Enable Magnet") 1846 end, 1847 MagAdj = {?__(5,"Radius Adjust Key"), mag_adjust, 1848 ?__(6,"Press [Insert] to customize hotkey for adjusting the magnet radius. ") ++ 1849 ?__(7,"Without a hotkey assigned, the magnet radius is adjusted by holding [Alt].")}, 1850 Reset = {?__(8,"Reset Radius"), reset_radius,?__(9,"Reset the magnet radius to 1.0")}, 1851 Cycle = {?__(10,"Next Magnet Type"), cycle_magnet, ?__(11,"Can be hotkeyed to cycle through the magnets.")}, 1852 Dome = {magnet_type(dome), dome, mag_thelp(dome), 1853 crossmark({dome, MagType})}, 1854 Straight = {magnet_type(straight), straight, mag_thelp(straight), 1855 crossmark({straight, MagType})}, 1856 Spike = {magnet_type(spike), spike, mag_thelp(spike), 1857 crossmark({spike, MagType})}, 1858 [{Toggle, toggle_magnet, Help}, separator, 1859 Dome, Straight, Spike, separator, 1860 Reset, separator, 1861 Cycle, MagAdj]. 1862 1863magnet_type(dome) -> ?__(1,"Dome"); 1864magnet_type(straight) -> ?__(2,"Straight"); 1865magnet_type(spike) -> ?__(3,"Spike"). 1866 1867mag_thelp(dome) -> ?__(1,"This magnet pulls and pushes geometry with an even and rounded effect."); 1868mag_thelp(straight) -> ?__(2,"This magnet pulls and pushes geometry with a straight effect."); 1869mag_thelp(spike) -> ?__(3,"This magnet pulls and pushes geometry out to a sharp point."). 1870 1871cycle_magnet() -> 1872 {MagBool,MagType,_} = wings_pref:get_value(tweak_magnet), 1873 case MagType of 1874 dome -> straight; 1875 straight -> spike; 1876 spike when MagBool =:= false -> dome; 1877 spike -> off 1878 end. 1879 1880crossmark({MagType, MagType}) -> [{crossmark, true}]; 1881crossmark({_, MagType}) when is_atom(MagType)-> [{crossmark, false}]; 1882crossmark("none") -> [{crossmark, false}]; 1883crossmark(false) -> [{crossmark, false}]; 1884crossmark(_) -> [{crossmark, true}]. 1885 1886 1887%%% 1888%%% Tweak Commands 1889%%% 1890 1891command(toggle_tweak, St) -> 1892 Pref = wings_pref:get_value(tweak_active), 1893 wings_pref:set_value(tweak_active, not Pref), 1894 wings_wm:send({tweak,tweak_palette}, update_palette), 1895 case wings_wm:is_geom() of 1896 true -> wings:info_line(); 1897 false -> ok 1898 end, 1899 St; 1900command({tweak_magnet,toggle_magnet}, St) -> 1901 toggle_magnet(), 1902 case wings_wm:is_geom() of 1903 true -> wings:info_line(); 1904 false -> ok 1905 end, 1906 wings_wm:send({tweak,tweak_magnet}, update_palette), 1907 St; 1908command({tweak_magnet,reset_radius}, St) -> 1909 Pref = wings_pref:get_value(tweak_magnet), 1910 wings_pref:set_value(tweak_magnet,setelement(3,Pref,1.0)), 1911 St; 1912command({tweak_magnet,cycle_magnet}, St) -> 1913 NewMag = cycle_magnet(), 1914 set_magnet_type(NewMag), 1915 wings_wm:send({tweak,tweak_magnet}, update_palette), 1916 case wings_wm:is_geom() of 1917 true -> wings:info_line(); 1918 false -> ok 1919 end, 1920 St; 1921command({tweak_magnet,mag_adjust}, St) -> 1922 St; 1923command({tweak_magnet,MagType}, St) -> 1924 set_magnet_type(MagType), 1925 wings_wm:send({tweak,tweak_magnet},update_palette), 1926 case wings_wm:is_geom() of 1927 true -> wings:info_line(); 1928 false -> ok 1929 end, 1930 St; 1931command(tweak_preferences, St) -> 1932 tweak_preferences_dialog(St); 1933command({axis_constraint, Axis}, St) -> 1934 toggle_axis(Axis), 1935 wings_wm:send({tweak,axis_constraint},update_palette), 1936 St; 1937command({set_tweak_pref,Mode,B,Mods}, St) when B =< 3-> 1938 set_tweak_pref(Mode, B, Mods), 1939 wings_wm:send({tweak,tweak_palette},update_palette), 1940 St; 1941command({tweak_palette, Cmd}, St) -> 1942 command(Cmd, St); 1943command({Mode,B}, St) when B =< 3-> 1944 Ctrl = wx_misc:getKeyState(?WXK_CONTROL), 1945 Shift = wx_misc:getKeyState(?WXK_SHIFT), 1946 Alt = wx_misc:getKeyState(?WXK_ALT), 1947 set_tweak_pref(Mode, B, {Ctrl, Shift, Alt}), 1948 wings_wm:send({tweak,tweak_palette},update_palette), 1949 St; 1950command(Mode, St) when Mode =:= move; Mode =:= move_normal; Mode =:= scale; 1951 Mode =:= scale_uniform; Mode =:= slide; Mode =:= relax -> 1952 set_tweak_pref(Mode, 1, {false, false, false}), 1953 wings_wm:send({tweak,tweak_palette},update_palette), 1954 St; 1955command(_What,_) -> 1956 io:format("Skipping ~p~n",[_What]), 1957 next. 1958 1959%%% 1960%%% Process Commands 1961%%% 1962 1963%% Delete Tweak bind key 1964set_tweak_pref(Mode, 3, {false,false,false}) -> 1965 Cam = wings_pref:get_value(camera_mode), 1966 TweakKeys = case wings_pref:get_value(tweak_prefs) of 1967 {Cam,TweakKeys0} -> 1968 lists:keydelete(Mode,2,TweakKeys0); 1969 _ -> set_tweak_keys(Cam) 1970 end, 1971 wings_pref:set_value(tweak_prefs, {Cam,TweakKeys}); 1972%% Set new Tweak bind key or swap functions if the bind keys already exist 1973set_tweak_pref(Mode, B, Modifiers) -> 1974 Cam = wings_pref:get_value(camera_mode), 1975 exceptions(Cam,B,Modifiers), 1976 Keys1 = tweak_keys(), 1977 Keys = case lists:keyfind(Mode,2,Keys1) of 1978 false -> 1979 orddict:store({B,Modifiers},Mode,Keys1); 1980 {OldKey,Mode} -> 1981 Keys2 = case orddict:find({B,Modifiers},Keys1) of 1982 {ok, OldMode} -> orddict:store(OldKey,OldMode,Keys1); 1983 error -> lists:keydelete(Mode,2,Keys1) 1984 end, 1985 orddict:store({B,Modifiers},Mode,Keys2) 1986 end, 1987 wings_pref:set_value(tweak_prefs, {Cam,Keys}). 1988 1989%% A bind keys that conflict with either menus or camera buttons are listed here 1990exceptions(wings_cam,2,{false,false,false}) -> cam_conflict(); 1991exceptions(mirai,2,{false,false,false}) -> cam_conflict(); 1992exceptions(nendo,2,{false,false,false}) -> cam_conflict(); 1993exceptions(maya,_,{false,false,true}) -> cam_conflict(); 1994exceptions(tds,2,{false,false,false}) -> cam_conflict(); 1995exceptions(tds,2,{false,false,true}) -> cam_conflict(); 1996exceptions(tds,2,{true,false,true}) -> cam_conflict(); 1997exceptions(blender,2,{false,false,false}) -> cam_conflict(); 1998exceptions(blender,2,{false,true,false}) -> cam_conflict(); 1999exceptions(blender,2,{true,false,false}) -> cam_conflict(); 2000exceptions(mb,1,{true,true,false}) -> cam_conflict(); 2001exceptions(mb,1,{true,false,false}) -> cam_conflict(); 2002exceptions(mb,1,{false,true,false}) -> cam_conflict(); 2003exceptions(sketchup,2,{false,false,false}) -> cam_conflict(); 2004exceptions(sketchup,2,{false,true,false}) -> cam_conflict(); 2005exceptions(_,3,{true,false,false}) -> menu_conflict(); 2006exceptions(_,_,_) -> ok. 2007 2008-spec menu_conflict() -> no_return(). 2009menu_conflict() -> 2010 wings_u:error_msg(?__(1,"Key combo was not assigned.\n 2011 Those keys would conflict with the right click Tweak menu.")). 2012 2013-spec cam_conflict() -> no_return(). 2014cam_conflict() -> 2015 wings_u:error_msg(?__(1,"Key combo was not assigned.\n 2016 That key combination would conflict with the Camera buttons")). 2017 2018%%% 2019%%% Toggle Draw Routines 2020%%% 2021 2022%% For things like Tweak Vectors. Using props should work per window. 2023toggle_draw(Bool) -> 2024 wings_wm:set_prop(tweak_draw, Bool). 2025 2026%%% 2027%%% Toggle Axes 2028%%% 2029 2030toggle_axis(Axis) -> 2031 toggle_axis_1(Axis). 2032 2033toggle_axis_1([X,Y,Z]) -> 2034 wings_pref:set_value(tweak_axis, screen), 2035 wings_pref:set_value(tweak_xyz, [X,Y,Z]); 2036toggle_axis_1(clear) -> 2037 wings_pref:set_value(tweak_xyz,[false,false,false]), 2038 wings_pref:set_value(tweak_point,none), 2039 wings_pref:set_value(tweak_axis, screen), 2040 wings_pref:set_value(tweak_radial, false); 2041toggle_axis_1(P) when P =:= from_cursor; P =:= from_element; P =:= from_default; P =:= none -> 2042 Pref = case wings_pref:get_value(tweak_point) of 2043 P -> none; 2044 _ -> P 2045 end, 2046 wings_pref:set_value(tweak_point,Pref); 2047toggle_axis_1(Axis) when Axis =:= x; Axis =:= y; Axis =:= z-> 2048 case wings_pref:get_value(tweak_xyz) of 2049 [X,Y,Z] -> 2050 NewPref = case Axis of 2051 x -> [not X, Y, Z]; 2052 y -> [X, not Y, Z]; 2053 z -> [X, Y, not Z] 2054 end, 2055 wings_pref:set_value(tweak_axis, screen), 2056 wings_pref:set_value(tweak_xyz, NewPref); 2057 _ -> ok 2058 end; 2059toggle_axis_1(radial) -> 2060 case wings_pref:get_value(tweak_radial) of 2061 true -> 2062 wings_pref:set_value(tweak_radial, false); 2063 false -> 2064 wings_pref:set_value(tweak_radial, true), 2065 case wings_pref:get_value(tweak_axis) of 2066 uniform -> 2067 wings_pref:set_value(tweak_axis, screen); 2068 _ -> ok 2069 end 2070 end; 2071toggle_axis_1(Axis) -> 2072 wings_pref:set_value(tweak_xyz,[false,false,false]), 2073 case wings_pref:get_value(tweak_axis) of 2074 Axis -> 2075 wings_pref:set_value(tweak_axis,screen); 2076 _otherwise -> 2077 wings_pref:set_value(tweak_axis, Axis) 2078 end. 2079 2080%%% 2081%%% Toggle Magnets 2082%%% 2083 2084toggle_magnet() -> 2085 {Mag, MagType, MagRad} = wings_pref:get_value(tweak_magnet), 2086 wings_pref:set_value(tweak_magnet,{not Mag, MagType, MagRad}). 2087 2088set_magnet_type(off) -> %% for cycling magnets 2089 {_, MagType, MagRad} = wings_pref:get_value(tweak_magnet), 2090 wings_pref:set_value(tweak_magnet,{false, MagType, MagRad}); 2091set_magnet_type(MagType) -> 2092 {_, _, MagRad} = wings_pref:get_value(tweak_magnet), 2093 wings_pref:set_value(tweak_magnet,{true, MagType, MagRad}). 2094 2095save_magnet_prefs(#tweak{magnet=Mag, mag_type=MT, mag_rad=MagR}) -> 2096 wings_pref:set_value(tweak_magnet, {Mag, MT, MagR}). 2097 2098%%% 2099%%% Tweak Preference Dialog 2100%%% 2101 2102tweak_preferences_dialog(St) -> 2103 ClkSpd = wings_pref:get_value(tweak_click_speed)/100000, 2104 MagAdj = wings_pref:get_value(tweak_mag_adj_sensitivity)*100.0, 2105 VecSize = wings_pref:get_value(tweak_vector_size), 2106 VecWidth = wings_pref:get_value(tweak_vector_width), 2107 TweakSpeed = wings_pref:get_value(tweak_speed), 2108 Menu = [{vframe, 2109 [{hframe,[{slider,{text,ClkSpd,[{key,tweak_click_speed},{range,{1.0,5.0}}]}}], 2110 [{title,?__(1,"Click Speed for Select/Deselect")}]}, 2111 2112 {vframe,[{hframe,[{slider,{text,TweakSpeed,[{key,tweak_speed},{range,{0.01,1.0}}]}}]}, 2113 {label,?__(2,"Lower to increase control or raise to increase speed.")}], 2114 [{title,?__(3,"Tweak Speed (Drag Response)")}]}, 2115 2116 {hframe,[{slider,{text,MagAdj,[{key,tweak_mag_adj_sensitivity},{range,{0.1,2.0}}]}}], 2117 [{title,?__(4,"Magnet Radius Adjustment Sensitivity")}]}, 2118 2119 {label_column,[{color,?__(5,"Magnet Radius Display Color"),tweak_magnet_color}]}, 2120 {?__(11,"Show Magnet influence"),tweak_magnet_influence}, 2121 {hframe, 2122 [{vframe,[{label,?__(6,"Length")}, 2123 {label,?__(7,"Width")}, 2124 {label,?__(8,"Color")}]}, 2125 {vframe,[{text,VecSize,[{key,tweak_vector_size},{range,{0.1,10.0}}]}, 2126 {text,VecWidth,[{key,tweak_vector_width},{range,{1.0,10.0}}]}, 2127 {color,tweak_vector_color}]}], 2128 [{title,?__(9,"Tweak Vector")}]} 2129 ]}], 2130 PrefQs = [{Lbl, make_query(Ps)} || {Lbl, Ps} <- Menu], 2131 wings_dialog:dialog(?__(10,"Tweak Preferences"), PrefQs, 2132 fun(Result) -> set_values(Result), St end). 2133 2134make_query([_|_]=List) -> 2135 [make_query(El) || El <- List]; 2136make_query({[_|_]=Str,Key}) -> 2137 case wings_pref:get_value(Key) of 2138 Def when Def =:= true; Def =:= false -> 2139 {Str,Def,[{key,Key}]}; 2140 Def -> 2141 {Str,{text,Def,[{key,Key}]}} 2142 end; 2143make_query({color,Key}) -> 2144 Def = wings_pref:get_value(Key), 2145 {color,Def,[{key,Key}]}; 2146make_query({color,[_|_]=Str,Key}) -> 2147 Def = wings_pref:get_value(Key), 2148 {Str,{color,Def,[{key,Key}]}}; 2149make_query(Tuple) when is_tuple(Tuple) -> 2150 list_to_tuple([make_query(El) || El <- tuple_to_list(Tuple)]); 2151make_query(Other) -> Other. 2152 2153set_values([{tweak_click_speed = Key, Value}|Result]) -> 2154 wings_pref:set_value(Key, Value*100000), 2155 set_values(Result); 2156set_values([{tweak_mag_adj_sensitivity = Key, Value}|Result]) -> 2157 wings_pref:set_value(Key, Value/100.0), 2158 set_values(Result); 2159 2160set_values([{Key,Value}|Result]) -> 2161 wings_pref:set_value(Key, Value), 2162 set_values(Result); 2163set_values([]) -> ok. 2164 2165%%% 2166%%% Tweak Mode Active Info Line 2167%%% 2168 2169tweak_info_line() -> 2170 M1 = ?__(1,"L: Click Select"), 2171 M2 = ?__(2,"LL: Paint Select"), 2172 M3 = wings_msg:button_format([], [], ?__(4,"Menu")), 2173 M4 = wings_msg:rmb_format(?__(3,"Tweak Menu")), 2174 Message = wings_msg:join([M1,M2,M3,M4]), 2175 wings_wm:message(Message). 2176 2177%% Go through list of user defined keys and tweak tool, and build an info line 2178tweak_keys_info() -> 2179 This = wings_wm:is_geom(), 2180 case wings_wm:lookup_prop(tweak_draw) of 2181 {value,true} when This -> 2182 case wings_pref:get_value(tweak_active) of 2183 true -> 2184 Keys = tweak_keys(), 2185 Msg = compose_info_line(Keys), 2186 draw_tweak_info_line(Msg); 2187 _ -> ok 2188 end; 2189 _ -> ok 2190 end. 2191 2192compose_info_line([{{Button,Modifiers},Mode}|D]) -> 2193 Mod = modifier(Modifiers), 2194 B = button(Button) ++ ": ", 2195 case mode(Mode) of 2196 true -> compose_info_line(D); 2197 M -> 2198 Message = [Mod,B,M], 2199 wings_msg:join([Message,compose_info_line(D)]) 2200 end; 2201compose_info_line([]) -> []. 2202 2203button(1) -> wings_s:lmb(); 2204button(2) -> wings_s:mmb(); 2205button(3) -> wings_s:rmb(). 2206 2207modifier({Ctrl,Shift,Alt}) -> 2208 C = if Ctrl -> [wings_s:key(ctrl),"+"]; true -> [] end, 2209 S = if Shift -> [wings_s:key(shift),"+"]; true -> [] end, 2210 A = if Alt -> [wings_s:key(alt),"+"]; true -> [] end, 2211 [C,S,A]. 2212 2213draw_tweak_info_line(Msg) -> 2214 {_,H} = wings_wm:win_size(), 2215 wings_io:info(0, H-?LINE_HEIGHT-3, wings_msg:join([Msg])). 2216 2217%%% 2218%%% XYZ Constraints Info line while Tweaking 2219%%% 2220 2221statusbar() -> 2222 Active = wings_pref:get_value(tweak_active), 2223 Draw = wings_wm:lookup_prop(tweak_draw) =:= {value,true}, 2224 Geom = wings_wm:is_geom(), 2225 case Active andalso Draw andalso Geom of 2226 true -> 2227 TweakAxis = wings_pref:get_value(tweak_axis), 2228 TweakPoint = wings_pref:get_value(tweak_point), 2229 RadialAxis = wings_pref:get_value(tweak_radial), 2230 Hotkeys = wings_hotkey:matching([axis_constraint,tweak]), 2231 TKeys = wings_pref:get_value(tweak_xyz), 2232 XYZHelp = xyzkey_help(TKeys, Hotkeys), 2233 Normal = toggle_bold(TweakAxis, normal, Hotkeys), 2234 DefaultAxis = toggle_bold(TweakAxis, default_axis, Hotkeys), 2235 ElementNormal = toggle_bold(TweakAxis, element_normal, Hotkeys), 2236 ElementNormalE = toggle_bold(TweakAxis, element_normal_edge, Hotkeys), 2237 Radial = toggle_bold(RadialAxis, radial, Hotkeys), 2238 FromCursor = toggle_bold(TweakPoint, from_cursor, Hotkeys), 2239 FromElement = toggle_bold(TweakPoint, from_element, Hotkeys), 2240 DefaultPoint = toggle_bold(TweakPoint, from_default, Hotkeys), 2241 Help = wings_msg:join([XYZHelp,Normal,DefaultAxis,ElementNormal, 2242 ElementNormalE,Radial, 2243 FromCursor,FromElement,DefaultPoint]), 2244 wings_msg:join([Help]); 2245 false -> [] 2246 end. 2247 2248toggle_bold(Axis0, Axis, Hotkeys) -> 2249 case matching_hotkey(Axis, Hotkeys) of 2250 [] -> []; 2251 {none,Name} when Axis0 =:= from_default andalso Axis0 =:= Axis; 2252 Axis0 =:= from_cursor andalso Axis0 =:= Axis; 2253 Axis0 =:= from_element andalso Axis0 =:= Axis -> 2254 [?__(1,"Active Point: "),{bold,Name}]; 2255 {none,Name} when Axis0 =:= Axis; Axis0 -> 2256 [?__(2,"Active Axis: "),{bold,Name}]; 2257 Keys when Axis0 =:= Axis; Axis0 -> 2258 [wings_hotkey:format_hotkey(Keys, pretty),": ", {bold,axis_string(Axis)}]; 2259 {none,_} -> 2260 []; 2261 Keys -> 2262 [wings_hotkey:format_hotkey(Keys, pretty),": ", axis_string(Axis)] 2263 end. 2264 2265xyzkey_help({_,_}, _) -> []; 2266xyzkey_help([X,Y,Z], Hotkeys) -> 2267 XStr = axis_string(x), 2268 YStr = axis_string(y), 2269 ZStr = axis_string(z), 2270 StrX = if X -> [{bold,XStr}]; true -> XStr end, 2271 StrY = if Y -> [{bold,YStr}]; true -> YStr end, 2272 StrZ = if Z -> [{bold,ZStr}]; true -> ZStr end, 2273 XKeys = case matching_hotkey(x, Hotkeys) of 2274 {none,_} when X -> [?__(1,"Active Axis: "),StrX]; 2275 {none,_} -> []; 2276 Xkey -> ["[",wings_hotkey:format_hotkey(Xkey, pretty),"]: ",StrX] 2277 end, 2278 YKeys = case matching_hotkey(y, Hotkeys) of 2279 {none,_} when Y -> [?__(1,"Active Axis: "),StrY]; 2280 {none,_} -> []; 2281 Ykey -> ["[",wings_hotkey:format_hotkey(Ykey, pretty),"]: ",StrY] 2282 end, 2283 ZKeys = case matching_hotkey(z, Hotkeys) of 2284 {none,_} when Z -> [?__(1,"Active Axis: "),StrZ]; 2285 {none,_} -> []; 2286 Zkey -> ["[",wings_hotkey:format_hotkey(Zkey, pretty),"]: ",StrZ] 2287 end, 2288 wings_msg:join([XKeys,YKeys,ZKeys]). 2289 2290matching_hotkey(Match, Hotkeys) -> 2291 case lists:keyfind(Match, 1, Hotkeys) of 2292 {_,Keys} -> Keys; 2293 false -> {none,axis_string(Match)} 2294 end. 2295 2296axis_string(Axis) -> 2297 case Axis of 2298 default_axis -> ?__(1,"Default Axis"); 2299 element_normal -> ?__(2,"Element Normal"); 2300 element_normal_edge -> ?__(3,"Element Normal (edges)"); 2301 radial -> ?__(4,"Radial"); 2302 from_cursor -> ?__(5,"From Cursor"); 2303 from_element -> ?__(6,"From Element"); 2304 from_default -> ?__(7,"From Default Point"); 2305 Other -> wings_s:dir(Other) 2306 end. 2307 2308%%% 2309%%% Info Line while Tweaking 2310%%% 2311 2312info_line(Mode) -> 2313 M = mode(Mode), 2314 Cam = camera_msg(), 2315 Spc = spacebar_msg(), 2316 Extra = extra_msg(Mode), 2317 Help = wings_msg:join([M, Cam, Spc, Extra]), 2318 wings_wm:message(Help). 2319 2320camera_msg() -> 2321 ?__(1,"[C]: Tumble camera [S]: Pan [D]: Dolly"). 2322 2323spacebar_msg() -> 2324 ?__(1,"[Space]: Switch to Tweak tool assigned to L"). 2325 2326extra_msg(slide) -> 2327 "[F1]: " ++ ?__(1,"Slide Collapse"); 2328extra_msg(_) -> []. 2329 2330%%% 2331%%% Right Side Info Line including Magnets and Tweak: Disabled 2332%%% 2333 2334tweak_disabled_msg() -> 2335 wings_wm:message_right(?__(1,"Tweak: Disabled")). 2336 2337tweak_magnet_help() -> 2338 {Mag, MagType, _} = wings_pref:get_value(tweak_magnet), 2339 Message = if 2340 Mag -> 2341 Hotkeys = wings_hotkey:matching([tweak_magnet,tweak]), 2342 MKey = case lists:keyfind(mag_adjust, 1, Hotkeys) of 2343 {_, Keys} -> "[" ++ wings_hotkey:format_hotkey(Keys, pretty) ++ "]"; 2344 false -> wings_s:key(alt) 2345 end, 2346 M1 = [?__(1,"Magnet: "),magnet_type(MagType)], 2347 M2 = [MKey, ?__(3,"+Drag: "), ?__(4,"Adjust Radius")], 2348 wings_msg:join([M1,M2]); 2349 true -> ?__(2,"Magnet: Off") 2350 end, 2351 wings_wm:message_right(Message). 2352tweak_magnet_radius_help(true) -> 2353 wings_wm:message(?__(1,"Drag right to increase and left to decrease the magnet radius.")); 2354tweak_magnet_radius_help(false) -> 2355 wings_wm:message(?__(2,"Magnet is currently off.")). 2356 2357%%% 2358%%% Support for highlighting the magnet influence 2359%%% 2360 2361%% This function will clean the vertices influence information when the list is empty or 2362%% it will add the vertices influence information to Pst field of the we# 2363set_edge_influence([],_,#we{pst=Pst}) -> 2364 remove_pst(Pst); 2365set_edge_influence(Vs,VsDyn,#we{pst=Pst,es=Etab}=We) -> 2366 case wings_pref:get_value(tweak_magnet_influence) of 2367 true -> 2368 ColFrom = col_to_vec(wings_pref:get_value(edge_color)), 2369 ColTo = col_to_vec(wings_pref:get_value(tweak_magnet_color)), 2370 Edges = wings_edge:from_vs(Vs,We), 2371 EdDyn = to_edges_raw({ColFrom,ColTo},Edges,VsDyn,Etab), 2372 add_pst(EdDyn,Pst); 2373 _ -> 2374 Pst 2375 end. 2376 2377%% It adds the plugin functionality 2378add_pst(InfData,Pst) -> 2379 case gb_trees:lookup(?MODULE, Pst) of 2380 none -> 2381 Data = gb_trees:empty(), 2382 NewData = gb_trees:insert(edge_info,InfData,Data), 2383 gb_trees:insert(?MODULE,NewData,Pst); 2384 {_,Data} -> 2385 NewData = gb_trees:enter(edge_info,InfData,Data), 2386 gb_trees:update(?MODULE,NewData,Pst) 2387 end. 2388 2389%% It removes the plugin functionality 2390remove_pst(none) -> none; 2391remove_pst(Pst) -> 2392 gb_trees:delete_any(?MODULE,Pst). 2393 2394%%% 2395%%% Functions of general purpose 2396%%% 2397to_edges_raw(_, [], _ , _) -> {[],<<>>}; 2398to_edges_raw(_, _, [] , _) -> {[],<<>>}; 2399to_edges_raw({ColFrom,ColTo}, Edges, VsDyn, Etab) -> 2400 ColRange=e3d_vec:sub(ColTo,ColFrom), 2401 to_edges_raw_1(Edges, ColFrom, ColRange, VsDyn, Etab, {[],<<>>}). 2402 2403to_edges_raw_1([], _, _, _, _, Acc) -> Acc; 2404to_edges_raw_1([Edge|Edges], Col, Range, VsDyn, Etab, {VAcc,ClBin0}) -> 2405 #edge{vs=Va0,ve=Vb0} = array:get(Edge, Etab), 2406 Infa = get_vs_influence(Va0,VsDyn), 2407 Infb = get_vs_influence(Vb0,VsDyn), 2408 {R1,G1,B1} = color_gradient(Col,Range,Infa), 2409 {R2,G2,B2} = color_gradient(Col,Range,Infb), 2410 ClBin = <<R1:?F32,G1:?F32,B1:?F32,R2:?F32,G2:?F32,B2:?F32,ClBin0/binary>>, 2411 VsPair={Va0,Vb0}, 2412 to_edges_raw_1(Edges, Col, Range, VsDyn, Etab, {[VsPair|VAcc],ClBin}). 2413 2414get_vs_influence(V, VsDyn) -> 2415 case lists:keysearch(V, 1, VsDyn) of 2416 false -> 0.0; 2417 {_, {_,Value}} -> Value 2418 end. 2419 2420%%% 2421%%% Functions to produce the visual effect (inspired by wpc_magnet_mask) 2422%%% 2423 2424update_dlist({edge_info,{EdList,ClBin}}, 2425 #dlo{plugins=Pdl,src_we=#we{vp=Vtab}}=D, _) -> 2426 Key = ?MODULE, 2427 case EdList of 2428 [] -> 2429 D#dlo{plugins=[{Key,none}|Pdl]}; 2430 _ -> 2431 Draw = edge_fun(EdList, ClBin, Vtab), 2432 D#dlo{plugins=[{Key,Draw}|Pdl]} 2433 end. 2434 2435edge_fun(EdList, ClBin, Vtab) -> 2436 EdBin = pump_edges(EdList,Vtab), 2437 [VboEs,VboCl] = gl:genBuffers(2), 2438 gl:bindBuffer(?GL_ARRAY_BUFFER, VboEs), 2439 gl:bufferData(?GL_ARRAY_BUFFER, byte_size(EdBin), EdBin, ?GL_STATIC_DRAW), 2440 gl:bindBuffer(?GL_ARRAY_BUFFER, VboCl), 2441 gl:bufferData(?GL_ARRAY_BUFFER, byte_size(ClBin), ClBin, ?GL_STATIC_DRAW), 2442 gl:bindBuffer(?GL_ARRAY_BUFFER, 0), 2443 N = byte_size(EdBin) div 12, 2444 D = fun(RS) -> 2445 gl:depthFunc(?GL_LEQUAL), 2446 gl:bindBuffer(?GL_ARRAY_BUFFER, VboEs), 2447 gl:vertexPointer(3, ?GL_FLOAT, 0, 0), 2448 gl:bindBuffer(?GL_ARRAY_BUFFER, VboCl), 2449 gl:colorPointer(3, ?GL_FLOAT, 0, 0), 2450 gl:bindBuffer(?GL_ARRAY_BUFFER, 0), 2451 gl:enableClientState(?GL_COLOR_ARRAY), 2452 gl:enableClientState(?GL_VERTEX_ARRAY), 2453 gl:drawArrays(?GL_LINES, 0, N), 2454 gl:disableClientState(?GL_VERTEX_ARRAY), 2455 gl:disableClientState(?GL_COLOR_ARRAY), 2456 gl:depthFunc(?GL_LESS), 2457 RS 2458 end, 2459 {call,D,[{vbo,VboEs},{vbo,VboCl}]}. 2460 2461%% pumping Lines 2462pump_edges(EdList, Vtab) -> 2463 pump_edges_1(EdList, Vtab, <<>>). 2464pump_edges_1([], _,Bin) -> Bin; 2465pump_edges_1([{Id1,Id2}|SegInf], Vtab, VsBin0) -> 2466 VsBin = 2467 case {array:get(Id1, Vtab),array:get(Id2, Vtab)} of 2468 {undefined,_} -> VsBin0; 2469 {_,undefined} -> VsBin0; 2470 {{X1,Y1,Z1},{X2,Y2,Z2}} -> 2471 <<VsBin0/binary,X1:?F32,Y1:?F32,Z1:?F32,X2:?F32,Y2:?F32,Z2:?F32>> 2472 end, 2473 pump_edges_1(SegInf,Vtab,VsBin). 2474 2475%% It'll will provide the vertices data for 'update_dlist' function 2476get_data(update_dlist, Data, Acc) -> % for draw lists 2477 case gb_trees:lookup(edge_info, Data) of 2478 none -> 2479 {ok, Acc}; 2480 {_,EdgeInfo} -> 2481 {ok, [{plugin, {?MODULE, {edge_info, EdgeInfo}}}|Acc]} 2482 end. 2483 2484%% It'll use the list prepared by 'update_dlist' function and then draw it (only for plain draw) 2485draw(plain, EdgeList, _D, SelMode, RS) -> 2486 gl:lineWidth(edge_width(SelMode)), 2487 wings_dl:call(EdgeList, RS); 2488draw(_,_,_,_, RS) -> RS. 2489 2490edge_width(edge) -> float(wings_pref:get_value(edge_width)); 2491edge_width(_) -> 1.0. 2492 2493col_to_vec({R,G,B}) when is_integer(R) -> {R/255.0,G/255.0,B/255.0}; 2494col_to_vec({_,_,_}=Col) -> Col; 2495col_to_vec({R,G,B,_}) when is_integer(R) -> col_to_vec({R,G,B}); 2496col_to_vec({R,G,B,_}) -> col_to_vec({R,G,B}). 2497 2498color_gradient(Cb, Cr, Perc) -> 2499 e3d_vec:add_prod(Cb, Cr, Perc). 2500 2501 2502%%% 2503help_msg() -> 2504 [help_msg_basic(), 2505 help_msg_magnet(), 2506 help_msg_axes(), 2507 help_msg_keys(), 2508 help_msg_using_keys(), 2509 help_msg_hotkeys(), 2510 help_msg_palette()]. 2511 2512help_msg_basic() -> 2513 [{bold,?__(1,"--What is Tweak?--")},"\n", 2514 ?__(2,"Tweak lets you edit a model quickly by clicking and dragging geometry."),"\n", 2515 ?__(3,"While Tweak is enabled, all the regular Wings commands are still available,"), 2516 ?__(4," but certain mouse buttons and modifier keys will activate the Tweak tools."),cr()]. 2517 2518help_msg_magnet() -> 2519 [{bold,?__(1,"--Magnets--")},"\n", 2520 {bullet, 2521 [?__(2,"Magnets allow for soft selection."), 2522 ?__(3,"There are 3 magnet types: Dome, Straight, and Spike."), 2523 ?__(4,"Adjust the Magnet's Radius by holding [Alt] and moving the mouse."), 2524 ?__(5,"Commands for switching magnets can be assigned to hotkeys in the Tweak Menu|Magnets."), 2525 ?__(6,"Magnet options can be set in the Tweak Preferences.")]}]. 2526 2527help_msg_axes() -> 2528 [{bold,?__(1,"--Axis Constraints--")},"\n", 2529 {bullet, 2530 [?__(2,"Axis Constraints only affect Tweak Move and Scale operations."), 2531 ?__(3,"[F1], [F2], and [F3] can be held to constrain movement to the 3 cardinal axes, X, Y, and Z."), 2532 ?__(4,"Click axis constraint hotkeys to toggle them."),"\n", 2533 ?__(5,"Any of the axes can be toggled to stay on via the Tweak Menu or the Tweak Palette."), 2534 ?__(6,"The From Element and From Point axes activate the Tweak Vector."), 2535 ?__(7,"Axes can be combined.")]}]. 2536 2537help_msg_keys() -> 2538 C = ", ", 2539 TweakTools = "("++mode(move)++C++mode(move_normal)++C++mode(scale)++C++ 2540 mode(scale_uniform)++C++mode(relax)++C++?__(3," and ")++ 2541 mode(slide)++")", 2542 Str = io_lib:format(?__(2,"Each of the Tweak Tools ~s can be assigned a modifier key combination (Ctrl, Shift, Alt)."),[TweakTools]), 2543 [{bold,?__(1,"--Assigning Tweak Keys--")},"\n", 2544 Str,"\n", 2545 ?__(4,"To assign a key combination to a Tweak Tool:"),"\n", 2546 ?__(5,"\t1) Open the Tweak Menu."),"\n", 2547 ?__(6,"\t2) Highlight one of the Tweak Tools."),"\n", 2548 ?__(7,"\t3) Press and hold the modifier key combination and/or press the activating mouse button."),cr()]. 2549 2550help_msg_using_keys() -> 2551 [{bold,?__(1,"--Using Tweak--")},"\n", 2552 ?__(2,"1) Enabled Tweak."),"\n", 2553 ?__(3,"2) Highlight or select one or more elements of geometry."),"\n", 2554 ?__(4,"3) Press the Tweak Key combination and associated mouse button to activate Tweak."),"\n", 2555 ?__(5,"4) Drag geometry to it new position."),"\n", 2556 ?__(6,"5) Release the mouse button to complete the Tweak."),cr(), 2557 ?__(7,"Releasing the Tweak Keys will not end the Tweak event."), 2558 ?__(8,"This allows other hotkeys to be used in mid-tweak to activate another Tweak tool, an axis constraint, aim the camera, or adjust the magnet radius."),"\n", 2559 ?__(9,"Pressing [C] in mid-tweak tumbles the camera."),"\n", 2560 ?__(10,"Pressing [S] or the Arrow Keys in mid-tweak pans the camera."),"\n", 2561 ?__(11,"Pressing [D] in mid-tweak dollies the camera."),"\n", 2562 ?__(12,"Pressing [Spacebar] in mid-tweak switches to the Tweak Tool assigned to the Lmb."),"\n", 2563 ?__(13,"Holding [F] while finalizing a Slide operation collapses all newly created short edges."),cr()]. 2564 2565help_msg_hotkeys() -> 2566 [{bold,?__(1,"--Hotkeyed Tweak Tools--")},"\n", 2567 ?__(2,"An alternative to usings Tweak Key combinations is to assign hotkeys to the various Tweak Tools usings the [Insert] method."),"\n", 2568 ?__(3,"Pressing the hotkey assigns that Tweak Tool to Lmb."),cr()]. 2569 2570help_msg_palette() -> 2571 [{bold,?__(1,"--Tweak Palette (Window|Tweak Palette)--")},"\n", 2572 ?__(2,"The Tweak Palette is a group of 3 windows that contain the main commands from the Tweak Menu."),"\n", 2573 ?__(3,"Use the Tweak Palette to switch between Tweak Tools, Magnet Types, or Axis Constraints.")]. 2574 2575cr() -> "\n\n". 2576