1%%
2%%  wpc_constraints.erl --
3%%
4%%    Plugin for setting default constraints directly from a model
5%%
6%%  Copyright (c) 2008-2013 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%%    $Id: wpc_constraints.erl optigon Exp $
12%%
13
14-module(wpc_constraints).
15-export([init/0,menu/2,command/2]).
16-define(NEED_ESDL, 1).
17-include_lib("src/wings.hrl").
18-include_lib("e3d/e3d.hrl").
19-define(NONZERO, 1.0e-6).
20
21init() ->
22    true.
23
24menu({Mode},Menu) when Mode == vertex; Mode == edge; Mode == face ->
25    lists:reverse(parse(Menu,Mode,[],false));
26menu(_,Menu) -> Menu.
27
28
29parse([], _, NewMenu, true) ->
30    NewMenu;
31
32parse([], Mode, NewMenu, false) ->
33    [general_menu(Mode), separator|NewMenu];
34
35parse([A={_,vertex_color,_}|Rest], Mode, NewMenu, false) ->
36    parse(Rest, Mode, [general_menu(Mode),separator,A|NewMenu], true);
37
38parse([Elem|Rest], Mode, NewMenu, Found) ->
39    parse(Rest, Mode, [Elem|NewMenu], Found).
40
41
42general_menu(Mode) ->
43    MenuTitle = ?__(1,"Set Constraint"),
44    {MenuTitle,{set_constraint,first_menu(Mode)}}.
45
46%%% Menu
47first_menu(vertex) ->
48    [general_menu1(vertex,center)];
49
50first_menu(edge) ->
51    [general_menu1(edge,total),
52     general_menu1(edge,average),
53     separator,
54     general_menu1(edge,angle),
55     general_menu1(edge,sub_angle),
56     general_menu1(edge,to_axis),
57     separator,
58     general_menu1(edge,scale),
59     general_menu1(edge,diff),
60     general_menu1(edge,center)];
61
62first_menu(face) ->
63    [general_menu1(face,center),
64     general_menu1(face,scale),
65     separator,
66     general_menu1(face,angle),
67     general_menu1(face,sub_angle),
68     general_menu1(face,to_axis)].
69
70general_menu1(Mode,Type) ->
71    {menu_title(Mode,Type),{Type,menu_items1(Mode,Type)},menu_string1(Mode,Type)}.
72
73general_menu2(Mode,Type,Axis) ->
74    RmbStr = ?__(1,"Choose a different axis to measure the second selection"),
75    F = fun(help, _Ns) ->
76		{menu_string2(Mode,Type,Axis)++mod_string(),[],RmbStr};
77	   (1,_Ns) -> {Mode,{set_constraint,{Type,{Axis,none}}}};
78	   (2,_Ns) -> ignore;
79	   (3,_Ns) -> menu_items2(Mode,Type,Axis)
80        end,
81    {menu_heading(Mode,Type,Axis),{Axis,F},[]}.
82
83general_menu3(Mode,Type,Axis1,Axis2) ->
84    Str = menu_string3(advanced,Mode,Type,Axis1,Axis2),
85    F = {Mode,{set_constraint,{Type,{Axis1,Axis2}}}},
86    {menu_heading(Mode,Type,Axis2),{Axis2,F},Str++mod_string()}.
87
88%%% Menu Strings
89menu_title(Mode,Type) ->
90    case {Mode,Type} of
91      {Mode,total} -> ?__(1,"Total Length");
92      {Mode,average} -> ?__(2,"Average Length");
93      {Mode,angle} -> ?__(3,"Angle");
94      {Mode,sub_angle} -> ?__(4,"Subtract Angle");
95      {edge,to_axis} -> ?__(5,"Edge To Axis");
96      {face,to_axis} -> ?__(6,"Face To Axis");
97      {Mode,scale} -> ?__(7,"Percentage");
98      {Mode,diff} -> ?__(8,"Difference");
99      {Mode,center} -> ?__(9,"Centers")
100    end.
101
102menu_heading(Mode,Type,Axis) ->
103    case {Mode,Type,Axis} of
104      {Mode,angle,normal} -> ?__(4,"Angle");
105      {Mode,center,normal} -> ?__(3,"Direct");
106      {Mode,Type,normal} -> ?__(1,"Normal");
107      {Mode,Type,'ASK'} -> ?__(2,"Pick");
108      {Mode,sub_angle,fifteen} -> [?__(5,"15"),?DEGREE];
109      {Mode,sub_angle,twenty_two} -> [?__(6,"22.5"),?DEGREE];
110      {Mode,sub_angle,thirty} -> [?__(7,"30"),?DEGREE];
111      {Mode,sub_angle,forty_five} -> [?__(8,"45"),?DEGREE];
112      {Mode,sub_angle,sixty} -> [?__(9,"60"),?DEGREE];
113      {Mode,sub_angle,ninety} -> [?__(10,"90"),?DEGREE];
114      {Mode,Type,Axis} -> wings_s:dir(Axis)
115    end.
116mode_strings(Ending,Mode) ->
117    case {Ending,Mode} of
118      {plural,vertex} -> ?__(1,"vertices");
119      {plural,edge} -> ?__(2,"edges");
120      {plural,face} -> ?__(3,"faces");
121      {singular,vertex} -> ?__(4,"vertex");
122      {singular,edge} -> ?__(5,"edge");
123      {singular,face} -> ?__(6,"face");
124      {second,Mode} -> ?__(8,"second");
125      {units,vertex} -> ?__(9,"distance");
126      {units,edge} -> ?__(10,"lengths");
127      {units,face} -> ?__(11,"areas")
128    end.
129
130mod_string() ->
131    ?__(1,". Constraint bound to held modifier key(s).").
132
133%%%% Help Strings
134menu_string1(Mode,Type) ->
135          UnitStr = mode_strings(units,Mode),
136          SMdeStr = mode_strings(singular,Mode),
137          PMdeStr = mode_strings(plural,Mode),
138    case {Mode,Type} of
139      {edge,total} ->
140          ?__(1,"Total the lengths of the selected edges and save the result as a distance constraint in the Preferences");
141      {edge,average} ->
142          ?__(2,"Calculate the average length of the selected edges and save the result as a distance constraint in the Preferences");
143      {Mode,angle} ->
144          Str = ?__(3,"Calculate the angle between any two ~s and save the result as a rotation constraint in the Preferences"),
145          wings_util:format(Str,[PMdeStr]);
146      {Mode,sub_angle} ->
147          ?__(4,"Calculate the difference between two angles and save the result as a rotation constraint in the Preferences");
148      {Mode,to_axis} ->
149          Str = ?__(5,"Measure the angle between a single ~s and a standard axis or defined vector. Save the result as a rotation constraint in the Preferences"),
150          wings_util:format(Str,[SMdeStr]);
151      {Mode,scale} ->
152          Str = ?__(6,"Calculate the difference in scale between the ~s of two ~s selections and save the result as a scale constraint in the Preferences"),
153          wings_util:format(Str,[UnitStr,SMdeStr]);
154      {Mode,diff} ->
155          Str = ?__(7,"Calculate the difference in ~s between two ~s selections and save the result as a distance constraint in the Preferences"),
156          wings_util:format(Str,[UnitStr,SMdeStr]);
157      {Mode,center} ->
158          ?__(8,"Calculate the distance between the center points of two selections and save the result as a distance constraint in the Preferences")
159    end.
160
161menu_string2(Mode,Type,Axis) ->
162    UnitStr = mode_strings(units,Mode),
163    SMdeStr = mode_strings(singular,Mode),
164    PMdeStr = mode_strings(plural,Mode),
165    AxisStr = menu_heading(edge,Type,Axis),
166    case {Mode,Type,Axis} of
167      {Mode,scale,normal} ->
168          Str = ?__(1,"Measure the ~s of both selections according to their ~s normals"),
169          wings_util:format(Str,[UnitStr,SMdeStr]);
170      {Mode,scale,'ASK'} ->
171          Str = ?__(2,"Pick an axis along which to measure the ~s both selections"),
172          wings_util:format(Str,[UnitStr]);
173      {Mode,scale,Axis} ->
174          Str = ?__(3,"Measure the ~s of both selections' ~s only along the ~s axis"),
175          wings_util:format(Str,[UnitStr,PMdeStr,AxisStr]);
176      {Mode,diff,normal} ->
177          Str = ?__(1,"Measure the ~s of both selections according to their ~s normals"),
178          wings_util:format(Str,[UnitStr,SMdeStr]);
179      {Mode,diff,'ASK'} ->
180          Str = ?__(2,"Pick an axis along which to measure the ~s both selections"),
181          wings_util:format(Str,[UnitStr]);
182      {Mode,diff,Axis} ->
183          Str = ?__(3,"Measure the ~s of both selections' ~s only along the ~s axis"),
184          wings_util:format(Str,[UnitStr,PMdeStr,AxisStr])
185    end.
186
187menu_string3(advanced,Mode,Type,Axis1,Axis2) ->
188    UnitStr = mode_strings(units,Mode),
189    PMdeStr = mode_strings(plural,Mode),
190    SMdeStr = mode_strings(singular,Mode),
191    SSelStr = mode_strings(second,Mode),
192    Axs2Str = menu_heading(Mode,Type,Axis2),
193    DegStr = menu_heading(Mode,sub_angle,Axis2),
194    case {Mode,Type,Axis1,Axis2} of
195      {Mode,total,none,normal} ->
196          ?__(1,"Measure the selected edges along their normals");
197      {Mode,total,none,'ASK'} ->
198          ?__(2,"Pick an axis along which to measure the selected edges");
199      {Mode,total,none,Axis2} ->
200          Str = ?__(3,"Measure the selected edges only along the ~s axis"),
201          wings_util:format(Str,[Axs2Str]);
202
203      {Mode,average,none,normal} ->
204          ?__(1,"Measure the selected edges along their normals")++
205          ?__(22," and then calculate their average length");
206      {Mode,average,none,'ASK'} ->
207          ?__(2,"Pick an axis along which to measure the selected edges")++
208          ?__(22," and then calculate their average length");
209      {Mode,average,none,Axis2} ->
210          Str = ?__(3,"Measure the selected edges only along the ~s axis"),
211          wings_util:format(Str,[Axs2Str])++?__(22," and then calculate their average length");
212
213      {Mode,angle,none,normal} ->
214          ?__(4,"Measure the selected angle");
215      {Mode,angle,none,'ASK'} ->
216          ?__(5,"Specify the axis from which to measure the selected angle");
217      {Mode,angle,none,Axis2} ->
218          Str = ?__(6,"Measure the selected angle as viewed from the ~s axis"),
219          wings_util:format(Str,[Axs2Str]);
220
221      {Mode,sub_angle,none,'ASK'} ->
222          ?__(19,"Pick a second angle from which to subtract the currently selected angle");
223      {Mode,sub_angle,none,Axis2} ->
224          Str = ?__(18,"Subtract the currently selected angle from ~s"),
225          wings_util:format(Str,[DegStr]);
226
227      {Mode,to_axis,none,'ASK'} ->
228          Str = ?__(20,"Pick a vector and calculate the angle to the original ~s"),
229          wings_util:format(Str,[SMdeStr]);
230      {Mode,to_axis,none,Axis2} ->
231          Str = ?__(21,"Calculate the angle from the ~s axis to the original ~s"),
232          wings_util:format(Str,[Axs2Str,SMdeStr]);
233
234      {Mode,center,none,normal} ->
235          ?__(7,"Measure the distance between the centers of the two selections");
236      {Mode,center,none,'ASK'} ->
237          ?__(8,"Pick an axis along which to measure the distance between the centers of the two selections");
238      {Mode,center,none,Axis2} ->
239          Str = ?__(9,"Measure only the distance along the ~s axis between the centers of the two selections"),
240          wings_util:format(Str,[Axs2Str]);
241      {Mode,Type,Axis1,normal} ->
242          Str = ?__(13,"Measure the ~s of the ~s selection's ~s along their normals"),
243          wings_util:format(Str,[UnitStr,SSelStr,PMdeStr]);
244      {Mode,Type,Axis1,'ASK'} ->
245          Str = ?__(11,"Pick an axis along which to measure the ~s of the ~s selection's ~s"),
246          wings_util:format(Str,[UnitStr,SSelStr,PMdeStr]);
247      {Mode,Type,Axis1,Axis2} ->
248          Str = ?__(12,"Measure the ~s of the ~s selection's ~s only along the ~s axis"),
249          wings_util:format(Str,[UnitStr,SSelStr,PMdeStr,Axs2Str])
250    end.
251
252%%% Menu items
253menu_items1(Mode,Type) ->
254    case Type of
255      total -> last_menu(Mode,Type,none);
256      average -> last_menu(Mode,Type,none);
257      angle -> last_menu(Mode,Type,none);
258      sub_angle -> last_menu(Mode,Type,none);
259      to_axis -> last_menu(Mode,Type,none);
260      scale -> middle_menu(Mode,Type);
261      diff ->  middle_menu(Mode,Type);
262      center ->last_menu(Mode,Type,none)
263    end.
264
265menu_items2(Mode,Type,Axis) ->
266    case Type of
267      scale -> last_menu(Mode,Type,Axis);
268      diff -> last_menu(Mode,Type,Axis)
269    end.
270
271middle_menu(Mode,Type) ->
272    case Type of
273      Type ->
274        [general_menu2(Mode,Type,normal),
275         general_menu2(Mode,Type,x),
276         general_menu2(Mode,Type,y),
277         general_menu2(Mode,Type,z),
278         general_menu2(Mode,Type,'ASK')]
279    end.
280
281last_menu(Mode,Type,Axis) ->
282    case Type of
283      sub_angle ->
284        [general_menu3(Mode,sub_angle,none,fifteen),
285         general_menu3(Mode,sub_angle,none,twenty_two),
286         general_menu3(Mode,sub_angle,none,thirty),
287         general_menu3(Mode,sub_angle,none,forty_five),
288         general_menu3(Mode,sub_angle,none,sixty),
289         general_menu3(Mode,sub_angle,none,ninety),
290         general_menu3(Mode,sub_angle,none,'ASK')];
291      to_axis ->
292        [general_menu3(Mode,Type,none,x),
293         general_menu3(Mode,Type,none,y),
294         general_menu3(Mode,Type,none,z),
295         general_menu3(Mode,Type,none,'ASK')];
296      Type ->
297        [general_menu3(Mode,Type,Axis,normal),
298         general_menu3(Mode,Type,Axis,x),
299         general_menu3(Mode,Type,Axis,y),
300         general_menu3(Mode,Type,Axis,z),
301         general_menu3(Mode,Type,Axis,'ASK')]
302    end.
303
304%%%
305%%% Commands
306%%%
307
308%%% Length
309command({edge,{set_constraint,{total,{none,'ASK'}}}},St) ->
310    wings:ask(selection_ask([along_axis]), St, fun distance/2);
311command({edge,{set_constraint,{total,{none,Axis}}}}, St) ->
312    distance(Axis,St);
313
314%%% Average
315command({edge,{set_constraint,{average,{none,'ASK'}}}},St) ->
316    wings:ask(selection_ask([along_axis]), St, fun average/2);
317command({edge,{set_constraint,{average,{none,Axis}}}}, St) ->
318    average(Axis,St);
319
320%%% Angle
321command({_Mode,{set_constraint,{angle,{none,'ASK'}}}},St) ->
322    check_angle_sel(St),
323    wings:ask(selection_ask([view_plane]), St, fun angle/2);
324command({_Mode,{set_constraint,{angle,{none,Axis}}}}, St) ->
325    check_angle_sel(St),
326    angle(Axis,St);
327
328%%% Sub Angle
329command({_Mode,{set_constraint,{sub_angle,{none,'ASK'}}}},St) ->
330    check_angle_sel(St),
331    wings:ask(secondary_sel_ask(sub_angle,none,none,St), St, fun sub_angle/2);
332command({_Mode,{set_constraint,{sub_angle,{none,Axis}}}}, St) ->
333    check_angle_sel(St),
334    sub_angle(Axis,St);
335
336%%% Angle to Axis
337command({_Mode,{set_constraint,{to_axis,{none,'ASK'}}}},St) ->
338    check_element(St),
339    wings:ask(selection_ask([to_axis]), St, fun to_axis/2);
340command({_Mode,{set_constraint,{to_axis,{none,Axis2}}}},St) ->
341    check_element(St),
342    to_axis(Axis2,St);
343
344%%% Scale Edge Mode
345command({edge,{set_constraint,{scale,{'ASK','ASK'}}}},St) ->
346    wings:ask(selection_ask([along_axis1,along_axis2]), St,
347      fun({Pn0,Pn1},St0) ->
348          scale({Pn0,Pn1},St0)
349    end);
350command({edge,{set_constraint,{scale,{'ASK',none}}}},St) ->
351    wings:ask(selection_ask([along_axis1]), St, fun(Pn1,St0) ->
352        scale({Pn1,Pn1},St0)
353    end);
354command({edge,{set_constraint,{scale,{'ASK',Axis2}}}},St) ->
355    wings:ask(selection_ask([along_axis1]), St, fun({Pn1},St0) ->
356        scale({Pn1,Axis2},St0)
357    end);
358command({edge,{set_constraint,{scale,{Axis1,'ASK'}}}},St) ->
359    wings:ask(selection_ask([along_axis2]), St, fun({Pn1},St0) ->
360        scale({Axis1,Pn1},St0)
361    end);
362command({edge,{set_constraint,{scale,{Axis1,none}}}},St) ->
363    scale({Axis1,Axis1},St);
364command({edge,{set_constraint,{scale,{Axis1,Axis2}}}},St) ->
365    scale({Axis1,Axis2},St);
366command({edge,{set_constraint,{scale,_}}},St) ->
367    wings:ask(selection_ask([along_axis1,along_axis2]), St,
368      fun({Pn0,Pn1},St0) ->
369          scale({Pn0,Pn1},St0)
370    end);
371
372%%% Scale Face Mode
373command({face,{set_constraint,{scale,{'ASK','ASK'}}}},St) ->
374    wings:ask(selection_ask([scale_ax_pnt1,scale_ax_pnt2]), St,
375      fun({Pn0,Pp0,Pn1,Pp1},St0) ->
376          scale({{Pn0,Pp0},{Pn1,Pp1}},St0)
377    end);
378command({face,{set_constraint,{scale,{'ASK',none}}}},St) ->
379    wings:ask(selection_ask([scale_ax_pnt1]), St, fun({Pn1,Pp1},St0) ->
380        scale({{Pn1,Pp1},{Pn1,Pp1}},St0)
381    end);
382command({face,{set_constraint,{scale,{'ASK',Axis2}}}},St) ->
383    wings:ask(selection_ask([scale_ax_pnt1]), St, fun({Pn1,Pp1},St0) ->
384        scale({{Pn1,Pp1},Axis2},St0)
385    end);
386command({face,{set_constraint,{scale,{Axis1,'ASK'}}}},St) ->
387    wings:ask(selection_ask([scale_ax_pnt2]), St, fun({Pn1,Pp1},St0) ->
388        scale({Axis1,{Pn1,Pp1}},St0)
389    end);
390command({face,{set_constraint,{scale,{Axis1,none}}}},St) ->
391    scale({Axis1,Axis1},St);
392command({face,{set_constraint,{scale,{Axis1,Axis2}}}},St) ->
393    scale({Axis1,Axis2},St);
394command({face,{set_constraint,{scale,_}}},St) ->
395    wings:ask(selection_ask([scale_ax_pnt1,scale_ax_pnt2]), St,
396      fun({Pn0,Pp0,Pn1,Pp1},St0) ->
397          scale({{Pn0,Pp0},{Pn1,Pp1}},St0)
398    end);
399
400%%% Difference
401command({edge,{set_constraint,{diff,{'ASK','ASK'}}}},St) ->
402    wings:ask(selection_ask([along_axis1,along_axis2]), St,
403      fun({Axis1,Axis2},St0) ->
404          difference({Axis1,Axis2},St0)
405    end);
406command({edge,{set_constraint,{diff,{'ASK',none}}}},St) ->
407    wings:ask(selection_ask([along_axis1]), St, fun(Axis1,St0) ->
408        difference({Axis1,Axis1},St0)
409    end);
410command({edge,{set_constraint,{diff,{'ASK',Axis2}}}},St) ->
411    wings:ask(selection_ask([along_axis1]), St, fun(Axis1,St0) ->
412        difference({Axis1,Axis2},St0)
413    end);
414command({edge,{set_constraint,{diff,{Axis1,'ASK'}}}},St) ->
415    wings:ask(selection_ask([along_axis2]), St, fun(Axis2,St0) ->
416        difference({Axis1,Axis2},St0)
417    end);
418command({edge,{set_constraint,{diff,{Axis1,none}}}},St) ->
419    difference({Axis1,Axis1},St);
420command({edge,{set_constraint,{diff,{Axis1,Axis2}}}},St) ->
421    difference({Axis1,Axis2},St);
422
423%%% Centers
424command({_Mode,{set_constraint,{center,{none,'ASK'}}}},St) ->
425    wings:ask(selection_ask([centers]), St, fun centers/2);
426command({_Mode,{set_constraint,{center,{none,Axis}}}}, St) ->
427    centers(Axis,St);
428
429command(_,_) -> next.
430
431%%% Axis Asks
432selection_ask(Asks) ->
433    Ask = selection_ask(Asks,[]),
434    {Ask,[],[],[vertex, edge, face]}.
435
436selection_ask([],Ask) ->
437    lists:reverse(Ask);
438
439selection_ask([Type|Rest],Ask) ->
440    {Data,Desc} = case Type of
441      along_axis ->
442        {axis,?__(1,"Choose the axis each edge will be measured along")};
443      along_axis1 ->
444        {axis,?__(1,"Choose the axis each edge will be measured along")
445            ++?__(2," for the first selection")};
446      along_axis2 ->
447        {axis,?__(1,"Choose the axis each edge will be measured along")
448            ++?__(3," for the second selection")};
449      view_plane ->
450        {axis_point,?__(4,"Choose the axis along which the angle will be measured orthographically")};
451      centers ->
452        {axis,?__(5,"Choose an axis for measuring the distance between the centers of the selections")};
453      scale_ax_pnt1 ->
454        {axis_point,?__(6,"Choose an axis along which the areas of the first selection will be measure orthographically")};
455      scale_ax_pnt2 ->
456        {axis_point,?__(7,"Choose an axis along which the areas of the second selection will be measure orthographically")};
457      to_axis ->
458        {axis,?__(8,"Choose vector to calculate the angle to the to the original selection")}
459    end,
460    selection_ask(Rest,[{Data,Desc}|Ask]).
461
462%%% Secondary selection
463secondary_sel_ask(sub_angle,none,none,OrigSt) ->
464    Desc = ?__(5,"Select an angle made from two edges or two faces to subtract the original angle from"),
465    Data = fun(check, St) -> check_selection(sub_angle,none,none,St,OrigSt);
466             (exit, {_,_,St}) ->
467               case check_selection(sub_angle,none,none,St,OrigSt) of
468                 {_," "++_} -> {[],[St]};
469                 {_,_} -> error
470               end
471             end,
472    {[{Data,Desc}],[],[],[edge,face]};
473secondary_sel_ask(scale_area,Axis1,Axis2,OrigSt) ->
474    Desc = ?__(4,"Select the faces whose total area will be divided into the first selection's area"),
475    Data = fun(check, St) -> check_selection(scale_area,Axis1,Axis2,St,OrigSt);
476             (exit, {_,_,St}) ->
477               case check_selection(scale_area,Axis1,Axis2,St,OrigSt) of
478                 {_," "++_} -> {[],[St]};
479                 {_,_} -> error
480               end
481             end,
482    {[{Data,Desc}],[],[],[face]};
483
484secondary_sel_ask(center,Axis1,Axis2,OrigSt) ->
485    Desc = ?__(3,"Center of selection will determine the distance from the center of the original selection"),
486    Data = fun(check, St) -> check_selection(center,Axis1,Axis2,St,OrigSt);
487             (exit, {_,_,St}) ->
488               case check_selection(center,Axis1,Axis2,St,OrigSt) of
489                 {_," "++_} -> {[],[St]};
490                 {_,_} -> error
491               end
492             end,
493    {[{Data,Desc}],[],[],[edge,vertex,face,body]};
494
495secondary_sel_ask(Type,Axis1,Axis2,OrigSt) ->
496    Desc = case Type of
497      scale -> ?__(1,"Select the edges to divide into the original selection");
498      difference -> ?__(2,"Select the edges to subtract from the original selection")
499    end,
500    Data = fun(check, St) -> check_selection(Type,Axis1,Axis2,St,OrigSt);
501             (exit, {_,_,St}) ->
502               case check_selection(Type,Axis1,Axis2,St,OrigSt) of
503                 {_," "++_} -> {[],[St]};
504                 {_,_} -> error
505               end
506             end,
507    {[{Data,Desc}],[],[],[edge]}.
508
509check_selection(_Type,_Axis1,_Axis2,#st{sel=[]},_OrigSt) ->
510    {none,?__(1,"Nothing selected")};
511
512check_selection(sub_angle,none,none,#st{sel=[{_Id0,Sel0},{_Id1,Sel1}]}=St,OrigSt) ->
513    case gb_sets:size(Sel0) == 1 andalso gb_sets:size(Sel1) == 1 of
514      true ->
515        OrigA = measure_angle(normal,OrigSt),
516        Angle = measure_angle(normal,St),
517        A0 = abs(Angle - OrigA),
518        A1 = case A0 of
519               0.0 -> 180.0;
520               _ -> A0
521             end,
522        Str = [?__(11," Original Angle ~s"),?DEGREE,?__(12,"\n Current Angle ~s"),
523               ?DEGREE,?__(13,"\n Difference ~s"),?DEGREE],
524        {none,wings_util:format(Str,[OrigA,Angle,A1])};
525      false ->
526        {none,?__(14,"Select exactly two edges or two faces to define angle")}
527    end;
528
529check_selection(sub_angle,none,none,#st{sel=[{_Id,Sel}]}=St,OrigSt) ->
530    case gb_sets:size(Sel) == 2 of
531      true ->
532        Angle = measure_angle(normal,OrigSt),
533        CAngle = measure_angle(normal,St),
534        OrigA = wings_util:nice_float(Angle),
535        CurrA = wings_util:nice_float(CAngle),
536        A0 = abs(Angle - CAngle),
537        A1 = case A0 of
538               0.0 -> 180.0;
539               _ -> A0
540             end,
541        A2 = wings_util:nice_float(A1),
542        Str1 = [wings_util:format(?__(16," Angle ~s"),[OrigA]),?DEGREE],
543        Str2 = [wings_util:format(?__(12,"\n Current Angle ~s"),[CurrA]),?DEGREE],
544        Str3 = [wings_util:format(?__(13,"\n Difference ~s"),[A2]),?DEGREE],
545        StrFinal = [Str1++Str2++Str3],
546        {none,?__(15," Original")++StrFinal};
547      false ->
548        {none,?__(14,"Select exactly two edges or two faces to define angle")}
549    end;
550
551check_selection(sub_angle,none,none,_St,_OrigSt) ->
552    {none,?__(14,"Select exactly two edges or two faces to define angle")};
553
554check_selection(scale,Axis1,Axis2,#st{selmode=edge}=St,OrigSt) ->
555    Original = add_edges(Axis1,OrigSt),
556    Current = add_edges(Axis2,St),
557    case Current < ?NONZERO of
558      true ->
559        {none,?__(3,"Current length is too short. Select edges that aren't perpendicular to the chosen axis.")};
560      false ->
561        Percent = Original/Current,
562        case Percent < ?NONZERO of
563          true ->
564            {none,?__(5,"Resulting percentage is to small")};
565          false ->
566            OStr = wings_util:nice_float(Original),
567            PStr = wings_util:nice_float(Percent*100),
568            RStr = wings_util:nice_float(1/Percent*100),
569            CStr = wings_util:nice_float(Current),
570            AxStr1 = axis_to_string(Axis1),
571            AxStr2 = axis_to_string(Axis2),
572            Str = ?__(4," Original ~s ~s\n Current ~s ~s\n Percent ~s%  Reciprocal ~s%"),
573            {none,wings_util:format(Str, [AxStr1,OStr,AxStr2,CStr,PStr,RStr])}
574        end
575    end;
576
577check_selection(difference,Axis1,Axis2,#st{selmode=edge}=St,OrigSt) ->
578    Original = add_edges(Axis1,OrigSt),
579    Current = add_edges(Axis2,St),
580    Difference = abs(Original - Current),
581    case Difference < ?NONZERO of
582      true ->
583        {none,?__(6,"Difference is too small")};
584      false ->
585        OStr = wings_util:nice_float(Original),
586        CStr = wings_util:nice_float(Current),
587        DStr = wings_util:nice_float(Difference),
588        AxStr1 = axis_to_string(Axis1),
589        AxStr2 = axis_to_string(Axis2),
590        Str = ?__(7," Original ~s ~s\n Current ~s ~s\n Difference ~s"),
591        {none,wings_util:format(Str,[AxStr1,OStr,AxStr2,CStr,DStr])}
592    end;
593
594check_selection(center,Axis1,_Axis2,St,OrigSt) ->
595    Original = wings_sel:center(OrigSt),
596    Current = wings_sel:center(St),
597    Distance = get_distance(Axis1,Original,Current),
598    case Distance < ?NONZERO of
599      true ->
600        {none,?__(8,"Distance between centers is too short")};
601      false ->
602        OStr = axis_to_string({center,Original}),
603        CStr = axis_to_string({center,Current}),
604        DStr = wings_util:nice_float(Distance),
605        AxStr1 = axis_to_string(Axis1),
606        Str = ?__(9," Original center ~s\n Current center ~s\n Distance ~s ~s"),
607        {none,wings_util:format(Str,[OStr,CStr,DStr,AxStr1])}
608    end;
609
610check_selection(scale_area,Axis1,Axis2,St,OrigSt) ->
611    Original = add_areas(Axis1,OrigSt),
612    Current = add_areas(Axis2,St),
613    case Current < ?NONZERO of
614      true ->
615        {none,?__(10,"Current area is too small")};
616      false ->
617        Percent = Original/Current,
618        case Percent < ?NONZERO of
619          true ->
620            {none,?__(5,"Resulting percentage is to small")};
621          false ->
622            OStr = wings_util:nice_float(Original),
623            PStr = wings_util:nice_float(Percent*100),
624            RStr = wings_util:nice_float(1/Percent*100),
625            CStr = wings_util:nice_float(Current),
626            AxStr1 = axis_to_string(Axis1),
627            AxStr2 = axis_to_string(Axis2),
628            Str = ?__(4," Original ~s ~s\n Current ~s ~s\n Percent ~s%  Reciprocal ~s%"),
629            {none,wings_util:format(Str, [AxStr1,OStr,AxStr2,CStr,PStr,RStr])}
630        end
631    end.
632
633axis_to_string(Axis) ->
634    case Axis of
635      normal ->
636        [];
637      {center,{_,_,_}} ->
638        {center,{X,Y,Z}} = Axis,
639        X1 = wings_util:nice_float(X),
640        Y1 = wings_util:nice_float(Y),
641        Z1 = wings_util:nice_float(Z),
642        Str = "<~s  ~s  ~s>",
643        wings_util:format(Str,[X1,Y1,Z1]);
644      {_,_,_} ->
645        {X,Y,Z} = Axis,
646        X1 = wings_util:nice_float(X),
647        Y1 = wings_util:nice_float(Y),
648        Z1 = wings_util:nice_float(Z),
649        Str = ?__(2,"along vector <~s  ~s  ~s>"),
650        wings_util:format(Str,[X1,Y1,Z1]);
651      {_,_} ->
652        {{X,Y,Z},_Point} = Axis,
653        X1 = wings_util:nice_float(X),
654        Y1 = wings_util:nice_float(Y),
655        Z1 = wings_util:nice_float(Z),
656        Str = ?__(2,"along vector <~s  ~s  ~s>"),
657        wings_util:format(Str,[X1,Y1,Z1]);
658      _xyz ->
659        Str = ?__(3,"along ~s axis"),
660        wings_util:format(Str,[wings_s:dir(Axis)])
661    end.
662
663%%% Distance functions
664distance(Axis,St) ->
665    Set = atom_to_list(wings_pref:get_value(con_dist_set)),
666    Keys = mod_key_combo(),
667    Length = add_edges(Axis,St),
668    length_check(Axis,Length),
669    set_constraint(Keys, Set, Length),
670    St.
671
672average(Axis,#st{sel=Sel}=St) ->
673    Set = atom_to_list(wings_pref:get_value(con_dist_set)),
674    Keys = mod_key_combo(),
675    EdgeNum = lists:foldl(fun({_Id,Sel0}, A) ->
676                          gb_sets:size(Sel0)+A
677                          end, 0, Sel),
678    Length = add_edges(Axis,St),
679    AvgLength = Length/EdgeNum,
680    length_check(Axis,AvgLength),
681    set_constraint(Keys, Set, AvgLength),
682    St.
683
684length_check(Axis,Length) ->
685    case Length < ?NONZERO of
686      true ->
687        case Axis of
688          area ->
689            wings_u:error_msg(?__(1,"Selection must have an area greater than zero"));
690          normal ->
691            wings_u:error_msg(?__(2,"Selection must have length greater than zero"));
692          {_,_,_} ->
693            wings_u:error_msg(?__(3,"Length along vector is too short"));
694          _xyz ->
695            AxStr = wings_s:dir(Axis),
696            Str = ?__(4,"Length along ~s axis is too short"),
697            wings_u:error_msg(wings_util:format(Str,[AxStr]))
698        end;
699      false -> okay
700    end.
701
702add_edges(Axis,St) ->
703    wings_sel:fold(fun(Edges,We,Acc)->
704            Es = gb_sets:to_list(Edges),
705            add_edges(Axis,Es,We) + Acc
706            end, 0, St).
707add_edges(Axis,Es,We) ->
708    lists:foldl(fun(Edge,A)->
709            #we{es=Etab} = We,
710            #edge{vs=Va,ve=Vb} = array:get(Edge,Etab),
711            Pos1 = wings_vertex:pos(Va,We),
712            Pos2 = wings_vertex:pos(Vb,We),
713            get_distance(Axis,Pos1,Pos2) + A
714    end, 0, Es).
715
716get_distance(Axis, {Xa,Ya,Za}, {Xb,Yb,Zb}) ->
717    case Axis of
718      x ->
719        abs(e3d_vec:dist({Xa,0.0,0.0},{Xb,0.0,0.0}));
720      y ->
721        abs(e3d_vec:dist({0.0,Ya,0.0},{0.0,Yb,0.0}));
722      z ->
723        abs(e3d_vec:dist({0.0,0.0,Za},{0.0,0.0,Zb}));
724      normal ->
725        abs(e3d_vec:dist({Xa,Ya,Za},{Xb,Yb,Zb}));
726      {_,_,_} ->
727        {Vx,Vy,Vz} = e3d_vec:norm(Axis),
728        abs(Vx*(Xa-Xb)+Vy*(Ya-Yb)+Vz*(Za-Zb))
729    end.
730%%% Angle selection checking
731check_element(#st{sel=[{_,Sel}]}) ->
732    case gb_sets:size(Sel) of
733      1 -> ok;
734      _ -> element_error()
735    end;
736check_element(_St) ->
737    element_error().
738
739-spec element_error() -> no_return().
740element_error() ->
741    Str = ?__(1,"Exactly one element must be selected"),
742    wings_u:error_msg(Str).
743
744check_angle_sel(#st{sel=[{_,Sel}]}) ->
745    case gb_sets:size(Sel) of
746      2 -> ok;
747      _ -> angle_error()
748    end;
749check_angle_sel(#st{sel=[{_,Sel1},{_,Sel2}]}) ->
750    case gb_sets:size(Sel1) == 1 andalso gb_sets:size(Sel2) == 1 of
751      true -> ok;
752      false -> angle_error()
753    end;
754check_angle_sel(_St) ->
755    angle_error().
756
757-spec angle_error() -> no_return().
758angle_error() ->
759    wings_u:error_msg(?__(1,"Exactly two elements must be selected")).
760
761%%% Main Angle functions
762sub_angle(Axis, St) ->
763    Keys = mod_key_combo(),
764    M = case Axis of
765          fifteen -> 15.0;
766          twenty_two -> 22.5;
767          thirty -> 30.0;
768          forty_five -> 45.0;
769          sixty -> 60.0;
770          ninety -> 90.0;
771          _  -> measure_angle(normal,Axis)
772        end,
773    N = measure_angle(normal,St),
774    Angle = abs(M - N),
775    set_angle(Keys,Angle,St).
776
777angle(Axis,St) ->
778    Keys = mod_key_combo(),
779    Angle = measure_angle(Axis,St),
780    set_angle(Keys,Angle,St).
781
782to_axis(Axis,#st{selmode=edge,shapes=Shs,sel=[{Id,Sel}]}=St) ->
783    Keys = mod_key_combo(),
784    Vec1 = wings_util:make_vector(Axis),
785    We = gb_trees:get(Id, Shs),
786    [Edge] = gb_sets:to_list(Sel),
787    #edge{vs=V0s,ve=V0e} = array:get(Edge, We#we.es),
788    Pos1 = wings_vertex:pos(V0s, We),
789    Pos2 = wings_vertex:pos(V0e, We),
790    Vec2 = e3d_vec:sub(Pos1,Pos2),
791    Norm1 = e3d_vec:norm(Vec1),
792    Norm2 = e3d_vec:norm(Vec2),
793    Angle = e3d_vec:degrees(Norm1,Norm2),
794    set_angle(Keys,Angle,St);
795
796to_axis(Axis,#st{selmode=face,shapes=Shs,sel=[{Id,Sel}]}=St) ->
797    Keys = mod_key_combo(),
798    Vec1 = wings_util:make_vector(Axis),
799    We = gb_trees:get(Id, Shs),
800    [Face] = gb_sets:to_list(Sel),
801    Norm1 = wings_face:normal(Face,We),
802    Norm2 = e3d_vec:norm(Vec1),
803    Angle = e3d_vec:degrees(Norm1,Norm2),
804    set_angle(Keys,Angle,St).
805
806measure_angle(Axis,#st{selmode=edge,shapes=Shs,sel=[{Id0,Sel0},{Id1,Sel1}]}) ->
807    We0 = gb_trees:get(Id0, Shs),
808    We1 = gb_trees:get(Id1, Shs),
809    [E0] = gb_sets:to_list(Sel0),
810    [E1] = gb_sets:to_list(Sel1),
811    #edge{vs=V0s,ve=V0e} = array:get(E0, We0#we.es),
812    #edge{vs=V1s,ve=V1e} = array:get(E1, We1#we.es),
813    Pos1 = wings_vertex:pos(V0s, We0),
814    Pos2 = wings_vertex:pos(V0e, We0),
815    Pos3 = wings_vertex:pos(V1s, We1),
816    Pos4 = wings_vertex:pos(V1e, We1),
817    [Vec0,Vec1] = get_angle(Axis,[Pos1,Pos2,Pos3,Pos4]),
818    raw_angle_to_angle(Vec0,Vec1,V0s,V0e,V1s,V1e);
819
820measure_angle(Axis,#st{selmode=edge,shapes=Shs,sel=[{Id,Sel}]}) ->
821    We = gb_trees:get(Id, Shs),
822    [E0,E1] = gb_sets:to_list(Sel),
823    #edge{vs=V0s,ve=V0e} = array:get(E0, We#we.es),
824    #edge{vs=V1s,ve=V1e} = array:get(E1, We#we.es),
825    Pos1 = wings_vertex:pos(V0s, We),
826    Pos2 = wings_vertex:pos(V0e, We),
827    Pos3 = wings_vertex:pos(V1s, We),
828    Pos4 = wings_vertex:pos(V1e, We),
829    [Vec0,Vec1] = get_angle(Axis,[Pos1,Pos2,Pos3,Pos4]),
830    raw_angle_to_angle(Vec0,Vec1,V0s,V0e,V1s,V1e);
831
832measure_angle(Axis,#st{selmode=face,shapes=Shs,sel=[{Id0,Sel0},{Id1,Sel1}]}) ->
833    We0 = gb_trees:get(Id0, Shs),
834    We1 = gb_trees:get(Id1, Shs),
835    [F0] = gb_sets:to_list(Sel0),
836    [F1] = gb_sets:to_list(Sel1),
837    N0 = wings_face:normal(F0,We0),
838    N1 = wings_face:normal(F1,We1),
839    Pos1 = wings_face:center(F0,We0),
840    Pos2 = e3d_vec:add(Pos1,e3d_vec:mul(N0,0.2)),
841    Pos3 = wings_face:center(F1,We1),
842    Pos4 = e3d_vec:add(Pos3,e3d_vec:mul(N1,0.2)),
843    [Vec0,Vec1] = get_angle(Axis,[Pos1,Pos2,Pos3,Pos4]),
844    e3d_vec:degrees(Vec0,Vec1);
845
846measure_angle(Axis,#st{selmode=face,shapes=Shs,sel=[{Id,Sel}]}) ->
847    We = gb_trees:get(Id, Shs),
848    [F0,F1] = gb_sets:to_list(Sel),
849    N0 = wings_face:normal(F0,We),
850    N1 = wings_face:normal(F1,We),
851    Pos1 = wings_face:center(F0,We),
852    Pos2 = e3d_vec:add(Pos1,e3d_vec:mul(N0,0.2)),
853    Pos3 = wings_face:center(F1,We),
854    Pos4 = e3d_vec:add(Pos3,e3d_vec:mul(N1,0.2)),
855    [Vec0,Vec1] = get_angle(Axis,[Pos1,Pos2,Pos3,Pos4]),
856    e3d_vec:degrees(Vec0,Vec1).
857
858get_angle(Axis,Vlist) ->
859    [{X0s,Y0s,Z0s},{X0e,Y0e,Z0e},{X1s,Y1s,Z1s},{X1e,Y1e,Z1e}] = Vlist,
860    case Axis of
861      x ->
862        [{0.0, Y0e-Y0s, Z0e-Z0s},
863         {0.0, Y1e-Y1s, Z1e-Z1s}];
864      y ->
865        [{X0e-X0s, 0.0, Z0e-Z0s},
866         {X1e-X1s, 0.0, Z1e-Z1s}];
867      z ->
868        [{X0e-X0s, Y0e-Y0s, 0.0},
869         {X1e-X1s, Y1e-Y1s, 0.0}];
870      normal ->
871        [{X0e-X0s, Y0e-Y0s, Z0e-Z0s},
872         {X1e-X1s, Y1e-Y1s, Z1e-Z1s}];
873      {_,_} -> %% from axis_point ask
874        {PlaneNorm,PlanePoint} = Axis,
875        Pn = e3d_vec:norm(PlaneNorm),
876        Dp = e3d_vec:dot(Pn, Pn),
877        VPoints = lists:foldl(fun(Point,A) ->
878                    M0 = e3d_vec:dot(e3d_vec:sub(PlanePoint, Point),Pn)/Dp,
879                    M1 = e3d_vec:add(Point, e3d_vec:mul(Pn, M0)),
880                    [M1|A]
881          end,[],Vlist),
882        [{X2s,Y2s,Z2s},{X2e,Y2e,Z2e},{X3s,Y3s,Z3s},{X3e,Y3e,Z3e}] = VPoints,
883
884        [{X2e-X2s, Y2e-Y2s, Z2e-Z2s},{X3e-X3s, Y3e-Y3s, Z3e-Z3s}]
885    end.
886
887raw_angle_to_angle(Vec0,Vec1,V0s,V0e,V1s,V1e) ->
888    RawAngle = e3d_vec:degrees(Vec0, Vec1),
889    case {V0s,V0e} of
890      {V1s,_} -> RawAngle;
891      {_,V1e} -> RawAngle;
892      {V1e,_} -> 180.0 - RawAngle;
893      {_,V1s} -> 180.0 - RawAngle;
894      {_,_}   -> RawAngle
895    end.
896
897set_angle(Keys, Angle, St) ->
898    A = case Angle >= ?NONZERO of
899        true -> Angle;
900        false-> 180.0
901    end,
902    set_constraint(Keys, "con_rot_", A),
903    St.
904
905%%% Scale functions
906scale({Axis1,Axis2},#st{selmode=edge}=St) ->
907    Keys = mod_key_combo(),
908    Length = add_edges(Axis1,St),
909    length_check(Axis1,Length),
910    wings:ask(secondary_sel_ask(scale,Axis1,Axis2,St), St, fun (St0,OrigSt) ->
911       scale(Axis1,Axis2,Keys,OrigSt,St0)
912    end);
913
914scale({Axis1,Axis2},#st{selmode=face}=St) ->
915    Keys = mod_key_combo(),
916    Area = add_areas(Axis1,St),
917    length_check(area,Area),
918    wings:ask(secondary_sel_ask(scale_area,Axis1,Axis2,St), St, fun (St0,OrigSt) ->
919       scale(Axis1,Axis2,Keys,OrigSt,St0)
920    end).
921
922scale(Axis1,Axis2,Keys,OrigSt,#st{selmode=edge}=St) ->
923    Length1 = add_edges(Axis1,OrigSt),
924    Length2 = add_edges(Axis2,St),
925    Percent = Length1/Length2,
926    set_constraint(Keys, "con_scale_", Percent),
927    OrigSt;
928
929scale(Axis1,Axis2,Keys,OrigSt,#st{selmode=face}=St) ->
930    Area1 = add_areas(Axis1,OrigSt),
931    Area2 = add_areas(Axis2,St),
932    Percent = Area1/Area2,
933    set_constraint(Keys, "con_scale_", Percent),
934    OrigSt.
935
936add_areas(Axis,St) ->
937    wings_sel:fold(fun(Faces,We,Acc) ->
938            Fs = gb_sets:to_list(Faces),
939            add_areas(Axis,Fs,We) + Acc
940            end, 0, St).
941
942add_areas(Axis,Fs,We) ->
943    lists:foldl(fun(Face,A) ->
944            Area = case Axis of
945                normal -> wings_face:area(Face,We);
946                _axis -> get_area(Axis,Face,We)
947            end,
948            Area + A
949    end, 0, Fs).
950
951get_area(Axis,Face,We) ->
952    #we{vp=Vtab} = We,
953    Vs = wings_face:vertices_ccw(Face, We),
954    Vlist0 = [array:get(V, Vtab) || V <- Vs],
955    Vlist1 = flatten_vpos_to_axis(Axis,Vlist0),
956    FaceVs = lists:seq(0, length(Vs)-1),
957    E3dFaces = [#e3d_face{vs=FaceVs}],
958    [Area] = e3d_mesh:face_areas(E3dFaces, Vlist1),
959    Area.
960
961flatten_vpos_to_axis(Axis,Vlist) ->
962    {PlaneNorm,PlanePoint} = case Axis of
963         x -> {{1.0,0.0,0.0},{1.0,0.0,0.0}};
964         y -> {{0.0,1.0,0.0},{0.0,1.0,0.0}};
965         z -> {{0.0,0.0,1.0},{0.0,0.0,1.0}};
966         {_,_} -> Axis
967    end,
968    Pn = e3d_vec:norm(PlaneNorm),
969    Dp = e3d_vec:dot(Pn, Pn),
970    NewVlist = lists:foldl(fun(Point,A) ->
971                M0 = e3d_vec:dot(e3d_vec:sub(PlanePoint, Point),Pn)/Dp,
972                M1 = e3d_vec:add(Point, e3d_vec:mul(Pn, M0)),
973                [M1|A]
974      end,[],Vlist),
975    NewVlist.
976
977%%% Difference functions
978difference({Axis1,Axis2},St) ->
979    Keys = mod_key_combo(),
980    wings:ask(secondary_sel_ask(difference,Axis1,Axis2,St), St, fun (St0,OrigSt) ->
981       difference(Axis1,Axis2,Keys,OrigSt,St0)
982    end).
983
984difference(Axis1,Axis2,Keys1,OrigSt,St) ->
985    Keys2 = mod_key_combo(),
986    Keys = case Keys2 of
987             {false,false,false} -> Keys1;
988             _if_keys_held_again -> Keys2
989           end,
990    Set = atom_to_list(wings_pref:get_value(con_dist_set)),
991    Length1 = add_edges(Axis1,OrigSt),
992    Length2 = add_edges(Axis2,St),
993    Difference = abs(Length1-Length2),
994    set_constraint(Keys, Set, Difference),
995    OrigSt.
996
997%%% Centers functions
998centers(Axis,St) ->
999    Keys = mod_key_combo(),
1000    wings:ask(secondary_sel_ask(center,Axis,none,St), St, fun (St0,OrigSt) ->
1001       centers(Axis,Keys,OrigSt,St0)
1002    end).
1003
1004centers(Axis,Keys1,OrigSt,St) ->
1005    Keys2 = mod_key_combo(),
1006    Keys = case Keys2 of
1007             {false,false,false} -> Keys1;
1008             _if_keys_held_again -> Keys2
1009           end,
1010    Set = atom_to_list(wings_pref:get_value(con_dist_set)),
1011    Center1 = wings_sel:center(OrigSt),
1012    Center2 = wings_sel:center(St),
1013    Distance = get_distance(Axis,Center1,Center2),
1014    set_constraint(Keys, Set, Distance),
1015    OrigSt.
1016
1017%%% Modifier keys
1018mod_key_combo() ->
1019    Shift = wings_io:is_modkey_pressed(?KMOD_SHIFT),
1020    Ctrl  = wings_io:is_modkey_pressed(?KMOD_CTRL),
1021    Alt   = wings_io:is_modkey_pressed(?KMOD_ALT),
1022    {Shift,Ctrl,Alt}.
1023
1024%%% Set preferences
1025set_constraint({Shift,Ctrl,Alt}, Key, Val) ->
1026    ModKeyCombo = case {Shift,Ctrl,Alt} of
1027      {true,false,false} -> "shift";
1028      {false,true,false} -> "ctrl";
1029      {true,true,false} -> "ctrl_shift";
1030      {false,false,true} -> "alt";
1031      {true,false,true} -> "shift_alt";
1032      {false,true,true} -> "ctrl_alt";
1033      {true,true,true} -> "ctrl_shift_alt";
1034      {false,false,false} ->
1035        case Key of
1036            "con_dist_" -> atom_to_list(wings_pref:get_value(con_dist_default));
1037          "con_dist_a_" -> atom_to_list(wings_pref:get_value(con_dist_default));
1038             "con_rot_" -> atom_to_list(wings_pref:get_value(con_rot_default));
1039           "con_scale_" -> atom_to_list(wings_pref:get_value(con_scale_default))
1040        end
1041    end,
1042    ComboStr = mod(ModKeyCombo),
1043    Tag = tag(Key),
1044    Msg = io_lib:format(?__(1,"The ~ts constraint bound to ~ts is now set to ~p"),[Tag,ComboStr,Val]),
1045    io:format("~ts\n",[Msg]),
1046    wings_u:message(Msg),
1047    wings_pref:set_value(list_to_atom(Key++ModKeyCombo), Val).
1048
1049tag(Key) ->
1050    case Key of
1051      "con_rot_" -> ?__(1,"Rotation");
1052      "con_scale_" -> ?__(2,"Scale Factor");
1053      "con_dist_" -> ?__(3,"Distance");
1054      "con_dist_a_" -> ?__(4,"Alternate Distance")
1055    end.
1056
1057mod(ModKeyCombo) ->
1058    wings_util:stringify(list_to_atom(ModKeyCombo)).
1059