# ##### 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 ##### # import itertools from . import is_ from .fake_entities import ArcEntity from mathutils import Vector, Matrix, Euler, Color from math import pi, radians, floor, ceil, degrees from copy import deepcopy class ShortVec(Vector): def __str__(self): return "Vec" + str((round(self.x, 2), round(self.y, 2), round(self.z, 2))) def __repr__(self): return self.__str__() def bspline_to_cubic(do, en, curve, errors=None): """ do: an instance of Do() en: a DXF entity curve: Blender geometry data of type "CURVE" inserts knots until every knot has multiplicity of 3; returns new spline controlpoints if degree of the spline is > 3 "None" is returned """ def clean_knots(): start = knots[:degree + 1] end = knots[-degree - 1:] if start.count(start[0]) < degree + 1: maxa = max(start) for i in range(degree + 1): knots[i] = maxa if end.count(end[0]) < degree + 1: mina = min(end) lenk = len(knots) for i in range(lenk - degree - 1, lenk): knots[i] = mina def insert_knot(t, k, p): """ http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS-knot-insert.html """ def a(t, ui, uip): if uip == ui: print("zero!") return 0 return (t - ui) / (uip - ui) new_spline = spline.copy() for pp in range(p, 1, -1): i = k - pp + 1 ai = a(t, knots[i], knots[i + p]) new_spline[i] = (1 - ai) * spline[i - 1] + ai * spline[i] ai = a(t, knots[k], knots[k + p]) new_spline.insert(k, (1 - ai) * spline[k - 1] + ai * spline[k % len(spline)]) knots.insert(k, t) return new_spline knots = list(en.knots) spline = [ShortVec(cp) for cp in en.control_points] degree = len(knots) - len(spline) - 1 if degree <= 3: clean_knots() k = 1 st = 1 while k < len(knots) - 1: t = knots[k] multilen = knots[st:-st].count(t) if multilen < degree: before = multilen while multilen < degree: spline = insert_knot(t, k, degree) multilen += 1 k += 1 k += before else: k += degree if degree <= 2: return quad_to_cube(spline) # the ugly truth if len(spline) % 3 == 0: spline.append([spline[-1]]) errors.add("Cubic spline: Something went wrong with knot insertion") return spline def quad_to_cube(spline): """ spline: list of (x,y,z)-tuples) Converts quad bezier to cubic bezier curve. """ s = [] for i, p in enumerate(spline): if i % 2 == 1: before = Vector(spline[i - 1]) after = Vector(spline[(i + 1) % len(spline)]) s.append(before + 2 / 3 * (Vector(p) - before)) s.append(after + 2 / 3 * (Vector(p) - after)) else: s.append(p) # degree == 1 if len(spline) == 2: s.append(spline[-1]) return s def bulge_to_arc(point, next, bulge): """ point: start point of segment in lwpolyline next: end point of segment in lwpolyline bulge: number between 0 and 1 Converts a bulge of lwpolyline to an arc with a bulge describing the amount of how much a straight segment should be bended to an arc. With the bulge one can find the center point of the arc that replaces the segment. """ rot = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1))) section = next - point section_length = section.length / 2 direction = -bulge / abs(bulge) correction = 1 sagitta_len = section_length * abs(bulge) radius = (sagitta_len**2 + section_length**2) / (2 * sagitta_len) if sagitta_len < radius: cosagitta_len = radius - sagitta_len else: cosagitta_len = sagitta_len - radius direction *= -1 correction *= -1 center = point + section / 2 + section.normalized() * cosagitta_len * direction @ rot cp = point - center cn = next - center cr = cp.to_3d().cross(cn.to_3d()) * correction start = Vector((1, 0)) if cr[2] > 0: angdir = 0 startangle = -start.angle_signed(cp.to_2d()) endangle = -start.angle_signed(cn.to_2d()) else: angdir = 1 startangle = start.angle_signed(cp.to_2d()) endangle = start.angle_signed(cn.to_2d()) return ArcEntity(startangle, endangle, center.to_3d(), radius, angdir) def bulgepoly_to_cubic(do, lwpolyline): """ do: instance of Do() lwpolyline: DXF entity of type polyline Bulges define how much a straight segment of a polyline should be transformed to an arc. Hence do.arc() is called for segments with a bulge and all segments are being connected to a cubic bezier curve in the end. Reference: http://www.afralisp.net/archive/lisp/Bulges1.htm """ def handle_segment(last, point, bulge): if bulge != 0 and not ((point - last).length == 0 or point == last): arc = bulge_to_arc(last, point, bulge) cubic_bezier = do.arc(arc, None, aunits=1, angdir=arc.angdir, angbase=0) else: la = last.to_3d() po = point.to_3d() section = point - last cubic_bezier = [la, la + section * 1 / 3, la + section * 2 / 3, po] return cubic_bezier points = lwpolyline.points bulges = lwpolyline.bulge lenpo = len(points) spline = [] for i in range(1, lenpo): spline += handle_segment(Vector(points[i - 1]), Vector(points[i]), bulges[i - 1])[:-1] if lwpolyline.is_closed: spline += handle_segment(Vector(points[-1]), Vector(points[0]), bulges[-1]) else: spline.append(points[-1]) return spline def bulgepoly_to_lenlist(lwpolyline): """ returns a list with the segment lengths of a lwpolyline """ def handle_segment(last, point, bulge): seglen = (point - last).length if bulge != 0 and seglen != 0: arc = bulge_to_arc(last, point, bulge) if arc.startangle > arc.endangle: arc.endangle += 2 * pi angle = arc.endangle - arc.startangle lenlist.append(abs(arc.radius * angle)) else: lenlist.append(seglen) points = lwpolyline.points bulges = lwpolyline.bulge lenpo = len(points) lenlist = [] for i in range(1, lenpo): handle_segment(Vector(points[i - 1][:2]), Vector(points[i][:2]), bulges[i - 1]) if lwpolyline.is_closed: handle_segment(Vector(points[-1][:2]), Vector(points[0][:2]), bulges[-1]) return lenlist def extrusion_to_matrix(entity): """ Converts an extrusion vector to a rotation matrix that denotes the transformation between world coordinate system and the entity's own coordinate system (described by the extrusion vector). """ def arbitrary_x_axis(extrusion_normal): world_y = Vector((0, 1, 0)) world_z = Vector((0, 0, 1)) if abs(extrusion_normal[0]) < 1 / 64 and abs(extrusion_normal[1]) < 1 / 64: a_x = world_y.cross(extrusion_normal) else: a_x = world_z.cross(extrusion_normal) a_x.normalize() return a_x, extrusion_normal.cross(a_x) az = Vector(entity.extrusion) ax, ay = arbitrary_x_axis(az) ax4 = ax.to_4d() ay4 = ay.to_4d() az4 = az.to_4d() ax4[3] = 0 ay4[3] = 0 az4[3] = 0 translation = Vector((0, 0, 0, 1)) if hasattr(entity, "elevation"): if type(entity.elevation) is tuple: translation = Vector(entity.elevation).to_4d() else: translation = (az * entity.elevation).to_4d() return Matrix((ax4, ay4, az4, translation)).transposed() def split_by_width(entity): """ Used to split a curve (polyline, lwpolyline) into smaller segments if their width is varying in the overall curve. """ class WidthTuple: def __init__(self, w): self.w1 = w[0] self.w2 = w[1] def __eq__(self, other): return self.w1 == other.w1 and self.w2 == other.w2 and self.w1 == self.w2 if is_.varying_width(entity): entities = [] en_template = deepcopy(entity) en_template.points = [] en_template.bulge = [] en_template.width = [] en_template.tangents = [] # is_closed is an attrib only on polyline if en_template.dxftype == 'POLYLINE': en_template.is_closed = False else: # disable closed flag (0x01) when is_closed is a @property en_template.flags ^= 1 i = 0 for pair, same_width in itertools.groupby(entity.width, key=lambda w: WidthTuple(w)): en = deepcopy(en_template) for segment in same_width: en.points.append(entity.points[i]) en.points.append(entity.points[(i + 1) % len(entity.points)]) en.bulge.append(entity.bulge[i]) en.width.append(entity.width[i]) i += 1 entities.append(en) if not entity.is_closed: entities.pop(-1) return entities else: return [entity]