some new things, mostly not working or finished
This commit is contained in:
@@ -3,7 +3,6 @@ import tkinter as tk
|
|||||||
|
|
||||||
class Fibonacci:
|
class Fibonacci:
|
||||||
def s5(self, n, r):
|
def s5(self, n, r):
|
||||||
"""Generate Fibonacci spiral polar coordinates"""
|
|
||||||
spirals = []
|
spirals = []
|
||||||
phi = (1 + 5 ** 0.5) / 2 # golden ratio
|
phi = (1 + 5 ** 0.5) / 2 # golden ratio
|
||||||
for i in range(n + 1):
|
for i in range(n + 1):
|
||||||
|
|||||||
46
fib/fib.c
Normal file
46
fib/fib.c
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// tiny struct to hold a 2x2 matrix
|
||||||
|
typedef struct {
|
||||||
|
uint64_t m00, m01, m10, m11;
|
||||||
|
} Matrix2x2;
|
||||||
|
|
||||||
|
// multiply two 2x2 matrices together
|
||||||
|
Matrix2x2 multiply(Matrix2x2 a, Matrix2x2 b) {
|
||||||
|
// standard 2x2 matrix multiplication
|
||||||
|
return (Matrix2x2) {
|
||||||
|
a.m00* b.m00 + a.m01 * b.m10, // top-left
|
||||||
|
a.m00* b.m01 + a.m01 * b.m11, // top-right
|
||||||
|
a.m10* b.m00 + a.m11 * b.m10, // bottom-left
|
||||||
|
a.m10* b.m01 + a.m11 * b.m11 // bottom-right
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// raise a matrix to the power n using binary exponentiation
|
||||||
|
Matrix2x2 matrix_pow(Matrix2x2 base, int n) {
|
||||||
|
Matrix2x2 result = { 1, 0, 0, 1 }; // start with identity
|
||||||
|
while (n > 0) {
|
||||||
|
if (n % 2 == 1) result = multiply(result, base); // if odd, multiply once
|
||||||
|
base = multiply(base, base); // square the base
|
||||||
|
n /= 2; // integer divide n by 2
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get nth fibonacci number using matrix exponentiation
|
||||||
|
uint64_t fibonacci_matrix(int n) {
|
||||||
|
if (n == 0) return 0; // edge case
|
||||||
|
Matrix2x2 base = { 1, 1, 1, 0 }; // Fibonacci Q-matrix
|
||||||
|
Matrix2x2 result = matrix_pow(base, n - 1); // raise to (n-1)
|
||||||
|
return result.m00; // top-left is F(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int n = 50; // how many numbers to print
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
printf("%llu ", fibonacci_matrix(i)); // print each fib number
|
||||||
|
}
|
||||||
|
printf("\n"); // newline at end
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
22
fib/fib.java
Normal file
22
fib/fib.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
public class recursive {
|
||||||
|
|
||||||
|
public static int fibonacci(int max) {
|
||||||
|
|
||||||
|
if (max <= 1) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return fibonacci(max - 2) + fibonacci(max - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
int max = 10;
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
System.out.print(fibonacci(i) + " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
fib/fib.py
Normal file
41
fib/fib.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# tiny class to hold a 2x2 matrix
|
||||||
|
class Matrix2x2:
|
||||||
|
def __init__(self, m00, m01, m10, m11):
|
||||||
|
self.m00 = m00
|
||||||
|
self.m01 = m01
|
||||||
|
self.m10 = m10
|
||||||
|
self.m11 = m11
|
||||||
|
|
||||||
|
# multiply two 2x2 matrices
|
||||||
|
def multiply(a, b):
|
||||||
|
return Matrix2x2(
|
||||||
|
a.m00 * b.m00 + a.m01 * b.m10, # top-left
|
||||||
|
a.m00 * b.m01 + a.m01 * b.m11, # top-right
|
||||||
|
a.m10 * b.m00 + a.m11 * b.m10, # bottom-left
|
||||||
|
a.m10 * b.m01 + a.m11 * b.m11 # bottom-right
|
||||||
|
)
|
||||||
|
|
||||||
|
# raise a matrix to the power n using binary exponentiation
|
||||||
|
def matrix_pow(base, n):
|
||||||
|
result = Matrix2x2(1, 0, 0, 1) # start with identity matrix
|
||||||
|
while n > 0:
|
||||||
|
if n % 2 == 1: # if n is odd, multiply once
|
||||||
|
result = multiply(result, base)
|
||||||
|
base = multiply(base, base) # square the base
|
||||||
|
n //= 2 # integer division
|
||||||
|
return result
|
||||||
|
|
||||||
|
# get nth fibonacci number using matrix exponentiation
|
||||||
|
def fibonacci_matrix(n):
|
||||||
|
if n == 0:
|
||||||
|
return 0 # edge case
|
||||||
|
base = Matrix2x2(1, 1, 1, 0) # Fibonacci Q-matrix
|
||||||
|
result = matrix_pow(base, n - 1)
|
||||||
|
return result.m00 # top-left is F(n)
|
||||||
|
|
||||||
|
# print first n fibonacci numbers
|
||||||
|
if __name__ == "__main__":
|
||||||
|
n = 50
|
||||||
|
for i in range(n):
|
||||||
|
print(fibonacci_matrix(i), end=" ")
|
||||||
|
print()
|
||||||
22
fib/sfib.c
Normal file
22
fib/sfib.c
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
typedef unsigned long long U;
|
||||||
|
|
||||||
|
U fib(int n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
U a = 1, b = 1, c = 1, d = 0, x = 1, y = 0, z = 0, w = 1, t;
|
||||||
|
while (n) {
|
||||||
|
if (n & 1) {
|
||||||
|
t = x * a + y * c; y = x * b + y * d; x = t;
|
||||||
|
t = z * a + w * c; w = z * b + w * d; z = t;
|
||||||
|
}
|
||||||
|
t = a * a + b * c; b = a * b + b * d; a = t;
|
||||||
|
t = c * a + d * c; d = c * b + d * d; c = t;
|
||||||
|
n >>= 1;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv[]) {
|
||||||
|
for (int i = 0;i < int(argv);i++) printf("%llu ", fib(i));
|
||||||
|
}
|
||||||
|
|
||||||
5
functions/firstlast.py
Normal file
5
functions/firstlast.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
def wort_funktion(s : str) -> str:
|
||||||
|
last = len(s)
|
||||||
|
print(s[0] + s[(len(s)) - 1])
|
||||||
|
|
||||||
|
wort_funktion("das ist ein string!")
|
||||||
70
functions/mark/main.py
Normal file
70
functions/mark/main.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Datei <20>ffnen
|
||||||
|
|
||||||
|
f = open("login.txt", "a")
|
||||||
|
|
||||||
|
|
||||||
|
#Login Prozess
|
||||||
|
|
||||||
|
def login():
|
||||||
|
benutzername = input("Geben sie ihre Benutzername ein:")
|
||||||
|
passwort = input("Geben Sie Ihren Passwort ein:")
|
||||||
|
|
||||||
|
global Registrierung
|
||||||
|
Registrierung = False #Sagt dem Programm ob der Nutzer in Zukunft registrieren m<>chte
|
||||||
|
falscherPasswort = False
|
||||||
|
try:
|
||||||
|
# Auslesen der Datei
|
||||||
|
with open("login.txt", "r") as f:
|
||||||
|
accounts = f.readlines()
|
||||||
|
|
||||||
|
angemeldet = False
|
||||||
|
|
||||||
|
for account in accounts:
|
||||||
|
name, pw = account.strip().split(",")
|
||||||
|
if benutzername == name and passwort == pw:
|
||||||
|
angemeldet = True
|
||||||
|
falscherPasswort = False
|
||||||
|
break
|
||||||
|
elif benutzername == name and not passwort == pw:
|
||||||
|
falscherPasswort = True
|
||||||
|
|
||||||
|
if angemeldet and not falscherPasswort:
|
||||||
|
print("Anmeldung erfolgreich!")
|
||||||
|
elif falscherPasswort:
|
||||||
|
print("Benutzername oder Passwort falsch (pw)")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Benutzername oder Passwort falsch")
|
||||||
|
|
||||||
|
newaccount = input("Wollen Sie einen neuen Account erstellen?")
|
||||||
|
if newaccount == "Ja":
|
||||||
|
Registrierung = True
|
||||||
|
else:
|
||||||
|
Registrierung = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Datei wurde nicht gefunden")
|
||||||
|
|
||||||
|
# Funktion zur Registrierung
|
||||||
|
def signin():
|
||||||
|
newname = input("Geben Sie ihr neuer Benutzername ein:")
|
||||||
|
newpw = input("Geben Sie ihr neues Passwort ein:")
|
||||||
|
with open("login.txt", "a") as f:
|
||||||
|
f.write("\n"+newname+",")
|
||||||
|
f.write(newpw+"\n")
|
||||||
|
|
||||||
|
print("Dein Account wurde erfolgreich registriert!")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
login()
|
||||||
|
|
||||||
|
# Aufruf der Funktion falls der Nutzer registrieren will
|
||||||
|
if Registrierung:
|
||||||
|
signin()
|
||||||
|
|
||||||
52
functions/passwort.py
Normal file
52
functions/passwort.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
def passwort_zahlen(laenge):
|
||||||
|
p = ""
|
||||||
|
for _ in range(laenge):
|
||||||
|
zufallszahl = random.randint(0, 9)
|
||||||
|
p = str(p) + str(zufallszahl)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def passwort_buchstaben(laenge):
|
||||||
|
p = ""
|
||||||
|
for _ in range(laenge):
|
||||||
|
buchstabe = random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
p = str(p) + str(buchstabe)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def passwort_kombi(laenge):
|
||||||
|
p = ""
|
||||||
|
for _ in range(laenge):
|
||||||
|
wahl = random.choice(["zahl", "buchstabe"])
|
||||||
|
if wahl == "zahl":
|
||||||
|
zufallszahl = random.randint(0, 9)
|
||||||
|
p = str(p) + str(zufallszahl)
|
||||||
|
else:
|
||||||
|
buchstabe = random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
p = str(p) + str(buchstabe)
|
||||||
|
return p
|
||||||
|
|
||||||
|
# Hauptprogramm
|
||||||
|
def main():
|
||||||
|
print("Passwortgenerator")
|
||||||
|
print("1: Nur Zahlen")
|
||||||
|
print("2: Nur Buchstaben")
|
||||||
|
print("3: Zahlen und Buchstaben")
|
||||||
|
|
||||||
|
auswahl = input("Deine Wahl (1/2/3): ")
|
||||||
|
laenge = int(input("Wie viele Zeichen soll das Passwort haben? "))
|
||||||
|
|
||||||
|
if auswahl == "1":
|
||||||
|
passwort = passwort_zahlen(laenge)
|
||||||
|
elif auswahl == "2":
|
||||||
|
passwort = passwort_buchstaben(laenge)
|
||||||
|
elif auswahl == "3":
|
||||||
|
passwort = passwort_kombi(laenge)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Passwort:", passwort)
|
||||||
|
|
||||||
|
# Programm starten
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
57
functions/sekunden.py
Normal file
57
functions/sekunden.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: ansi -*-
|
||||||
|
|
||||||
|
def timetosec(time_str):
|
||||||
|
parts = time_str.split(':')
|
||||||
|
parts = [int(p) for p in parts]
|
||||||
|
|
||||||
|
if len(parts) == 3: # HH:MM:SS
|
||||||
|
hours, minutes, seconds = parts
|
||||||
|
elif len(parts) == 2: # MM:SS
|
||||||
|
hours = 0
|
||||||
|
minutes, seconds = parts
|
||||||
|
elif len(parts) == 1: # SS
|
||||||
|
hours = 0
|
||||||
|
minutes = 0
|
||||||
|
seconds = parts[0]
|
||||||
|
else:
|
||||||
|
raise ValueError("Zeitformat")
|
||||||
|
|
||||||
|
total_seconds = hours * 3600 + minutes * 60 + seconds
|
||||||
|
return total_seconds
|
||||||
|
|
||||||
|
print(timetosec("02:15:30")) # 8130
|
||||||
|
print(timetosec("15:30")) # 930
|
||||||
|
print(timetosec("45")) # 45
|
||||||
|
|
||||||
|
|
||||||
|
def format_seconds(seconds: int) -> str:
|
||||||
|
"""
|
||||||
|
Meine Interpretation von dem Moodle eintrag. Der leer ist.
|
||||||
|
Wandelt Sekunden in das n<>chst beste Format um
|
||||||
|
:param seconds -> int
|
||||||
|
:returns str
|
||||||
|
"""
|
||||||
|
if seconds < 0:
|
||||||
|
return "falsche eingable"
|
||||||
|
|
||||||
|
intervals = (
|
||||||
|
('Tag', 86400), # 60*60*24
|
||||||
|
('Stunde', 3600), # 60*60
|
||||||
|
('Minute', 60),
|
||||||
|
('Sekunde', 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for name, count in intervals:
|
||||||
|
value = seconds // count
|
||||||
|
if value:
|
||||||
|
seconds -= value * count
|
||||||
|
if value == 1:
|
||||||
|
result.append(f"{value} {name}")
|
||||||
|
else:
|
||||||
|
result.append(f"{value} {name}en")
|
||||||
|
return ', '.join(result) if result else "0 Sekunden"
|
||||||
|
|
||||||
|
print(format_seconds(3661)) # 1std 1m 1s
|
||||||
|
print(format_seconds(86465)) # 1t 1m 5s
|
||||||
|
print(format_seconds(59)) # 59s
|
||||||
18
functions/summe.py
Normal file
18
functions/summe.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
def summe(n) -> float:
|
||||||
|
lst = []
|
||||||
|
for i in range(1,n):
|
||||||
|
lst.append(i)
|
||||||
|
formatted = " + ".join(str(x) for x in lst)
|
||||||
|
print(f"summe = {formatted} ; sum = {total(lst)}")
|
||||||
|
|
||||||
|
def total(lst: list) -> float:
|
||||||
|
total = None
|
||||||
|
for i in lst:
|
||||||
|
i += total
|
||||||
|
if total:
|
||||||
|
return total
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
summe(10)
|
||||||
31
functions/umrechner.py
Normal file
31
functions/umrechner.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
def euroToDollar(euro_amount, kurs=1.15):
|
||||||
|
return euro_amount * kurs
|
||||||
|
|
||||||
|
def dollarToEuro(dollar_amount, kurs=1.15):
|
||||||
|
return dollar_amount / kurs
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Währungs‐Umrechner ")
|
||||||
|
print("Aktueller Kurs: 1€ = {:.2f} USD".format(1.15))
|
||||||
|
richtung = input("1) € auf $ \n 2) $ auf € \nIhre Wahl: ")
|
||||||
|
if richtung == "1":
|
||||||
|
euro_str = input("€: ")
|
||||||
|
try:
|
||||||
|
euro = float(euro_str.replace(",","."))
|
||||||
|
dollar = euroToDollar(euro, kurs=1.15)
|
||||||
|
print(f"{euro:.2f}€ entsprechen {dollar:.2f}$")
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
elif richtung == "2":
|
||||||
|
usd_str = input("$: ")
|
||||||
|
try:
|
||||||
|
usd = float(usd_str.replace(",","."))
|
||||||
|
euro = dollarToEuro(usd, kurs=1.15)
|
||||||
|
print(f"{usd:.2f} $ entsprechen {euro:.2f}€")
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
else:
|
||||||
|
print("wähle richtig")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
143
key/key.py
Normal file
143
key/key.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import trimesh
|
||||||
|
from shapely.geometry import Polygon
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog, messagebox
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
import os
|
||||||
|
|
||||||
|
class KeyForge3DApp:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("KeyForge3D - Key Shape Extractor and 3D Model Generator")
|
||||||
|
self.root.geometry("600x400")
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
self.image_path = None
|
||||||
|
self.scale_factor = 0.1 # Default scale: 1 pixel = 0.1 mm (adjust with a reference object if needed)
|
||||||
|
self.key_thickness = 2.0 # Thickness of the key in mm
|
||||||
|
self.num_cuts = 5 # Number of bitting cuts (adjust based on key type)
|
||||||
|
self.cut_depth_increment = 0.33 # Depth increment per bitting value in mm (e.g., Schlage standard)
|
||||||
|
|
||||||
|
# GUI Elements
|
||||||
|
self.label = tk.Label(root, text="KeyForge3D: Extract and 3D Print Keys", font=("Arial", 16))
|
||||||
|
self.label.pack(pady=10)
|
||||||
|
|
||||||
|
self.upload_button = tk.Button(root, text="Upload Key Image", command=self.upload_image)
|
||||||
|
self.upload_button.pack(pady=5)
|
||||||
|
|
||||||
|
self.image_label = tk.Label(root)
|
||||||
|
self.image_label.pack(pady=5)
|
||||||
|
|
||||||
|
self.process_button = tk.Button(root, text="Process Key and Generate 3D Model", command=self.process_key, state=tk.DISABLED)
|
||||||
|
self.process_button.pack(pady=5)
|
||||||
|
|
||||||
|
self.result_label = tk.Label(root, text="", font=("Arial", 12))
|
||||||
|
self.result_label.pack(pady=5)
|
||||||
|
|
||||||
|
def upload_image(self):
|
||||||
|
"""Allow the user to upload an image of a key."""
|
||||||
|
self.image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.jpg *.jpeg *.png")])
|
||||||
|
if self.image_path:
|
||||||
|
# Display the uploaded image
|
||||||
|
img = Image.open(self.image_path)
|
||||||
|
img = img.resize((300, 150), Image.Resampling.LANCZOS) # Resize for display
|
||||||
|
img_tk = ImageTk.PhotoImage(img)
|
||||||
|
self.image_label.config(image=img_tk)
|
||||||
|
self.image_label.image = img_tk # Keep a reference to avoid garbage collection
|
||||||
|
self.process_button.config(state=tk.NORMAL)
|
||||||
|
self.result_label.config(text="Image uploaded. Click 'Process Key' to generate the 3D model.")
|
||||||
|
|
||||||
|
def process_key(self):
|
||||||
|
"""Process the key image, extract the shape, and generate a 3D model."""
|
||||||
|
if not self.image_path:
|
||||||
|
messagebox.showerror("Error", "Please upload an image first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Load the image
|
||||||
|
image = cv2.imread(self.image_path)
|
||||||
|
if image is None:
|
||||||
|
raise ValueError("Could not load the image.")
|
||||||
|
|
||||||
|
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
|
# Apply Gaussian blur and edge detection
|
||||||
|
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
||||||
|
edges = cv2.Canny(blurred, 50, 150)
|
||||||
|
|
||||||
|
# Find contours
|
||||||
|
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
# Filter contours to find the key (long, thin shape)
|
||||||
|
key_contour = None
|
||||||
|
for contour in contours:
|
||||||
|
perimeter = cv2.arcLength(contour, True)
|
||||||
|
approx = cv2.approxPolyDP(contour, 0.02 * perimeter, True)
|
||||||
|
x, y, w, h = cv2.boundingRect(contour)
|
||||||
|
aspect_ratio = w / float(h)
|
||||||
|
if 2 < aspect_ratio < 5 and w > 100: # Adjust these values based on your image
|
||||||
|
key_contour = contour
|
||||||
|
break
|
||||||
|
|
||||||
|
if key_contour is None:
|
||||||
|
raise ValueError("Could not detect a key in the image.")
|
||||||
|
|
||||||
|
# Extract the key region
|
||||||
|
x, y, w, h = cv2.boundingRect(key_contour)
|
||||||
|
key_region = gray[y:y+h, x:x+w]
|
||||||
|
|
||||||
|
# Convert contour to a 2D polygon
|
||||||
|
points = key_contour.reshape(-1, 2) * self.scale_factor
|
||||||
|
key_polygon = Polygon(points)
|
||||||
|
|
||||||
|
# Extrude the polygon to create a 3D model
|
||||||
|
key_mesh = trimesh.creation.extrude_polygon(key_polygon, height=self.key_thickness)
|
||||||
|
|
||||||
|
# Analyze the bitting
|
||||||
|
blade = key_region[h//2:h, :] # Focus on the lower half (blade)
|
||||||
|
height, width = blade.shape
|
||||||
|
segment_width = width // self.num_cuts
|
||||||
|
bitting = []
|
||||||
|
|
||||||
|
for i in range(self.num_cuts):
|
||||||
|
segment = blade[:, i * segment_width:(i + 1) * segment_width]
|
||||||
|
# Find the highest point (shallowest cut) in the segment
|
||||||
|
cut_depth = np.argmax(segment, axis=0).mean()
|
||||||
|
# Scale the depth to real-world dimensions
|
||||||
|
depth_value = (cut_depth / height) * self.cut_depth_increment * 9
|
||||||
|
bitting.append(depth_value)
|
||||||
|
|
||||||
|
# Apply bitting cuts to the 3D model
|
||||||
|
for i, depth in enumerate(bitting):
|
||||||
|
cut_x = (i * segment_width * self.scale_factor) + (segment_width * self.scale_factor / 2)
|
||||||
|
cut_y = 0 # Adjust based on blade position
|
||||||
|
cut_width = segment_width * self.scale_factor
|
||||||
|
cut_height = depth
|
||||||
|
# Create a box for the cut and subtract it from the key mesh
|
||||||
|
cut_box = trimesh.creation.box(
|
||||||
|
extents=[cut_width, cut_height, self.key_thickness + 1],
|
||||||
|
transform=trimesh.transformations.translation_matrix([cut_x, cut_y, 0])
|
||||||
|
)
|
||||||
|
key_mesh = key_mesh.difference(cut_box)
|
||||||
|
|
||||||
|
# Export the 3D model as STL
|
||||||
|
output_path = "key_model.stl"
|
||||||
|
key_mesh.export(output_path)
|
||||||
|
|
||||||
|
# Display the results
|
||||||
|
bitting_code = [int(d / self.cut_depth_increment) for d in bitting]
|
||||||
|
self.result_label.config(
|
||||||
|
text=f"Success! Bitting Code: {bitting_code}\n3D Model saved as '{output_path}'"
|
||||||
|
)
|
||||||
|
messagebox.showinfo("Success", f"3D model generated and saved as '{output_path}'.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to process the key: {str(e)}")
|
||||||
|
self.result_label.config(text="Error processing the key. See error message.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
root = tk.Tk()
|
||||||
|
app = KeyForge3DApp(root)
|
||||||
|
root.mainloop()
|
||||||
847
key/main.py
Normal file
847
key/main.py
Normal file
@@ -0,0 +1,847 @@
|
|||||||
|
"""
|
||||||
|
Key to STL Generator - Educational Tool
|
||||||
|
Analyzes key images and creates 3D STL models of the key teeth profile
|
||||||
|
Enhanced version with manual selection tools
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFilter, ImageTk, ImageEnhance
|
||||||
|
import numpy as np
|
||||||
|
from scipy import ndimage, signal
|
||||||
|
from scipy.ndimage import median_filter, gaussian_filter
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog, messagebox, ttk
|
||||||
|
import struct
|
||||||
|
|
||||||
|
class KeyToSTL:
|
||||||
|
def __init__(self):
|
||||||
|
self.original_image = None
|
||||||
|
self.image = None
|
||||||
|
self.processed_image = None
|
||||||
|
self.key_profile = []
|
||||||
|
self.key_bounds = None
|
||||||
|
self.manual_bounds = None
|
||||||
|
self.groove_regions = []
|
||||||
|
|
||||||
|
def load_image(self, filepath):
|
||||||
|
"""Load and preprocess the key image"""
|
||||||
|
self.original_image = Image.open(filepath)
|
||||||
|
self.original_image = self.original_image.convert('RGB')
|
||||||
|
self.image = self.original_image.convert('L')
|
||||||
|
print(f"Image loaded: {self.image.size}")
|
||||||
|
return self.image
|
||||||
|
|
||||||
|
def set_manual_bounds(self, x1, y1, x2, y2):
|
||||||
|
"""Set manual key bounds from user selection"""
|
||||||
|
self.manual_bounds = {
|
||||||
|
'left': min(x1, x2),
|
||||||
|
'right': max(x1, x2),
|
||||||
|
'top': min(y1, y2),
|
||||||
|
'bottom': max(y1, y2)
|
||||||
|
}
|
||||||
|
print(f"Manual bounds set: {self.manual_bounds}")
|
||||||
|
|
||||||
|
def add_groove_region(self, x1, y1, x2, y2):
|
||||||
|
"""Add a groove region that should be emphasized"""
|
||||||
|
region = {
|
||||||
|
'left': min(x1, x2),
|
||||||
|
'right': max(x1, x2),
|
||||||
|
'top': min(y1, y2),
|
||||||
|
'bottom': max(y1, y2)
|
||||||
|
}
|
||||||
|
self.groove_regions.append(region)
|
||||||
|
print(f"Groove region added: {region}")
|
||||||
|
|
||||||
|
def clear_groove_regions(self):
|
||||||
|
"""Clear all groove regions"""
|
||||||
|
self.groove_regions = []
|
||||||
|
print("Groove regions cleared")
|
||||||
|
|
||||||
|
def denoise_image(self, median_size=3, gaussian_sigma=1.5):
|
||||||
|
"""Apply strong denoising to handle static/noise in images"""
|
||||||
|
if self.image is None:
|
||||||
|
raise ValueError("No image loaded")
|
||||||
|
|
||||||
|
img_array = np.array(self.image)
|
||||||
|
denoised = median_filter(img_array, size=median_size)
|
||||||
|
denoised = gaussian_filter(denoised, sigma=gaussian_sigma)
|
||||||
|
|
||||||
|
return denoised
|
||||||
|
|
||||||
|
def auto_detect_key(self, noise_handling=True):
|
||||||
|
"""Automatically detect the key region with noise handling"""
|
||||||
|
if self.image is None:
|
||||||
|
raise ValueError("No image loaded")
|
||||||
|
|
||||||
|
if noise_handling:
|
||||||
|
img_array = self.denoise_image(median_size=5, gaussian_sigma=2.0)
|
||||||
|
else:
|
||||||
|
img_array = np.array(self.image)
|
||||||
|
|
||||||
|
img_pil = Image.fromarray(img_array.astype(np.uint8))
|
||||||
|
enhancer = ImageEnhance.Contrast(img_pil)
|
||||||
|
enhanced = enhancer.enhance(2.5)
|
||||||
|
|
||||||
|
img_array = np.array(enhanced)
|
||||||
|
img_array = gaussian_filter(img_array, sigma=1.0)
|
||||||
|
|
||||||
|
sobel_x = ndimage.sobel(img_array, axis=1)
|
||||||
|
sobel_y = ndimage.sobel(img_array, axis=0)
|
||||||
|
edge_magnitude = np.hypot(sobel_x, sobel_y)
|
||||||
|
|
||||||
|
edge_magnitude = (edge_magnitude / edge_magnitude.max() * 255).astype(np.uint8)
|
||||||
|
|
||||||
|
threshold = np.percentile(edge_magnitude[edge_magnitude > 0], 90)
|
||||||
|
binary_edges = edge_magnitude > threshold
|
||||||
|
|
||||||
|
from scipy.ndimage import binary_closing, binary_opening, binary_dilation, binary_erosion
|
||||||
|
|
||||||
|
binary_edges = binary_opening(binary_edges, structure=np.ones((2, 2)))
|
||||||
|
binary_edges = binary_closing(binary_edges, structure=np.ones((3, 3)))
|
||||||
|
binary_edges = binary_erosion(binary_edges, structure=np.ones((2, 2)))
|
||||||
|
binary_edges = binary_dilation(binary_edges, structure=np.ones((2, 2)))
|
||||||
|
|
||||||
|
self.processed_image = Image.fromarray((binary_edges * 255).astype(np.uint8))
|
||||||
|
return self.processed_image
|
||||||
|
|
||||||
|
def find_key_bounds(self, margin_percent=0.1):
|
||||||
|
"""Find the bounding box of the key with noise-resistant algorithm"""
|
||||||
|
if self.processed_image is None:
|
||||||
|
raise ValueError("Process image first")
|
||||||
|
|
||||||
|
# Use manual bounds if available
|
||||||
|
if self.manual_bounds:
|
||||||
|
self.key_bounds = self.manual_bounds.copy()
|
||||||
|
print(f"Using manual bounds: {self.key_bounds}")
|
||||||
|
return self.key_bounds
|
||||||
|
|
||||||
|
edges_array = np.array(self.processed_image)
|
||||||
|
height, width = edges_array.shape
|
||||||
|
|
||||||
|
row_content = np.sum(edges_array, axis=1)
|
||||||
|
col_content = np.sum(edges_array, axis=0)
|
||||||
|
|
||||||
|
row_smooth = gaussian_filter(row_content.astype(float), sigma=height * 0.02)
|
||||||
|
col_smooth = gaussian_filter(col_content.astype(float), sigma=width * 0.01)
|
||||||
|
|
||||||
|
row_threshold = np.percentile(row_smooth[row_smooth > 0], 25)
|
||||||
|
col_threshold = np.percentile(col_smooth[col_smooth > 0], 15)
|
||||||
|
|
||||||
|
rows_with_content = np.where(row_smooth > row_threshold)[0]
|
||||||
|
cols_with_content = np.where(col_smooth > col_threshold)[0]
|
||||||
|
|
||||||
|
if len(rows_with_content) > 0 and len(cols_with_content) > 0:
|
||||||
|
row_margin = int(height * margin_percent)
|
||||||
|
col_margin = int(width * margin_percent * 0.5)
|
||||||
|
|
||||||
|
self.key_bounds = {
|
||||||
|
'top': max(0, rows_with_content[0] - row_margin),
|
||||||
|
'bottom': min(height - 1, rows_with_content[-1] + row_margin),
|
||||||
|
'left': max(0, cols_with_content[0] - col_margin),
|
||||||
|
'right': min(width - 1, cols_with_content[-1] + col_margin)
|
||||||
|
}
|
||||||
|
print(f"Key bounds detected: {self.key_bounds}")
|
||||||
|
else:
|
||||||
|
self.key_bounds = {
|
||||||
|
'top': height // 4,
|
||||||
|
'bottom': 3 * height // 4,
|
||||||
|
'left': width // 10,
|
||||||
|
'right': 9 * width // 10
|
||||||
|
}
|
||||||
|
print("Using fallback bounds")
|
||||||
|
|
||||||
|
return self.key_bounds
|
||||||
|
|
||||||
|
def extract_key_profile(self, use_top=True, consensus_window=5):
|
||||||
|
"""Extract key profile with noise-resistant consensus approach"""
|
||||||
|
if self.processed_image is None or self.key_bounds is None:
|
||||||
|
raise ValueError("Process image and find bounds first")
|
||||||
|
|
||||||
|
edges_array = np.array(self.processed_image)
|
||||||
|
bounds = self.key_bounds
|
||||||
|
|
||||||
|
roi = edges_array[bounds['top']:bounds['bottom'],
|
||||||
|
bounds['left']:bounds['right']]
|
||||||
|
|
||||||
|
height, width = roi.shape
|
||||||
|
profile = []
|
||||||
|
|
||||||
|
for x in range(width):
|
||||||
|
start_col = max(0, x - consensus_window // 2)
|
||||||
|
end_col = min(width, x + consensus_window // 2 + 1)
|
||||||
|
|
||||||
|
window = roi[:, start_col:end_col]
|
||||||
|
|
||||||
|
edge_positions = []
|
||||||
|
for col in range(window.shape[1]):
|
||||||
|
column = window[:, col]
|
||||||
|
edge_pixels = np.where(column > 128)[0]
|
||||||
|
|
||||||
|
if len(edge_pixels) > 0:
|
||||||
|
if use_top:
|
||||||
|
edge_positions.append(edge_pixels[0])
|
||||||
|
else:
|
||||||
|
edge_positions.append(edge_pixels[-1])
|
||||||
|
|
||||||
|
if len(edge_positions) > 0:
|
||||||
|
y_pos = int(np.median(edge_positions))
|
||||||
|
actual_y = y_pos + bounds['top']
|
||||||
|
actual_x = x + bounds['left']
|
||||||
|
profile.append((actual_x, actual_y))
|
||||||
|
else:
|
||||||
|
if len(profile) > 0:
|
||||||
|
profile.append((x + bounds['left'], profile[-1][1]))
|
||||||
|
else:
|
||||||
|
profile.append((x + bounds['left'], bounds['top'] + height // 2))
|
||||||
|
|
||||||
|
profile = self._fill_profile_gaps(profile)
|
||||||
|
|
||||||
|
# Apply groove emphasis
|
||||||
|
if self.groove_regions:
|
||||||
|
profile = self._emphasize_grooves(profile)
|
||||||
|
|
||||||
|
self.key_profile = profile
|
||||||
|
return profile
|
||||||
|
|
||||||
|
def _emphasize_grooves(self, profile, emphasis_factor=1.5):
|
||||||
|
"""Emphasize groove regions in the profile"""
|
||||||
|
emphasized = []
|
||||||
|
|
||||||
|
for x, y in profile:
|
||||||
|
# Check if point is in any groove region
|
||||||
|
in_groove = False
|
||||||
|
for groove in self.groove_regions:
|
||||||
|
if groove['left'] <= x <= groove['right']:
|
||||||
|
in_groove = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if in_groove:
|
||||||
|
# Find average Y in this region
|
||||||
|
region_ys = [py for px, py in profile if groove['left'] <= px <= groove['right']]
|
||||||
|
if region_ys:
|
||||||
|
avg_y = np.mean(region_ys)
|
||||||
|
# Emphasize deviation from average
|
||||||
|
deviation = y - avg_y
|
||||||
|
new_y = avg_y + deviation * emphasis_factor
|
||||||
|
emphasized.append((x, int(new_y)))
|
||||||
|
else:
|
||||||
|
emphasized.append((x, y))
|
||||||
|
else:
|
||||||
|
emphasized.append((x, y))
|
||||||
|
|
||||||
|
return emphasized
|
||||||
|
|
||||||
|
def _fill_profile_gaps(self, profile, max_gap=10):
|
||||||
|
"""Fill gaps in profile with interpolation"""
|
||||||
|
if len(profile) < 2:
|
||||||
|
return profile
|
||||||
|
|
||||||
|
filled = [profile[0]]
|
||||||
|
|
||||||
|
for i in range(1, len(profile)):
|
||||||
|
prev_x, prev_y = filled[-1]
|
||||||
|
curr_x, curr_y = profile[i]
|
||||||
|
|
||||||
|
if abs(curr_y - prev_y) > max_gap:
|
||||||
|
steps = int(abs(curr_x - prev_x))
|
||||||
|
if steps > 1:
|
||||||
|
for j in range(1, steps):
|
||||||
|
interp_x = prev_x + j
|
||||||
|
interp_y = int(prev_y + (curr_y - prev_y) * j / steps)
|
||||||
|
filled.append((interp_x, interp_y))
|
||||||
|
|
||||||
|
filled.append((curr_x, curr_y))
|
||||||
|
|
||||||
|
return filled
|
||||||
|
|
||||||
|
def smooth_profile_advanced(self, window_size=11, poly_order=3):
|
||||||
|
"""Apply Savitzky-Golay filter with noise handling"""
|
||||||
|
if not self.key_profile:
|
||||||
|
raise ValueError("Extract profile first")
|
||||||
|
|
||||||
|
profile_array = np.array(self.key_profile)
|
||||||
|
x_coords = profile_array[:, 0]
|
||||||
|
y_coords = profile_array[:, 1]
|
||||||
|
|
||||||
|
y_median = median_filter(y_coords, size=5)
|
||||||
|
|
||||||
|
window_size = min(window_size, len(y_median))
|
||||||
|
if window_size % 2 == 0:
|
||||||
|
window_size -= 1
|
||||||
|
window_size = max(window_size, poly_order + 2)
|
||||||
|
if window_size % 2 == 0:
|
||||||
|
window_size += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
smoothed_y = signal.savgol_filter(y_median, window_size, poly_order)
|
||||||
|
except:
|
||||||
|
smoothed_y = gaussian_filter(y_median, sigma=window_size / 3.0)
|
||||||
|
|
||||||
|
self.key_profile = [(int(x), int(y)) for x, y in zip(x_coords, smoothed_y)]
|
||||||
|
return self.key_profile
|
||||||
|
|
||||||
|
def remove_outliers(self, threshold=3.0, window_size=10):
|
||||||
|
"""Remove outlier points using local statistics"""
|
||||||
|
if not self.key_profile:
|
||||||
|
return
|
||||||
|
|
||||||
|
profile_array = np.array(self.key_profile)
|
||||||
|
y_coords = profile_array[:, 1].astype(float)
|
||||||
|
|
||||||
|
cleaned_y = y_coords.copy()
|
||||||
|
|
||||||
|
for i in range(len(y_coords)):
|
||||||
|
start = max(0, i - window_size // 2)
|
||||||
|
end = min(len(y_coords), i + window_size // 2 + 1)
|
||||||
|
window = y_coords[start:end]
|
||||||
|
|
||||||
|
local_median = np.median(window)
|
||||||
|
mad = np.median(np.abs(window - local_median))
|
||||||
|
|
||||||
|
if mad > 0:
|
||||||
|
z_score = abs(y_coords[i] - local_median) / (1.4826 * mad)
|
||||||
|
if z_score > threshold:
|
||||||
|
cleaned_y[i] = local_median
|
||||||
|
|
||||||
|
self.key_profile = [(int(x), int(y)) for x, y in zip(profile_array[:, 0], cleaned_y)]
|
||||||
|
return self.key_profile
|
||||||
|
|
||||||
|
def normalize_profile(self):
|
||||||
|
"""Normalize profile to 0-1 range for depth"""
|
||||||
|
if not self.key_profile:
|
||||||
|
raise ValueError("Extract profile first")
|
||||||
|
|
||||||
|
profile_array = np.array(self.key_profile)
|
||||||
|
y_coords = profile_array[:, 1]
|
||||||
|
|
||||||
|
y_min, y_max = y_coords.min(), y_coords.max()
|
||||||
|
|
||||||
|
if y_max - y_min < 1:
|
||||||
|
return [(x, 0.5) for x, _ in self.key_profile]
|
||||||
|
|
||||||
|
normalized = []
|
||||||
|
for x, y in self.key_profile:
|
||||||
|
normalized_depth = (y - y_min) / (y_max - y_min)
|
||||||
|
normalized.append((x, normalized_depth))
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
def generate_stl(self, output_path, depth_scale=2.0, base_thickness=1.0):
|
||||||
|
"""Generate STL file from the key profile"""
|
||||||
|
if not self.key_profile:
|
||||||
|
raise ValueError("Extract profile first")
|
||||||
|
|
||||||
|
normalized_profile = self.normalize_profile()
|
||||||
|
|
||||||
|
profile_array = np.array(self.key_profile)
|
||||||
|
x_coords = profile_array[:, 0]
|
||||||
|
x_min, x_max = x_coords.min(), x_coords.max()
|
||||||
|
|
||||||
|
key_length = float(x_max - x_min)
|
||||||
|
key_width = 10.0
|
||||||
|
|
||||||
|
scaled_profile = []
|
||||||
|
for (x, _), (_, normalized_depth) in zip(self.key_profile, normalized_profile):
|
||||||
|
scaled_x = float(x - x_min)
|
||||||
|
actual_depth = normalized_depth * depth_scale
|
||||||
|
scaled_profile.append((scaled_x, actual_depth))
|
||||||
|
|
||||||
|
vertices, faces = self._create_mesh(scaled_profile, key_width, base_thickness)
|
||||||
|
self._write_stl(output_path, vertices, faces)
|
||||||
|
|
||||||
|
print(f"STL file saved: {output_path}")
|
||||||
|
print(f"Dimensions: {key_length:.1f} x {key_width:.1f} x {base_thickness + depth_scale:.1f} units")
|
||||||
|
|
||||||
|
def _create_mesh(self, profile, width, base_thickness):
|
||||||
|
"""Create 3D mesh from 2D profile"""
|
||||||
|
vertices = []
|
||||||
|
faces = []
|
||||||
|
|
||||||
|
n = len(profile)
|
||||||
|
|
||||||
|
for x, depth in profile:
|
||||||
|
z_top = base_thickness + depth
|
||||||
|
vertices.append([x, 0.0, z_top])
|
||||||
|
vertices.append([x, width, z_top])
|
||||||
|
vertices.append([x, 0.0, 0.0])
|
||||||
|
vertices.append([x, width, 0.0])
|
||||||
|
|
||||||
|
for i in range(n - 1):
|
||||||
|
base = i * 4
|
||||||
|
next_base = (i + 1) * 4
|
||||||
|
|
||||||
|
v1, v2 = base, base + 1
|
||||||
|
v3, v4 = next_base, next_base + 1
|
||||||
|
faces.append([v1, v3, v2])
|
||||||
|
faces.append([v2, v3, v4])
|
||||||
|
|
||||||
|
v1, v2 = base + 2, base + 3
|
||||||
|
v3, v4 = next_base + 2, next_base + 3
|
||||||
|
faces.append([v1, v2, v3])
|
||||||
|
faces.append([v2, v4, v3])
|
||||||
|
|
||||||
|
v1, v2 = base, base + 2
|
||||||
|
v3, v4 = next_base, next_base + 2
|
||||||
|
faces.append([v1, v3, v2])
|
||||||
|
faces.append([v2, v3, v4])
|
||||||
|
|
||||||
|
v1, v2 = base + 1, base + 3
|
||||||
|
v3, v4 = next_base + 1, next_base + 3
|
||||||
|
faces.append([v1, v2, v3])
|
||||||
|
faces.append([v2, v4, v3])
|
||||||
|
|
||||||
|
faces.append([0, 2, 1])
|
||||||
|
faces.append([1, 2, 3])
|
||||||
|
|
||||||
|
last = (n - 1) * 4
|
||||||
|
faces.append([last, last + 1, last + 2])
|
||||||
|
faces.append([last + 1, last + 3, last + 2])
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
||||||
|
def _write_stl(self, filepath, vertices, faces):
|
||||||
|
"""Write vertices and faces to binary STL file"""
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
header = b'Key STL - Python Generated' + b' ' * (80 - 26)
|
||||||
|
f.write(header)
|
||||||
|
f.write(struct.pack('<I', len(faces)))
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
v1 = vertices[face[0]]
|
||||||
|
v2 = vertices[face[1]]
|
||||||
|
v3 = vertices[face[2]]
|
||||||
|
|
||||||
|
normal = self._calculate_normal(v1, v2, v3)
|
||||||
|
|
||||||
|
f.write(struct.pack('<fff', *normal))
|
||||||
|
f.write(struct.pack('<fff', *v1))
|
||||||
|
f.write(struct.pack('<fff', *v2))
|
||||||
|
f.write(struct.pack('<fff', *v3))
|
||||||
|
f.write(struct.pack('<H', 0))
|
||||||
|
|
||||||
|
def _calculate_normal(self, v1, v2, v3):
|
||||||
|
"""Calculate face normal vector"""
|
||||||
|
v1, v2, v3 = np.array(v1), np.array(v2), np.array(v3)
|
||||||
|
edge1 = v2 - v1
|
||||||
|
edge2 = v3 - v1
|
||||||
|
normal = np.cross(edge1, edge2)
|
||||||
|
|
||||||
|
length = np.linalg.norm(normal)
|
||||||
|
if length > 0:
|
||||||
|
normal = normal / length
|
||||||
|
else:
|
||||||
|
normal = np.array([0.0, 0.0, 1.0])
|
||||||
|
|
||||||
|
return normal.tolist()
|
||||||
|
|
||||||
|
|
||||||
|
class KeySTLGUI:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("Key to STL Generator - Manual Selection Tools")
|
||||||
|
self.root.geometry("1200x800")
|
||||||
|
|
||||||
|
self.key_processor = KeyToSTL()
|
||||||
|
self.canvas_image = None
|
||||||
|
self.photo_image = None
|
||||||
|
|
||||||
|
# Selection state
|
||||||
|
self.selection_mode = None # 'key_bounds' or 'groove'
|
||||||
|
self.selection_start = None
|
||||||
|
self.current_rect = None
|
||||||
|
self.drawn_rectangles = []
|
||||||
|
|
||||||
|
self._create_widgets()
|
||||||
|
self._bind_canvas_events()
|
||||||
|
|
||||||
|
def _create_widgets(self):
|
||||||
|
main_container = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
|
||||||
|
main_container.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Control panel
|
||||||
|
control_frame = ttk.Frame(main_container, padding="10")
|
||||||
|
main_container.add(control_frame, weight=0)
|
||||||
|
|
||||||
|
ttk.Label(control_frame, text="Key to STL Generator",
|
||||||
|
font=('Arial', 14, 'bold')).pack(pady=10)
|
||||||
|
|
||||||
|
# Step 1
|
||||||
|
ttk.Label(control_frame, text="Step 1: Load Image",
|
||||||
|
font=('Arial', 10, 'bold')).pack(pady=(10, 5))
|
||||||
|
ttk.Button(control_frame, text="📁 Load Image",
|
||||||
|
command=self.load_image, width=22).pack(pady=5)
|
||||||
|
|
||||||
|
ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10)
|
||||||
|
|
||||||
|
# Step 2 - Manual Selection Tools
|
||||||
|
ttk.Label(control_frame, text="Step 2: Manual Selection (Optional)",
|
||||||
|
font=('Arial', 10, 'bold')).pack(pady=(5, 5))
|
||||||
|
|
||||||
|
tool_frame = ttk.LabelFrame(control_frame, text="Selection Tools", padding="5")
|
||||||
|
tool_frame.pack(fill='x', pady=5)
|
||||||
|
|
||||||
|
ttk.Button(tool_frame, text="🔲 Select Key Area",
|
||||||
|
command=self.start_key_selection, width=20).pack(pady=2)
|
||||||
|
ttk.Label(tool_frame, text="Drag rectangle around key",
|
||||||
|
font=('Arial', 8), foreground='gray').pack()
|
||||||
|
|
||||||
|
ttk.Separator(tool_frame, orient='horizontal').pack(fill='x', pady=5)
|
||||||
|
|
||||||
|
ttk.Button(tool_frame, text="🎯 Mark Groove Region",
|
||||||
|
command=self.start_groove_selection, width=20).pack(pady=2)
|
||||||
|
ttk.Label(tool_frame, text="Mark areas with teeth cuts",
|
||||||
|
font=('Arial', 8), foreground='gray').pack()
|
||||||
|
|
||||||
|
ttk.Button(tool_frame, text="🗑️ Clear Grooves",
|
||||||
|
command=self.clear_grooves, width=20).pack(pady=2)
|
||||||
|
|
||||||
|
ttk.Button(tool_frame, text="↩️ Cancel Selection",
|
||||||
|
command=self.cancel_selection, width=20).pack(pady=2)
|
||||||
|
|
||||||
|
self.selection_status = ttk.Label(tool_frame, text="No active tool",
|
||||||
|
font=('Arial', 8, 'italic'), foreground='blue')
|
||||||
|
self.selection_status.pack(pady=5)
|
||||||
|
|
||||||
|
ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10)
|
||||||
|
|
||||||
|
# Step 3 - Auto Process
|
||||||
|
ttk.Label(control_frame, text="Step 3: Auto Process",
|
||||||
|
font=('Arial', 10, 'bold')).pack(pady=(5, 5))
|
||||||
|
|
||||||
|
self.noise_handling_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(control_frame, text="Enable noise reduction",
|
||||||
|
variable=self.noise_handling_var).pack(pady=2)
|
||||||
|
|
||||||
|
ttk.Button(control_frame, text="🔍 Auto Detect Key",
|
||||||
|
command=self.auto_process, width=22).pack(pady=5)
|
||||||
|
|
||||||
|
self.edge_top_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(control_frame, text="Use top edge",
|
||||||
|
variable=self.edge_top_var).pack(pady=2)
|
||||||
|
|
||||||
|
ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10)
|
||||||
|
|
||||||
|
# Step 4 - Refine
|
||||||
|
ttk.Label(control_frame, text="Step 4: Refine Profile",
|
||||||
|
font=('Arial', 10, 'bold')).pack(pady=(5, 5))
|
||||||
|
|
||||||
|
ttk.Label(control_frame, text="Smoothing:").pack()
|
||||||
|
self.smooth_var = tk.IntVar(value=11)
|
||||||
|
ttk.Scale(control_frame, from_=5, to=21, variable=self.smooth_var,
|
||||||
|
orient=tk.HORIZONTAL).pack()
|
||||||
|
|
||||||
|
ttk.Button(control_frame, text="✨ Smooth",
|
||||||
|
command=self.smooth_profile, width=22).pack(pady=2)
|
||||||
|
ttk.Button(control_frame, text="🧹 Remove Outliers",
|
||||||
|
command=self.remove_outliers, width=22).pack(pady=2)
|
||||||
|
|
||||||
|
ttk.Separator(control_frame, orient='horizontal').pack(fill='x', pady=10)
|
||||||
|
|
||||||
|
# Step 5 - Export
|
||||||
|
ttk.Label(control_frame, text="Step 5: Export STL",
|
||||||
|
font=('Arial', 10, 'bold')).pack(pady=(5, 5))
|
||||||
|
|
||||||
|
ttk.Label(control_frame, text="Teeth Depth (mm):").pack()
|
||||||
|
self.depth_var = tk.DoubleVar(value=2.0)
|
||||||
|
ttk.Entry(control_frame, textvariable=self.depth_var, width=15).pack()
|
||||||
|
|
||||||
|
ttk.Label(control_frame, text="Base (mm):").pack()
|
||||||
|
self.base_var = tk.DoubleVar(value=1.0)
|
||||||
|
ttk.Entry(control_frame, textvariable=self.base_var, width=15).pack()
|
||||||
|
|
||||||
|
ttk.Button(control_frame, text="💾 Generate STL",
|
||||||
|
command=self.generate_stl, width=22).pack(pady=10)
|
||||||
|
|
||||||
|
# Canvas
|
||||||
|
canvas_frame = ttk.Frame(main_container)
|
||||||
|
main_container.add(canvas_frame, weight=1)
|
||||||
|
|
||||||
|
self.canvas = tk.Canvas(canvas_frame, bg='#f0f0f0', highlightthickness=0, cursor='crosshair')
|
||||||
|
|
||||||
|
h_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview)
|
||||||
|
v_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.VERTICAL, command=self.canvas.yview)
|
||||||
|
|
||||||
|
self.canvas.configure(xscrollcommand=h_scrollbar.set, yscrollcommand=v_scrollbar.set)
|
||||||
|
|
||||||
|
h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Status
|
||||||
|
self.status_label = ttk.Label(self.root, text="Ready - Load a key image to start",
|
||||||
|
relief=tk.SUNKEN, padding=(5, 2))
|
||||||
|
self.status_label.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
|
||||||
|
def _bind_canvas_events(self):
|
||||||
|
"""Bind mouse events for manual selection"""
|
||||||
|
self.canvas.bind('<Button-1>', self.on_canvas_click)
|
||||||
|
self.canvas.bind('<B1-Motion>', self.on_canvas_drag)
|
||||||
|
self.canvas.bind('<ButtonRelease-1>', self.on_canvas_release)
|
||||||
|
|
||||||
|
def start_key_selection(self):
|
||||||
|
"""Start key bounds selection mode"""
|
||||||
|
if self.photo_image is None:
|
||||||
|
messagebox.showwarning("Warning", "Please load an image first")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.selection_mode = 'key_bounds'
|
||||||
|
self.selection_status.config(text="✓ Drag rectangle around key area")
|
||||||
|
self.canvas.config(cursor='crosshair')
|
||||||
|
self.status_label.config(text="SELECTION MODE: Draw rectangle around the key")
|
||||||
|
|
||||||
|
def start_groove_selection(self):
|
||||||
|
"""Start groove region selection mode"""
|
||||||
|
if self.photo_image is None:
|
||||||
|
messagebox.showwarning("Warning", "Please load an image first")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.selection_mode = 'groove'
|
||||||
|
self.selection_status.config(text="✓ Drag rectangle over groove area")
|
||||||
|
self.canvas.config(cursor='crosshair')
|
||||||
|
self.status_label.config(text="GROOVE MODE: Draw rectangles over areas with teeth cuts")
|
||||||
|
|
||||||
|
def cancel_selection(self):
|
||||||
|
"""Cancel current selection mode"""
|
||||||
|
self.selection_mode = None
|
||||||
|
self.selection_start = None
|
||||||
|
if self.current_rect:
|
||||||
|
self.canvas.delete(self.current_rect)
|
||||||
|
self.current_rect = None
|
||||||
|
self.selection_status.config(text="No active tool")
|
||||||
|
self.canvas.config(cursor='')
|
||||||
|
self.status_label.config(text="Selection cancelled")
|
||||||
|
|
||||||
|
def clear_grooves(self):
|
||||||
|
"""Clear all groove regions"""
|
||||||
|
self.key_processor.clear_groove_regions()
|
||||||
|
# Redraw without groove rectangles
|
||||||
|
self._redraw_with_annotations()
|
||||||
|
self.status_label.config(text="✓ Groove regions cleared")
|
||||||
|
|
||||||
|
def on_canvas_click(self, event):
|
||||||
|
"""Handle canvas click for selection start"""
|
||||||
|
if self.selection_mode:
|
||||||
|
self.selection_start = (self.canvas.canvasx(event.x),
|
||||||
|
self.canvas.canvasy(event.y))
|
||||||
|
if self.current_rect:
|
||||||
|
self.canvas.delete(self.current_rect)
|
||||||
|
|
||||||
|
def on_canvas_drag(self, event):
|
||||||
|
"""Handle canvas drag for drawing selection rectangle"""
|
||||||
|
if self.selection_mode and self.selection_start:
|
||||||
|
x1, y1 = self.selection_start
|
||||||
|
x2 = self.canvas.canvasx(event.x)
|
||||||
|
y2 = self.canvas.canvasy(event.y)
|
||||||
|
|
||||||
|
if self.current_rect:
|
||||||
|
self.canvas.delete(self.current_rect)
|
||||||
|
|
||||||
|
# Draw rectangle with different colors for different modes
|
||||||
|
color = 'green' if self.selection_mode == 'key_bounds' else 'orange'
|
||||||
|
self.current_rect = self.canvas.create_rectangle(
|
||||||
|
x1, y1, x2, y2,
|
||||||
|
outline=color, width=3, dash=(5, 5)
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_canvas_release(self, event):
|
||||||
|
"""Handle canvas release to finalize selection"""
|
||||||
|
if self.selection_mode and self.selection_start:
|
||||||
|
x1, y1 = self.selection_start
|
||||||
|
x2 = self.canvas.canvasx(event.x)
|
||||||
|
y2 = self.canvas.canvasy(event.y)
|
||||||
|
|
||||||
|
# Ensure we have a valid rectangle
|
||||||
|
if abs(x2 - x1) > 5 and abs(y2 - y1) > 5:
|
||||||
|
if self.selection_mode == 'key_bounds':
|
||||||
|
self.key_processor.set_manual_bounds(int(x1), int(y1), int(x2), int(y2))
|
||||||
|
self.status_label.config(text="✓ Key area selected")
|
||||||
|
# Redraw with permanent rectangle
|
||||||
|
if self.current_rect:
|
||||||
|
self.canvas.delete(self.current_rect)
|
||||||
|
self.current_rect = self.canvas.create_rectangle(
|
||||||
|
x1, y1, x2, y2,
|
||||||
|
outline='green', width=2
|
||||||
|
)
|
||||||
|
self.selection_mode = None
|
||||||
|
self.selection_status.config(text="Key area set")
|
||||||
|
|
||||||
|
elif self.selection_mode == 'groove':
|
||||||
|
self.key_processor.add_groove_region(int(x1), int(y1), int(x2), int(y2))
|
||||||
|
self.status_label.config(text=f"✓ Groove region added (Total: {len(self.key_processor.groove_regions)})")
|
||||||
|
# Draw permanent groove rectangle
|
||||||
|
if self.current_rect:
|
||||||
|
self.canvas.delete(self.current_rect)
|
||||||
|
groove_rect = self.canvas.create_rectangle(
|
||||||
|
x1, y1, x2, y2,
|
||||||
|
outline='orange', width=2, dash=(3, 3)
|
||||||
|
)
|
||||||
|
self.drawn_rectangles.append(groove_rect)
|
||||||
|
# Stay in groove mode for multiple selections
|
||||||
|
|
||||||
|
self.selection_start = None
|
||||||
|
self.current_rect = None
|
||||||
|
|
||||||
|
def _redraw_with_annotations(self):
|
||||||
|
"""Redraw image with all annotations"""
|
||||||
|
if self.key_processor.original_image:
|
||||||
|
self._display_image(self.key_processor.original_image.convert('L'))
|
||||||
|
|
||||||
|
# Redraw key bounds if set
|
||||||
|
if self.key_processor.manual_bounds:
|
||||||
|
bounds = self.key_processor.manual_bounds
|
||||||
|
self.canvas.create_rectangle(
|
||||||
|
bounds['left'], bounds['top'],
|
||||||
|
bounds['right'], bounds['bottom'],
|
||||||
|
outline='green', width=2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Redraw groove regions
|
||||||
|
self.drawn_rectangles = []
|
||||||
|
for groove in self.key_processor.groove_regions:
|
||||||
|
rect = self.canvas.create_rectangle(
|
||||||
|
groove['left'], groove['top'],
|
||||||
|
groove['right'], groove['bottom'],
|
||||||
|
outline='orange', width=2, dash=(3, 3)
|
||||||
|
)
|
||||||
|
self.drawn_rectangles.append(rect)
|
||||||
|
|
||||||
|
def load_image(self):
|
||||||
|
filepath = filedialog.askopenfilename(
|
||||||
|
title="Select Key Image",
|
||||||
|
filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.gif"), ("All files", "*.*")]
|
||||||
|
)
|
||||||
|
|
||||||
|
if filepath:
|
||||||
|
try:
|
||||||
|
self.key_processor.load_image(filepath)
|
||||||
|
self._display_image(self.key_processor.image)
|
||||||
|
self.status_label.config(text=f"✓ Loaded: {filepath.split('/')[-1]}")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to load image: {str(e)}")
|
||||||
|
|
||||||
|
def auto_process(self):
|
||||||
|
try:
|
||||||
|
noise_handling = self.noise_handling_var.get()
|
||||||
|
|
||||||
|
self.status_label.config(text="Processing... applying noise reduction" if noise_handling else "Processing... detecting edges")
|
||||||
|
self.root.update()
|
||||||
|
|
||||||
|
self.key_processor.auto_detect_key(noise_handling=noise_handling)
|
||||||
|
|
||||||
|
self.status_label.config(text="Processing... finding key bounds")
|
||||||
|
self.root.update()
|
||||||
|
|
||||||
|
self.key_processor.find_key_bounds()
|
||||||
|
|
||||||
|
self.status_label.config(text="Processing... extracting profile")
|
||||||
|
self.root.update()
|
||||||
|
|
||||||
|
use_top = self.edge_top_var.get()
|
||||||
|
profile = self.key_processor.extract_key_profile(use_top=use_top, consensus_window=7)
|
||||||
|
|
||||||
|
self._display_image_with_profile()
|
||||||
|
|
||||||
|
groove_info = f" (with {len(self.key_processor.groove_regions)} groove regions)" if self.key_processor.groove_regions else ""
|
||||||
|
self.status_label.config(text=f"✓ Profile extracted: {len(profile)} points{groove_info}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Auto processing failed: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def smooth_profile(self):
|
||||||
|
try:
|
||||||
|
window = self.smooth_var.get()
|
||||||
|
if window % 2 == 0:
|
||||||
|
window += 1
|
||||||
|
self.key_processor.smooth_profile_advanced(window_size=window)
|
||||||
|
self._display_image_with_profile()
|
||||||
|
self.status_label.config(text="✓ Profile smoothed")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to smooth: {str(e)}")
|
||||||
|
|
||||||
|
def remove_outliers(self):
|
||||||
|
try:
|
||||||
|
self.key_processor.remove_outliers()
|
||||||
|
self._display_image_with_profile()
|
||||||
|
self.status_label.config(text="✓ Outliers removed")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to remove outliers: {str(e)}")
|
||||||
|
|
||||||
|
def generate_stl(self):
|
||||||
|
if not self.key_processor.key_profile:
|
||||||
|
messagebox.showwarning("Warning", "Please extract key profile first (Step 3)")
|
||||||
|
return
|
||||||
|
|
||||||
|
filepath = filedialog.asksaveasfilename(
|
||||||
|
title="Save STL File",
|
||||||
|
defaultextension=".stl",
|
||||||
|
filetypes=[("STL files", "*.stl"), ("All files", "*.*")]
|
||||||
|
)
|
||||||
|
|
||||||
|
if filepath:
|
||||||
|
try:
|
||||||
|
self.key_processor.generate_stl(
|
||||||
|
filepath,
|
||||||
|
depth_scale=self.depth_var.get(),
|
||||||
|
base_thickness=self.base_var.get()
|
||||||
|
)
|
||||||
|
messagebox.showinfo("Success",
|
||||||
|
f"✓ STL file generated!\n\n{filepath}\n\nYou can now scale it in your 3D slicer.")
|
||||||
|
self.status_label.config(text=f"✓ STL saved: {filepath.split('/')[-1]}")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to generate STL: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def _display_image(self, image):
|
||||||
|
if image is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.photo_image = ImageTk.PhotoImage(image)
|
||||||
|
self.canvas.delete("all")
|
||||||
|
self.canvas.create_image(0, 0, image=self.photo_image, anchor=tk.NW)
|
||||||
|
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
||||||
|
|
||||||
|
def _display_image_with_profile(self):
|
||||||
|
if self.key_processor.processed_image and self.key_processor.key_profile:
|
||||||
|
display_img = self.key_processor.processed_image.convert('RGB')
|
||||||
|
draw = ImageDraw.Draw(display_img)
|
||||||
|
|
||||||
|
# Draw key bounds
|
||||||
|
if self.key_processor.key_bounds:
|
||||||
|
bounds = self.key_processor.key_bounds
|
||||||
|
draw.rectangle(
|
||||||
|
[bounds['left'], bounds['top'], bounds['right'], bounds['bottom']],
|
||||||
|
outline='green', width=3
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw groove regions
|
||||||
|
for groove in self.key_processor.groove_regions:
|
||||||
|
draw.rectangle(
|
||||||
|
[groove['left'], groove['top'], groove['right'], groove['bottom']],
|
||||||
|
outline='orange', width=2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw profile
|
||||||
|
if len(self.key_processor.key_profile) > 1:
|
||||||
|
draw.line(self.key_processor.key_profile, fill='red', width=3)
|
||||||
|
|
||||||
|
for x, y in self.key_processor.key_profile[::10]:
|
||||||
|
draw.ellipse([x-2, y-2, x+2, y+2], fill='yellow', outline='red')
|
||||||
|
|
||||||
|
self._display_image(display_img)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Key to STL Generator - Manual Selection Tools")
|
||||||
|
print("Requirements: pip install scipy pillow numpy")
|
||||||
|
print("\nHow to use manual selection:")
|
||||||
|
print("1. Load your key image")
|
||||||
|
print("2. Click 'Select Key Area' and drag rectangle around key")
|
||||||
|
print("3. Click 'Mark Groove Region' and drag rectangles over teeth cuts")
|
||||||
|
print("4. Click 'Auto Detect Key' to process with your selections")
|
||||||
|
print("5. Refine and export STL")
|
||||||
|
|
||||||
|
root = tk.Tk()
|
||||||
|
app = KeySTLGUI(root)
|
||||||
|
root.mainloop()
|
||||||
50
math/LaunchProcess0279191077.debugTarget
Normal file
50
math/LaunchProcess0279191077.debugTarget
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TargetConfig Name="C:\Users\Sebastian\Desktop\INF6B\math\mandelbrot.exe" LastUsed="2025-11-06T09:04:37.9520296Z">
|
||||||
|
<EngineConfig />
|
||||||
|
<EngineOptions>
|
||||||
|
<Property name="DebugChildProcesses" value="false" />
|
||||||
|
</EngineOptions>
|
||||||
|
<TargetOptions>
|
||||||
|
<Option name="LaunchProcess">
|
||||||
|
<Property name="Executable" value="C:\Users\Sebastian\Desktop\INF6B\math\mandelbrot.exe" />
|
||||||
|
<Property name="Arguments" value="" />
|
||||||
|
</Option>
|
||||||
|
<Option name="RestoreCommandHistory">
|
||||||
|
<Property name="History">
|
||||||
|
<Property value="gn" />
|
||||||
|
<Property value="g" />
|
||||||
|
<Property value="p" />
|
||||||
|
<Property value=".restart" />
|
||||||
|
<Property value="uf mandelbrot!Render" />
|
||||||
|
<Property value="x mandelbrot!Render" />
|
||||||
|
<Property value="dv" />
|
||||||
|
<Property value="!heap" />
|
||||||
|
<Property value="x mandelbrot!*" />
|
||||||
|
<Property value="u 0x507cc0" />
|
||||||
|
<Property value="uf 0x507cc0" />
|
||||||
|
<Property value="uf mandelbrot!Complex" />
|
||||||
|
<Property value="x mandelbrot!complex" />
|
||||||
|
<Property value="x mandelbrot!complex_scale" />
|
||||||
|
<Property value="u 0x508360" />
|
||||||
|
</Property>
|
||||||
|
</Option>
|
||||||
|
<Option name="RestoreBreakpoints">
|
||||||
|
<Property name="Breakpoints">
|
||||||
|
<Property>
|
||||||
|
<Property name="Id" value="0" />
|
||||||
|
<Property name="IsEnabled" value="true" />
|
||||||
|
<Property name="OffsetExpression" value="mandelbrot!color_mandelbrot" />
|
||||||
|
<Property name="Command" value="" />
|
||||||
|
<Property name="PassCount" value="1" />
|
||||||
|
<Property name="SymbolName" value="mandelbrot!color_mandelbrot" />
|
||||||
|
<Property name="Location" value="mandelbrot!color_mandelbrot" />
|
||||||
|
<Property name="Address" value="0" />
|
||||||
|
<Property name="Module" value="mandelbrot" />
|
||||||
|
<Property name="Filename" value="C:\Users\Sebastian\Desktop\INF6B\math\mandelbrot.c" />
|
||||||
|
<Property name="Line" value="47" />
|
||||||
|
<Property name="Type" value="Software" />
|
||||||
|
</Property>
|
||||||
|
</Property>
|
||||||
|
</Option>
|
||||||
|
</TargetOptions>
|
||||||
|
</TargetConfig>
|
||||||
98
math/complex.c
Normal file
98
math/complex.c
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include "complex.h"
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
Complex complex_new(double re, double im) {
|
||||||
|
Complex z;
|
||||||
|
z.re = re;
|
||||||
|
z.im = im;
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add
|
||||||
|
Complex complex_add(Complex a, Complex b) {
|
||||||
|
return complex_new(a.re + b.re, a.im + b.im);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract
|
||||||
|
Complex complex_sub(Complex a, Complex b) {
|
||||||
|
return complex_new(a.re - b.re, a.im - b.im);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply
|
||||||
|
Complex complex_mul(Complex a, Complex b) {
|
||||||
|
return complex_new(a.re * b.re - a.im * b.im,
|
||||||
|
a.re * b.im + a.im * b.re);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide
|
||||||
|
Complex complex_div(Complex a, Complex b) {
|
||||||
|
double denom = b.re * b.re + b.im * b.im;
|
||||||
|
return complex_new((a.re * b.re + a.im * b.im) / denom,
|
||||||
|
(a.im * b.re - a.re * b.im) / denom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar multiply
|
||||||
|
Complex complex_scale(Complex a, double alpha) {
|
||||||
|
return complex_new(a.re * alpha, a.im * alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conjugate
|
||||||
|
Complex complex_conjugate(Complex a) {
|
||||||
|
return complex_new(a.re, -a.im);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reciprocal
|
||||||
|
Complex complex_reciprocal(Complex a) {
|
||||||
|
double scale = a.re * a.re + a.im * a.im;
|
||||||
|
return complex_new(a.re / scale, -a.im / scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magnitude (faster than hypot)
|
||||||
|
double complex_abs(Complex a) {
|
||||||
|
return sqrt(a.re * a.re + a.im * a.im);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase
|
||||||
|
double complex_phase(Complex a) {
|
||||||
|
return atan2(a.im, a.re);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential
|
||||||
|
Complex complex_exp(Complex a) {
|
||||||
|
double exp_re = exp(a.re);
|
||||||
|
return complex_new(exp_re * cos(a.im),
|
||||||
|
exp_re * sin(a.im));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sine
|
||||||
|
Complex complex_sin(Complex a) {
|
||||||
|
return complex_new(sin(a.re) * cosh(a.im),
|
||||||
|
cos(a.re) * sinh(a.im));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cosine
|
||||||
|
Complex complex_cos(Complex a) {
|
||||||
|
return complex_new(cos(a.re) * cosh(a.im),
|
||||||
|
-sin(a.re) * sinh(a.im));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tangent
|
||||||
|
Complex complex_tan(Complex a) {
|
||||||
|
Complex s = complex_sin(a);
|
||||||
|
Complex c = complex_cos(a);
|
||||||
|
return complex_div(s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print function
|
||||||
|
void complex_print(Complex a) {
|
||||||
|
if (a.im == 0)
|
||||||
|
printf("%g", a.re);
|
||||||
|
else if (a.re == 0)
|
||||||
|
printf("%gi", a.im);
|
||||||
|
else if (a.im < 0)
|
||||||
|
printf("%g - %gi", a.re, -a.im);
|
||||||
|
else
|
||||||
|
printf("%g + %gi", a.re, a.im);
|
||||||
|
}
|
||||||
25
math/complex.h
Normal file
25
math/complex.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef COMPLEX_H
|
||||||
|
#define COMPLEX_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double re;
|
||||||
|
double im;
|
||||||
|
} Complex;
|
||||||
|
|
||||||
|
Complex complex_new(double re, double im);
|
||||||
|
Complex complex_add(Complex a, Complex b);
|
||||||
|
Complex complex_sub(Complex a, Complex b);
|
||||||
|
Complex complex_mul(Complex a, Complex b);
|
||||||
|
Complex complex_div(Complex a, Complex b);
|
||||||
|
Complex complex_scale(Complex a, double alpha);
|
||||||
|
Complex complex_conjugate(Complex a);
|
||||||
|
Complex complex_reciprocal(Complex a);
|
||||||
|
double complex_abs(Complex a);
|
||||||
|
double complex_phase(Complex a);
|
||||||
|
Complex complex_exp(Complex a);
|
||||||
|
Complex complex_sin(Complex a);
|
||||||
|
Complex complex_cos(Complex a);
|
||||||
|
Complex complex_tan(Complex a);
|
||||||
|
void complex_print(Complex a);
|
||||||
|
|
||||||
|
#endif
|
||||||
227
math/main.cpp
Normal file
227
math/main.cpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <d3dcompiler.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "d3d11.lib")
|
||||||
|
#pragma comment(lib, "D3DCompiler.lib")
|
||||||
|
|
||||||
|
HWND hwnd = nullptr;
|
||||||
|
ID3D11Device* device = nullptr;
|
||||||
|
ID3D11DeviceContext* context = nullptr;
|
||||||
|
IDXGISwapChain* swapChain = nullptr;
|
||||||
|
ID3D11RenderTargetView* rtv = nullptr;
|
||||||
|
ID3D11VertexShader* vs = nullptr;
|
||||||
|
ID3D11PixelShader* ps = nullptr;
|
||||||
|
ID3D11InputLayout* inputLayout = nullptr;
|
||||||
|
ID3D11Buffer* vertexBuffer = nullptr;
|
||||||
|
|
||||||
|
// Vertex struct
|
||||||
|
struct Vertex { float x, y; };
|
||||||
|
|
||||||
|
// Vertex shader
|
||||||
|
const char* vsSrc = R"(
|
||||||
|
struct VS_INPUT { float2 pos : POSITION; };
|
||||||
|
struct PS_INPUT { float4 pos : SV_POSITION; float2 uv : TEXCOORD; };
|
||||||
|
PS_INPUT main(VS_INPUT input) {
|
||||||
|
PS_INPUT output;
|
||||||
|
output.pos = float4(input.pos,0,1);
|
||||||
|
output.uv = input.pos*0.5+0.5;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
char psSrc[2048]; // Pixel shader
|
||||||
|
|
||||||
|
// Mandelbrot view presets
|
||||||
|
struct View { float xMin, xMax, yMin, yMax; };
|
||||||
|
View views[] = {
|
||||||
|
{-2.5f, 1.0f, -1.2f, 1.2f}, // Full set
|
||||||
|
{-0.748f, -0.743f, 0.1f, 0.105f}, // Seahorse Valley
|
||||||
|
{-0.74877f, -0.74872f, 0.06505f, 0.06510f}, // Mini zoom
|
||||||
|
};
|
||||||
|
int currentView = 0;
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
||||||
|
bool InitWindow(HINSTANCE, int);
|
||||||
|
bool InitD3D();
|
||||||
|
void CleanUp();
|
||||||
|
bool CompileShaders();
|
||||||
|
void Render();
|
||||||
|
|
||||||
|
// Main
|
||||||
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
|
||||||
|
if (!InitWindow(hInstance, nCmdShow)) return 0;
|
||||||
|
if (!InitD3D()) return 0;
|
||||||
|
|
||||||
|
CompileShaders();
|
||||||
|
|
||||||
|
MSG msg = {};
|
||||||
|
while (msg.message != WM_QUIT) {
|
||||||
|
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Render(); // non-blocking rendering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanUp();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Window ---
|
||||||
|
bool InitWindow(HINSTANCE hInstance, int nCmdShow) {
|
||||||
|
const char CLASS_NAME[] = "MandelbrotDX11";
|
||||||
|
WNDCLASS wc = {};
|
||||||
|
wc.lpfnWndProc = WndProc;
|
||||||
|
wc.hInstance = hInstance;
|
||||||
|
wc.lpszClassName = CLASS_NAME;
|
||||||
|
RegisterClass(&wc);
|
||||||
|
|
||||||
|
hwnd = CreateWindowEx(0, CLASS_NAME, "DX11 Mandelbrot", WS_OVERLAPPEDWINDOW,
|
||||||
|
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr, nullptr, hInstance, nullptr);
|
||||||
|
if (!hwnd) return false;
|
||||||
|
ShowWindow(hwnd, nCmdShow);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WinProc ---
|
||||||
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||||
|
switch (msg) {
|
||||||
|
case WM_DESTROY: PostQuitMessage(0); return 0;
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
if (wParam == VK_ESCAPE) PostQuitMessage(0);
|
||||||
|
if (wParam == VK_LEFT) currentView = (currentView + 2) % 3;
|
||||||
|
if (wParam == VK_RIGHT) currentView = (currentView + 1) % 3;
|
||||||
|
CompileShaders();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- D3D11 Init ---
|
||||||
|
bool InitD3D() {
|
||||||
|
DXGI_SWAP_CHAIN_DESC scd = {};
|
||||||
|
scd.BufferCount = 1;
|
||||||
|
scd.BufferDesc.Width = 800;
|
||||||
|
scd.BufferDesc.Height = 600;
|
||||||
|
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
scd.BufferDesc.RefreshRate.Numerator = 60;
|
||||||
|
scd.BufferDesc.RefreshRate.Denominator = 1;
|
||||||
|
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
|
scd.OutputWindow = hwnd;
|
||||||
|
scd.SampleDesc.Count = 1;
|
||||||
|
scd.Windowed = TRUE;
|
||||||
|
|
||||||
|
D3D_FEATURE_LEVEL featureLevel;
|
||||||
|
if (FAILED(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
|
||||||
|
0, nullptr, 0, D3D11_SDK_VERSION, &scd,
|
||||||
|
&swapChain, &device, &featureLevel, &context))) return false;
|
||||||
|
|
||||||
|
ID3D11Texture2D* backBuffer = nullptr;
|
||||||
|
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
|
||||||
|
device->CreateRenderTargetView(backBuffer, nullptr, &rtv);
|
||||||
|
backBuffer->Release();
|
||||||
|
context->OMSetRenderTargets(1, &rtv, nullptr);
|
||||||
|
|
||||||
|
D3D11_VIEWPORT vp = {};
|
||||||
|
vp.Width = 800; vp.Height = 600; vp.MinDepth = 0; vp.MaxDepth = 1;
|
||||||
|
context->RSSetViewports(1, &vp);
|
||||||
|
|
||||||
|
Vertex verts[] = { {-1,-1},{-1,1},{1,-1},{1,1} };
|
||||||
|
D3D11_BUFFER_DESC bd = {};
|
||||||
|
bd.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
bd.ByteWidth = sizeof(verts);
|
||||||
|
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||||
|
D3D11_SUBRESOURCE_DATA initData = {};
|
||||||
|
initData.pSysMem = verts;
|
||||||
|
device->CreateBuffer(&bd, &initData, &vertexBuffer);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Compile shaders ---
|
||||||
|
bool CompileShaders() {
|
||||||
|
// Pixel shader template with smooth coloring
|
||||||
|
snprintf(psSrc, sizeof(psSrc),
|
||||||
|
R"(struct PS_INPUT { float4 pos:SV_POSITION; float2 uv: TEXCOORD; };
|
||||||
|
float mandelbrot(float2 c){
|
||||||
|
float2 z=0;
|
||||||
|
float iter=0;
|
||||||
|
const int MAX_ITER=256;
|
||||||
|
for(int i=0;i<MAX_ITER;i++){
|
||||||
|
float x=z.x*z.x - z.y*z.y + (c.x*%.8f+%.8f);
|
||||||
|
float y=2*z.x*z.y + (c.y*%.8f+%.8f);
|
||||||
|
z=float2(x,y);
|
||||||
|
if(dot(z,z)>4){iter=i; break;}
|
||||||
|
}
|
||||||
|
return iter;
|
||||||
|
}
|
||||||
|
float4 main(PS_INPUT input):SV_TARGET{
|
||||||
|
float iter=mandelbrot(input.uv);
|
||||||
|
const float MAX_ITER=256.0;
|
||||||
|
// Original color mapping (smooth)
|
||||||
|
float t = iter / MAX_ITER;
|
||||||
|
float r = clamp(9.0*(1.0-t)*t*t*t,0,1);
|
||||||
|
float g = clamp(15.0*(1.0-t)*(1.0-t)*t*t,0,1);
|
||||||
|
float b = clamp(8.5*(1.0-t)*(1.0-t)*(1.0-t)*t,0,1);
|
||||||
|
return float4(r,g,b,1.0);
|
||||||
|
})",
|
||||||
|
(views[currentView].xMax - views[currentView].xMin), views[currentView].xMin,
|
||||||
|
(views[currentView].yMax - views[currentView].yMin), views[currentView].yMin
|
||||||
|
);
|
||||||
|
|
||||||
|
ID3DBlob* vsBlob = nullptr, * psBlob = nullptr, * err = nullptr;
|
||||||
|
|
||||||
|
if (FAILED(D3DCompile(vsSrc, strlen(vsSrc), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, &vsBlob, &err))) {
|
||||||
|
if (err) { OutputDebugStringA((char*)err->GetBufferPointer()); err->Release(); } return false;
|
||||||
|
}
|
||||||
|
if (FAILED(D3DCompile(psSrc, strlen(psSrc), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, &psBlob, &err))) {
|
||||||
|
if (err) { OutputDebugStringA((char*)err->GetBufferPointer()); err->Release(); } return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vs) vs->Release();
|
||||||
|
if (ps) ps->Release();
|
||||||
|
if (inputLayout) inputLayout->Release();
|
||||||
|
|
||||||
|
device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vs);
|
||||||
|
device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &ps);
|
||||||
|
|
||||||
|
D3D11_INPUT_ELEMENT_DESC layout[] = { {"POSITION",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0} };
|
||||||
|
device->CreateInputLayout(layout, 1, vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout);
|
||||||
|
|
||||||
|
vsBlob->Release(); psBlob->Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Render ---
|
||||||
|
void Render() {
|
||||||
|
float clear[4] = { 0,0,0,1 };
|
||||||
|
context->ClearRenderTargetView(rtv, clear);
|
||||||
|
|
||||||
|
UINT stride = sizeof(Vertex), offset = 0;
|
||||||
|
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
|
||||||
|
context->IASetInputLayout(inputLayout);
|
||||||
|
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
||||||
|
|
||||||
|
context->VSSetShader(vs, nullptr, 0);
|
||||||
|
context->PSSetShader(ps, nullptr, 0);
|
||||||
|
|
||||||
|
context->Draw(4, 0);
|
||||||
|
swapChain->Present(0, 0); // non-blocking present
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cleanup ---
|
||||||
|
void CleanUp() {
|
||||||
|
if (vertexBuffer) vertexBuffer->Release();
|
||||||
|
if (inputLayout) inputLayout->Release();
|
||||||
|
if (vs) vs->Release();
|
||||||
|
if (ps) ps->Release();
|
||||||
|
if (rtv) rtv->Release();
|
||||||
|
if (swapChain) swapChain->Release();
|
||||||
|
if (context) context->Release();
|
||||||
|
if (device) device->Release();
|
||||||
|
}
|
||||||
61
math/mandelbrot.c
Normal file
61
math/mandelbrot.c
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "mandelbrot.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
// Clamp helper
|
||||||
|
static int clamp(int val) {
|
||||||
|
if (val < 0) return 0;
|
||||||
|
if (val > 255) return 255;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mandelbrot iterations
|
||||||
|
int mandelbrot(Complex z0, int max_iter) {
|
||||||
|
Complex z = z0;
|
||||||
|
for (int i = 0; i < max_iter; i++) {
|
||||||
|
if (complex_abs(z) > 2.0) return i;
|
||||||
|
z = complex_add(complex_mul(z, z), z0);
|
||||||
|
}
|
||||||
|
return max_iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple HSV -> RGB helper
|
||||||
|
static Color hsv_to_rgb(double h, double s, double v) {
|
||||||
|
double r = 0, g = 0, b = 0;
|
||||||
|
|
||||||
|
int i = (int)(h * 6);
|
||||||
|
double f = h * 6 - i;
|
||||||
|
double p = v * (1 - s);
|
||||||
|
double q = v * (1 - f * s);
|
||||||
|
double t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0: r = v; g = t; b = p; break;
|
||||||
|
case 1: r = q; g = v; b = p; break;
|
||||||
|
case 2: r = p; g = v; b = t; break;
|
||||||
|
case 3: r = p; g = q; b = v; break;
|
||||||
|
case 4: r = t; g = p; b = v; break;
|
||||||
|
case 5: r = v; g = p; b = q; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color c = { (unsigned char)clamp((int)(r * 255)),
|
||||||
|
(unsigned char)clamp((int)(g * 255)),
|
||||||
|
(unsigned char)clamp((int)(b * 255)) };
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color Mandelbrot
|
||||||
|
Color color_mandelbrot(Complex z0, int max_iter) {
|
||||||
|
int iter = mandelbrot(z0, max_iter);
|
||||||
|
|
||||||
|
if (iter == max_iter)
|
||||||
|
return (Color) { 0, 0, 0 }; // black inside the set
|
||||||
|
|
||||||
|
// Map iteration to color hue
|
||||||
|
double t = (double)iter / max_iter;
|
||||||
|
double hue = 0.95 + 10 * t; // arbitrary scaling for more variation
|
||||||
|
hue = fmod(hue, 1.0);
|
||||||
|
double sat = 0.6; // saturation
|
||||||
|
double val = 1.0; // brightness
|
||||||
|
|
||||||
|
return hsv_to_rgb(hue, sat, val);
|
||||||
|
}
|
||||||
12
math/mandelbrot.h
Normal file
12
math/mandelbrot.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef MANDELBROT_H
|
||||||
|
#define MANDELBROT_H
|
||||||
|
|
||||||
|
#include "complex.h"
|
||||||
|
|
||||||
|
typedef struct { unsigned char r, g, b; } Color;
|
||||||
|
|
||||||
|
int mandelbrot(Complex z0, int max_iter);
|
||||||
|
Color grey_mandelbrot(Complex z0, int max_iter); // optional, for backward compatibility
|
||||||
|
Color color_mandelbrot(Complex z0, int max_iter);
|
||||||
|
|
||||||
|
#endif
|
||||||
24
math/mandelbrot.hlsl
Normal file
24
math/mandelbrot.hlsl
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
cbuffer ViewportBuffer : register(b0)
|
||||||
|
{
|
||||||
|
float xmin;
|
||||||
|
float xmax;
|
||||||
|
float ymin;
|
||||||
|
float ymax;
|
||||||
|
int max_iter;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PS_IN { float4 pos : SV_POSITION; };
|
||||||
|
|
||||||
|
float4 PSMain(PS_IN input) : SV_TARGET
|
||||||
|
{
|
||||||
|
float2 uv = input.pos.xy * 0.5 + 0.5; // [-1,1] -> [0,1]
|
||||||
|
float2 c = float2(xmin,ymin) + uv * float2(xmax-xmin, ymax-ymin);
|
||||||
|
float2 z = c;
|
||||||
|
int i;
|
||||||
|
for(i=0;i<max_iter;i++){
|
||||||
|
if(dot(z,z)>4) break;
|
||||||
|
z = float2(z.x*z.x - z.y*z.y, 2*z.x*z.y)+c;
|
||||||
|
}
|
||||||
|
float t = i/(float)max_iter;
|
||||||
|
return float4(t,0.5*t,1.0-t,1);
|
||||||
|
}
|
||||||
2403
niacin/compiler.py
Normal file
2403
niacin/compiler.py
Normal file
File diff suppressed because it is too large
Load Diff
21
niacin/cpp/Makefile
Normal file
21
niacin/cpp/Makefile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -Werror -O2 -std=c99 -D_DEFAULT_SOURCE
|
||||||
|
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
|
||||||
|
LDFLAGS = -z relro -z now
|
||||||
|
|
||||||
|
SOURCES = main.c vm_core.c
|
||||||
|
OBJECTS = $(SOURCES:.c=.o)
|
||||||
|
TARGET = popvm
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJECTS)
|
||||||
|
$(CC) $(LDFLAGS) -o $@ $^
|
||||||
|
|
||||||
|
%.o: %.c
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJECTS) $(TARGET)
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
76
niacin/cpp/main.c
Normal file
76
niacin/cpp/main.c
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#include "vm_types.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Safe file reading
|
||||||
|
static uint8_t* read_file(const char* filename, size_t* size) {
|
||||||
|
FILE* file = fopen(filename, "rb");
|
||||||
|
if (!file) {
|
||||||
|
fprintf(stderr, "Cannot open file: %s\n", filename);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long file_size = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (file_size <= 0 || file_size > 10 * 1024 * 1024) { // 10MB limit
|
||||||
|
fclose(file);
|
||||||
|
fprintf(stderr, "Invalid file size\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* buffer = malloc(file_size);
|
||||||
|
if (!buffer) {
|
||||||
|
fclose(file);
|
||||||
|
fprintf(stderr, "Memory allocation failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(buffer, 1, file_size, file) != (size_t)file_size) {
|
||||||
|
fclose(file);
|
||||||
|
free(buffer);
|
||||||
|
fprintf(stderr, "File read error\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
*size = file_size;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 2) {
|
||||||
|
printf("Usage: %s <file.popclass>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t file_size;
|
||||||
|
uint8_t* bytecode = read_file(argv[1], &file_size);
|
||||||
|
if (!bytecode) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_t* vm = vm_create(bytecode, file_size);
|
||||||
|
if (!vm) {
|
||||||
|
fprintf(stderr, "Failed to create VM\n");
|
||||||
|
free(bytecode);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vm_validate_bytecode(vm)) {
|
||||||
|
fprintf(stderr, "Invalid bytecode file\n");
|
||||||
|
vm_destroy(vm);
|
||||||
|
free(bytecode);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Executing program...\n");
|
||||||
|
vm_execute(vm);
|
||||||
|
printf("Program finished\n");
|
||||||
|
|
||||||
|
vm_destroy(vm);
|
||||||
|
free(bytecode);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
niacin/cpp/sample.popclass
Normal file
BIN
niacin/cpp/sample.popclass
Normal file
Binary file not shown.
285
niacin/cpp/vm_core.c
Normal file
285
niacin/cpp/vm_core.c
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
#include "vm_types.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
// Safe memory allocation with overflow checking
|
||||||
|
static void* safe_malloc(size_t size) {
|
||||||
|
if (size == 0 || size > SIZE_MAX / 2) return NULL;
|
||||||
|
void* ptr = malloc(size);
|
||||||
|
if (!ptr) {
|
||||||
|
fprintf(stderr, "Memory allocation failed\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* safe_realloc(void* ptr, size_t size) {
|
||||||
|
if (size == 0 || size > SIZE_MAX / 2) return NULL;
|
||||||
|
void* new_ptr = realloc(ptr, size);
|
||||||
|
if (!new_ptr) {
|
||||||
|
fprintf(stderr, "Memory reallocation failed\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
return new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize VM with security limits
|
||||||
|
vm_t* vm_create(uint8_t* bytecode, size_t size) {
|
||||||
|
vm_t* vm = safe_malloc(sizeof(vm_t));
|
||||||
|
memset(vm, 0, sizeof(vm_t));
|
||||||
|
|
||||||
|
vm->bytecode = bytecode;
|
||||||
|
vm->bytecode_size = size;
|
||||||
|
vm->ip = 0;
|
||||||
|
vm->halted = false;
|
||||||
|
|
||||||
|
// Security limits
|
||||||
|
vm->max_stack_size = 65536;
|
||||||
|
vm->max_call_depth = 256;
|
||||||
|
vm->current_call_depth = 0;
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe bytecode reading with bounds checking
|
||||||
|
static bool read_u8(vm_t* vm, uint8_t* result) {
|
||||||
|
if (vm->ip >= vm->bytecode_size) return false;
|
||||||
|
*result = vm->bytecode[vm->ip++];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_u16(vm_t* vm, uint16_t* result) {
|
||||||
|
if (vm->ip + 2 > vm->bytecode_size) return false;
|
||||||
|
memcpy(result, &vm->bytecode[vm->ip], 2);
|
||||||
|
vm->ip += 2;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_u32(vm_t* vm, uint32_t* result) {
|
||||||
|
if (vm->ip + 4 > vm->bytecode_size) return false;
|
||||||
|
memcpy(result, &vm->bytecode[vm->ip], 4);
|
||||||
|
vm->ip += 4;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_i32(vm_t* vm, int32_t* result) {
|
||||||
|
if (vm->ip + 4 > vm->bytecode_size) return false;
|
||||||
|
memcpy(result, &vm->bytecode[vm->ip], 4);
|
||||||
|
vm->ip += 4;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_f32(vm_t* vm, float* result) {
|
||||||
|
if (vm->ip + 4 > vm->bytecode_size) return false;
|
||||||
|
memcpy(result, &vm->bytecode[vm->ip], 4);
|
||||||
|
vm->ip += 4;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack operations with bounds checking
|
||||||
|
static bool stack_push(frame_t* frame, value_t value) {
|
||||||
|
if (frame->stack_size >= frame->stack_capacity) {
|
||||||
|
size_t new_capacity = frame->stack_capacity * 2;
|
||||||
|
if (new_capacity == 0) new_capacity = 16;
|
||||||
|
|
||||||
|
value_t* new_stack = safe_realloc(frame->stack, new_capacity * sizeof(value_t));
|
||||||
|
if (!new_stack) return false;
|
||||||
|
|
||||||
|
frame->stack = new_stack;
|
||||||
|
frame->stack_capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame->stack[frame->stack_size++] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stack_pop(frame_t* frame, value_t* result) {
|
||||||
|
if (frame->stack_size == 0) return false;
|
||||||
|
*result = frame->stack[--frame->stack_size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking and conversion
|
||||||
|
static bool type_check_binary(value_t a, value_t b, type_code_t* result_type) {
|
||||||
|
// Simple type checking - in real implementation, add proper type promotion
|
||||||
|
if (a.type == b.type) {
|
||||||
|
*result_type = a.type;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t value_to_int(value_t val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case TYPE_I8: return val.data.i8;
|
||||||
|
case TYPE_U8: return val.data.u8;
|
||||||
|
case TYPE_I16: return val.data.i16;
|
||||||
|
case TYPE_U16: return val.data.u16;
|
||||||
|
case TYPE_I32: return val.data.i32;
|
||||||
|
case TYPE_U32: return (int32_t)val.data.u32;
|
||||||
|
case TYPE_BOOL: return val.data.boolean ? 1 : 0;
|
||||||
|
case TYPE_CHAR: return (int32_t)val.data.character;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static float value_to_float(value_t val) {
|
||||||
|
switch (val.type) {
|
||||||
|
case TYPE_F32: return val.data.f32;
|
||||||
|
case TYPE_I32: return (float)val.data.i32;
|
||||||
|
case TYPE_U32: return (float)val.data.u32;
|
||||||
|
default: return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytecode validation
|
||||||
|
bool vm_validate_bytecode(vm_t* vm) {
|
||||||
|
// Check magic number
|
||||||
|
if (vm->bytecode_size < 4 || memcmp(vm->bytecode, "POPC", 4) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add more validation as needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute single instruction with full error checking
|
||||||
|
bool vm_execute_instruction(vm_t* vm) {
|
||||||
|
if (vm->halted || vm->ip >= vm->bytecode_size) return false;
|
||||||
|
|
||||||
|
uint8_t opcode;
|
||||||
|
if (!read_u8(vm, &opcode)) return false;
|
||||||
|
|
||||||
|
frame_t* frame = vm->current_frame;
|
||||||
|
if (!frame) return false;
|
||||||
|
|
||||||
|
switch (opcode) {
|
||||||
|
case OP_PUSH_CONST: {
|
||||||
|
uint32_t const_idx;
|
||||||
|
if (!read_u32(vm, &const_idx) || const_idx >= vm->constants_count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!stack_push(frame, vm->constants[const_idx])) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_PUSH_INT: {
|
||||||
|
uint8_t width;
|
||||||
|
int32_t value;
|
||||||
|
if (!read_u8(vm, &width) || !read_i32(vm, &value)) return false;
|
||||||
|
|
||||||
|
value_t val = { 0 };
|
||||||
|
switch (width) {
|
||||||
|
case 8: val.type = TYPE_I8; val.data.i8 = (int8_t)value; break;
|
||||||
|
case 16: val.type = TYPE_I16; val.data.i16 = (int16_t)value; break;
|
||||||
|
case 32: val.type = TYPE_I32; val.data.i32 = value; break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
if (!stack_push(frame, val)) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_ADD: {
|
||||||
|
value_t a, b;
|
||||||
|
if (!stack_pop(frame, &a) || !stack_pop(frame, &b)) return false;
|
||||||
|
|
||||||
|
type_code_t result_type;
|
||||||
|
if (!type_check_binary(a, b, &result_type)) return false;
|
||||||
|
|
||||||
|
value_t result = { 0 };
|
||||||
|
result.type = result_type;
|
||||||
|
|
||||||
|
if (result_type == TYPE_F32) {
|
||||||
|
result.data.f32 = value_to_float(b) + value_to_float(a);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.data.i32 = value_to_int(b) + value_to_int(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stack_push(frame, result)) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_CMP_EQ: {
|
||||||
|
value_t a, b;
|
||||||
|
if (!stack_pop(frame, &a) || !stack_pop(frame, &b)) return false;
|
||||||
|
|
||||||
|
value_t result = { 0 };
|
||||||
|
result.type = TYPE_BOOL;
|
||||||
|
result.data.boolean = (value_to_int(a) == value_to_int(b));
|
||||||
|
|
||||||
|
if (!stack_push(frame, result)) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_JMP: {
|
||||||
|
int32_t offset;
|
||||||
|
if (!read_i32(vm, &offset)) return false;
|
||||||
|
|
||||||
|
// Check for negative jumps (security)
|
||||||
|
if (offset < 0 && (size_t)(-offset) > vm->ip) return false;
|
||||||
|
if (vm->ip + offset >= vm->bytecode_size) return false;
|
||||||
|
|
||||||
|
vm->ip += offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_PRINT: {
|
||||||
|
value_t val;
|
||||||
|
if (!stack_pop(frame, &val)) return false;
|
||||||
|
|
||||||
|
switch (val.type) {
|
||||||
|
case TYPE_I32: printf("%d\n", val.data.i32); break;
|
||||||
|
case TYPE_F32: printf("%f\n", val.data.f32); break;
|
||||||
|
case TYPE_BOOL: printf("%s\n", val.data.boolean ? "true" : "false"); break;
|
||||||
|
case TYPE_STR: printf("%s\n", val.data.string); break;
|
||||||
|
default: printf("<unknown>\n"); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_HALT:
|
||||||
|
vm->halted = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Unknown opcode: 0x%02x\n", opcode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main execution loop
|
||||||
|
void vm_execute(vm_t* vm) {
|
||||||
|
while (!vm->halted && vm_execute_instruction(vm)) {
|
||||||
|
// Continue execution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
void vm_destroy(vm_t* vm) {
|
||||||
|
if (!vm) return;
|
||||||
|
|
||||||
|
// Free constants
|
||||||
|
for (size_t i = 0; i < vm->constants_count; i++) {
|
||||||
|
if (vm->constants[i].type == TYPE_STR && vm->constants[i].data.string) {
|
||||||
|
free(vm->constants[i].data.string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(vm->constants);
|
||||||
|
|
||||||
|
// Free functions
|
||||||
|
free(vm->functions);
|
||||||
|
|
||||||
|
// Free frames (simplified - in real impl, walk call stack)
|
||||||
|
if (vm->current_frame) {
|
||||||
|
free(vm->current_frame->locals);
|
||||||
|
free(vm->current_frame->stack);
|
||||||
|
free(vm->current_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(vm);
|
||||||
|
}
|
||||||
120
niacin/cpp/vm_types.h
Normal file
120
niacin/cpp/vm_types.h
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#ifndef VM_TYPES_H
|
||||||
|
#define VM_TYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Type codes
|
||||||
|
typedef enum {
|
||||||
|
TYPE_I8 = 0x01,
|
||||||
|
TYPE_U8 = 0x02,
|
||||||
|
TYPE_I16 = 0x03,
|
||||||
|
TYPE_U16 = 0x04,
|
||||||
|
TYPE_I32 = 0x05,
|
||||||
|
TYPE_U32 = 0x06,
|
||||||
|
TYPE_F32 = 0x07,
|
||||||
|
TYPE_BOOL = 0x08,
|
||||||
|
TYPE_CHAR = 0x09,
|
||||||
|
TYPE_STR = 0x0A
|
||||||
|
} type_code_t;
|
||||||
|
|
||||||
|
// Opcodes
|
||||||
|
typedef enum {
|
||||||
|
OP_PUSH_CONST = 0x01,
|
||||||
|
OP_PUSH_INT = 0x02,
|
||||||
|
OP_PUSH_FLOAT = 0x03,
|
||||||
|
OP_PUSH_STR = 0x04,
|
||||||
|
OP_LOAD_LOCAL = 0x10,
|
||||||
|
OP_STORE_LOCAL = 0x11,
|
||||||
|
OP_ADD = 0x20,
|
||||||
|
OP_SUB = 0x21,
|
||||||
|
OP_MUL = 0x22,
|
||||||
|
OP_DIV = 0x23,
|
||||||
|
OP_MOD = 0x24,
|
||||||
|
OP_CMP_EQ = 0x40,
|
||||||
|
OP_CMP_NEQ = 0x41,
|
||||||
|
OP_CMP_LT = 0x42,
|
||||||
|
OP_CMP_GT = 0x43,
|
||||||
|
OP_CMP_LE = 0x44,
|
||||||
|
OP_CMP_GE = 0x45,
|
||||||
|
OP_JMP = 0x50,
|
||||||
|
OP_JMP_IF = 0x51,
|
||||||
|
OP_JMP_IF_NOT = 0x52,
|
||||||
|
OP_CALL = 0x60,
|
||||||
|
OP_RET = 0x61,
|
||||||
|
OP_DUP = 0x80,
|
||||||
|
OP_POP = 0x81,
|
||||||
|
OP_PRINT = 0x90,
|
||||||
|
OP_HALT = 0xA0
|
||||||
|
} opcode_t;
|
||||||
|
|
||||||
|
// Value union with type safety
|
||||||
|
typedef union {
|
||||||
|
int8_t i8;
|
||||||
|
uint8_t u8;
|
||||||
|
int16_t i16;
|
||||||
|
uint16_t u16;
|
||||||
|
int32_t i32;
|
||||||
|
uint32_t u32;
|
||||||
|
float f32;
|
||||||
|
bool boolean;
|
||||||
|
char character;
|
||||||
|
char* string;
|
||||||
|
} value_data_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
type_code_t type;
|
||||||
|
value_data_t data;
|
||||||
|
} value_t;
|
||||||
|
|
||||||
|
// Function definition
|
||||||
|
typedef struct {
|
||||||
|
uint32_t name_index;
|
||||||
|
uint8_t arg_count;
|
||||||
|
uint8_t local_count;
|
||||||
|
uint16_t reserved;
|
||||||
|
uint32_t code_size;
|
||||||
|
uint32_t code_offset;
|
||||||
|
} function_t;
|
||||||
|
|
||||||
|
// Call frame
|
||||||
|
typedef struct frame {
|
||||||
|
struct frame* prev;
|
||||||
|
uint32_t return_ip;
|
||||||
|
value_t* locals;
|
||||||
|
value_t* stack;
|
||||||
|
size_t stack_size;
|
||||||
|
size_t stack_capacity;
|
||||||
|
size_t locals_count;
|
||||||
|
} frame_t;
|
||||||
|
|
||||||
|
// Virtual machine state
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* bytecode;
|
||||||
|
size_t bytecode_size;
|
||||||
|
uint32_t ip;
|
||||||
|
|
||||||
|
value_t* constants;
|
||||||
|
size_t constants_count;
|
||||||
|
|
||||||
|
function_t* functions;
|
||||||
|
size_t functions_count;
|
||||||
|
|
||||||
|
frame_t* current_frame;
|
||||||
|
bool halted;
|
||||||
|
|
||||||
|
// Security settings
|
||||||
|
size_t max_stack_size;
|
||||||
|
size_t max_call_depth;
|
||||||
|
size_t current_call_depth;
|
||||||
|
} vm_t;
|
||||||
|
|
||||||
|
// Function declarations
|
||||||
|
vm_t* vm_create(uint8_t* bytecode, size_t size);
|
||||||
|
bool vm_validate_bytecode(vm_t* vm);
|
||||||
|
bool vm_execute_instruction(vm_t* vm);
|
||||||
|
void vm_execute(vm_t* vm);
|
||||||
|
void vm_destroy(vm_t* vm);
|
||||||
|
|
||||||
|
#endif
|
||||||
16
niacin/sample.popasm
Normal file
16
niacin/sample.popasm
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
; POP Class File Version 1.0
|
||||||
|
; Magic: b'POPC'
|
||||||
|
|
||||||
|
; Constant Pool
|
||||||
|
; Count: 0
|
||||||
|
|
||||||
|
; Function Table
|
||||||
|
; Count: 1
|
||||||
|
; func[0]: func_0, args=0, locals=1, code_size=15, offset=0x00000024
|
||||||
|
|
||||||
|
; Function 0
|
||||||
|
0x00000024: PUSH_INT i32 12
|
||||||
|
0x0000002a: STORE_LOCAL local[0]
|
||||||
|
0x0000002d: LOAD_LOCAL local[0]
|
||||||
|
0x00000030: PRINT
|
||||||
|
0x00000031: RET 0
|
||||||
BIN
niacin/sample.popclass
Normal file
BIN
niacin/sample.popclass
Normal file
Binary file not shown.
4
niacin/sample.src
Normal file
4
niacin/sample.src
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fun main() {
|
||||||
|
u8 testint = 255;
|
||||||
|
print(testint);
|
||||||
|
}
|
||||||
13
simulations/donut.c/donutdbg.debugtarget
Normal file
13
simulations/donut.c/donutdbg.debugtarget
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TargetConfig Name="C:\Users\Sebastian\Desktop\INF6B\simulations\donut.c\donut.exe" LastUsed="2025-11-07T20:28:26.2310451Z">
|
||||||
|
<EngineConfig />
|
||||||
|
<EngineOptions>
|
||||||
|
<Property name="DebugChildProcesses" value="false" />
|
||||||
|
</EngineOptions>
|
||||||
|
<TargetOptions>
|
||||||
|
<Option name="LaunchProcess">
|
||||||
|
<Property name="Executable" value="C:\Users\Sebastian\Desktop\INF6B\simulations\donut.c\donut.exe" />
|
||||||
|
<Property name="Arguments" value="" />
|
||||||
|
</Option>
|
||||||
|
</TargetOptions>
|
||||||
|
</TargetConfig>
|
||||||
Reference in New Issue
Block a user