#define PROG_NAME "make_tesla_valve" #define PROG_DESC "Create a tomogram of a Tesla valve" #define PROG_VERS "1.0" #define make_tesla_valve_C_COPYRIGHT \ "Copyright © 2016 by the State University of Campinas (UNICAMP)" /* Last edited on 2021-07-09 01:05:44 by jstolfi */ #define PROG_HELP \ " " PROG_NAME " \\\n" \ " -objectSize {SX} {SY} {SZ} \\\n" \ " -voxelSize {VSIZE} \\\n" \ " -tubeDiameter {TUBE_DIAM} \\\n" \ " -helixDiameter {HELIX_DIAM} \\\n" \ " -helixTurns {HELIX_TURNS} \\\n" \ " -numStages {NUM_STAGES} \\\n" \ " -stagesPerTurn {STAGES_PER_TURN} \\\n" \ " -loopRadius {LOOP_RADIUS} \\\n" \ " [ -loopTwist {LOOP_TWIST} ] \\\n" \ " [ -loopExtent {LOOP_EXTENT} ] \\\n" \ " [ -cups {BOTCUP} {TOPCUP} ] \\\n" \ " " argparser_help_info_HELP " \\\n" \ " < {INFILE} \\\n" \ " > {OUTFILE}" #define PROG_INFO \ "NAME\n" \ " " PROG_NAME " - " PROG_DESC "\n" \ "\n" \ "SYNOPSIS\n" \ PROG_HELP "\n" \ "\n" \ "DESCRIPTION\n" \ " The program writes to standard output a tomogram (3D voxel" \ " array) containing an antialiased model of a multistage Tesla valve.\n" \ "\n" \ "OUTPUT FILE FORMAT\n" \ " The output file format is as follows:\n" \ "\n" \ " " ppv_array_read_FORMAT_INFO "\n" \ "\n" \ " The array will have three indices ({d = 3}), which are, in" \ " order, {Z}, {Y}, and {X}. This means that all voxels of each" \ " horizontal plane occur in consecutive position, and ditto for" \ " the voxels in any row parallel to the {X} axis.\n" \ "\n" \ "OPTIONS\n" \ " -objectSize {SX} {SY} {SZ}\n" \ " This mandatory argument defines the size (in millimeters) of the object's bounding box" \ " along the X, Y, and Z axes, respectively. Parts of the object that" \ " fall outside this range will be clipped\n" \ "\n" \ " -voxelSize {VSIZE}\n" \ " This mandatory argument defines the size of a voxel in millimeters.\n" \ "\n" \ " -tubeDiameter {TUBE_DIAM}\n" \ " This mandatory argument specifies the inner diameter" \ " of the tubes in millimeters.\n" \ "\n" \ " -helixDiameter {HELIX_DIAM}\n" \ " This mandatory argument specifies the diameter" \ " of the helix that is the midline of the main tube.\n" \ "\n" \ " -helixTurns {HELIX_TURNS}\n" \ " This mandatory argument specifies the number (possibly fractional) of full turns" \ " of the helix that is the midline of the main tube. It must be" \ " sufficient to accomodate all stages.\n" \ "\n" \ " -numStages {NUM_STAGES}\n" \ " This mandatory argument specifies the total number of stages" \ " (backflow loops) in the valve. If 0, the valve will have only the" \ " main channel.\n" \ "\n" \ " -stagesPerTurn {STAGES_PER_TURN}\n" \ " This mandatory argument specifies the number of stages (backflow loops)" \ " for each turn of the helical main tube.\n" \ "\n" \ " -loopExtent {LOOP_EXTENT}\n" \ " This optional argument specifies the relative extent of the" \ " backflow loop compared to the nominal extent of the main channel" \ " in each stage of the valve. It is normally smaller than 1.0. If too" \ " large, the channels may run into each other. If omitted, it defaults to" \ " " stringify(mtv_loopExtent_DEFAULT) ".\n" \ "\n" \ " -loopRadius {LOOP_RADIUS}\n" \ " This mandatory argument specifies the radius of the midline" \ " of the terminal part of the backflow channel.\n" \ "\n" \ " -loopTwist {LOOP_TWIST}\n" \ " This optional argument specifies the twist (in degrees) of the backflow loop" \ " around the main channel. If not given, the program assumes zero twist.\n" \ "\n" \ " -cups {BOTCUP} {TOPCUP}\n" \ " This optional argument specifies whether to place cups" \ " at the top and bottom ends of the valve. The values are" \ " either 'T','t','Y','y','1' for TRUE, 'F','f','N','n','0' for" \ " FALSE. If omitted, assumes \"-cups F F\" (no cups).\n" \ "\n" \ "DOCUMENTATION OPTIONS\n" \ argparser_help_info_HELP_INFO "\n" \ "\n" \ "SEE ALSO\n" \ " tomo_to_stl(1), salamic(1).\n" \ "\n" \ "AUTHOR\n" \ " Created 2016-03-10 by Jorge Stolfi, IC-UNICAMP.\n" \ "\n" \ "MODIFICATION HISTORY\n" \ " 2016-03-09 J. Stolfi: Created.\n" \ " 2016-03-13 J. Stolfi: Removing voxel-based modeling to {voxm.h}.\n" \ " 2016-03-18 J. Stolfi: Added single compact valve.\n" \ " 2016-03-20 J. Stolfi: Added single circular-arc valve.\n" \ " 2016-03-20 J. Stolfi: Options \"-omitLoops\", \"-tubeDiameter\", \"-cups\".\n" \ " 2016-03-21 J. Stolfi: Size is \"-arraySize\" and 3 numbers.\n" \ " 2016-03-21 J. Stolfi: Added \"-voxelSize\" option, dims in mm.\n" \ " 2016-03-21 J. Stolfi: Added \"-loopStyle\" option.\n" \ " 2016-04-02 J. Stolfi: Substantial rewrite of {voxm} library and spiral valve code.\n" \ " 2016-04-03 J. Stolfi: Removed \"-type\" and \"-loopStyle\" options.\n" \ " 2016-04-21 J. Stolfi: Renamed \"-diameter\" to \"-tubeDiameter\".\n" \ " 2016-04-21 J. Stolfi: Added \"-helixDiameter\", \"-numTurns\", \"-stagesPerTurn\", \"-loopExtent\".\n" \ " 2016-04-22 J. Stolfi: Added \"-loopRadius\", \"-loopTwist\", \"-helixTurns\".\n" \ " 2016-04-22 J. Stolfi: Replaced \"-numTurns\" by \"-numStages\".\n" \ " 2016-04-22 J. Stolfi: Removed \"-omitLoops\".\n" \ "\n" \ "WARRANTY\n" \ argparser_help_info_NO_WARRANTY "\n" \ "\n" \ "RIGHTS\n" \ " " make_tesla_valve_C_COPYRIGHT ".\n" \ "\n" \ argparser_help_info_STANDARD_RIGHTS #define stringify(x) strngf(x) #define strngf(x) #x /* Hack to include non-string defines in strings. Don't ask why 2 levels. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* COMMAND-LINE OPTIONS */ typedef struct mtv_options_t { r3_t objectSize; /* Size of object in {X}, {Y}, and {Z} (mm). */ double voxelSize; /* Size of a voxel in millimeters. */ double tubeDiameter; /* Inner diameter of tubing (mm). */ double helixDiameter; /* Diameter of helix (mm). */ double helixTurns; /* Number of full turns in the helical part of main channel. */ int32_t numStages; /* Total number of stages (backflow loops). */ double stagesPerTurn; /* Number of stages per turn of helix. */ double loopRadius; /* Radius of midline of terminal part of backflow channel (mm). */ double loopExtent; /* Relative extent of backflow loop in each stage. */ double loopTwist; /* Twist of backflow loop around main channel (degrees). */ bool_t cups_bot; /* {TRUE} to include the bottom cup. */ bool_t cups_top; /* {TRUE} to include the top cup. */ } mtv_options_t; #define mtv_voxelSize_MIN (0.10) /* Min voxel size (mm). */ #define mtv_voxelSize_MAX (2.00) /* Max voxel size (mm). */ #define mtv_arraySize_MIN (100) /* Min tomogram size along any axis. */ #define mtv_arraySize_MAX (10000) /* Max tomogram size along any axis. */ #define mtv_arrayTotSize_MAX (1024*1024*(int64_t)1024) /* Max tomogram size along any axis. */ #define mtv_objectSize_MAX (1500.0) /* Max object size along any axis (mm). */ #define mtv_tubeDiameter_MIN (0.5) /* Min inner tube diameter (mm). */ #define mtv_tubeDiameter_MAX (50.0) /* Max inner tube diameter (mm). */ #define mtv_helixDiameter_MIN (0.5) /* Min diameter of helix (mm). */ #define mtv_helixDiameter_MAX (50.0) /* Max diameter of helix (mm). */ #define mtv_loopExtent_DEFAULT 0.6 /* Default relative backflow loop extent. */ /* INTERNAL PROTOTYPES */ mtv_options_t *mtv_parse_options(int32_t argc, char **argv); /* Parses the command line arguments and packs them as an {mtv_options_t}. */ void mtv_valve_make(ppv_array_t *A, mtv_options_t *o); /* Splat into {A} a single tesla valve with optional cups. */ void mtv_splat_cups ( ppv_array_t *A, r3_t *ctr, double radZ, double fuzzR, mtv_options_t *o, double *tbbotZ, double *tbtopZ, double *rdbotZ, double *rdtopZ ); /* Splats the top and bottom cups (if specified in {o}) and computes the ends of the tubing. The cups axis is vertical and goes through {ctr}. The cups span {radZ} above and below {ctr}. They occupy the same vertical space, even if they are omitted. Returns in {*tbbotZ} and {*tbtopZ} the {Z} coordinates of the bottom and top ends of the tubing, that lie just inside the respctive cups. Returns in {*rdbotZ} and {*rdtopZ} the {Z} coordinates of the bottom and top ends of the supporting rods, that lie in the bottom wall of the cups. */ void mtv_splat_inner_support ( ppv_array_t *A, r3_t *ctr, double rdbotZ, double rdtopZ, double cupR, double fuzzR, mtv_options_t *o ); /* Splats into the tomogram {A} the supporting rods in the internal space between the cups. The rods will span from {rdbotZ} to {rdtopZ} and assume that there are cups at those places, with a flat part of radius at least {cupR} on the outer bottom. */ void mtv_splat_outer_support ( ppv_array_t *A, r3_t *ctr, double rdbotZ, double rdtopZ, double valveR, double plateT, double fuzzR, mtv_options_t *o ); /* Splats into the tomogram {A} a square cage with 4 rods at the corners. The cage rods will extend from {rdbotZ} to {rdtopZ} and the squaretop and base will have thickness {plateT}, placed just beyond those coordinates. */ void mtv_splat_tubes ( ppv_array_t *A, r3_t *ctr, double botZ, double topZ, double helixR, double bfR, double inR, double tubeT, double fuzzR, mtv_options_t *o, bool_t sub ); /* Splats into the tomogram {A} the tubes of a single- or multi-stage Tesla valve. The valve is roughly vertical spanning from {botZ} to {topZ}. The valve axis goes through {ctr}. The main channel midline is a helix with radius {helixR} around that axis. The midline of each backflow loop has radius {bfR}. The tubes have inner radius {inR} and wall thickness {tubeT}. The distance function saturates {fuzzR} from the surface. All dimensions are in multiples of the voxel size. If {sub} is true, subtracts a wire that clears the tube's hollow. */ void mtv_tomogram_write(char *outFile, ppv_array_t *A); int32_t main(int32_t argc,char** argv); /* IMPLEMENTATIONS */ int32_t main(int32_t argc, char** argv) { mtv_options_t *o = mtv_parse_options(argc, argv); /* Allocate the tomogram: */ ppv_dim_t d = 3; ppv_size_t sz[d]; int64_t nvt = 1; /* Total number of volxels. */ for (ppv_axis_t k = 0; k < d; k++) { double szk = o->objectSize.c[2-k]; /* Object size (mm) alog tomogram axis {k}. */ double nvk = ceil(szk/o->voxelSize); demand(nvk <= mtv_arraySize_MAX, "voxel array dimension is too big"); sz[k] = (int32_t)nvk; nvt = nvt*sz[k]; } demand(nvt <= mtv_arrayTotSize_MAX, "voxel array is too big"); ppv_sample_t maxsmp = 255; ppv_array_t *A = ppv_array_new(d, sz, maxsmp); /* Splat object: */ mtv_valve_make(A, o); /* Write it out: */ mtv_tomogram_write("-", A); return 0; } void mtv_valve_make(ppv_array_t *A, mtv_options_t *o) { /* Grab the array dimensions in voxels: */ int32_t NX = (int32_t)A->size[2]; int32_t NY = (int32_t)A->size[1]; int32_t NZ = (int32_t)A->size[0]; double vsz = o->voxelSize; /* Voxel side (mm). */ double fuzzR = 1.5; /* Half-thickness of fuzzy layer (vx). */ /* The unit for most linear dimensions is the voxel side (vx). */ r3_t ctr = (r3_t){{ 0.5*NX, 0.5*NY, 0.5*NZ }}; /* Center of the array (vx). */ double radZ = 0.97*NZ/2; /* Half-height of valve, with cups (vx). */ double radXY = 0.97*fmin(NX, NY)/2; /* Max deviation allowed from axis (vx). */ fprintf(stderr, "valve height = %.1f mm\n", 2*radZ*o->voxelSize); /* Tube parameters: */ double tubeT = 2.5/vsz; /* Thickness of tube wall (vx). */ double inR = o->tubeDiameter/vsz/2; /* Inner tube radius (vx). */ /* Checks for proper surface extraction: */ assert(fuzzR > 1.0); demand(tubeT > fuzzR, "tube wall thickness is too small"); demand(inR > fuzzR, "inner tube diameter is too small"); double helixR = o->helixDiameter/2/vsz; /* Radius of helical midline of main conduit (vx). */ double bfR = o->loopRadius/vsz; /* Radius of turn in the backflow chanel (vx). */ /* Estimate the radius of the valve {valveR} (vx): */ double valveinR = helixR - inR - tubeT; /* Free space around axis of valve; may be neg (vx). */ double valveotR = helixR + 2*bfR + inR + tubeT; /* Max extent of valve away from axis (vx). */ fprintf(stderr, "valve inner radius %.2f outer radius = %.2f\n", valveinR*vsz, valveotR*vsz); demand(valveotR < radXY, "valve too wide for voxel array"); /* Plop down the cups (actual or virtual): */ double cupR = 13.0/vsz; /* Usable radius of threaded cups (vx). */ double tbbotZ; /* {Z} coordinate of bottom end of valve tubing (vx). */ double tbtopZ; /* {Z} coordinate of bottom end of valve tubing (vx). */ double rdbotZ; /* {Z} coordinate of bottom end of supporting rods (vx). */ double rdtopZ; /* {Z} coordinate of bottom end of supporting rods (vx). */ mtv_splat_cups(A, &ctr, radZ, fuzzR, o, &tbbotZ, &tbtopZ, &rdbotZ, &rdtopZ); if (o->cups_bot && o->cups_top) { /* Plop down the supporting rods: */ bool_t innerSupport = (valveinR >= cupR); /* Enough space for inner support? */ if (innerSupport) { mtv_splat_inner_support(A, &ctr, rdbotZ, rdtopZ, cupR, fuzzR, o); } else { double plateT = 2.0/vsz; /* Thickness of end plates. */ mtv_splat_outer_support(A, &ctr, rdbotZ, rdtopZ, valveotR, plateT, fuzzR, o); } } /* Plop down the tubes: */ mtv_splat_tubes(A, &ctr, tbbotZ, tbtopZ, helixR, bfR, inR, tubeT, fuzzR, o, FALSE); /* Clear the bore: */ mtv_splat_tubes(A, &ctr, tbbotZ, tbtopZ, helixR, bfR, inR, tubeT, fuzzR, o, TRUE); } void mtv_splat_cups ( ppv_array_t *A, r3_t *ctr, double radZ, double fuzzR, mtv_options_t *o, double *tbbotZ, double *tbtopZ, double *rdbotZ, double *rdtopZ ) { /* Dimensions are the same whether cups are splatted or not. */ /* Modified for threaded cups: */ double vsz = o->voxelSize; /* Voxel size in mm. */ double hgt = 14.0/vsz; /* Height of cup. */ double thk = 3.0/vsz; /* Thickness of cup wall. */ /* Checks for proper surface extraction: */ assert(fuzzR > 1.0); /* Bottom cup (upside down): */ double topZ_bcup = ctr->c[2] - radZ; /* {Z} of top of bottom cup. */ double botZ_bcup = topZ_bcup + hgt; /* {Z} of bottom of bottom cup. */ if (o->cups_bot) { mtv_cup_threaded_make(A, ctr, botZ_bcup, topZ_bcup, thk, fuzzR, vsz); } /* Top cup: */ double topZ_tcup = ctr->c[2] + radZ; /* {Z} of top of top cup. */ double botZ_tcup = topZ_tcup - hgt; /* {Z} of bottom of top cup. */ if (o->cups_top) { mtv_cup_threaded_make(A, ctr, botZ_tcup, topZ_tcup, thk, fuzzR, vsz); } (*tbtopZ) = botZ_tcup + thk; /* Top {Z} of tubing. */ (*tbbotZ) = botZ_bcup - thk; /* Botom {Z} of tubing. */ (*rdtopZ) = botZ_tcup + 0.5*thk; /* Top {Z} of rods. */ (*rdbotZ) = botZ_bcup - 0.5*thk; /* Botom {Z} of rods. */ } void mtv_splat_inner_support ( ppv_array_t *A, r3_t *ctr, double rdbotZ, double rdtopZ, double cupR, double fuzzR, mtv_options_t *o ) { /* Derived rod parameters: */ double vsz = o->voxelSize; /* Voxel size in mm. */ double rdmidZ = (rdbotZ + rdtopZ)/2; /* {Z} coordinate of rod center (vx). */ double rodH = fabs(rdtopZ - rdbotZ)/2; /* Half-height of rod (vx). */ double rodR = 2.0/vsz; /* Radius of rod (vx). */ double rodF = 0.00; /* Radius of rod fillet (vx). */ double cageR = cupR - 2*rodR; /* Offset of rods from axis (vx). */ fprintf(stderr, "cage radius %.2f\n", cageR*vsz); auto double fuzzy_rod(r3_t *p); /* Indicator function for the rod. */ int N = 4; /* Number of rods. */ double a0 = 0; /* Azimuth of initial rod. */ int i; for (i = 0; i < N; i++) { double ai = a0 + 2.0*M_PI*((double)i)/((double)N); /* Position and orientation of rod: */ r3_motion_state_t state; state.p.c[0] = ctr->c[0] + cageR*cos(ai); state.p.c[1] = ctr->c[1] + cageR*sin(ai); state.p.c[2] = rdmidZ; r3x3_ident(&(state.M)); voxm_splat_object(A, fuzzy_rod, &state, hypot(rodH, rodR) + fuzzR, FALSE); } return; double fuzzy_rod(r3_t *p) { return voxm_obj_rod(p, rodH, rodR, rodF, fuzzR); } } void mtv_splat_outer_support ( ppv_array_t *A, r3_t *ctr, double rdbotZ, double rdtopZ, double valveR, double plateT, double fuzzR, mtv_options_t *o ) { /* Derived rod parameters: */ double vsz = o->voxelSize; /* Voxel size in mm. */ double rdmidZ = (rdbotZ + rdtopZ)/2; /* {Z} coordinate of rod center (vx). */ double rodH = fabs(rdtopZ - rdbotZ)/2; /* Half-height of rod (vx). */ double rodRi = 5.5/vsz; /* Inner radius of rod (vx). */ double rodRo = 7.5/vsz; /* Outer radius of rod (vx). */ double rodF = 0.00; /* Radius of rod fillet (vx). */ double cageR = valveR + 2*rodRo; /* Offset of rods from axis (vx). */ fprintf(stderr, "cage radius %.2f\n", cageR*vsz); double plateF = 0.25*plateT; /* Fillet radius of top and bottom plate edges in {X} direction. */ double plateS = 2*rodRo; /* Rounding radius of vertical edges of plate. */ double plateR = cageR/sqrt(2) + plateS + plateF; /* Half-extent of plate in {X} and {Y}. */ auto double fuzzy_rod(r3_t *p); /* Indicator function for the rod. */ auto double fuzzy_plate(r3_t *p); /* Indicator function for the plate. */ int N = 4; /* Number of rods. */ double a0 = M_PI/4; /* Azimuth of initial rod. */ int i; for (i = 0; i < N; i++) { double ai = a0 + 2.0*M_PI*((double)i)/((double)N); /* Position and orientation of rod: */ r3_motion_state_t state; state.p.c[0] = ctr->c[0] + cageR*cos(ai); state.p.c[1] = ctr->c[1] + cageR*sin(ai); state.p.c[2] = rdmidZ; r3x3_ident(&(state.M)); voxm_splat_object(A, fuzzy_rod, &state, hypot(rodH, rodRo) + fuzzR, FALSE); } for (int dir = -1; dir <= +1; dir += 2) { /* Splat the plate at end {dir}: */ double plateZ = (dir < 0 ? rdbotZ : rdtopZ) + dir*plateT/2; /* {Z} of plate center. */ /* Position and orientation of plate: */ r3_motion_state_t state; state.p.c[0] = ctr->c[0]; state.p.c[1] = ctr->c[1]; state.p.c[2] = plateZ; r3x3_ident(&(state.M)); voxm_splat_object(A, fuzzy_plate, &state, hypot(sqrt(2)*plateR, plateT) + fuzzR, FALSE); } return; double fuzzy_rod(r3_t *p) { return voxm_obj_tube(p, rodH, rodRi, rodRo, rodF, fuzzR); } double fuzzy_plate(r3_t *p) { return voxm_obj_rounded_box(p, plateR, plateR, plateT/2, plateS, plateF, fuzzR); } } void mtv_splat_tubes ( ppv_array_t *A, r3_t *ctr, double tbbotZ, double tbtopZ, double helixR, double bfR, double inR, double tubeT, double fuzzR, mtv_options_t *o, bool_t sub ) { double otR = inR + tubeT; /* Outer tube radius (vx). */ /* Stage count and angular spans: */ int32_t stageN = o->numStages; /* Total number of stages (backflow loops). */ double mcA = 2*M_PI/o->stagesPerTurn; /* Nominal angular extent of each stage (rad). */ double bfD = o->loopExtent*mcA; /* Angular extent of each backflow channel (rad). */ /* The helix should make 1 full turn even if there is only 1 stage: */ double helixN = o->helixTurns; /* Number of turns of the helical part of the main channel. */ double beW = o->loopTwist*M_PI/180; /* Torsion angle of backflow channel at end of the turn (rad). */ double bmW = beW/2; /* Torsion angle of backflow channel at beginning of the turn (rad). */ r3_motion_state_t S0, S1; mtv_valve_helical_tubing_make ( A, ctr, tbbotZ, tbtopZ, helixN, helixR, stageN, mcA, bfD, bfR, bmW, beW, inR, otR, fuzzR, sub, &S0, &S1 ); } void mtv_tomogram_write(char *outFile, ppv_array_t *A) { FILE *wr = open_write(outFile, TRUE); bool_t plain = FALSE; ppv_array_write_file(wr, A, plain); fclose(wr); } mtv_options_t *mtv_parse_options(int32_t argc, char **argv) { /* Initialize argument parser: */ argparser_t *pp = argparser_new(stderr, argc, argv); argparser_set_help(pp, PROG_NAME " version " PROG_VERS ", usage:\n" PROG_HELP); argparser_set_info(pp, PROG_INFO); argparser_process_help_info_options(pp); /* Allocate the command line argument record: */ mtv_options_t *o = (mtv_options_t *)malloc(sizeof(mtv_options_t)); /* Parse keyword parameters: */ argparser_get_keyword(pp, "-voxelSize"); o->voxelSize = argparser_get_next_double(pp, mtv_voxelSize_MIN, mtv_voxelSize_MAX); /* Array size: */ double objSize_MIN = mtv_arraySize_MIN*o->voxelSize; double objSize_MAX = fmin(mtv_objectSize_MAX, mtv_arraySize_MAX*o->voxelSize); argparser_get_keyword(pp, "-objectSize"); int k; for (k = 0; k < 3; k++) { o->objectSize.c[k] = argparser_get_next_double(pp, objSize_MIN, objSize_MAX); } argparser_get_keyword(pp, "-tubeDiameter"); o->tubeDiameter = argparser_get_next_double(pp, mtv_tubeDiameter_MIN, mtv_tubeDiameter_MAX); argparser_get_keyword(pp, "-helixDiameter"); o->helixDiameter = argparser_get_next_double(pp, mtv_helixDiameter_MIN, mtv_helixDiameter_MAX); argparser_get_keyword(pp, "-helixTurns"); o->helixTurns = argparser_get_next_double(pp, 0.50, 100.0); double loopRadius_MIN = 2.0 + o->tubeDiameter/2; double loopRadius_MAX = mtv_helixDiameter_MAX/2; argparser_get_keyword(pp, "-loopRadius"); o->loopRadius = argparser_get_next_double(pp, loopRadius_MIN, loopRadius_MAX); argparser_get_keyword(pp, "-numStages"); o->numStages = (int32_t)argparser_get_next_int(pp, 0, 200); argparser_get_keyword(pp, "-stagesPerTurn"); o->stagesPerTurn = argparser_get_next_double(pp, 1.0, 20.0); if (argparser_keyword_present(pp, "-loopExtent")) { o->loopExtent = argparser_get_next_double(pp, 0.10, 20.0); } else { o->loopExtent = mtv_loopExtent_DEFAULT; } if (argparser_keyword_present(pp, "-loopTwist")) { o->loopTwist = argparser_get_next_double(pp, -180.0, +180.0); } else { o->loopTwist = 0.0; } if (argparser_keyword_present(pp, "-cups")) { o->cups_bot = argparser_get_next_bool(pp); o->cups_top = argparser_get_next_bool(pp); } else { o->cups_bot = FALSE; o->cups_top = FALSE; } /* Parse positional arguments: */ argparser_skip_parsed(pp); /* Check for spurious arguments: */ argparser_finish(pp); return o; }