make_a_triangle.py

Created by 546f6d

Created on May 31, 2025

13 KB

Make a Triangle is a program that allows you to draw a triangle in the style of mathematical software like GeoGebra.

Press the “EXE” key to access the triangle information menu.

The community repository can be found here: https://github.com/Oignontom8283/make-a-triangle_numworks-python

If you’d like to contribute, feel free to check it out, or if you want to test a beta version (if one is available) 😃.

The project started in September 2024.


# -*- coding: utf-8 -*-
# version : v1.0.3
# development started in 09/2024.
# numworks shop repot : https://my.numworks.com/python/546f6d/make_triangle/
# github repot : https://github.com/Oignontom8283/make-a-triangle_numworks-python/

from math import *
from time import *
import ion

### Custom libraries ###


### Numworks Python 2D Engine - Start module definition ###

# Version : v1.0.0
# Author(s) : https://github.com/Oignontom8283/

import kandinsky as kd
from time import *
from math import *

color = kd.color

camera = (0, 0)
zoom = 1

# pixels per unit
scale = 1

# diap^lay grid
grid = False

debug = False

class screen: 
    width = 320
    height = 222


class coord:
    def __init__(self, x, y, rounder=1):
        self.x = x
        self.y = y
        self.rounder = rounder
    def tuple(self):
        return (self.x, self.y)
    def __str__(self):
        return "(x: " + str(round(self.x, self.rounder)) + ", y: " + str(round(self.y, self.rounder)) + ")"
    def __repr__(self):
        return "(x: " + str(round(self.x, self.rounder)) + ", y: " + str(round(self.y, self.rounder)) + ")"


def set_camera(x, y):
    global camera
    camera = (x, y)

def get_camera():
    global camera
    return camera

def center_camera_on(x, y):
    global camera
    
    px = x * scale * zoom
    py = y * scale * zoom

    # Centrer la caméra sur le point
    camera = (px, py)

def set_scale(s):
    global scale
    scale = s

def get_scale():
    global scale
    return scale

def set_zoom(z):
    global zoom
    zoom = z

def get_zoom():
    global zoom
    return zoom


def set_grid(g:bool):
    global grid
    grid = g

def get_grid():
    global grid
    return grid


def set_debug(d:bool):
    global debug
    debug = d

def get_debug():
    global debug
    return debug

def normalize_coord(x, y):
    """
    Transform the coordinates (x, y) in the world to the screen coordinates.
    The zoom is centered on the screen center.
    """
    # Position du point dans le monde (avant zoom)
    dx = x * scale
    dy = y * scale

    # Décale selon la caméra (toujours en unités monde)
    dx -= camera[0]
    dy -= camera[1]

    # Applique le zoom autour du centre de l'écran
    screen_x = int(dx * zoom + screen.width / 2)
    screen_y = int(dy * zoom + screen.height / 2)

    return coord(screen_x, screen_y)

def bresenham(x1, y1, x2, y2):
    points = []

    dx = abs(x2 - x1)
    dy = abs(y2 - y1)
    x, y = x1, y1

    sx = 1 if x2 > x1 else -1
    sy = 1 if y2 > y1 else -1

    if dx > dy:
        err = dx / 2.0
        while x != x2:
            points.append((x, y))
            err -= dy
            if err < 0:
                y += sy
                err += dx
            x += sx
    else:
        err = dy / 2.0
        while y != y2:
            points.append((x, y))
            err -= dx
            if err < 0:
                x += sx
                err += dy
            y += sy

    points.append((x, y))
    return points

def trace_line(x1, y1, x2, y2, color=color(0, 0, 0)):
    points = bresenham(x1, y1, x2, y2)
    for point in points:
        kd.set_pixel(point[0], point[1], color)

def draw_line(x1, y1, x2, y2, color=color(0, 0, 0)):
    a = normalize_coord(x1, y1)
    b = normalize_coord(x2, y2)
    trace_line(a.x, a.y, b.x, b.y, color)

def draw_rect(x, y, w, h, color=color(0, 0, 0)):
    a = normalize_coord(x, y)
    b = normalize_coord(x + w, y + h)
    kd.fill_rect(a.x, a.y, b.x - a.x, b.y - a.y, color)

def draw_cross(x, y, size:tuple=(5, 5), color=color(0, 0, 0), diagonal=False, absolute=False):
    a = normalize_coord(x, y)
    sizeX = int(size[0] * zoom * scale if not absolute else size[0] * zoom)
    sizeY = int(size[1] * zoom * scale if not absolute else size[1] * zoom)

    if diagonal:
        # Draw diagonal lines
        trace_line(a.x - sizeX, a.y - sizeY, a.x + sizeX, a.y + sizeY, color)
        trace_line(a.x - sizeX, a.y + sizeY, a.x + sizeX, a.y - sizeY, color)
    else:
        kd.fill_rect(a.x - int(sizeX / 2), a.y, sizeX, 1, color)
        kd.fill_rect(a.x, a.y - int(sizeY / 2), 1, sizeY, color)



def draw_circle(x, y, r, color=color(0, 0, 0), fill=False, absolute=False):
    a = normalize_coord(x, y)
    r = int(r * zoom * scale if not absolute else r)
    x0 = a.x
    y0 = a.y
    x = 0
    y = r
    d = 3 - 2 * r

    def draw_circle_points(xc, yc, x, y):
        if fill:
            # Draw horizontal lines between symmetric points
            for i in range(xc - x, xc + x + 1):
                kd.set_pixel(i, yc + y, color)
                kd.set_pixel(i, yc - y, color)
            for i in range(xc - y, xc + y + 1):
                kd.set_pixel(i, yc + x, color)
                kd.set_pixel(i, yc - x, color)
        else:
            kd.set_pixel(xc + x, yc + y, color)
            kd.set_pixel(xc - x, yc + y, color)
            kd.set_pixel(xc + x, yc - y, color)
            kd.set_pixel(xc - x, yc - y, color)
            kd.set_pixel(xc + y, yc + x, color)
            kd.set_pixel(xc - y, yc + x, color)
            kd.set_pixel(xc + y, yc - x, color)
            kd.set_pixel(xc - y, yc - x, color)

    while x <= y:
        draw_circle_points(x0, y0, x, y)
        if d < 0:
            d += 4 * x + 6
        else:
            d += 4 * (x - y) + 10
            y -= 1
        x += 1

def draw_point(x, y, r, color=color(0, 0, 0), fill=False):
    draw_circle(x, y, r, color, fill, True)

def draw_circle_arc(x, y, r, start_angle, end_angle, color=color(0, 0, 0), fill=False, absolute=False):
    """"fill=True is not optimal for now"""
    a = normalize_coord(x, y)
    r = int(r * zoom * scale if not absolute else r)
    x0 = a.x
    y0 = a.y
    start_angle = start_angle % 360
    end_angle = end_angle % 360

    if fill:
        for angle in range(start_angle, end_angle + 1):
            radian = angle * (pi / 180)
            x1 = int(x0 + r * cos(radian))
            y1 = int(y0 + r * sin(radian))
            # Draw line from center to arc point
            points = bresenham(x0, y0, x1, y1)
            for px, py in points:
                kd.set_pixel(px, py, color)
    else:
        for angle in range(start_angle, end_angle + 1):
            radian = angle * (pi / 180)
            x1 = int(x0 + r * cos(radian))
            y1 = int(y0 + r * sin(radian))
            kd.set_pixel(x1, y1, color)

def draw_text(x, y, text, offset=(0, 0), color=color(0, 0, 0)):
    a = normalize_coord(x, y)
    kd.draw_string(text, a.x + offset[0], a.y + offset[1], color)

def clear(backgroundColor=color(255, 255, 255), scale_min=10):
    """
    Its recomended to not change the scale minimum value for the grid to be displayed
    """
    kd.fill_rect(0, 0, screen.width, screen.height, backgroundColor)

    if grid and scale > 10:
        gap = int(scale * zoom)

        # Calcule la position du point (0,0) à l'écran
        origin = normalize_coord(0, 0)
        offset_x = origin.x % gap
        offset_y = origin.y % gap

        grid_color = color(220, 220, 220)

        # Lignes verticales (rectangles fins de 1 pixel de large)
        for x in range(offset_x, screen.width, gap):
            kd.fill_rect(x, 0, 1, screen.height, grid_color)

        # Lignes horizontales (rectangles fins de 1 pixel de haut)
        for y in range(offset_y, screen.height, gap):
            kd.fill_rect(0, y, screen.width, 1, grid_color)

    if debug:
        # display zoom and camera
        kd.draw_string("Zoom: " + str(round(zoom, 3)),0, 0, color(0, 0, 0))
        kd.draw_string("Scale: " + str(round(scale, 3)),0, 15, color(0, 0, 0))
        kd.draw_string("Camera: (x:" + str(round(camera[0])) + " ,y:" + str(round(camera[1])) + ")", 0, 30, color(0, 0, 0))

def draw_angle_arc(p1x, p1y, vertexX, vertexY, p2x, p2y, radius, color=color(0, 0, 0)):
    from math import atan2, degrees

    # Vectors
    v1 = (p1x - vertexX, p1y - vertexY)
    v2 = (p2x - vertexX, p2y - vertexY)

    # Calcule of the angle of each vector
    angle1 = degrees(atan2(v1[1], v1[0])) % 360
    angle2 = degrees(atan2(v2[1], v2[0])) % 360

    # Calculation of the angle between v1 and v2 in the trigonometric direction
    angle = (angle2 - angle1) % 360
    if angle > 180:
        angle = 360 - angle  # On veut le plus petit angle entre les deux vecteurs

    # Determining the limits for drawing the arc
    if (angle2 - angle1) % 360 > 180:
        start_angle, end_angle = angle2, angle1
    else:
        start_angle, end_angle = angle1, angle2

    # Write the arc
    draw_circle_arc(vertexX, vertexY, radius, start_angle, end_angle, color, False, True)

    return angle


### Numworks Python 2D Engine - End module definition ###



### Script start ###

a = int(input("distance A : "))
b = int(input("distance B : "))
c = int(input("distance C : "))

#a = 5
#b = 4
#c = 3

try:
    A = coord(0, 0)
    B = coord(a, 0)
    C = coord((a**2 + c**2 - b**2) / (2 * a), sqrt(c**2 - ((a**2 + c**2 - b**2) / (2 * a))**2))
except:
    print("[ERROR]: Impossible to create the triangle with these values")
    raise NameError("exit by erro")


ABp = coord(x=(A.x + B.x) / 2, y=(A.y + B.y) / 2)
BCp = coord(x=(B.x + C.x) / 2, y=(B.y + C.y) / 2)
CAp = coord(x=(C.x + A.x) / 2, y=(C.y + A.y) / 2)

# Calculating the distance between points
ABd = sqrt(abs(A.x - B.x) ** 2 + abs(A.y - B.y) ** 2)
BCd = sqrt(abs(B.x - C.x) ** 2 + abs(B.y - C.y) ** 2)
CAd = sqrt(abs(C.x - A.x) ** 2 + abs(C.y - A.y) ** 2)

# Calculathe the center of the trangle
G = coord(x=(A.x + B.x + C.x) / 3, y=(A.y + B.y + C.y) / 3)

# Calculate the distances from the points to the center
Ra = sqrt(abs(A.x - G.x) ** 2 + abs(A.y - G.y) ** 2)
Rb = sqrt(abs(B.x - G.x) ** 2 + abs(B.y - G.y) ** 2)
Rc = sqrt(abs(C.x - G.x) ** 2 + abs(C.y - G.y) ** 2)

# Find the point farthest from the center
R = max(Ra, Rb, Rc)

# Calculat the surface of the triangle
S = sqrt((ABd + BCd + CAd) * (-ABd + BCd + CAd) * (ABd - BCd + CAd) * (ABd + BCd - CAd)) / 4

# set_zoom(0.9 * (screen.width / (R * 2)))
set_zoom(1)
set_scale(0.9 * (screen.width / (R * 2)))
set_grid(True)
center_camera_on(G.x, G.y)

points_size = 5
speed = 1
info = False

def main():

    black = color(0, 0, 0)
    red = color(255, 0, 0)
    blue = color(0, 0, 255)
    green = color(0, 150, 0)
    majenta = color(255, 0, 255)
    grey = color(200, 200, 200)
    
    if info:
        kd.draw_string("A = " + str(A), 0, 0, red)
        kd.draw_string("B = " + str(B), 0, 15, red)
        kd.draw_string("C = " + str(C), 0, 15*2, red)

        kd.draw_string("a (AB) = " + str(ABd), 0, 15*4, black)
        kd.draw_string("b (BC) = " + str(BCd), 0, 15*5, black)
        kd.draw_string("c (CA) = " + str(CAd), 0, 15*6, black)

        kd.draw_string("G = " + str(G), 0, 15*8, green)
        kd.draw_string("Rayon = " + str(round(R, 3)), 0, 15*9, blue)
        kd.draw_string("Surface = " + str(round(S,3)), 0, 15*10, majenta)
        
        return

    draw_point(A.x, A.y, points_size, blue, True)
    draw_point(B.x, B.y, points_size, blue, True)
    draw_point(C.x, C.y, points_size, blue, True)

    draw_line(A.x, A.y, B.x, B.y, black)
    draw_line(B.x, B.y, C.x, C.y, black)
    draw_line(C.x, C.y, A.x, A.y, black)

    radius = 20
    AB_angle = draw_angle_arc(C.x, C.y, A.x, A.y, B.x, B.y, radius, red)  # angle en A
    AC_angle = draw_angle_arc(A.x, A.y, B.x, B.y, C.x, C.y, radius, red)  # angle en B
    BA_angle = draw_angle_arc(B.x, B.y, C.x, C.y, A.x, A.y, radius, red)  # angle en C

    draw_text(A.x, A.y, "A " + str(round(AB_angle, 1)) + "°", color=red, offset=(10, 0))
    draw_text(B.x, B.y, "B " + str(round(AC_angle, 1)) + "°", color=red, offset=(10, 0))
    draw_text(C.x, C.y, "C " + str(round(BA_angle, 1)) + "°", color=red, offset=(10, 0))

    draw_text(ABp.x, ABp.y, "a " + str(round(ABd, 1)), black)
    draw_text(BCp.x, BCp.y, "b " + str(round(BCd, 1)), black)
    draw_text(CAp.x, CAp.y, "c " + str(round(CAd, 1)), black)

    draw_cross(G.x, G.y, (points_size * 2, points_size * 2), green, False, True)
    draw_text(G.x, G.y, "G", (2, 2), color=green)

    #draw_circle(G.x, G.y, R, grey)

set_debug(False)
clear()
main()
delay = 0
while True:
    change = False
    if ion.keydown(ion.KEY_UP):
        camera = (camera[0], camera[1] - speed)
        change = True
    if ion.keydown(ion.KEY_DOWN):
        camera = (camera[0], camera[1] + speed)
        change = True
    if ion.keydown(ion.KEY_LEFT):
        camera = (camera[0] - speed, camera[1])
        change = True
    if ion.keydown(ion.KEY_RIGHT):
        camera = (camera[0] + speed, camera[1])
        change = True
    if ion.keydown(ion.KEY_PLUS):
        if zoom < 4:
            zoom += 0.01
            change = True
    if ion.keydown(ion.KEY_MINUS):
        if zoom > 0.01:
            zoom -= 0.01
            change = True

    if ion.keydown(ion.KEY_SINE) and speed > 1:
        speed -= 1
        change = True
    
    if ion.keydown(ion.KEY_COSINE) and speed < 10:
        speed += 1
        change = True
    
    if ion.keydown(ion.KEY_EXE):
        info = not info
        set_grid(not info)
        change = True
        delay = 3
    
    if change == True:
        change = False
        clear()
        main()
        if delay > 0:
          sleep(delay)
          delay = 0

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.