Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'archipack/archipack_2d.py')
-rw-r--r--archipack/archipack_2d.py968
1 files changed, 0 insertions, 968 deletions
diff --git a/archipack/archipack_2d.py b/archipack/archipack_2d.py
deleted file mode 100644
index e286e730..00000000
--- a/archipack/archipack_2d.py
+++ /dev/null
@@ -1,968 +0,0 @@
-# -*- coding:utf-8 -*-
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-# ----------------------------------------------------------
-# Author: Stephen Leger (s-leger)
-#
-# ----------------------------------------------------------
-from mathutils import Vector, Matrix
-from math import sin, cos, pi, atan2, sqrt, acos
-import bpy
-# allow to draw parts with gl for debug puropses
-from .archipack_gl import GlBaseLine
-
-
-class Projection(GlBaseLine):
-
- def __init__(self):
- GlBaseLine.__init__(self)
-
- def proj_xy(self, t, next=None):
- """
- length of projection of sections at crossing line / circle intersections
- deformation unit vector for profil in xy axis
- so f(x_profile) = position of point in xy plane
- """
- if next is None:
- return self.normal(t).v.normalized(), 1
- v0 = self.normal(1).v.normalized()
- v1 = next.normal(0).v.normalized()
- direction = v0 + v1
- adj = (v0 * self.length) * (v1 * next.length)
- hyp = (self.length * next.length)
- c = min(1, max(-1, adj / hyp))
- size = 1 / cos(0.5 * acos(c))
- return direction.normalized(), min(3, size)
-
- def proj_z(self, t, dz0, next=None, dz1=0):
- """
- length of projection along crossing line / circle
- deformation unit vector for profil in z axis at line / line intersection
- so f(y) = position of point in yz plane
- """
- return Vector((0, 1)), 1
- """
- NOTE (to myself):
- In theory this is how it has to be done so sections follow path,
- but in real world results are better when sections are z-up.
- So return a dumb 1 so f(y) = y
- """
- if next is None:
- dz = dz0 / self.length
- else:
- dz = (dz1 + dz0) / (self.length + next.length)
- return Vector((0, 1)), sqrt(1 + dz * dz)
- # 1 / sqrt(1 + (dz0 / self.length) * (dz0 / self.length))
- if next is None:
- return Vector((-dz0, self.length)).normalized(), 1
- v0 = Vector((self.length, dz0))
- v1 = Vector((next.length, dz1))
- direction = Vector((-dz0, self.length)).normalized() + Vector((-dz1, next.length)).normalized()
- adj = v0 * v1
- hyp = (v0.length * v1.length)
- c = min(1, max(-1, adj / hyp))
- size = -cos(pi - 0.5 * acos(c))
- return direction.normalized(), size
-
-
-class Line(Projection):
- """
- 2d Line
- Internally stored as p: origin and v:size and direction
- moving p will move both ends of line
- moving p0 or p1 move only one end of line
- p1
- ^
- | v
- p0 == p
- """
- def __init__(self, p=None, v=None, p0=None, p1=None):
- """
- Init by either
- p: Vector or tuple origin
- v: Vector or tuple size and direction
- or
- p0: Vector or tuple 1 point location
- p1: Vector or tuple 2 point location
- Will convert any into Vector 2d
- both optionnals
- """
- Projection.__init__(self)
- if p is not None and v is not None:
- self.p = Vector(p).to_2d()
- self.v = Vector(v).to_2d()
- elif p0 is not None and p1 is not None:
- self.p = Vector(p0).to_2d()
- self.v = Vector(p1).to_2d() - self.p
- else:
- self.p = Vector((0, 0))
- self.v = Vector((0, 0))
- self.line = None
-
- @property
- def copy(self):
- return Line(self.p.copy(), self.v.copy())
-
- @property
- def p0(self):
- return self.p
-
- @property
- def p1(self):
- return self.p + self.v
-
- @p0.setter
- def p0(self, p0):
- """
- Note: setting p0
- move p0 only
- """
- p1 = self.p1
- self.p = Vector(p0).to_2d()
- self.v = p1 - p0
-
- @p1.setter
- def p1(self, p1):
- """
- Note: setting p1
- move p1 only
- """
- self.v = Vector(p1).to_2d() - self.p
-
- @property
- def length(self):
- """
- 3d length
- """
- return self.v.length
-
- @property
- def angle(self):
- """
- 2d angle on xy plane
- """
- return atan2(self.v.y, self.v.x)
-
- @property
- def a0(self):
- return self.angle
-
- @property
- def angle_normal(self):
- """
- 2d angle of perpendicular
- lie on the right side
- p1
- |--x
- p0
- """
- return atan2(-self.v.x, self.v.y)
-
- @property
- def reversed(self):
- return Line(self.p, -self.v)
-
- @property
- def oposite(self):
- return Line(self.p + self.v, -self.v)
-
- @property
- def cross_z(self):
- """
- 2d Vector perpendicular on plane xy
- lie on the right side
- p1
- |--x
- p0
- """
- return Vector((self.v.y, -self.v.x))
-
- @property
- def cross(self):
- return Vector((self.v.y, -self.v.x))
-
- def signed_angle(self, u, v):
- """
- signed angle between two vectors range [-pi, pi]
- """
- return atan2(u.x * v.y - u.y * v.x, u.x * v.x + u.y * v.y)
-
- def delta_angle(self, last):
- """
- signed delta angle between end of line and start of this one
- this value is object's a0 for segment = self
- """
- if last is None:
- return self.angle
- return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v)
-
- def normal(self, t=0):
- """
- 2d Line perpendicular on plane xy
- at position t in current segment
- lie on the right side
- p1
- |--x
- p0
- """
- return Line(self.lerp(t), self.cross_z)
-
- def sized_normal(self, t, size):
- """
- 2d Line perpendicular on plane xy
- at position t in current segment
- and of given length
- lie on the right side when size > 0
- p1
- |--x
- p0
- """
- return Line(self.lerp(t), size * self.cross_z.normalized())
-
- def lerp(self, t):
- """
- 3d interpolation
- """
- return self.p + self.v * t
-
- def intersect(self, line):
- """
- 2d intersection on plane xy
- return
- True if intersect
- p: point of intersection
- t: param t of intersection on current line
- """
- c = line.cross_z
- d = self.v.dot(c)
- if d == 0:
- return False, 0, 0
- t = c.dot(line.p - self.p) / d
- return True, self.lerp(t), t
-
- def intersect_ext(self, line):
- """
- same as intersect, but return param t on both lines
- """
- c = line.cross_z
- d = self.v.dot(c)
- if d == 0:
- return False, 0, 0, 0
- dp = line.p - self.p
- c2 = self.cross_z
- u = c.dot(dp) / d
- v = c2.dot(dp) / d
- return u > 0 and v > 0 and u < 1 and v < 1, self.lerp(u), u, v
-
- def point_sur_segment(self, pt):
- """ _point_sur_segment
- point: Vector 2d
- t: param t de l'intersection sur le segment courant
- d: distance laterale perpendiculaire positif a droite
- """
- dp = pt - self.p
- dl = self.length
- if dl == 0:
- return dp.length < 0.00001, 0, 0
- d = (self.v.x * dp.y - self.v.y * dp.x) / dl
- t = self.v.dot(dp) / (dl * dl)
- return t > 0 and t < 1, d, t
-
- def steps(self, len):
- steps = max(1, round(self.length / len, 0))
- return 1 / steps, int(steps)
-
- def in_place_offset(self, offset):
- """
- Offset current line
- offset > 0 on the right part
- """
- self.p += offset * self.cross_z.normalized()
-
- def offset(self, offset):
- """
- Return a new line
- offset > 0 on the right part
- """
- return Line(self.p + offset * self.cross_z.normalized(), self.v)
-
- def tangeant(self, t, da, radius):
- p = self.lerp(t)
- if da < 0:
- c = p + radius * self.cross_z.normalized()
- else:
- c = p - radius * self.cross_z.normalized()
- return Arc(c, radius, self.angle_normal, da)
-
- def straight(self, length, t=1):
- return Line(self.lerp(t), self.v.normalized() * length)
-
- def translate(self, dp):
- self.p += dp
-
- def rotate(self, a):
- """
- Rotate segment ccw arroud p0
- """
- ca = cos(a)
- sa = sin(a)
- self.v = Matrix([
- [ca, -sa],
- [sa, ca]
- ]) @ self.v
- return self
-
- def scale(self, length):
- self.v = length * self.v.normalized()
- return self
-
- def tangeant_unit_vector(self, t):
- return self.v.normalized()
-
- def as_curve(self, context):
- """
- Draw Line with open gl in screen space
- aka: coords are in pixels
- """
- curve = bpy.data.curves.new('LINE', type='CURVE')
- curve.dimensions = '2D'
- spline = curve.splines.new('POLY')
- spline.use_endpoint_u = False
- spline.use_cyclic_u = False
- pts = self.pts
- spline.points.add(len(pts) - 1)
- for i, p in enumerate(pts):
- x, y, z = p
- spline.points[i].co = (x, y, 0, 1)
- curve_obj = bpy.data.objects.new('LINE', curve)
- context.scene.collection.objects.link(curve_obj)
- curve_obj.select_set(state=True)
-
- def make_offset(self, offset, last=None):
- """
- Return offset between last and self.
- Adjust last and self start to match
- intersection point
- """
- line = self.offset(offset)
- if last is None:
- return line
-
- if hasattr(last, "r"):
- res, d, t = line.point_sur_segment(last.c)
- c = (last.r * last.r) - (d * d)
- # print("t:%s" % t)
- if c <= 0:
- # no intersection !
- p0 = line.lerp(t)
- else:
- # center is past start of line
- if t > 0:
- p0 = line.lerp(t) - line.v.normalized() * sqrt(c)
- else:
- p0 = line.lerp(t) + line.v.normalized() * sqrt(c)
- # compute da of arc
- u = last.p0 - last.c
- v = p0 - last.c
- da = self.signed_angle(u, v)
- # da is ccw
- if last.ccw:
- # da is cw
- if da < 0:
- # so take inverse
- da = 2 * pi + da
- elif da > 0:
- # da is ccw
- da = 2 * pi - da
- last.da = da
- line.p0 = p0
- else:
- # intersect line / line
- # 1 line -> 2 line
- c = line.cross_z
- d = last.v.dot(c)
- if d == 0:
- return line
- v = line.p - last.p
- t = c.dot(v) / d
- c2 = last.cross_z
- u = c2.dot(v) / d
- # intersect past this segment end
- # or before last segment start
- # print("u:%s t:%s" % (u, t))
- if u > 1 or t < 0:
- return line
- p = last.lerp(t)
- line.p0 = p
- last.p1 = p
-
- return line
-
- @property
- def pts(self):
- return [self.p0.to_3d(), self.p1.to_3d()]
-
-
-class Circle(Projection):
- def __init__(self, c, radius):
- Projection.__init__(self)
- self.r = radius
- self.r2 = radius * radius
- self.c = c
-
- def intersect(self, line):
- v = line.p - self.c
- A = line.v.dot(line.v)
- B = 2 * v.dot(line.v)
- C = v.dot(v) - self.r2
- d = B * B - 4 * A * C
- if A <= 0.0000001 or d < 0:
- # dosent intersect, find closest point of line
- res, d, t = line.point_sur_segment(self.c)
- return False, line.lerp(t), t
- elif d == 0:
- t = -B / 2 * A
- return True, line.lerp(t), t
- else:
- AA = 2 * A
- dsq = sqrt(d)
- t0 = (-B + dsq) / AA
- t1 = (-B - dsq) / AA
- if abs(t0) < abs(t1):
- return True, line.lerp(t0), t0
- else:
- return True, line.lerp(t1), t1
-
- def translate(self, dp):
- self.c += dp
-
-
-class Arc(Circle):
- """
- Represent a 2d Arc
- TODO:
- make it possible to define an arc by start point end point and center
- """
- def __init__(self, c, radius, a0, da):
- """
- a0 and da arguments are in radians
- c Vector 2d center
- radius float radius
- a0 radians start angle
- da radians delta angle from start to end
- a0 = 0 on the right side
- a0 = pi on the left side
- da > 0 CCW contrary-clockwise
- da < 0 CW clockwise
- stored internally as radians
- """
- Circle.__init__(self, Vector(c).to_2d(), radius)
- self.line = None
- self.a0 = a0
- self.da = da
-
- @property
- def angle(self):
- """
- angle of vector p0 p1
- """
- v = self.p1 - self.p0
- return atan2(v.y, v.x)
-
- @property
- def ccw(self):
- return self.da > 0
-
- def signed_angle(self, u, v):
- """
- signed angle between two vectors
- """
- return atan2(u.x * v.y - u.y * v.x, u.x * v.x + u.y * v.y)
-
- def delta_angle(self, last):
- """
- signed delta angle between end of line and start of this one
- this value is object's a0 for segment = self
- """
- if last is None:
- return self.a0
- return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v)
-
- def scale_rot_matrix(self, u, v):
- """
- given vector u and v (from and to p0 p1)
- apply scale factor to radius and
- return a matrix to rotate and scale
- the center around u origin so
- arc fit v
- """
- # signed angle old new vectors (rotation)
- a = self.signed_angle(u, v)
- # scale factor
- scale = v.length / u.length
- ca = scale * cos(a)
- sa = scale * sin(a)
- return scale, Matrix([
- [ca, -sa],
- [sa, ca]
- ])
-
- @property
- def p0(self):
- """
- start point of arc
- """
- return self.lerp(0)
-
- @property
- def p1(self):
- """
- end point of arc
- """
- return self.lerp(1)
-
- @p0.setter
- def p0(self, p0):
- """
- rotate and scale arc so it intersect p0 p1
- da is not affected
- """
- u = self.p0 - self.p1
- v = p0 - self.p1
- scale, rM = self.scale_rot_matrix(u, v)
- self.c = self.p1 + rM @ (self.c - self.p1)
- self.r *= scale
- self.r2 = self.r * self.r
- dp = p0 - self.c
- self.a0 = atan2(dp.y, dp.x)
-
- @p1.setter
- def p1(self, p1):
- """
- rotate and scale arc so it intersect p0 p1
- da is not affected
- """
- p0 = self.p0
- u = self.p1 - p0
- v = p1 - p0
-
- scale, rM = self.scale_rot_matrix(u, v)
- self.c = p0 + rM @ (self.c - p0)
- self.r *= scale
- self.r2 = self.r * self.r
- dp = p0 - self.c
- self.a0 = atan2(dp.y, dp.x)
-
- @property
- def length(self):
- """
- arc length
- """
- return self.r * abs(self.da)
-
- @property
- def oposite(self):
- a0 = self.a0 + self.da
- if a0 > pi:
- a0 -= 2 * pi
- if a0 < -pi:
- a0 += 2 * pi
- return Arc(self.c, self.r, a0, -self.da)
-
- def normal(self, t=0):
- """
- Perpendicular line starting at t
- always on the right side
- """
- p = self.lerp(t)
- if self.da < 0:
- return Line(p, self.c - p)
- else:
- return Line(p, p - self.c)
-
- def sized_normal(self, t, size):
- """
- Perpendicular line starting at t and of a length size
- on the right side when size > 0
- """
- p = self.lerp(t)
- if self.da < 0:
- v = self.c - p
- else:
- v = p - self.c
- return Line(p, size * v.normalized())
-
- def lerp(self, t):
- """
- Interpolate along segment
- t parameter [0, 1] where 0 is start of arc and 1 is end
- """
- a = self.a0 + t * self.da
- return self.c + Vector((self.r * cos(a), self.r * sin(a)))
-
- def steps(self, length):
- """
- Compute step count given desired step length
- """
- steps = max(1, round(self.length / length, 0))
- return 1.0 / steps, int(steps)
-
- def intersect_ext(self, line):
- """
- same as intersect, but return param t on both lines
- """
- res, p, v = self.intersect(line)
- v0 = self.p0 - self.c
- v1 = p - self.c
- u = self.signed_angle(v0, v1) / self.da
- return res and u > 0 and v > 0 and u < 1 and v < 1, p, u, v
-
- # this is for wall
- def steps_by_angle(self, step_angle):
- steps = max(1, round(abs(self.da) / step_angle, 0))
- return 1.0 / steps, int(steps)
-
- def as_lines(self, steps):
- """
- convert Arc to lines
- """
- res = []
- p0 = self.lerp(0)
- for step in range(steps):
- p1 = self.lerp((step + 1) / steps)
- s = Line(p0=p0, p1=p1)
- res.append(s)
- p0 = p1
-
- if self.line is not None:
- p0 = self.line.lerp(0)
- for step in range(steps):
- p1 = self.line.lerp((step + 1) / steps)
- res[step].line = Line(p0=p0, p1=p1)
- p0 = p1
- return res
-
- def offset(self, offset):
- """
- Offset circle
- offset > 0 on the right part
- """
- if self.da > 0:
- radius = self.r + offset
- else:
- radius = self.r - offset
- return Arc(self.c, radius, self.a0, self.da)
-
- def tangeant(self, t, length):
- """
- Tangent line so we are able to chain Circle and lines
- Beware, counterpart on Line does return an Arc !
- """
- a = self.a0 + t * self.da
- ca = cos(a)
- sa = sin(a)
- p = self.c + Vector((self.r * ca, self.r * sa))
- v = Vector((length * sa, -length * ca))
- if self.da > 0:
- v = -v
- return Line(p, v)
-
- def tangeant_unit_vector(self, t):
- """
- Return Tangent vector of length 1
- """
- a = self.a0 + t * self.da
- ca = cos(a)
- sa = sin(a)
- v = Vector((sa, -ca))
- if self.da > 0:
- v = -v
- return v
-
- def straight(self, length, t=1):
- """
- Return a tangent Line
- Counterpart on Line also return a Line
- """
- return self.tangeant(t, length)
-
- def point_sur_segment(self, pt):
- """
- Point pt lie on arc ?
- return
- True when pt lie on segment
- t [0, 1] where it lie (normalized between start and end)
- d distance from arc
- """
- dp = pt - self.c
- d = dp.length - self.r
- a = atan2(dp.y, dp.x)
- t = (a - self.a0) / self.da
- return t > 0 and t < 1, d, t
-
- def rotate(self, a):
- """
- Rotate center so we rotate ccw around p0
- """
- ca = cos(a)
- sa = sin(a)
- rM = Matrix([
- [ca, -sa],
- [sa, ca]
- ])
- p0 = self.p0
- self.c = p0 + rM @ (self.c - p0)
- dp = p0 - self.c
- self.a0 = atan2(dp.y, dp.x)
- return self
-
- # make offset for line / arc, arc / arc
- def make_offset(self, offset, last=None):
-
- line = self.offset(offset)
-
- if last is None:
- return line
-
- if hasattr(last, "v"):
- # intersect line / arc
- # 1 line -> 2 arc
- res, d, t = last.point_sur_segment(line.c)
- c = line.r2 - (d * d)
- if c <= 0:
- # no intersection !
- p0 = last.lerp(t)
- else:
-
- # center is past end of line
- if t > 1:
- # Arc take precedence
- p0 = last.lerp(t) - last.v.normalized() * sqrt(c)
- else:
- # line take precedence
- p0 = last.lerp(t) + last.v.normalized() * sqrt(c)
-
- # compute a0 and da of arc
- u = p0 - line.c
- v = line.p1 - line.c
- line.a0 = atan2(u.y, u.x)
- da = self.signed_angle(u, v)
- # da is ccw
- if self.ccw:
- # da is cw
- if da < 0:
- # so take inverse
- da = 2 * pi + da
- elif da > 0:
- # da is ccw
- da = 2 * pi - da
- line.da = da
- last.p1 = p0
- else:
- # intersect arc / arc x1 = self x0 = last
- # rule to determine right side ->
- # same side of d as p0 of self
- dc = line.c - last.c
- tmp = Line(last.c, dc)
- res, d, t = tmp.point_sur_segment(self.p0)
- r = line.r + last.r
- dist = dc.length
- if dist > r or \
- dist < abs(last.r - self.r):
- # no intersection
- return line
- if dist == r:
- # 1 solution
- p0 = dc * -last.r / r + self.c
- else:
- # 2 solutions
- a = (last.r2 - line.r2 + dist * dist) / (2.0 * dist)
- v2 = last.c + dc * a / dist
- h = sqrt(last.r2 - a * a)
- r = Vector((-dc.y, dc.x)) * (h / dist)
- p0 = v2 + r
- res, d1, t = tmp.point_sur_segment(p0)
- # take other point if we are not on the same side
- if d1 > 0:
- if d < 0:
- p0 = v2 - r
- elif d > 0:
- p0 = v2 - r
-
- # compute da of last
- u = last.p0 - last.c
- v = p0 - last.c
- last.da = self.signed_angle(u, v)
-
- # compute a0 and da of current
- u, v = v, line.p1 - line.c
- line.a0 = atan2(u.y, u.x)
- line.da = self.signed_angle(u, v)
- return line
-
- # DEBUG
- @property
- def pts(self):
- n_pts = max(1, int(round(abs(self.da) / pi * 30, 0)))
- t_step = 1 / n_pts
- return [self.lerp(i * t_step).to_3d() for i in range(n_pts + 1)]
-
- def as_curve(self, context):
- """
- Draw 2d arc with open gl in screen space
- aka: coords are in pixels
- """
- curve = bpy.data.curves.new('ARC', type='CURVE')
- curve.dimensions = '2D'
- spline = curve.splines.new('POLY')
- spline.use_endpoint_u = False
- spline.use_cyclic_u = False
- pts = self.pts
- spline.points.add(len(pts) - 1)
- for i, p in enumerate(pts):
- x, y = p
- spline.points[i].co = (x, y, 0, 1)
- curve_obj = bpy.data.objects.new('ARC', curve)
- context.scene.collection.objects.link(curve_obj)
- curve_obj.select_set(state=True)
-
-
-class Line3d(Line):
- """
- 3d Line
- mostly a gl enabled for future use in manipulators
- coords are in world space
- """
- def __init__(self, p=None, v=None, p0=None, p1=None, z_axis=None):
- """
- Init by either
- p: Vector or tuple origin
- v: Vector or tuple size and direction
- or
- p0: Vector or tuple 1 point location
- p1: Vector or tuple 2 point location
- Will convert any into Vector 3d
- both optionnals
- """
- if p is not None and v is not None:
- self.p = Vector(p).to_3d()
- self.v = Vector(v).to_3d()
- elif p0 is not None and p1 is not None:
- self.p = Vector(p0).to_3d()
- self.v = Vector(p1).to_3d() - self.p
- else:
- self.p = Vector((0, 0, 0))
- self.v = Vector((0, 0, 0))
- if z_axis is not None:
- self.z_axis = z_axis
- else:
- self.z_axis = Vector((0, 0, 1))
-
- @property
- def p0(self):
- return self.p
-
- @property
- def p1(self):
- return self.p + self.v
-
- @p0.setter
- def p0(self, p0):
- """
- Note: setting p0
- move p0 only
- """
- p1 = self.p1
- self.p = Vector(p0).to_3d()
- self.v = p1 - p0
-
- @p1.setter
- def p1(self, p1):
- """
- Note: setting p1
- move p1 only
- """
- self.v = Vector(p1).to_3d() - self.p
-
- @property
- def cross_z(self):
- """
- 3d Vector perpendicular on plane xy
- lie on the right side
- p1
- |--x
- p0
- """
- return self.v.cross(Vector((0, 0, 1)))
-
- @property
- def cross(self):
- """
- 3d Vector perpendicular on plane defined by z_axis
- lie on the right side
- p1
- |--x
- p0
- """
- return self.v.cross(self.z_axis)
-
- def normal(self, t=0):
- """
- 3d Vector perpendicular on plane defined by z_axis
- lie on the right side
- p1
- |--x
- p0
- """
- n = Line3d()
- n.p = self.lerp(t)
- n.v = self.cross
- return n
-
- def sized_normal(self, t, size):
- """
- 3d Line perpendicular on plane defined by z_axis and of given size
- positioned at t in current line
- lie on the right side
- p1
- |--x
- p0
- """
- p = self.lerp(t)
- v = size * self.cross.normalized()
- return Line3d(p, v, z_axis=self.z_axis)
-
- def offset(self, offset):
- """
- offset > 0 on the right part
- """
- return Line3d(self.p + offset * self.cross.normalized(), self.v)
-
- # unless override, 2d methods should raise NotImplementedError
- def intersect(self, line):
- raise NotImplementedError
-
- def point_sur_segment(self, pt):
- raise NotImplementedError
-
- def tangeant(self, t, da, radius):
- raise NotImplementedError