1%% 2%% wpc_thread -- 3%% 4%% Helicoidal and Non-Helicoidal Thread Plugin 5%% 6%% Copyright (c) 2020 Micheus 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(wpc_thread). 14-export([init/0,menu/2,command/2]). 15-include_lib("src/wings.hrl"). 16-import(math, [cos/1,sin/1,pi/0]). 17 18init() -> true. 19 20menu({shape}, Menu) -> 21 init_menu(Menu); 22menu(_, Menu) -> Menu. 23 24init_menu(Menu) -> 25 case parse_menu(Menu,[]) of 26 [] -> Menu ++ menu(); 27 Menu0 -> Menu0 28 end. 29 30menu() -> 31 [{thread(),thread,?__(2,"Create a thread"),[option]}]. 32 33parse_menu([],_) -> []; 34parse_menu([{_,spring,_}=Spiral|Rest],Acc) -> 35 lists:reverse([Spiral|Acc]) ++ menu() ++ Rest; 36parse_menu([Elem|Rest],Acc) -> 37 parse_menu(Rest, [Elem|Acc]). 38 39 40thread() -> 41 ?__(1,"Thread"). 42 43command({shape,{thread, Ask}}, St) -> make_thread(Ask, St); 44command(_, _) -> next. 45 46%%% The rest are local functions. 47 48%%% 49%%% Thread 50%%% 51 52thread_dialog() -> 53 Hook = fun(Var, Val, Sto) -> 54 case Var of 55 thread_type -> 56 wings_dialog:enable(direction, Val==helicoid, Sto); 57 _ -> ok 58 end 59 end, 60 61 [{label_column, [ 62 {?__(1,"Sections"), {text,8,[{key,sections},{range,{3,infinity}}]}}, 63 {" ", separator}, 64 {?__(2,"Top Radius"), {text,0.5,[{key,top_radius},{range,{0.0,infinity}}]}}, 65 {?__(3,"Bottom Radius"), {text,0.5,[{key,bottom_radius},{range,{0.0,infinity}}]}}, 66 {" ", separator}, 67 {?__(4,"Pitch"), {text,0.3,[{key,pitch},{range,{0.0001,infinity}}]}}, 68 {?__(5,"Crest Height"), {text,0.35,[{key,crest_h},{range,{0.0,infinity}}]}}, 69 {?__(6,"Crest Amount"), {text,5,[{key,occurrences},{range,{1,infinity}}]}}] 70 }, 71 {hradio, [ 72 {?__(10,"Helicoidal"),helicoid}, 73 {?__(11,"Non-helicoidal"),non_helicoid}], 74 non_helicoid, [{key,thread_type},{title,?__(12,"Thread Type")},{hook,Hook}]}, 75 {hradio, [ 76 {?__(7,"Left hand"),left}, 77 {?__(8,"Right hand"),right}], 78 right, [{key,direction},{title,?__(9,"Direction")}]}, 79 wings_shapes:transform_obj_dlg()]. 80 81make_thread(Arg, St) when is_atom(Arg) -> 82 Qs = thread_dialog(), 83 Label = ?__(1,"Thread Options"), 84 wings_dialog:dialog_preview({shape,thread}, Arg, Label, Qs, St); 85make_thread(Arg0, _St) -> 86 Arg = maps:from_list(Arg0), 87 Sections = maps:get(sections, Arg), 88 Pitch = maps:get(pitch, Arg), 89 TopRadius = maps:get(top_radius, Arg), 90 BotRadius = maps:get(bottom_radius, Arg), 91 CrestH = maps:get(crest_h, Arg), 92 Occurrences = maps:get(occurrences, Arg), 93 Dir = maps:get(direction, Arg), 94 Type = maps:get(thread_type, Arg), 95 Modify = [{maps:get(rot_x, Arg), maps:get(rot_y, Arg), maps:get(rot_z, Arg)}, 96 {maps:get(mov_x, Arg), maps:get(mov_y, Arg), maps:get(mov_z, Arg)}, 97 maps:get(ground, Arg)], 98 99 Height = Pitch*(Occurrences+0.5), 100 Rows = Occurrences+1, 101 make_thread(Type, Dir, Sections, Height, TopRadius, BotRadius, CrestH, Pitch, Rows, Modify). 102 103%%% 104%%% Thread 105%%% 106 107make_thread(Type, Dir, Sections, Height, TopRadius, BotRadius, CrestH, Pitch, Rows, [Rot, Mov, Ground]) -> 108 Vs1 = thread_verts(Type,Sections,TopRadius,BotRadius,CrestH,Pitch,Rows,Height), 109 Fs0 = thread_faces(Type,Sections,Rows), 110 {Vs0,Fs} = set_direction(Dir,Vs1,Fs0), 111 Vs = wings_shapes:transform_obj(Rot,Mov,Ground,Vs0), 112 {new_shape,thread(),Fs,Vs}. 113 114thread_verts(Type, Sections, TopRadius, BotRadius, CrestH, Pitch, Rows, Height) -> 115 RadRange = BotRadius-TopRadius, 116 RadInc = RadRange/(Rows-1), 117 Y = Height/2.0, 118 PitchInc = Pitch/2.0, 119 SubPitchInc = Pitch/Sections, 120 Delta = pi()*2/Sections, 121 Rings = lists:seq(Sections-1,0,-1), 122 lists:foldr(fun(Idx, Acc) -> 123 YInc = Idx*Pitch, 124 RInc = Idx*RadInc, 125 Ring0 = ring_of_verts(Type, Rings, SubPitchInc, Delta, Y-YInc, TopRadius+RInc+CrestH), 126 Ring1 = ring_of_verts(Type, Rings, SubPitchInc, Delta, Y-(YInc+PitchInc), TopRadius+RInc), 127 Ring0++Ring1++Acc 128 end, [], lists:seq(0,Rows-1)). 129 130thread_faces(helicoid=Type, N, Rows) -> 131 R0 = (Rows-1)*2, 132 R = (Rows*2-1)*N, 133 Top = lists:seq(0,N-1), 134 Bottom = lists:seq(R+N-1,R, -1), 135 Sides = build_sides(Type,R0,N), 136 [Top, Bottom | Sides]; 137thread_faces(non_helicoid=Type, N, Rows) -> 138 R0 = (Rows-1)*2, 139 R = (Rows*2-1)*N, 140 Top = lists:reverse(lists:seq(N-1,0,-1)), 141 Bottom = lists:seq(R+N-1,R, -1), 142 Sides = build_sides(Type,R0,N), 143 [Top, Bottom | Sides]. 144 145build_sides(helicoid=Type, R, N) -> 146 Ns = lists:seq(0, N-2), 147 Fs = [[0, N-1, N],[N-1, 2*N, N]], 148 build_sides(Type,0,R,N,Ns,Fs); 149build_sides(non_helicoid=Type, R, N) -> 150 Ns = lists:seq(0, N-1), 151 build_sides(Type,0,R,N,Ns,[]). 152 153build_sides(_, Idx, R, _, _, Acc) when Idx > R -> 154 Acc; 155build_sides(helicoid=Type, Idx, R, N, Ns, Acc) -> 156 Sides = [[Idx*N + I, (Idx+1)*N + I, 157 (Idx+1)*N + ((I+1) rem N), Idx*N + ((Idx*N+I+1) rem N)] || I <- Ns], 158 if Idx < (R-1) -> 159 Stitch = [[Idx*N + N-1, (Idx+1)*N + N-1, (Idx+2)*N + N, (Idx+2)*N]]; 160 Idx < R -> 161 Stitch = [[Idx*N + N-1, (Idx+1)*N + N-1, (Idx+2)*N]]; 162 true -> 163 Stitch = [[Idx*N + N-1, (Idx+1)*N + N-1, (Idx+1)*N]] 164 end, 165 build_sides(Type,Idx+1,R,N,Ns,Acc++Sides++Stitch); 166build_sides(non_helicoid=Type, Idx, R, N, Ns, Acc) -> 167 Sides = [[Idx*N + I, (Idx+1)*N + I, 168 (Idx+1)*N + ((I+1) rem N), Idx*N + ((Idx*N+I+1) rem N)] || I <- Ns], 169 build_sides(Type,Idx+1,R,N,Ns,Acc++Sides). 170 171ring_of_verts(helicoid, Rings, YInc, Delta, YAxis, XZAxis) -> 172 [{XZAxis*cos(I*Delta), YAxis+YInc*I, XZAxis*sin(I*Delta)} || I <- Rings]; 173ring_of_verts(non_helicoid, Rings, _, Delta, YAxis, XZAxis) -> 174 [{XZAxis*cos(I*Delta), YAxis, XZAxis*sin(I*Delta)} || I <- Rings]. 175 176set_direction(left,Vs,Fs) -> 177 {Vs,Fs}; 178set_direction(right,Vs0,Fs0) -> 179 FlipX = e3d_mat:scale(-1.0, 1.0, 1.0), 180 Vs = [e3d_mat:mul_point(FlipX, Pos) || Pos <- Vs0], 181 Fs = [lists:reverse(F) || F <- Fs0], 182 {Vs,Fs}.