提交 66cfd335 编写于 作者: 小康2022's avatar 小康2022 👍

version 2.6.2-dev

上级 1eb6827c
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
......
......@@ -13,7 +13,7 @@
<img src="https://img.shields.io/badge/License-Mulan PSL v2-green" alt="License" />
</a>
<a href="./CHANGELOG.md">
<img src="https://img.shields.io/badge/ChangeLog-2023/05/21-orange" alt="ChangeLog" />
<img src="https://img.shields.io/badge/ChangeLog-2023/05/26-orange" alt="ChangeLog" />
</a>
<a href="./TODO.md">
<img src="https://img.shields.io/badge/ToDos-10-yellow" alt="ToDos" />
......@@ -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 可能会比较多。
......
""" 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('<Button-1>', lambda event: modify(event))
self.canvas_3d.bind('<B1-Motion>', self.spin)
if __name__ == '__main__':
Application()
......@@ -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',
]
""" Main File """
import math # 数学函数
import math # 数学支持
import sys # DPI 兼容
import tkinter # 基础模块
from fractions import Fraction # 图片缩放
......
""" 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]
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册