diff --git a/CHANGELOG.md b/CHANGELOG.md
index db6e82bfd7f412c4188d241edb12d929e31261f9..650493b606b4eee31424a302ef0ce077d7e309a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,14 @@
ChangeLog/更新日志
=================
+[2.6.2] - 2023-05-26
+--------------------
+
+### Features/新增
+
+- [X] Added `tkintertools` sub-module `tools_3d` to support drawing 3D graphics
+新增`tkintertools`子模块`tools_3d`以支持绘制3d图形
+
### Optimized/优化
[2.6.1] - 2023-05-21
diff --git a/README.md b/README.md
index 024c8e21be6103037f23bd5f5a0b3b45e1ab4cf2..003c9ce9a90ec853372efa6b17588cbc4c0061c6 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
@@ -56,11 +56,11 @@ pip install tkintertools
### Development version/开发版本
-* Version/版本 : 2.6.1
-* Release Date/发布日期 : 2023/05/21
+* Version/版本 : 2.6.2
+* Release Date/发布日期 : 2023/05/26
```
-pip install tkintertools-dev==2.6.1
+pip install tkintertools-dev==2.6.2
```
这个是作者正在开发的版本,有新功能,但不能保证稳定,bug 可能会比较多。
diff --git a/test.py b/test.py
index a1ab420f45749fdadc2cad6fab5dbd3a61e1020d..7b62b0af3284f20775b4979ff9f98101f61bf999 100644
--- a/test.py
+++ b/test.py
@@ -1,118 +1,169 @@
""" Test program """
from math import cos, pi
-from random import randint
-from tkinter import TclError, messagebox
+from random import randint, sample
+from tkinter import Event, TclError, messagebox
import tkintertools as tkt
-
-
-def colorful(
- ind=0, # type: int
- color=[None, '#F1F1F1'] # type: list[str | None]
-): # type: (...) -> None
- """ Change color randomly and Gradiently """
- if not ind:
- color[0], color[1] = color[1], '#%06X' % randint(0, 1 << 24)
- color = tkt.color(color, ind)
- color_ = tkt.color(color)
- canvas_doc.configure(bg=color)
- for widget in canvas_main._widget:
- widget.color_fill[0], widget.color_text[0] = color, color_
- widget.state()
- root.after(20, colorful, 0 if ind >= 1 else ind+0.01)
-
-
-def draw(ind=0, n=200): # type: (int, int) -> None
- """ Draw a sphere """
- canvas_graph.create_oval(
- canvas_graph.rx*(500-ind/3),
- canvas_graph.ry*(300-ind/3),
- canvas_graph.rx*(600+ind),
- canvas_graph.ry*(400+ind),
- fill='' if ind else 'white',
- outline=tkt.color(('#000000', '#FFFFFF'), cos(ind*pi/2/n)), width=3)
- if ind < n:
- root.after(10, draw, ind+1)
-
-
-def update(ind=0): # type: (int) -> None
- """ Load the progress """
- bar.load(ind)
- if ind < 1:
- root.after(2, update, ind+0.0002)
-
-
-def shutdown(): # type: () -> None
- """ Ask before quit """
- if messagebox.askyesno('Test program', 'Do you want to exit the test program?'):
- root.quit()
-
-
-root = tkt.Tk('tkintertools', 1280, 720, shutdown=shutdown)
-root.minsize(640, 360)
-canvas_main = tkt.Canvas(root, 1280, 720, 0, 0)
-canvas_doc = tkt.Canvas(root, 1280, 720, -1280, 0)
-canvas_graph = tkt.Canvas(root, 1280, 720, 1280, 0)
-
-
-tkt.Button(
- canvas_main, 10, 660, 200, 50, text='Doc',
- command=lambda: (tkt.move(root, canvas_main, 1280*canvas_main.rx, 0, 500, mode='rebound'),
- tkt.move(root, canvas_doc, 1280*canvas_doc.rx, 0, 500, mode='rebound')))
-tkt.Button(
- canvas_main, 1070, 660, 200, 50, text='Image',
- command=lambda: (tkt.move(root, canvas_main, -1280*canvas_main.rx, 0, 500, mode='rebound'),
- tkt.move(root, canvas_graph, -1280*canvas_graph.rx, 0, 500, mode='rebound')))
-tkt.Button(
- canvas_doc, 1070, 660, 200, 50, text='Back',
- command=lambda: (tkt.move(root, canvas_main, -1280*canvas_main.rx, 0, 500, mode='rebound'),
- tkt.move(root, canvas_doc, -1280*canvas_doc.rx, 0, 500, mode='rebound')))
-tkt.Button(
- canvas_graph, 10, 660, 200, 50, text='Back',
- command=lambda: (tkt.move(root, canvas_main, 1280*canvas_main.rx, 0, 500, mode='rebound'),
- tkt.move(root, canvas_graph, 1280*canvas_graph.rx, 0, 500, mode='rebound')))
-
-tkt.Text(canvas_main, 10, 10, 625, 300, radius=20,
- text=('Centered and Rounded TextBox', 'Click to Input'), justify='center')
-tkt.Text(canvas_main, 645, 10, 625, 300,
- text=('Right-leaning TextBox', 'Click to Input'), cursor=' _')
-tkt.Entry(canvas_main, 10, 320, 300, 35, radius=10,
- text=('Rounded InputBox', 'Click to Input'), justify='center')
-tkt.Entry(canvas_main, 970, 320, 300, 35, text=(
- 'InputBox', 'Click to Input'), show='•')
-tkt.Button(canvas_main, 10, 365, 300, 40, radius=10, text='Rounded Button', command=lambda: tkt.move(
- canvas_main, label_1, 0, -170 * canvas_main.ry, 500, mode='flat'))
-tkt.Button(canvas_main, 1070, 365, 200, 40, text='Button', command=lambda: tkt.move(
- canvas_main, label_2, 0, -170 * canvas_main.ry, 500, mode='smooth'))
-tkt.CheckButton(canvas_main, 10, 415, 35, radius=10,
- text='Rounded CheckButton')
-tkt.CheckButton(canvas_main, 1235, 415, 35, value=True,
- text='CheckButton', justify='left')
-
-label_1 = tkt.Label(canvas_main, 235, 730, 400, 150,
- radius=20, text='Rounded Label\nmove mode: flat')
-label_2 = tkt.Label(canvas_main, 645, 730, 400, 150,
- text='Label\nmove mode: smooth')
-button_1 = tkt.Button(canvas_doc, 1070, 10, 200, 50, text='Colorful',
- command=lambda: (button_1.set_live(False), colorful()))
-button_2 = tkt.Button(canvas_graph, 10, 10, 200, 50, text='Draw',
- command=lambda: (button_2.set_live(False), draw()))
-
-load = tkt.Button(canvas_main, 540, 365, 200, 40, text='Load',
- command=lambda: (update(), load.set_live(False)))
-bar = tkt.Progressbar(canvas_main, 320, 320, 640, 35)
-
-font_chooseer = tkt.Button(canvas_main, 500, 465, 280, 40, text='Select a Font', command=lambda: tkt.askfont(
- root, lambda font: canvas_main.itemconfigure(font_chooseer.text, font=font)))
-
-canvas_doc.create_text(
- 15, 360, text=tkt.__doc__, font=(tkt.FONT, 14), anchor='w')
-
-try:
- canvas_graph.create_image(
- 1150, 130, image=tkt.PhotoImage('tkintertools.png'))
-except TclError:
- print('\033[31mLoad tkintertools.png Error\033[0m')
-
-root.mainloop()
+from tkintertools import constants as cnt
+from tkintertools import tools_3d as t3d
+
+
+class Application:
+
+ def __init__(self):
+ self.root = tkt.Tk('tkintertools', 1280, 720, shutdown=self.shutdown)
+ self.root.minsize(640, 360)
+ self.canvas_main = tkt.Canvas(self.root, 1280, 720, 0, 0)
+ self.canvas_doc = tkt.Canvas(self.root, 1280, 720, -1280, 0)
+ self.canvas_graph = tkt.Canvas(self.root, 1280, 720, 1280, 0)
+ self.canvas_3d = tkt.Canvas(self.root, 1280, 720, 1280, 0)
+
+ self.canvas_main_init()
+ self.canvas_doc_init()
+ self.canvas_graph_init()
+ self.canvas_3d_init()
+
+ self.root.mainloop()
+
+ def shutdown(self): # type: () -> None
+ """ Ask before quit """
+ if messagebox.askyesno('Test program', 'Do you want to exit the test program?'):
+ self.root.quit()
+
+ def colorful(
+ self,
+ ind=0, # type: int
+ color=[None, '#F1F1F1'] # type: list[str | None]
+ ): # type: (...) -> None
+ """ Change color randomly and Gradiently """
+ if not ind:
+ color[0], color[1] = color[1], '#%06X' % randint(0, 1 << 24)
+ color = tkt.color(color, ind)
+ color_ = tkt.color(color)
+ self.canvas_doc.configure(bg=color)
+ for widget in self.canvas_main._widget:
+ widget.color_fill[0], widget.color_text[0] = color, color_
+ widget.state()
+ self.root.after(20, self.colorful, 0 if ind >= 1 else ind+0.01)
+
+ def draw(self, ind=0, n=200): # type: (int, int) -> None
+ """ Draw a sphere """
+ self.canvas_graph.create_oval(
+ self.canvas_graph.rx*(500-ind/3),
+ self.canvas_graph.ry*(300-ind/3),
+ self.canvas_graph.rx*(600+ind),
+ self.canvas_graph.ry*(400+ind),
+ fill='' if ind else 'white',
+ outline=tkt.color(('#000000', '#FFFFFF'), cos(ind*pi/2/n)), width=3)
+ if ind < n:
+ self.root.after(10, self.draw, ind+1)
+
+ def update(self, ind=0): # type: (int) -> None
+ """ Load the progress """
+ self.bar.load(ind)
+ if ind < 1:
+ self.root.after(2, self.update, ind+0.0002)
+
+ def canvas_main_init(self):
+ tkt.Button(
+ self.canvas_main, 10, 660, 200, 50, text='Doc',
+ command=lambda: (tkt.move(self.root, self.canvas_main, 1280*self.canvas_main.rx, 0, 500, mode='rebound'),
+ tkt.move(self.root, self.canvas_doc, 1280*self.canvas_doc.rx, 0, 500, mode='rebound')))
+ tkt.Button(
+ self.canvas_main, 1070, 660, 200, 50, text='Image',
+ command=lambda: (tkt.move(self.root, self.canvas_main, -1280*self.canvas_main.rx, 0, 500, mode='rebound'),
+ tkt.move(self.root, self.canvas_graph, -1280*self.canvas_graph.rx, 0, 500, mode='rebound')))
+ tkt.Text(self.canvas_main, 10, 10, 625, 300, radius=20,
+ text=('Centered and Rounded TextBox', 'Click to Input'), justify='center')
+ tkt.Text(self.canvas_main, 645, 10, 625, 300,
+ text=('Right-leaning TextBox', 'Click to Input'), cursor=' _')
+ tkt.Entry(self.canvas_main, 10, 320, 300, 35, radius=10,
+ text=('Rounded InputBox', 'Click to Input'), justify='center')
+ tkt.Entry(self.canvas_main, 970, 320, 300, 35, text=(
+ 'InputBox', 'Click to Input'), show='•')
+ tkt.Button(self.canvas_main, 10, 365, 300, 40, radius=10, text='Rounded Button', command=lambda: tkt.move(
+ self.canvas_main, label_1, 0, -170 * self.canvas_main.ry, 500, mode='flat'))
+ tkt.Button(self.canvas_main, 1070, 365, 200, 40, text='Button', command=lambda: tkt.move(
+ self.canvas_main, label_2, 0, -170 * self.canvas_main.ry, 500, mode='smooth'))
+ tkt.CheckButton(self.canvas_main, 10, 415, 35, radius=10,
+ text='Rounded CheckButton')
+ tkt.CheckButton(self.canvas_main, 1235, 415, 35, value=True,
+ text='CheckButton', justify='left')
+ label_1 = tkt.Label(self.canvas_main, 235, 730, 400, 150,
+ radius=20, text='Rounded Label\nmove mode: flat')
+ label_2 = tkt.Label(self.canvas_main, 645, 730, 400, 150,
+ text='Label\nmove mode: smooth')
+ load = tkt.Button(self.canvas_main, 540, 365, 200, 40, text='Load',
+ command=lambda: (self.update(), load.set_live(False)))
+ self.bar = tkt.Progressbar(self.canvas_main, 320, 320, 640, 35)
+
+ font_chooseer = tkt.Button(self.canvas_main, 500, 465, 280, 40, text='Select a Font', command=lambda: tkt.askfont(
+ self.root, lambda font: self.canvas_main.itemconfigure(font_chooseer.text, font=font)))
+
+ def canvas_doc_init(self):
+ tkt.Button(
+ self.canvas_doc, 1070, 660, 200, 50, text='Back',
+ command=lambda: (tkt.move(self.root, self.canvas_main, -1280*self.canvas_main.rx, 0, 500, mode='rebound'),
+ tkt.move(self.root, self.canvas_doc, -1280*self.canvas_doc.rx, 0, 500, mode='rebound')))
+ button_1 = tkt.Button(self.canvas_doc, 1070, 10, 200, 50, text='Colorful',
+ command=lambda: (button_1.set_live(False), self.colorful()))
+ self.canvas_doc.create_text(
+ 15, 360, text=tkt.__doc__, font=(cnt.FONT, 14), anchor='w')
+
+ def canvas_graph_init(self):
+ tkt.Button(
+ self.canvas_graph, 1070, 660, 200, 50, text='3D',
+ command=lambda: (tkt.move(self.root, self.canvas_graph, -1280*self.canvas_main.rx, 0, 500, mode='rebound'),
+ tkt.move(self.root, self.canvas_3d, -1280*self.canvas_graph.rx, 0, 500, mode='rebound')))
+ tkt.Button(
+ self.canvas_graph, 10, 660, 200, 50, text='Back',
+ command=lambda: (tkt.move(self.root, self.canvas_main, 1280*self.canvas_main.rx, 0, 500, mode='rebound'),
+ tkt.move(self.root, self.canvas_graph, 1280*self.canvas_graph.rx, 0, 500, mode='rebound')))
+ button_2 = tkt.Button(self.canvas_graph, 10, 10, 200, 50, text='Draw',
+ command=lambda: (button_2.set_live(False), self.draw()))
+ try:
+ self.canvas_graph.create_image(
+ 1150, 130, image=tkt.PhotoImage('tkintertools.png'))
+ except TclError:
+ print('\033[31mLoad tkintertools.png Error\033[0m')
+
+ def canvas_3d_init(self):
+ self.create_3d()
+ tkt.Button(
+ self.canvas_3d, 10, 660, 200, 50, text='Back',
+ command=lambda: (tkt.move(self.root, self.canvas_graph, 1280*self.canvas_main.rx, 0, 500, mode='rebound'),
+ tkt.move(self.root, self.canvas_3d, 1280*self.canvas_graph.rx, 0, 500, mode='rebound')))
+
+ def spin(self, event): # type: (Event, list[float]) -> None
+ dx, dy = event.x - self.pos[0], event.y - self.pos[1]
+ self.pos = [event.x, event.y]
+ for item in self.lst_3ditems:
+ item.rotate(0, -dy/100, dx/100)
+ for item in self.lst_3ditems:
+ item.update(500, 640, 360)
+
+ def create_3d(self):
+ self.lst_3ditems = [] # type: list[t3d.Cuboid]
+ self.pos = [0, 0] # type: list[float]
+
+ def modify(event):
+ self.pos = [event.x, event.y]
+
+ for _ in range(10):
+ cube = t3d.Cuboid(
+ self.canvas_3d, *sample(range(-200, 200), 3), *sample(range(50, 100), 3))
+ cube.draw(500, 640, 360)
+ self.lst_3ditems.append(cube)
+ x, y, z = sample(range(-200, 200), 3)
+ tetr = t3d.Tetrahedron(
+ self.canvas_3d, *[(x+randint(-100, 100), y+randint(-100, 100), z+randint(-100, 100)) for _ in range(4)])
+ tetr.draw(500, 640, 360)
+ self.lst_3ditems.append(tetr)
+ self.canvas_3d.focus_set()
+ self.canvas_3d.bind('', lambda event: modify(event))
+ self.canvas_3d.bind('', self.spin)
+
+
+if __name__ == '__main__':
+ Application()
diff --git a/tkintertools/__init__.py b/tkintertools/__init__.py
index 78a59b668c0485ae4aa68f89dcd11fb4eef393e9..7e1f680328ea719fe49f756cdd43de5749a59af2 100644
--- a/tkintertools/__init__.py
+++ b/tkintertools/__init__.py
@@ -37,7 +37,6 @@ if sys.version_info < (3, 7): # Version Check
from .__main__ import (Button, Canvas, CheckButton, Entry, Label, PhotoImage,
Progressbar, SetProcessDpiAwareness, Singleton, Text,
Tk, Toplevel, askfont, color, move, text)
-from .constants import *
__author__ = 'Xiaokang2022<2951256653@qq.com>'
__version__ = '2.6.1'
@@ -49,5 +48,5 @@ __all__ = [
# Tool Classes
'PhotoImage', 'Singleton',
# Tool Functions
- 'move', 'text', 'color', 'askfont', 'SetProcessDpiAwareness'
+ 'move', 'text', 'color', 'askfont', 'SetProcessDpiAwareness',
]
diff --git a/tkintertools/__main__.py b/tkintertools/__main__.py
index 61e053ce69131ce0439da8a473a991662d4c362b..a875808c6f3127eae7dfb6a9f376c58de345d4c9 100644
--- a/tkintertools/__main__.py
+++ b/tkintertools/__main__.py
@@ -1,6 +1,6 @@
""" Main File """
-import math # 数学函数
+import math # 数学支持
import sys # DPI 兼容
import tkinter # 基础模块
from fractions import Fraction # 图片缩放
diff --git a/tkintertools/tools_3d.py b/tkintertools/tools_3d.py
new file mode 100644
index 0000000000000000000000000000000000000000..817c5a3330a0e7fccfecfc0dae936cb4a1010cff
--- /dev/null
+++ b/tkintertools/tools_3d.py
@@ -0,0 +1,309 @@
+""" 3D support """
+
+import math # 数学支持
+import statistics # 数据统计
+import tkinter # 基础模块
+from typing import Iterable # 类型提示
+
+import tkintertools # 类型提示
+
+
+def _cross(
+ matrix, # type: list[list[float]]
+ vector, # type: list[float]
+): # type: (...) -> list[float]
+ """ 转换矩阵 """
+ for i in range(3):
+ matrix[0][i] = sum(matrix[i][j]*vector[j] for j in range(3))
+ return matrix[0]
+
+
+class Point:
+ """ 点 """
+
+ def __init__(self, coords): # type: (list[float]) -> None
+ self.coords = list(coords) # 利用列表引用
+
+ def translate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> Point
+ """ 平移 """
+ self.coords[0] += dx
+ self.coords[1] += dy
+ self.coords[2] += dz
+ return self
+
+ def rotate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> Point
+ """ 旋转 """
+ sa, sb, sc = math.sin(dx), math.sin(dy), math.sin(dz)
+ ca, cb, cc = math.cos(dx), math.cos(dy), math.cos(dz)
+ M = [[cc*cb, cc*sb*sa-sc*ca, cc*sb*ca+sc*sa],
+ [sc*cb, sc*sb*sa+cc*ca, sc*sb*ca-cc*sa],
+ [-sb, cb*sa, cb*ca]]
+ self.coords[0], self.coords[1], self.coords[2] = _cross(M, self.coords)
+ return self
+
+ def project(self, distance): # type: (float) -> list[float]
+ """ 投影 """
+ try:
+ coefficient = distance/(distance - self.coords[0])
+ except:
+ return [distance, distance]
+ return [self.coords[1]*coefficient, self.coords[2]*coefficient]
+
+
+class Line:
+ """ 线 """
+
+ def __init__(
+ self,
+ x1, # type: float
+ y1, # type: float
+ z1, # type: float
+ x2, # type: float
+ y2, # type: float
+ z2, # type: float
+ ): # type: (...) -> None
+ self.coords = [[x1, y1, z1], [x2, y2, z2]]
+ self.points = [Point(coord) for coord in self.coords]
+
+ def translate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> Line
+ """ 平移 """
+ for point in self.points:
+ point.translate(dx, dy, dz)
+ return self
+
+ def rotate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> Line
+ """ 旋转 """
+ for point in self.points:
+ point.rotate(dx, dy, dz)
+ return self
+
+ def project(self, distance): # type: (float) -> list[list[float]]
+ """ 投影 """
+ return [point.project(distance) for point in self.points]
+
+
+class Side:
+ """ 面 """
+
+ def __init__(self, *coords): # type: (list[float]) -> None
+ self.coords = list(coords)
+ self.lines = [Line(*coords[ind-1], *coords[ind])
+ for ind in range(len(coords))]
+
+ def translate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> Side
+ """ 平移 """
+ for line in self.lines:
+ line.translate(dx, dy, dz)
+ return self
+
+ def rotate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> Side
+ """ 旋转 """
+ for line in self.lines:
+ line.rotate(dx, dy, dz)
+ return self
+
+ def project(self, distance): # type: (float) -> list[list[list[float]]]
+ """ 投影 """
+ return [line.project(distance) for line in self.lines]
+
+
+class Geometry:
+ """ 几何体 """
+
+ def __init__(self, canvas, *sides): # type: (tkintertools.Canvas, Side) -> None
+ """
+ `canvas`: 显示的画布\n
+ `size`: 平面类`Side`\n
+ """
+ self.canvas = canvas
+ self.coords = [] # type: list[list[float]]
+ self.sides = [] # type: list[Side]
+ self.items = [] # type: list[tkinter._CanvasItemId]
+ if sides:
+ self.append(*sides)
+
+ def translate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> None
+ """ 平移 """
+ for side in self.sides:
+ side.translate(dx, dy, dz)
+
+ def rotate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> None
+ """ 旋转 """
+ for side in self.sides:
+ side.rotate(dx, dy, dz)
+
+ def center(self): # type: () -> tuple[float, float, float]
+ """ 几何中心 """ # NOTE: 对凹面几何体无效
+ data = list(zip(*self.coords)) # 转置
+ x_c = statistics.mean(data[0])
+ y_c = statistics.mean(data[1])
+ z_c = statistics.mean(data[2])
+ return x_c, y_c, z_c
+
+ def append(self, *sides): # type: (Side) -> None
+ """ 添加面 """
+ for side in sides:
+ for line in side.lines:
+ for point in line.points:
+ if point not in self.coords:
+ self.coords.append(point)
+ self.sides.append(side)
+
+ def update(self, distance, dx=0, dy=0): # type: (float, float, float) -> None
+ """ 更新几何体 """
+ c = 0
+ for side in self.sides:
+ coords = side.project(distance)
+ for coord in coords:
+
+ k = []
+
+ for lst in coord:
+ if dx or dy:
+ lst[0] += 640
+ lst[1] += 360
+ k.append(lst[0])
+ k.append(lst[1])
+
+ self.canvas.coords(self.items[c], k)
+ c += 1
+
+ def draw(self, distance, dx=0, dy=0): # type: (float, float, float) -> None
+ """ 绘制 """
+ for side in self.sides:
+ coords = side.project(distance)
+ for coord in coords:
+
+ if dx or dy:
+ for lst in coord:
+ lst[0] += dx
+ lst[1] += dy
+
+ self.items.append(self.canvas.create_polygon(
+ *coord, outline='black'))
+
+
+class Cuboid(Geometry):
+ """ 长方体 """
+
+ def __init__(
+ self,
+ canvas, # type: tkintertools.Canvas
+ x, # type: float
+ y, # type: float
+ z, # type: float
+ length, # type: float
+ width, # type: float
+ height, # type: float
+ ): # type: (...) -> None
+ """
+ `canvas`: 父画布\n
+ `x`: 左上角x坐标\n
+ `y`: 左上角y坐标\n
+ `z`: 左上角z坐标\n
+ `length`: 长度\n
+ `width`: 宽度\n
+ `height`: 高度\n
+ """
+ self.canvas = canvas
+ self.coords = [[x+l, y+w, z+h]
+ for l in (0, length)
+ for w in (0, width)
+ for h in (0, height)]
+ self.sides = [
+ Side(self.coords[0], self.coords[1],
+ self.coords[3], self.coords[2]),
+ Side(self.coords[0], self.coords[1],
+ self.coords[5], self.coords[4]),
+ Side(self.coords[0], self.coords[2],
+ self.coords[6], self.coords[4]),
+ Side(self.coords[1], self.coords[3],
+ self.coords[7], self.coords[5]),
+ Side(self.coords[2], self.coords[3],
+ self.coords[7], self.coords[6]),
+ Side(self.coords[4], self.coords[5],
+ self.coords[7], self.coords[6]),
+ ]
+ self.items = [] # type: list[tkinter._CanvasItemId]
+
+
+class Tetrahedron(Geometry):
+ """ 四面体 """
+
+ def __init__(
+ self,
+ canvas, # type: tkintertools.Canvas
+ p1, # type: Iterable[float]
+ p2, # type: Iterable[float]
+ p3, # type: Iterable[float]
+ p4, # type: Iterable[float]
+ ): # type: (...) -> None
+ """
+ `canvas`: 父画布\n
+ `p1`: 第一个顶点\n
+ `p2`: 第二个顶点\n
+ `p3`: 第三个顶点\n
+ `p4`: 第四个顶点\n
+ """
+ self.canvas = canvas
+ self.coords = [list(p1), list(p2), list(p3), list(p4)]
+ self.sides = [
+ Side(p1, p2, p3),
+ Side(p1, p2, p4),
+ Side(p1, p3, p4),
+ Side(p2, p3, p4),
+ ]
+ self.items = [] # type: list[tkinter._CanvasItemId]
+
+
+ORIGIN = Point((0,)*3)
+""" 原点 """
+
+LINE_X = Line(0, 0, 0, 1, 0, 0)
+""" X 轴单位直线 """
+LINE_Y = Line(0, 0, 0, 0, 1, 0)
+""" Y 轴单位直线 """
+LINE_Z = Line(0, 0, 0, 0, 0, 1)
+""" Z 轴单位直线 """
+
+SIDE_YZ = Side((0, 1, 1), (0, 1, -1), (0, -1, -1), (0, -1, 1))
+""" 垂直 X 轴单位平面 """
+SIDE_ZX = Side((1, 0, 1), (1, 0, -1), (-1, 0, -1), (-1, 0, 1))
+""" 垂直 Y 轴单位平面 """
+SIDE_XY = Side((1, 1, 0), (1, -1, 0), (-1, -1, 0), (-1, 1, 0))
+""" 垂直 Z 轴单位平面 """
+
+
+# class Ellipsoid:
+# """ 椭球体 """
+
+# def __init__(
+# self,
+# x, # type: float
+# y, # type: float
+# z, # type: float
+# length, # type: float
+# width, # type: float
+# height # type: float
+# ): # type: (...) -> None
+# self.coords = [[x+l, y+w, z+h]
+# for l in (0, length)
+# for w in (0, width)
+# for h in (0, height)]
+# self.points = [Point(coord) for coord in self.coords]
+
+# def translate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> None
+# """ 平移 """
+# for point in self.points:
+# point.translate(dx, dy, dz)
+
+# def rotate(self, dx=0, dy=0, dz=0): # type: (float, float, float) -> None
+# """ 旋转 """
+# for point in self.points:
+# point.rotate(dx, dy, dz)
+
+# # type: (float) -> tuple[list[float], list[float]]
+# def project(self, distance):
+# """ 投影 """
+# coefficient = distance/(distance - self.coords[0])
+# return [self.coords[1]*coefficient, self.coords[2]*coefficient]