diff --git a/.gitignore b/.gitignore index a47fb1d..aaa9108 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea -__pycache__ \ No newline at end of file +__pycache__ +*.dxf \ No newline at end of file diff --git a/graphic.py b/graphic.py index e69de29..3ed795d 100644 --- a/graphic.py +++ b/graphic.py @@ -0,0 +1,131 @@ +import copy +import math +import ezdxf +from type3d import Vector3D +import numpy as np +import transformation +from typing import List, Union, Tuple + + +class Line: + def __init__( + self, + start_p: Vector3D, + end_p: Vector3D, + tension_k: float, + n_point: int, + ): + self._start_p: Vector3D = start_p + self._end_p: Vector3D = end_p + self._tension_k: float = tension_k + self._n_point: int = n_point + self._span = 0 + self._points = None + self._rotation = None + + def curve(self): + if self._points: + return self._points + start_p = self._start_p + end_p = self._end_p + tension_k = self._tension_k + n_point = self._n_point + # 右手坐标系,Z朝上 + line = Vector3D(end_p.x, end_p.y, end_p.z) - Vector3D( + start_p.x, start_p.y, start_p.z + ) + # 计算与X轴的角度 + xy_project = copy.deepcopy(line) # 投影到xy平面上 + xy_project.z = 0 + x_abs_angel = xy_project.angle_to(Vector3D(1, 0, 0)) + x_angel = x_abs_angel * np.sign(xy_project.y) + height = end_p.z - start_p.z + span: float = abs(xy_project) + self._span = span + span_l = np.linspace(0, span, n_point) # 档距 + z_points: Tuple[float] = ( + start_p.z + + span_l * height / span + - span_l * (span - span_l) * tension_k / math.cos(math.atan(height / span)) + ) + p_points: List[ + Union[List[float, float, float], Tuple[float, float, float]] + ] = list() # 未旋转之前的伪坐标 + for foo in range(len(span_l)): + p_points.append((span_l[foo] + start_p.x, start_p.y, z_points[foo])) + # 绕Z轴旋转 + rotation = transformation.Rotation( + x_angel, + [start_p.x, start_p.y, start_p.z], + [start_p.x, start_p.y, start_p.z + 1], + ) + self._rotation = rotation + points = rotation.rotate( + p_points, + ) + self._points = points + return points + + def sag(self): + # msp.add_polyline3d( + # [(start_p.x, start_p.y, start_p.z), (end_p.x, end_p.y, end_p.z)] + # ) + start_p = self._start_p + end_p = self._end_p + span = self._span + height = end_p.z - start_p.z + middle_span = span / 2 + points = self._points + n_point = self._n_point + middle_z = points[int(n_point / 2)][2] + rotation = self._rotation + sag_points = rotation.rotate( + [ + [start_p.x, start_p.y, start_p.z], + [ + middle_span + start_p.x, + start_p.y, + start_p.z + middle_span * height / span, + ], + [middle_span + start_p.x, start_p.y, middle_z], + [ + middle_span + start_p.x, + start_p.y, + start_p.z + middle_span * height / span, + ], + [start_p.x + span, start_p.y, end_p.z], + ] + ) + return sag_points + + def swing(self, angel): + points = self._points + start_p = self._start_p + end_p = self._end_p + swing_rotation = transformation.Rotation( + angel, + [start_p.x, start_p.y, start_p.z], + [end_p.x, end_p.y, end_p.z], + ) + swing_point = swing_rotation.rotate(points) + return swing_point + + +class Canvas: + def __init__(self): + self._doc = None + self._msp = None + + def init_canvas(self): + doc = ezdxf.new() + self._doc = doc + msp = doc.modelspace() + self._msp = msp + + def draw(self, points): + msp = self._msp + msp.add_polyline3d(points) + + def save(self, file_path): + doc = self._doc + doc.saveas(file_path) diff --git a/main.py b/main.py index 58b477b..90c8f84 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,13 @@ import math -from transformation import rotation -import numpy as np + +from graphic import Canvas, Line +from type3d import Vector3D if __name__ == "__main__": - t = rotation( - 45 / 180 * math.pi, np.array([0, 0, 1]), np.array([[1, 1, 0], [2, 3, 4]]) - ) - print(t) + canvas = Canvas() + canvas.init_canvas() + line = Line(Vector3D(20, 30, 0), Vector3D(400, 100, 1), 0.3 * 1e-3, 400) + canvas.draw(line.curve()) + canvas.draw(line.sag()) + canvas.draw(line.swing(80/180*math.pi)) + canvas.save("lwpolyline1.dxf") diff --git a/transformation.py b/transformation.py index df2030c..ff1c3f5 100644 --- a/transformation.py +++ b/transformation.py @@ -1,5 +1,6 @@ import numpy as np from math import cos, sin +from typing import List, Union, Tuple def create_rx(unit_axis: np.ndarray) -> np.ndarray: @@ -32,16 +33,41 @@ def create_t(axis: np.ndarray) -> np.ndarray: return np.array([[1, 0, 0, -x1], [0, 1, 0, -y1], [0, 0, 1, -z1], [0, 0, 0, 1]]) -def rotation(angel: float, axis: np.ndarray, points: np.ndarray): +def rotation_matrix( + angel: float, axis_start: np.ndarray, axis_end: np.ndarray +) -> np.ndarray: # 依据《计算机图形学》第4版 9.28公式 + axis = axis_end - axis_start unit_axis = axis / np.linalg.norm(axis) rz = create_rz(angel) rx = create_rx(unit_axis) ry = create_ry(unit_axis) - t = create_t(axis) + t = create_t(axis_start) t_i = np.linalg.inv(t) rx_i = np.linalg.inv(rx) ry_i = np.linalg.inv(ry) - r_transform = t_i.dot(rx_i.dot(ry_i.dot(rz.dot(ry.dot(rx.dot(t)))))) - expand_point = np.concatenate((points, np.ones((points.shape[0], 1))), axis=1) - return r_transform.dot(expand_point.T) + r_transform = t_i @ rx_i @ ry_i @ rz @ ry @ rx @ t + return r_transform + + +def rotation( + angel: float, axis_start: np.ndarray, axis_end: np.ndarray, points: np.ndarray +) -> np.ndarray: + _rotation = Rotation(angel, axis_start.tolist(), axis_end.tolist()) + return _rotation.rotate(points.tolist()) + + +class Rotation: + def __init__(self, angel: float, axis_start: List[float], axis_end: List[float]): + self._r_transform = rotation_matrix( + angel, np.array(axis_start), np.array(axis_end) + ) + + def rotate(self, points: List[Union[List[float], Tuple[float]]])->List: + np_points = np.array(points) + r_transform = self._r_transform + expand_point = np.concatenate( + (np_points, np.ones((np_points.shape[0], 1))), axis=1 + ) + transformed_points = r_transform @ expand_point.T + return transformed_points[:-1, :].T.tolist() diff --git a/type3d.py b/type3d.py index fbbab4c..098a94e 100644 --- a/type3d.py +++ b/type3d.py @@ -1,5 +1,22 @@ +import math + + class Vector3D: def __init__(self, x, y, z): - self.x = x - self.y = y - self.z = z + self.x:float = x + self.y:float = y + self.z:float = z + + def __sub__(self, other): + return Vector3D(self.x - other.x, self.y - other.y, self.z - other.z) + + def __abs__(self) -> float: + return (self.x ** 2 + self.y ** 2 + self.z ** 2) ** 0.5 + + # 计算两向量间夹角 + def angle_to(self, other): + return math.acos( + abs(self.x * other.x + self.y * other.y + self.z * other.z) + / abs(self) + / abs(other) + )