def triangulate_earclip(vertex_chain): if not isinstance(vertex_chain, list): if isinstance(vertex_chain, tuple): vertex_chain = list(vertex_chain) else: raise GraphicsError( "\n\nGraphicsError: input argument for triangulation must be a list or tuple," f" not {vertex_chain}") if len(vertex_chain) == 3: return [tuple(vertex_chain)] elif len(vertex_chain) < 3: raise GraphicsError( "\n\nGraphicsError: polygon for triangulation must have at least 3 vertices, currently, " f"only {len(vertex_chain)} vertices are present") vertex_chain = vertex_chain.copy() if is_clockwise(vertex_chain): vertex_chain.reverse() triangles = [] size = len(vertex_chain) while size > 3: for i, point in enumerate(vertex_chain): p1 = vertex_chain[i - 1] p3 = vertex_chain[(i + 1) % size] if not is_reflex(p1, point, p3): if contains_no_other_points(p1, point, p3, vertex_chain): vertex_chain.remove(point) size -= 1 triangles.append((p1, point, p3)) triangles.append(tuple(vertex_chain)) return triangles
def slope(self, p2): if not isinstance(p2, Point): raise GraphicsError( f"\n\np2 argument for slope calculation must be a Point class, not {p2}" ) if self.x == p2.x: raise GraphicsError( f"\n\nThe two points have the same x value, cannot calculate slope!" ) return (self.y - p2.y) / (self.x - p2.x)
def __init__(self, p, start_angle, end_angle, radius, radius2=None, outline=None, outline_width=None, cursor="arrow", arrow=None, resolution=10, smooth=True, bounds_width=10, layer=0, tag=None): if not isinstance(p, list): raise GraphicsError(f"\n\nGraphicsError: anchor for arc (p) must be a list in the form [x, y], not {p}") if not (isinstance(start_angle, int) or isinstance(start_angle, float)): raise GraphicsError(f"\n\nGraphicsError: start_angle must be an integer or float, not {start_angle}") if not (isinstance(end_angle, int) or isinstance(end_angle, float)): raise GraphicsError(f"\n\nGraphicsError: end_angle must be an integer or float, not {end_angle}") if not (isinstance(radius, int) or isinstance(radius, float)): raise GraphicsError(f"\n\nGraphicsError: Arc radius must be an integer or float, not {radius}") if radius2 is not None: if not (isinstance(radius2, int) or isinstance(radius2, float)): raise GraphicsError(f"\n\nGraphicsError: Arc radius2 must be an integer or float, not {radius2}") self.start_angle = start_angle self.end_angle = end_angle self.anchor = p self.radius1 = radius if radius2 is None: self.radius2 = radius else: self.radius2 = radius angle_change = (end_angle - start_angle) / resolution range_end = 90 / angle_change decimal_points = min([len(str(range_end).split('.')[1]), 3]) x_coeff = sum([abs(math.cos(math.radians(i * angle_change + start_angle))) for i in range(int(range_end * 10 ** decimal_points))]) y_coeff = sum([abs(math.sin(math.radians(i * angle_change + start_angle))) for i in range(int(range_end * 10 ** decimal_points))]) x_coeff /= 10 ** decimal_points y_coeff /= 10 ** decimal_points angle_change = (end_angle - start_angle) / resolution x_change = self.radius1 / 2 / x_coeff y_change = self.radius2 / 2 / y_coeff self.points = [[p[0] - x_change/2, p[1] - self.radius2 / 2]] for i in range(resolution): cur_angle = (i * angle_change) + start_angle self.points.append([self.points[-1][0] + x_change * math.cos(math.radians(cur_angle)), self.points[-1][1] + y_change * math.sin(math.radians(cur_angle))]) CurvedLine.__init__(self, *self.points, outline=outline, outline_width=outline_width, arrow=arrow, resolution=0, interpolation="spline", smooth=smooth, bounds_width=bounds_width, layer=layer, tag=tag)
def triangulate_chazelle(vertex_chain): if not isinstance(vertex_chain, list): if isinstance(vertex_chain, tuple): vertex_chain = list(vertex_chain) else: raise GraphicsError( "\n\nGraphicsError: input argument for triangulation must be a list or tuple," f" not {vertex_chain}") if len(vertex_chain) == 3: return [tuple(vertex_chain)] elif len(vertex_chain) < 3: raise GraphicsError( "\n\nGraphicsError: polygon for triangulation must have at least 3 vertices, currently, " f"only {len(vertex_chain)} vertices are present")
def create_square_wave(filename, duration, freq, tag=None, volume=1, sample_rate=7000): if not (isinstance(duration, float) or isinstance(duration, int)): raise GraphicsError( f"\n\nGraphicsError: duration value for square wave must be an int or float, not {duration}" ) if not (isinstance(volume, float) or isinstance(volume, int)): raise GraphicsError( f"\n\nGraphicsError: volume for square wave must be a float or int, not {volume}" ) if not (0 <= volume <= 1): raise GraphicsError( f"\n\nGraphicsError: volume for square wave must be between 0 & 1, not {volume}" ) if not (isinstance(sample_rate, float) or isinstance(sample_rate, int)): raise GraphicsError( f"\n\nGraphicsError: sample rate for square wave must be an integer or float," f" not {sample_rate}") audio = [0] num_samples = duration * sample_rate volume *= 127.5 i = 0 f = 0 check = sample_rate / (freq * 0.5) for _ in range(num_samples): if f > check: i = 1 - i f = 0 audio.append(volume * i) else: f += 1 audio.append(audio[-1]) audio.pop() return create_sound_from_values(filename, audio, tag=tag, channels=1, sample_rate=sample_rate)
def bind_frame_to(self, obj): if not isinstance(obj, AnimatedImage): if obj in GraphicsObject.tagged_objects: obj = GraphicsObject.tagged_objects[obj] if not isinstance(obj, AnimatedImage): raise GraphicsError( "\n\nGraphicsError: object to bind frame to must be an Animated Image object, " f"not {obj}") else: raise GraphicsError( "\n\nGraphicsError: object to bind frame to must be an Animated Image object, " f"not {obj}") obj.frame_bound_objects.add(self) self.set_autoflush(False) return self
def set_frame_increment_callback(self, func): if not (callable(func) or func is None): raise GraphicsError( "\n\nGraphicsError: Frame Increment callback for Animated Image must be a function, " f"not {func}") self.callbacks["frame increment"] = func return self
def combination(n, k): if isinstance(n, int): if isinstance(k, int): if n + 1 > k: # faster than n >= k return goopylib.math.CBezierCurve.combination(n, k) else: raise GraphicsError( f"\n\nGraphicsError: n must be >= k, not n={n} & k={k}") else: raise GraphicsError( f"\n\nGraphicsError: k argument for combination must be an integer, not {k}" ) else: raise GraphicsError( f"\n\nGraphicsError: n argument for combination must be an integer, not {n}" )
def bezier_curve(t, control_points): if isinstance(t, (float, int)): return goopylib.math.CBezierCurve.bezier_curve(t, control_points) else: raise GraphicsError( f"\n\nGraphicsError: t parameter for bezier curve must be a float or int, not {t}" )
def move_y(self, dy): if not (isinstance(dy, int) or isinstance(dy, float)): raise GraphicsError( "\n\nThe value to move the point by (dy) must be a number (integer or float), " f"not {dy}") self.y += dy return self
def move_to_y(self, y): if not (isinstance(y, int) or isinstance(y, float)): raise GraphicsError( f"\n\nThe value to move the point by (y) must be a number (integer or float), not {y}" ) self.y = y return self
def CosineCurve(t, control_points): if not len(control_points) > 2: raise GraphicsError("Length of Control Points must be greater than 2") total_distance = control_points[-1].x - control_points[0].x time_x = t * total_distance + control_points[0].x for i, point in enumerate(control_points): if time_x < point.x: lower_point = i - 1 break upper_point = lower_point + 1 points_distance = control_points[lower_point].distance_x( control_points[upper_point]) x = (1 - t) * control_points[lower_point].x + t * control_points[upper_point].x proportion_done = (time_x - control_points[lower_point].x) / points_distance ft = t * math.pi f = (1 - math.cos(ft)) / 2 return Point(x, lower_point * (1 - f) + upper_point * f)
def get_text(self): if self.drawn and self.graphwin.is_open(): return self.widget.get("1.0", "end-1c") else: raise GraphicsError( "\n\nGraphicsError: get_text() function for the Multiline Entry object can only be " "used if the object is drawn and the window is open")
def move_to_x(self, x): if not (isinstance(x, int) or isinstance(x, float)): raise GraphicsError( f"\n\nThe value to move the point by (x) must be a number (integer or float), not {x}" ) self.x = x return self
def move_x(self, dx): if not (isinstance(dx, int) or isinstance(dx, float)): raise GraphicsError( "\n\nThe value to move the point by (dx) must be a number (integer or float), " f"not {dx}") self.x += dx return self
def set_style(style="default"): global global_style if style not in STYLES.keys(): raise GraphicsError( f"The style specified ({style}) does not exist, must be one of {list(STYLES.keys())}" ) global_style = style
def rational_bezier_curve(t, control_points, weights): if isinstance(t, (float, int)): return goopylib.math.CBezierCurve.rational_bezier_curve( t, control_points, weights) else: raise GraphicsError( "\n\nGraphicsError: t parameter for rational bezier curve must be a float or int, " f"not {t}")
def __init__(self, *checkboxes, state=0, cursor="arrow", layer=0, tag=None, autoflush=True): if len(checkboxes) > 0: for obj in checkboxes: if not isinstance(obj, Checkbox): raise GraphicsError( f"\n\nGraphicsError: All Radio Button states must be Checkboxes, not {obj}" ) if obj in GraphicsObject.cyclebutton_instances: GraphicsObject.cyclebutton_instances.remove(obj) obj.set_state(False) self.checkboxes = checkboxes super().__init__(options=(), cursor=cursor, layer=layer, tag=tag) if autoflush: GraphicsObject.radiobutton_instances.add(self) self.state = state number_of_states = 0 self.anchor = [0, 0] for state in self.checkboxes: state_anchor = state.get_anchor() if state_anchor is not None: self.anchor[0] += state_anchor[0] self.anchor[1] += state_anchor[1] number_of_states += 1 if number_of_states != 0: self.anchor = [ self.anchor[0] // number_of_states, self.anchor[1] // number_of_states ] self.current_state = self.checkboxes[self.state].set_state(True) else: raise GraphicsError( "\n\nGraphicsError: RadioButton must have at least 1 state, not 0" )
def create_sawtooth_wave(filename, duration, freq, reverse=False, tag=None, volume=1, sample_rate=7000): if not (isinstance(duration, float) or isinstance(duration, int)): raise GraphicsError( f"\n\nGraphicsError: duration value for sawtooth wave must be an int or float, not {duration}" ) if not (isinstance(volume, float) or isinstance(volume, int)): raise GraphicsError( f"\n\nGraphicsError: volume for sawtooth wave must be a float or int, not {volume}" ) if not (0 <= volume <= 1): raise GraphicsError( f"\n\nGraphicsError: volume for sawtooth wave must be between 0 & 1, not {volume}" ) if not (isinstance(sample_rate, float) or isinstance(sample_rate, int)): raise GraphicsError( f"\n\nGraphicsError: sample rate for sawtooth wave must be an integer or float," f" not {sample_rate}") audio = [] num_samples = int(duration * sample_rate) scale = freq / sample_rate volume *= 127.5 for x in range(num_samples): x *= scale audio.append( volume * (x - int(x)) ) # The int function is equivalent to the floor function for positive numbers if reverse: audio.reverse() return create_sound_from_values(filename, audio, tag=tag, channels=1, sample_rate=sample_rate)
def __init__(self, center, radius1, radius2, bounds=None, fill=None, outline=None, outline_width=None, cursor="arrow", layer=0, tag=None): if not isinstance(center, list): raise GraphicsError( "\n\nGraphicsError: center argument for Oval must be a list in the form [x, y], " f"not {center}") if not isinstance(radius1, int): raise GraphicsError( f"\n\nGraphicsError: radius1 argument for Oval must be an integer, not {radius1}" ) if not isinstance(radius2, int): raise GraphicsError( f"\n\nGraphicsError: radius2 argument for Oval must be an integer, not {radius2}" ) p1 = [center[0] - radius1, center[1] - radius2] p2 = [center[0] + radius1, center[1] + radius2] self.radius1 = radius1 self.radius2 = radius2 self.center = center gpBBox.BBox.__init__(self, p1, p2, bounds=bounds, fill=fill, outline=outline, outline_width=outline_width, cursor=cursor, layer=layer, tag=tag)
def LinearInterpolation(p0, p1, t): if not isinstance(p0, Point): raise GraphicsError( f"\n\nGraphicsError: p0 Point to Interpolate between must be a Point object, not {p0}" ) if not isinstance(p1, Point): raise GraphicsError( f"\n\nGraphicsError: p1 Point to Interpolate between must be a Point object, not {p1}" ) if not (isinstance(t, int) or isinstance(t, float)): raise GraphicsError( f"\n\nGraphicsError: t parameter for interpolation must be an int or float, not {t}" ) if not 0 <= t <= 1: raise GraphicsError( f"\n\nGraphicsError: t parameter must be between 0 & 1, 0 <= t <= 1, not {t}" ) return (1 - t) * p0.y + t * p1.y
def create_sin_wave(filename, duration, freq, tag=None, volume=1, sample_rate=7000): if not (isinstance(duration, float) or isinstance(duration, int)): raise GraphicsError( f"\n\nGraphicsError: duration value for sin wave must be an int or float, not {duration}" ) if not (isinstance(freq, int) or isinstance(freq, float)): raise GraphicsError( f"\n\nGraphicsError: freq value for sin wave must be an int or float, not {freq}" ) if not (isinstance(volume, float) or isinstance(volume, int)): raise GraphicsError( f"\n\nGraphicsError: volume for sin wave must be a float or int, not {volume}" ) if not (0 <= volume <= 1): raise GraphicsError( f"\n\nGraphicsError: volume for sin wave must be between 0 & 1, not {volume}" ) if not (isinstance(sample_rate, float) or isinstance(sample_rate, int)): raise GraphicsError( f"\n\nGraphicsError: sample rate for sin wave must be an integer or float," f" not {sample_rate}") audio = [] num_samples = int(duration * sample_rate) scale = 6.283 * (freq / sample_rate) volume *= 127.5 for x in range(num_samples): audio.append(volume * mathsin(x * scale)) return create_sound_from_values(filename, audio, tag=tag, channels=1, sample_rate=sample_rate)
def __init__(self, p1, p2, bounds=None, fill=None, outline=None, outline_width=None, layer=0, cursor="arrow", is_rounded=False, roundness=5, tag=None): # This makes sure that the p1 & p2 arguments of the Rectangle are lists, raises an error otherwise if not isinstance(p1, list): raise GraphicsError( "\n\nGraphicsError: p1 argument for Rectangle must be a list in the form [x, y], " f"not {p1}") if not isinstance(p2, list): raise GraphicsError( "\n\nGraphicsError: p2 argument for Rectangle must be a list in the form [x, y], " f"not {p2}") # Makes sure that the roundness argument is an integer, raises an error otherwise if not isinstance(roundness, int): raise GraphicsError( "\n\nGraphicsError: roundness argument for Rectangle must be an integer, " f"not {roundness}") # A call to the super class to initialize important variables of the Rectangle class. gpBBox.BBox.__init__(self, p1, p2, bounds=bounds, fill=fill, outline=outline, outline_width=outline_width, cursor=cursor, layer=layer, tag=tag) self.is_rounded = is_rounded # A variable defining whether the rectangle is rounded self.sharpness = roundness # Usually values between 2 & 10 work well.
def set_value(self, value): if value < self.minimum or value > self.maximum: raise GraphicsError( f"\n\nValue to set the Slider Bar must be within {self.minimum} and {self.maximum} not {value}" ) self.state = value try: self.redraw() except AttributeError: self.reload()
def __init__(self, x, y): if not ((isinstance(x, int) or isinstance(x, float)) and (isinstance(y, int) or isinstance(y, float))): raise GraphicsError( "\n\nx (x={}) & y (y={}) positions must be integers".format( x, y)) self.x = x self.y = y
def move_up_layer(self, layers=1): if not isinstance(layers, int): raise GraphicsError( f"\n\nGraphicsError: layers to move up must be an integer, not {layers}" ) if layers < 0: raise GraphicsError( "\n\nGraphicsError: layers to move up must be greater than (or equal to) 0, " f"not {layers}") GraphicsObject.object_layers[self.layer].remove(self) self.layer += layers while self.layer > len(GraphicsObject.object_layers) - 1: GraphicsObject.object_layers.append([]) GraphicsObject.object_layers[self.layer].add(self) for obj in self.imgs: obj.move_up_layer(layers=layers) return self
def bind_state_to(self, other): if not isinstance(other, Checkbox): raise GraphicsError( f"Object to bind this Checkbox to must be another Checkbox, not {other}" ) other.bound_objects.add(self) self.autoflush = False self.set_state(other.state) return self
def set_layer(self, layer=0): if not isinstance(layer, int): raise GraphicsError( f"\n\nGraphicsError: layer to set to must be an integer, not {layer}" ) if layer < 0: raise GraphicsError( "\n\nGraphicsError: layer to set to must be greater than (or equal to) 0, " f"not {layer}") for obj in self.imgs: obj.set_layer(layer=layer) GraphicsObject.object_layers[self.layer].remove(self) while layer > len(GraphicsObject.object_layers) - 1: GraphicsObject.object_layers.append({*()}) GraphicsObject.object_layers[layer].add(self) self.layer = layer return self
def windows_command(command): buf = c_buffer(255) command = command.encode(getfilesystemencoding()) error_code = windll.winmm.mciSendStringA(command, buf, 254, 0) if error_code: error_buffer = c_buffer(255) windll.winmm.mciGetErrorStringA(error_code, error_buffer, 254) raise GraphicsError( f'\n\tError {error_code} for command: \n\t\t{command.decode()}\n\t{error_buffer.value.decode()}' ) return buf.value
def HermiteInterpolation(p0, p1, p2, p3, t, tension, bias): for i, p in enumerate([p0, p1, p2, p3]): if not isinstance(p, Point): raise GraphicsError( f"\n\nGraphicsError: p{i} Point to Interpolate between must be a Point object, not {p}" ) if not (isinstance(t, int) or isinstance(t, float)): raise GraphicsError( f"\n\nGraphicsError: t parameter for interpolation must be an int or float, not {t}" ) if not 0 <= t <= 1: raise GraphicsError( f"\n\nGraphicsError: t parameter must be between 0 & 1, 0 <= t <= 1, not {t}" ) if not (isinstance(tension, int) or isinstance(tension, float)): raise GraphicsError( f"\n\nGraphicsError: tension for interpolation must be an int or float, not {tension}" ) if not (isinstance(bias, int) or isinstance(bias, float)): raise GraphicsError( f"\n\nGraphicsError: bias for interpolation must be an int or float, not {bias}" ) t2 = t**2 t3 = t**3 m0 = (p1.y - p0.y) * (1 + bias) * (1 - tension) / 2 m0 += (p2.y - p1.y) * (1 - bias) * (1 - tension) / 2 m1 = (p2.y - p1.y) * (1 + bias) * (1 - tension) / 2 m1 += (p3.y - p2.y) * (1 - bias) * (1 - tension) / 2 a0 = 2 * t3 - 3 * t2 + 1 a1 = t3 - 2 * t2 + t a2 = t3 - t2 a3 = -2 * t3 + 3 * t2 return a0 * p1.y + a1 * m0 + a2 * m1 + a3 * p2.y