def generate_trig(args): max_t = args.end_t min_t = args.start_t def time_t(time_step): return min_t + (max_t - min_t) * time_step / args.num_time_steps def x_t(time_step): t = time_t(time_step) return args.scale * (t + math.sin(t)**args.power) def y_t(time_step): t = time_t(time_step) return args.scale * args.y_coeff * math.sin(t) r_t = marble_path.numerical_rotation_function(x_t, y_t) if args.kink_replace_circle: x_t, y_t, r_t = combine_functions.replace_kinks_with_circles( args=args, time_t=time_t, x_t=x_t, y_t=y_t, r_t=r_t, kink_args=args, num_time_steps=args.num_time_steps) slope_angle_t = slope_function.slope_function( x_t=x_t, y_t=y_t, time_t=time_t, slope_angle=args.slope_angle, num_time_steps=args.num_time_steps, overlap_args=None, kink_args=args) z_t = marble_path.arclength_height_function(x_t, y_t, args.num_time_steps, slope_angle_t=slope_angle_t) print("Start x, y, z: %.4f %.4f %.4f" % (x_t(0), y_t(0), z_t(0))) print("End x, y, z: %.4f %.4f %.4f" % (x_t(args.num_time_steps), y_t( args.num_time_steps), z_t(args.num_time_steps))) for triangle in marble_path.generate_path( x_t=x_t, y_t=y_t, z_t=z_t, r_t=r_t, tube_args=args, num_time_steps=args.num_time_steps, time_t=time_t, slope_angle_t=slope_angle_t): yield triangle
def generate_limacon(args): # the domain of the limacon will be -domain_size to +domain_size domain_size = args.domain_size min_time = -domain_size time_step_width = (domain_size * 2) / (args.time_steps) def theta_t(time_step): return min_time + time_step_width * time_step def x_t(time_step): theta = theta_t(time_step) r = args.constant_factor - args.cosine_factor * math.cos(theta) return math.cos(theta) * r def y_t(time_step): theta = theta_t(time_step) r = args.constant_factor - args.cosine_factor * math.cos(theta) return math.sin(theta) * r min_y = min(y_t(y) for y in range(0, args.time_steps+1)) max_y = max(y_t(y) for y in range(0, args.time_steps+1)) y_scale = args.length / (max_y - min_y) min_x = min(x_t(x) for x in range(0, args.time_steps+1)) max_x = max(x_t(x) for x in range(0, args.time_steps+1)) if args.width is None: x_scale = y_scale else: x_scale = args.width / (max_x - min_x) def scaled_x_t(time_step): return (x_t(time_step) - min_x) * x_scale def scaled_y_t(time_step): return (y_t(time_step) - min_y) * y_scale z_t = marble_path.arclength_height_function(scaled_x_t, scaled_y_t, args.time_steps, args.slope_angle) def r_t(time_step): theta = theta_t(time_step) return get_normal_rotation(theta, x_scale, y_scale, args.constant_factor, args.cosine_factor) print("Center of tube at time step 0: ", scaled_x_t(0), scaled_y_t(0)) print("Angle of tube: ", r_t(0)) for triangle in marble_path.generate_path(x_t=scaled_x_t, y_t=scaled_y_t, z_t=z_t, r_t=r_t, tube_args=args, num_time_steps=args.time_steps): yield triangle
def generate_helix(args): """ helix_radius is the measurement from the axis to the center of any part of the ramp tube_radius is the measurement from the center of ramp to its outer wall wall_thickness is how thick to make the wall special case: if wall_thickness >= tube_radius, there is no inner opening tube_start_angle and tube_end_angle represent which part of the ramp to draw. 0 represents the part furthest from the axis 180 represents the part closest to the axis 0..180 covers the bottom of the arc. for the top, for example, do 180..0 special case: if 0..360 or any rotation of that is supplied, the entire circle is drawn tube_sides is how many sides a complete tube would have. tube_start_angle and tube_end_angle are discretized to these subdivisions helix_sides is how many sides a complete helix rotation has vertical_displacement is how far to move up in one complete rotation tube_radius*2 means the next layer will be barely touching the previous layer rotations is how far around to go. will be discretized using helix_sides """ num_helix_subdivisions = math.ceil(args.rotations * args.helix_sides) print("Num helix: {}".format(num_helix_subdivisions)) if num_helix_subdivisions <= 0: raise ValueError("Must complete some positive fraction of a rotation") x_t = helix_x_t(args) y_t = helix_y_t(args) r_t = helix_r_t(args) def z_t(helix_subdivision): # tube_radius included again to keep everything positive # negative sign in slope is on account of the decision that positive slope means down return args.tube_radius - math.sin( args.slope_angle / 180 * math.pi ) * 2 * math.pi * args.helix_radius * helix_subdivision / args.helix_sides for triangle in marble_path.generate_path( x_t=x_t, y_t=y_t, z_t=z_t, r_t=r_t, tube_args=args, num_time_steps=num_helix_subdivisions): yield triangle
def generate_zigzag(args): num_time_steps = args.subdivisions_per_zigzag * args.num_zigzags y_delta = args.zigzag_length / args.subdivisions_per_zigzag * 2 def x_t(time_step): return time_step * args.zigzag_width / args.subdivisions_per_zigzag def y_t(time_step): time_step = time_step % args.subdivisions_per_zigzag if time_step < args.subdivisions_per_zigzag / 2: return time_step * y_delta else: return args.zigzag_length - ( time_step - args.subdivisions_per_zigzag / 2) * y_delta # overkill - we could easily calculate it ourselves z_t = marble_path.arclength_height_function(x_t, y_t, num_time_steps, args.slope_angle) def r_t(time_step): # west to east is represented by -90, since south to north is 0 return -90 build_shape.print_stats(x_t=x_t, y_t=y_t, z_t=z_t, r_t=r_t, num_time_steps=num_time_steps) tangent = math.atan(args.zigzag_length / (args.zigzag_width / 2)) print("Rotation of the zigzag: %.4f / %.4f degrees" % (tangent, tangent * 180 / math.pi)) for triangle in marble_path.generate_path(x_t=x_t, y_t=y_t, z_t=z_t, r_t=r_t, tube_args=args, num_time_steps=num_time_steps): yield triangle
def generate_astroid(args): # there are 4 corners in the astroid # we want to chop off those corners and replace them with approximated circles # to do this, we first calculate where the corners occur # this happens when the tube on one side of the corner touches the tube on the other # we will need to keep in mind the angle of the tube at that point if args.cusp_method is Cusp.CHOP: corner_t, corner_rotation = find_corner(args.outer_radius, args.tube_radius, args.astroid_power) x_offset = y_offset = 0 elif args.cusp_method is Cusp.OFFSET: corner_t = 0.0 corner_rotation = 90.0 x_offset = y_offset = args.tube_radius else: raise ValueError('Not supported: %s' % args.cusp_method) num_time_steps = args.subdivisions_per_side * 12 # the extra factor of 3 is for the rounded corners #for i in range(num_time_steps): # print(i, # astroid_step(args.outer_radius, corner_t, corner_rotation, args.astroid_power, i, args.subdivisions_per_side), # tube_angle(args.outer_radius, corner_t, corner_rotation, args.astroid_power, i, args.subdivisions_per_side)) # start at 45 degrees so we can connect easily to the post in the middle time_step_offset = int(args.subdivisions_per_side * 1.5) # TODO: might be nice to make a combined x_y_t that saves on these calculations def x_t(time_step): return astroid_step(outer_radius=args.outer_radius, cusp_method=args.cusp_method, tube_radius=args.tube_radius, corner_t=corner_t, corner_rotation=corner_rotation, astroid_power=args.astroid_power, time_step=time_step + time_step_offset, subdivisions_per_side=args.subdivisions_per_side)[0] def y_t(time_step): return astroid_step(outer_radius=args.outer_radius, cusp_method=args.cusp_method, tube_radius=args.tube_radius, corner_t=corner_t, corner_rotation=corner_rotation, astroid_power=args.astroid_power, time_step=time_step + time_step_offset, subdivisions_per_side=args.subdivisions_per_side)[1] z_t = marble_path.arclength_height_function(x_t, y_t, num_time_steps, args.slope_angle) def r_t(time_step): return tube_angle(outer_radius=args.outer_radius, cusp_method=args.cusp_method, corner_t=corner_t, corner_rotation=corner_rotation, astroid_power=args.astroid_power, time_step=time_step + time_step_offset, subdivisions_per_side=args.subdivisions_per_side) for triangle in marble_path.generate_path(x_t=x_t, y_t=y_t, z_t=z_t, r_t=r_t, tube_args=args, num_time_steps=num_time_steps): yield triangle
def generate_shape(module, args): module.describe_curve(args) if getattr(module, 'build_time_t', None) is not None: time_t = module.build_time_t(args) else: time_t = lambda t: t if getattr(module, 'build_x_y_r_t', None) is not None: x_t, y_t, r_t = module.build_x_y_r_t(args) elif getattr(module, 'build_x_y_t', None) is not None: x_t, y_t = module.build_x_y_t(args) r_t = marble_path.numerical_rotation_function(x_t, y_t) else: x_t = module.build_x_t(args) y_t = module.build_y_t(args) r_t = marble_path.numerical_rotation_function(x_t, y_t) num_time_steps = args.num_time_steps # TODO: because the circle replacement does not keep the endpoints # the same, this will disrupt any attempt to set a scale such as # in generate_hypotrochoid's closest_approach. For now, those # arguments are incompatible if getattr(args, 'kink_replace_circle', None): x_t, y_t, r_t = combine_functions.replace_kinks_with_circles( args=args, time_t=time_t, x_t=x_t, y_t=y_t, r_t=r_t, kink_args=args, num_time_steps=num_time_steps) slope_angle_t = slope_function.slope_function( x_t=x_t, y_t=y_t, time_t=time_t, slope_angle=args.slope_angle, num_time_steps=num_time_steps, overlap_args=args, kink_args=args) if getattr(args, 'zero_circle', None): updated_functions = combine_functions.add_both_zero_circles( args=args, num_time_steps=num_time_steps, x_t=x_t, y_t=y_t, slope_angle_t=slope_angle_t, r_t=r_t) num_time_steps, x_t, y_t, slope_angle_t, r_t = updated_functions z_t = marble_path.arclength_height_function(x_t, y_t, num_time_steps, slope_angle_t=slope_angle_t) print_stats(x_t, y_t, z_t, r_t, num_time_steps) for triangle in marble_path.generate_path(x_t=x_t, y_t=y_t, z_t=z_t, r_t=r_t, tube_args=args, num_time_steps=num_time_steps, time_t=time_t, slope_angle_t=slope_angle_t): yield triangle