From 4a2fb2ee56408a61f345ecf2955697faa9d86019 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Mon, 21 Oct 2019 16:44:39 -0700 Subject: [PATCH] Split VNF structures out into vnf.scad --- beziers.scad | 3 + geometry.scad | 288 ------------------------------------------------ vnf.scad | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 303 insertions(+), 288 deletions(-) create mode 100644 vnf.scad diff --git a/beziers.scad b/beziers.scad index f032ecf..7ec9fe9 100644 --- a/beziers.scad +++ b/beziers.scad @@ -9,6 +9,9 @@ ////////////////////////////////////////////////////////////////////// +include + + // Section: Terminology // **Polyline**: A series of points joined by straight line segements. // diff --git a/geometry.scad b/geometry.scad index b8b58ba..8bc3092 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1874,292 +1874,4 @@ module region(r) } -// Section: Creating Polyhedrons with VNF Structures -// VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the -// first item is a list of vertex points, and the second is a list of face indices into the vertex -// list. Each VNF is self contained, with face indices referring only to its own vertex list. -// You can construct a `polyhedron()` in parts by describing each part in a self-contained VNF, then -// merge the various VNFs to get the completed polyhedron vertex list and faces. - - -// Function: is_vnf() -// Description: Returns true if the given value looks passingly like a VNF structure. -function is_vnf(x) = is_list(x) && len(x)==2 && is_list(x[0]) && is_list(x[1]) && (x[0]==[] || is_vector(x[0][0])) && (x[1]==[] || is_vector(x[1][0])); - - -// Function: is_vnf_list() -// Description: Returns true if the given value looks passingly like a list of VNF structures. -function is_vnf_list(x) = is_list(x) && all([for (v=x) is_vnf(v)]); - - -// Function: vnf_vertices() -// Description: Given a VNF structure, returns the list of vertex points. -function vnf_vertices(vnf) = vnf[0]; - - -// Function: vnf_faces() -// Description: Given a VNF structure, returns the list of faces, where each face is a list of indices into the VNF vertex list. -function vnf_faces(vnf) = vnf[1]; - - -// Function: vnf_get_vertex() -// Usage: -// vvnf = vnf_get_vertex(vnf, p); -// Description: -// Finds the index number of the given vertex point `p` in the given VNF structure `vnf`. If said -// point does not already exist in the VNF vertex list, it is added. Returns: `[INDEX, VNF]` where -// INDEX if the index of the point, and VNF is the possibly modified new VNF structure. -// If `p` is given as a list of points, then INDEX will be a list of indices. -// Arguments: -// vnf = The VNF structue to get the point index from. -// p = The point, or list of points to get the index of. -// Example: -// vnf1 = vnf_get_vertex(p=[3,5,8]); // Returns: [0, [[[3,5,8]],[]]] -// vnf2 = vnf_get_vertex(vnf1, p=[3,2,1]); // Returns: [1, [[[3,5,8],[3,2,1]],[]]] -// vnf3 = vnf_get_vertex(vnf2, p=[3,5,8]); // Returns: [0, [[[3,5,8],[3,2,1]],[]]] -// vnf4 = vnf_get_vertex(vnf3, p=[[1,3,2],[3,2,1]]); // Returns: [[1,2], [[[3,5,8],[3,2,1],[1,3,2]],[]]] -function vnf_get_vertex(vnf=[[],[]], p) = - is_path(p)? _vnf_get_vertices(vnf, p) : - let( - p = quant(p,1/1024), // OpenSCAD internally quantizes objects to 1/1024. - v = search([p], vnf[0])[0] - ) [ - v != []? v : len(vnf[0]), - [ - concat(vnf[0], v != []? [] : [p]), - vnf[1] - ] - ]; - - -// Internal use only -function _vnf_get_vertices(vnf=[[],[]], pts, _i=0, _idxs=[]) = - _i>=len(pts)? [_idxs, vnf] : - let( - vvnf = vnf_get_vertex(vnf, pts[_i]) - ) _vnf_get_vertices(vvnf[1], pts, _i=_i+1, _idxs=concat(_idxs,[vvnf[0]])); - - -// Function: vnf_add_face() -// Usage: -// vnf_add_face(vnf, pts); -// Description: -// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure. -// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make -// sure that the points are in the correct order to make the face normal point outwards. -// Arguments: -// vnf = The VNF structure to add a face to. -// pts = The vertex points for the face. -function vnf_add_face(vnf=[[],[]], pts) = - let( - vvnf = vnf_get_vertex(vnf, pts), - face = deduplicate(vvnf[0], closed=true), - vnf2 = vvnf[1] - ) [ - vnf_vertices(vnf2), - concat(vnf_faces(vnf2), len(face)>2? [face] : []) - ]; - - -// Function: vnf_add_faces() -// Usage: -// vnf_add_faces(vnf, faces); -// Description: -// Given a VNF structure and a list of faces, where each face is given as a list of vertex points, -// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`. -// It is up to the caller to make sure that the points are in the correct order to make the face -// normals point outwards. -// Arguments: -// vnf = The VNF structure to add a face to. -// faces = The list of faces, where each face is given as a list of vertex points. -function vnf_add_faces(vnf=[[],[]], faces, _i=0) = - _i=len(vnfs)? _acc : - vnf_merge( - vnfs, _i=_i+1, - _acc = let(base=len(_acc[0])) [ - concat(_acc[0], vnfs[_i][0]), - concat(_acc[1], [for (f=vnfs[_i][1]) [for (i=f) i+base]]), - ] - ); - - -// Function: vnf_triangulate() -// Usage: -// vnf2 = vnf_triangulate(vnf); -// Description: -// Forces triangulation of faces in the VNF that have more than 3 vertices. -function vnf_triangulate(vnf) = - let( - vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf - ) [vnf[0], triangulate_faces(vnf[0], vnf[1])]; - - -// Function: vnf_vertex_array() -// Usage: -// vnf = vnf_vertex_array(points, cols, [caps], [cap1], [cap2], [reverse], [col_wrap], [row_wrap], [vnf]); -// Description: -// Creates a VNF structure from a vertex list, by dividing the vertices into columns and rows, -// adding faces to tile the surface. You can optionally have faces added to wrap the last column -// back to the first column, or wrap the last row to the first. Endcaps can be added to either -// the first and/or last rows. -// Arguments: -// points = A list of vertices to divide into columns and rows. -// cols = The number of points in a column. -// caps = If true, add endcap faces to the first AND last rows. -// cap1 = If true, add an endcap face to the first row. -// cap2 = If true, add an endcap face to the last row. -// col_wrap = If true, add faces to connect the last column to the first. -// row_wrap = If true, add faces to connect the last row to the first. -// reverse = If true, reverse all face normals. -// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx". -// vnf = If given, add all the vertices and faces to this existing VNF structure. -// Example(3D): -// vnf = vnf_vertex_array( -// points=[ -// for (h = [0:5:180-EPSILON]) [ -// for (t = [0:5:360-EPSILON]) -// cylindrical_to_xyz(100 + 12 * cos((h/2 + t)*6), t, h) -// ] -// ], -// col_wrap=true, caps=true, reverse=true, style="alt" -// ); -// vnf_polyhedron(vnf); -// Example(3D): Both `col_wrap` and `row_wrap` are true to make a torus. -// vnf = vnf_vertex_array( -// points=[ -// for (a=[0:5:360-EPSILON]) -// affine3d_apply( -// circle(d=20), -// [xrot(90), right(30), zrot(a)] -// ) -// ], -// col_wrap=true, row_wrap=true, reverse=true -// ); -// vnf_polyhedron(vnf); -// Example(3D): Möbius Strip. Note that `row_wrap` is not used, and the first and last profile copies are the same. -// vnf = vnf_vertex_array( -// points=[ -// for (a=[0:5:360]) affine3d_apply( -// square([1,10], center=true), -// [zrot(a/2+60), xrot(90), right(30), zrot(a)] -// ) -// ], -// col_wrap=true, reverse=true -// ); -// vnf_polyhedron(vnf); -// Example(3D): Assembling a Polyhedron from Multiple Parts -// wall_points = [ -// for (a = [-90:2:90]) affine3d_apply( -// circle(d=100), -// [scale([1-0.1*cos(a*6), 1-0.1*cos((a+90)*6), 1]), up(a)] -// ) -// ]; -// cap = [ -// for (a = [0:0.01:1+EPSILON]) affine3d_apply( -// wall_points[0], -// [scale([a,a,1]), up(90-5*sin(a*360*2))] -// ) -// ]; -// cap1 = [for (p=cap) down(90, p=zscale(-1, p=p))]; -// cap2 = [for (p=cap) up(90, p=p)]; -// vnf1 = vnf_vertex_array(points=wall_points, col_wrap=true); -// vnf2 = vnf_vertex_array(points=cap1, col_wrap=true); -// vnf3 = vnf_vertex_array(points=cap2, col_wrap=true, reverse=true); -// vnf_polyhedron([vnf1, vnf2, vnf3]); -function vnf_vertex_array( - points, - caps, cap1, cap2, - col_wrap=false, - row_wrap=false, - reverse=false, - style="default", - vnf=[[],[]] -) = - assert((!caps)||(caps&&col_wrap)) - assert(in_list(style,["default","alt","quincunx"])) - let( - pts = flatten(points), - rows = len(points), - cols = len(points[0]), - errchk = [for (row=points) assert(len(row)==cols, "All rows much have the same number of columns.") 0], - cap1 = first_defined([cap1,caps,false]), - cap2 = first_defined([cap2,caps,false]), - colcnt = cols - (col_wrap?0:1), - rowcnt = rows - (row_wrap?0:1) - ) - vnf_merge([ - vnf, [ - concat( - pts, - style!="quincunx"? [] : [ - for (r = [0:1:rowcnt-1]) ( - for (c = [0:1:colcnt-1]) ( - let( - i1 = ((r+0)%rows)*cols + ((c+0)%cols), - i2 = ((r+1)%rows)*cols + ((c+0)%cols), - i3 = ((r+1)%rows)*cols + ((c+1)%cols), - i4 = ((r+0)%rows)*cols + ((c+1)%cols) - ) mean([pts[i1], pts[i2], pts[i3], pts[i4]]) - ) - ) - ] - ), - concat( - [ - for (r = [0:1:rowcnt-1]) ( - for (c = [0:1:colcnt-1]) each ( - let( - i1 = ((r+0)%rows)*cols + ((c+0)%cols), - i2 = ((r+1)%rows)*cols + ((c+0)%cols), - i3 = ((r+1)%rows)*cols + ((c+1)%cols), - i4 = ((r+0)%rows)*cols + ((c+1)%cols) - ) - style=="quincunx"? ( - let(i5 = pcnt + r*colcnt + c) - reverse? [[i1,i2,i5],[i2,i3,i5],[i3,i4,i5],[i4,i1,i5]] : [[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]] - ) : style=="alt"? ( - reverse? [[i1,i2,i4],[i2,i3,i4]] : [[i1,i4,i2],[i2,i4,i3]] - ) : ( - reverse? [[i1,i2,i3],[i1,i3,i4]] : [[i1,i3,i2],[i1,i4,i3]] - ) - ) - ) - ], - !cap1? [] : [ - reverse? - [for (c = [0:1:cols-1]) c] : - [for (c = [cols-1:-1:0]) c] - ], - !cap2? [] : [ - reverse? - [for (c = [cols-1:-1:0]) (rows-1)*cols + c] : - [for (c = [0:1:cols-1]) (rows-1)*cols + c] - ] - ) - ] - ]); - - -// Module: vnf_polyhedron() -// Usage: -// vnf_polyhedron(vnf); -// vnf_polyhedron([VNF, VNF, VNF, ...]); -// Description: -// Given a VNF structure, or a list of VNF structures, creates a polyhedron from them. -// Arguments: -// vnf = A VNF structure, or list of VNF structures. -module vnf_polyhedron(vnf) { - vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf; - polyhedron(vnf[0], vnf[1]); -} - - // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/vnf.scad b/vnf.scad new file mode 100644 index 0000000..4455ccd --- /dev/null +++ b/vnf.scad @@ -0,0 +1,300 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: vnf.scad +// VNF structures, holding Vertices 'N' Faces for use with `polyhedron().` +// To use, add the following lines to the beginning of your file: +// ``` +// use +// use +// ``` +////////////////////////////////////////////////////////////////////// + + +// Section: Creating Polyhedrons with VNF Structures +// VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the +// first item is a list of vertex points, and the second is a list of face indices into the vertex +// list. Each VNF is self contained, with face indices referring only to its own vertex list. +// You can construct a `polyhedron()` in parts by describing each part in a self-contained VNF, then +// merge the various VNFs to get the completed polyhedron vertex list and faces. + + +// Function: is_vnf() +// Description: Returns true if the given value looks passingly like a VNF structure. +function is_vnf(x) = is_list(x) && len(x)==2 && is_list(x[0]) && is_list(x[1]) && (x[0]==[] || is_vector(x[0][0])) && (x[1]==[] || is_vector(x[1][0])); + + +// Function: is_vnf_list() +// Description: Returns true if the given value looks passingly like a list of VNF structures. +function is_vnf_list(x) = is_list(x) && all([for (v=x) is_vnf(v)]); + + +// Function: vnf_vertices() +// Description: Given a VNF structure, returns the list of vertex points. +function vnf_vertices(vnf) = vnf[0]; + + +// Function: vnf_faces() +// Description: Given a VNF structure, returns the list of faces, where each face is a list of indices into the VNF vertex list. +function vnf_faces(vnf) = vnf[1]; + + +// Function: vnf_get_vertex() +// Usage: +// vvnf = vnf_get_vertex(vnf, p); +// Description: +// Finds the index number of the given vertex point `p` in the given VNF structure `vnf`. If said +// point does not already exist in the VNF vertex list, it is added. Returns: `[INDEX, VNF]` where +// INDEX if the index of the point, and VNF is the possibly modified new VNF structure. +// If `p` is given as a list of points, then INDEX will be a list of indices. +// Arguments: +// vnf = The VNF structue to get the point index from. +// p = The point, or list of points to get the index of. +// Example: +// vnf1 = vnf_get_vertex(p=[3,5,8]); // Returns: [0, [[[3,5,8]],[]]] +// vnf2 = vnf_get_vertex(vnf1, p=[3,2,1]); // Returns: [1, [[[3,5,8],[3,2,1]],[]]] +// vnf3 = vnf_get_vertex(vnf2, p=[3,5,8]); // Returns: [0, [[[3,5,8],[3,2,1]],[]]] +// vnf4 = vnf_get_vertex(vnf3, p=[[1,3,2],[3,2,1]]); // Returns: [[1,2], [[[3,5,8],[3,2,1],[1,3,2]],[]]] +function vnf_get_vertex(vnf=[[],[]], p) = + is_path(p)? _vnf_get_vertices(vnf, p) : + let( + p = quant(p,1/1024), // OpenSCAD internally quantizes objects to 1/1024. + v = search([p], vnf[0])[0] + ) [ + v != []? v : len(vnf[0]), + [ + concat(vnf[0], v != []? [] : [p]), + vnf[1] + ] + ]; + + +// Internal use only +function _vnf_get_vertices(vnf=[[],[]], pts, _i=0, _idxs=[]) = + _i>=len(pts)? [_idxs, vnf] : + let( + vvnf = vnf_get_vertex(vnf, pts[_i]) + ) _vnf_get_vertices(vvnf[1], pts, _i=_i+1, _idxs=concat(_idxs,[vvnf[0]])); + + +// Function: vnf_add_face() +// Usage: +// vnf_add_face(vnf, pts); +// Description: +// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure. +// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make +// sure that the points are in the correct order to make the face normal point outwards. +// Arguments: +// vnf = The VNF structure to add a face to. +// pts = The vertex points for the face. +function vnf_add_face(vnf=[[],[]], pts) = + let( + vvnf = vnf_get_vertex(vnf, pts), + face = deduplicate(vvnf[0], closed=true), + vnf2 = vvnf[1] + ) [ + vnf_vertices(vnf2), + concat(vnf_faces(vnf2), len(face)>2? [face] : []) + ]; + + +// Function: vnf_add_faces() +// Usage: +// vnf_add_faces(vnf, faces); +// Description: +// Given a VNF structure and a list of faces, where each face is given as a list of vertex points, +// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`. +// It is up to the caller to make sure that the points are in the correct order to make the face +// normals point outwards. +// Arguments: +// vnf = The VNF structure to add a face to. +// faces = The list of faces, where each face is given as a list of vertex points. +function vnf_add_faces(vnf=[[],[]], faces, _i=0) = + _i=len(vnfs)? _acc : + vnf_merge( + vnfs, _i=_i+1, + _acc = let(base=len(_acc[0])) [ + concat(_acc[0], vnfs[_i][0]), + concat(_acc[1], [for (f=vnfs[_i][1]) [for (i=f) i+base]]), + ] + ); + + +// Function: vnf_triangulate() +// Usage: +// vnf2 = vnf_triangulate(vnf); +// Description: +// Forces triangulation of faces in the VNF that have more than 3 vertices. +function vnf_triangulate(vnf) = + let( + vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf + ) [vnf[0], triangulate_faces(vnf[0], vnf[1])]; + + +// Function: vnf_vertex_array() +// Usage: +// vnf = vnf_vertex_array(points, cols, [caps], [cap1], [cap2], [reverse], [col_wrap], [row_wrap], [vnf]); +// Description: +// Creates a VNF structure from a vertex list, by dividing the vertices into columns and rows, +// adding faces to tile the surface. You can optionally have faces added to wrap the last column +// back to the first column, or wrap the last row to the first. Endcaps can be added to either +// the first and/or last rows. +// Arguments: +// points = A list of vertices to divide into columns and rows. +// cols = The number of points in a column. +// caps = If true, add endcap faces to the first AND last rows. +// cap1 = If true, add an endcap face to the first row. +// cap2 = If true, add an endcap face to the last row. +// col_wrap = If true, add faces to connect the last column to the first. +// row_wrap = If true, add faces to connect the last row to the first. +// reverse = If true, reverse all face normals. +// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx". +// vnf = If given, add all the vertices and faces to this existing VNF structure. +// Example(3D): +// vnf = vnf_vertex_array( +// points=[ +// for (h = [0:5:180-EPSILON]) [ +// for (t = [0:5:360-EPSILON]) +// cylindrical_to_xyz(100 + 12 * cos((h/2 + t)*6), t, h) +// ] +// ], +// col_wrap=true, caps=true, reverse=true, style="alt" +// ); +// vnf_polyhedron(vnf); +// Example(3D): Both `col_wrap` and `row_wrap` are true to make a torus. +// vnf = vnf_vertex_array( +// points=[ +// for (a=[0:5:360-EPSILON]) +// affine3d_apply( +// circle(d=20), +// [xrot(90), right(30), zrot(a)] +// ) +// ], +// col_wrap=true, row_wrap=true, reverse=true +// ); +// vnf_polyhedron(vnf); +// Example(3D): Möbius Strip. Note that `row_wrap` is not used, and the first and last profile copies are the same. +// vnf = vnf_vertex_array( +// points=[ +// for (a=[0:5:360]) affine3d_apply( +// square([1,10], center=true), +// [zrot(a/2+60), xrot(90), right(30), zrot(a)] +// ) +// ], +// col_wrap=true, reverse=true +// ); +// vnf_polyhedron(vnf); +// Example(3D): Assembling a Polyhedron from Multiple Parts +// wall_points = [ +// for (a = [-90:2:90]) affine3d_apply( +// circle(d=100), +// [scale([1-0.1*cos(a*6), 1-0.1*cos((a+90)*6), 1]), up(a)] +// ) +// ]; +// cap = [ +// for (a = [0:0.01:1+EPSILON]) affine3d_apply( +// wall_points[0], +// [scale([a,a,1]), up(90-5*sin(a*360*2))] +// ) +// ]; +// cap1 = [for (p=cap) down(90, p=zscale(-1, p=p))]; +// cap2 = [for (p=cap) up(90, p=p)]; +// vnf1 = vnf_vertex_array(points=wall_points, col_wrap=true); +// vnf2 = vnf_vertex_array(points=cap1, col_wrap=true); +// vnf3 = vnf_vertex_array(points=cap2, col_wrap=true, reverse=true); +// vnf_polyhedron([vnf1, vnf2, vnf3]); +function vnf_vertex_array( + points, + caps, cap1, cap2, + col_wrap=false, + row_wrap=false, + reverse=false, + style="default", + vnf=[[],[]] +) = + assert((!caps)||(caps&&col_wrap)) + assert(in_list(style,["default","alt","quincunx"])) + let( + pts = flatten(points), + rows = len(points), + cols = len(points[0]), + errchk = [for (row=points) assert(len(row)==cols, "All rows much have the same number of columns.") 0], + cap1 = first_defined([cap1,caps,false]), + cap2 = first_defined([cap2,caps,false]), + colcnt = cols - (col_wrap?0:1), + rowcnt = rows - (row_wrap?0:1) + ) + vnf_merge([ + vnf, [ + concat( + pts, + style!="quincunx"? [] : [ + for (r = [0:1:rowcnt-1]) ( + for (c = [0:1:colcnt-1]) ( + let( + i1 = ((r+0)%rows)*cols + ((c+0)%cols), + i2 = ((r+1)%rows)*cols + ((c+0)%cols), + i3 = ((r+1)%rows)*cols + ((c+1)%cols), + i4 = ((r+0)%rows)*cols + ((c+1)%cols) + ) mean([pts[i1], pts[i2], pts[i3], pts[i4]]) + ) + ) + ] + ), + concat( + [ + for (r = [0:1:rowcnt-1]) ( + for (c = [0:1:colcnt-1]) each ( + let( + i1 = ((r+0)%rows)*cols + ((c+0)%cols), + i2 = ((r+1)%rows)*cols + ((c+0)%cols), + i3 = ((r+1)%rows)*cols + ((c+1)%cols), + i4 = ((r+0)%rows)*cols + ((c+1)%cols) + ) + style=="quincunx"? ( + let(i5 = pcnt + r*colcnt + c) + reverse? [[i1,i2,i5],[i2,i3,i5],[i3,i4,i5],[i4,i1,i5]] : [[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]] + ) : style=="alt"? ( + reverse? [[i1,i2,i4],[i2,i3,i4]] : [[i1,i4,i2],[i2,i4,i3]] + ) : ( + reverse? [[i1,i2,i3],[i1,i3,i4]] : [[i1,i3,i2],[i1,i4,i3]] + ) + ) + ) + ], + !cap1? [] : [ + reverse? + [for (c = [0:1:cols-1]) c] : + [for (c = [cols-1:-1:0]) c] + ], + !cap2? [] : [ + reverse? + [for (c = [cols-1:-1:0]) (rows-1)*cols + c] : + [for (c = [0:1:cols-1]) (rows-1)*cols + c] + ] + ) + ] + ]); + + +// Module: vnf_polyhedron() +// Usage: +// vnf_polyhedron(vnf); +// vnf_polyhedron([VNF, VNF, VNF, ...]); +// Description: +// Given a VNF structure, or a list of VNF structures, creates a polyhedron from them. +// Arguments: +// vnf = A VNF structure, or list of VNF structures. +module vnf_polyhedron(vnf) { + vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf; + polyhedron(vnf[0], vnf[1]); +} + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap