def getmat(bone, active, context, ignoreparent): '''Helper function for visual transform copy, gets the active transform in bone space ''' data_bone = context.active_object.data.bones[bone.name] #all matrices are in armature space unless commented otherwise otherloc = active.matrix # final 4x4 mat of target, location. bonemat_local = Matrix(data_bone.matrix_local) # self rest matrix if data_bone.parent: parentposemat = Matrix( context.active_object.pose.bones[data_bone.parent.name].matrix) parentbonemat = Matrix(data_bone.parent.matrix_local) else: parentposemat = bonemat_local.copy() parentbonemat = bonemat_local.copy() # FIXME! why copy from the parent if setting identity ?, Campbell parentposemat.identity() parentbonemat.identity() if parentbonemat == parentposemat or ignoreparent: newmat = bonemat_local.inverted() * otherloc else: bonemat = parentbonemat.inverted() * bonemat_local newmat = bonemat.inverted() * parentposemat.inverted() * otherloc return newmat
def write_matrix4x4(self, mat: Matrix) -> None: new_mat = mat.copy() convert_ls3d_matrix4x4(new_mat) packed = struct.pack("<16f", *new_mat[0], *new_mat[1], *new_mat[2], *new_mat[3]) self.stream.write(packed)
def execute(self, ctx): index = get_index(ctx) if index != None: if self.redo: if VP.view[index].index < len(VP.view[index].direction): VP.view[index].index += 1 else: if VP.view[index].index > 0: VP.view[index].index -= 1 if 0 <= VP.view[index].index < len(VP.view[index].direction): dindix = VP.view[index].index v = Matrix(VP.view[index].direction[dindix]) ctx.area.spaces.active.region_3d.view_matrix = v.copy() VP.view[index].last = v.copy() VP.record = False return {"FINISHED"}
def orientBone(bone, m, arma, gm_tonext, toscale): armaBone = arma.bones[bone.name] if bone.name in gm_tonext: (raw_tonext, raw_left) = gm_tonext[bone.name] if bone.name == 'Neck1' or bone.name == 'Neck': resize = 1 elif armaBone.length > 0.1: resize = (raw_tonext.length * 10) / armaBone.length elif bone.name == 'Hips': lowerback_trans = arma.bones['LowerBack'].head resize = raw_tonext.length / (lowerback_trans.length / 10) elif bone.name == 'Spine1': resize = raw_tonext.length / (arma.bones['Neck'].head.length / 10) elif bone.name == "LeftHand": resize = raw_tonext.length / ( arma.bones['LeftFingerBase'].head.length / 10) elif bone.name == "RightHand": resize = raw_tonext.length / ( arma.bones['RightFingerBase'].head.length / 10) else: resize = 1 # todo: document the silly reason for the above trickery localscale = toscale * resize tonext = m * raw_tonext up = tonext.normalized() leftP = raw_left.normalized() left = m * leftP fwd = left.cross(up) M_up3 = Matrix(((left.x, up.x, fwd.x), (left.y, up.y, fwd.y), (left.z, up.z, fwd.z))) M_down3 = M_up3.copy() M_down3.invert() assignBone(bone, M_up3, armaBone, toscale * resize) m = M_down3 * m else: assignBone(bone, Matrix.Identity(3), armaBone, 1) for c in bone.children: orientBone(c, m, arma, gm_tonext, 1 / resize)
def getWorld(self): t = Vector(self.translation).to_4d() mr = Matrix() for row in range(3): mr[row][0:3] = self.rotation[row] mr.transpose() # = Inverse rotation p = -(mr * t) # Camera position in world coordinates p[3] = 1.0 m = mr.copy() m.col[3] = p # Set translation to camera position return m
def get_world_matrix_from_translation_vec(translation_vec, rotation): t = Vector(translation_vec).to_4d() camera_rotation = Matrix() for row in range(3): camera_rotation[row][0:3] = rotation[row] camera_rotation.transpose() # = Inverse rotation camera_center = -(camera_rotation * t) # Camera position in world coordinates camera_center[3] = 1.0 camera_rotation = camera_rotation.copy() camera_rotation.col[3] = camera_center # Set translation to camera position return camera_rotation
def test_matrix_inverse_safe(self): mat = Matrix( ((1, 4, 0, -1), (2, -1, 0, -2), (0, 3, 0, 3), (-2, 9, 0, 0))) # Warning, if we change epsilon in py api we have to update this!!! epsilon = 1e-8 inv_mat_safe = mat.copy() inv_mat_safe[0][0] += epsilon inv_mat_safe[1][1] += epsilon inv_mat_safe[2][2] += epsilon inv_mat_safe[3][3] += epsilon inv_mat_safe.invert() ''' inv_mat_safe = Matrix(((1.0, -0.5, 0.0, -0.5), (0.222222, -0.111111, -0.0, 0.0), (-333333344.0, 316666656.0, 100000000.0, 150000000.0), (0.888888, -0.9444444, 0.0, -0.5))) ''' self.assertEqual(mat.inverted_safe(), inv_mat_safe)
def test_matrix_inverse_safe(self): mat = Matrix(((1, 4, 0, -1), (2, -1, 0, -2), (0, 3, 0, 3), (-2, 9, 0, 0))) # Warning, if we change epsilon in py api we have to update this!!! epsilon = 1e-8 inv_mat_safe = mat.copy() inv_mat_safe[0][0] += epsilon inv_mat_safe[1][1] += epsilon inv_mat_safe[2][2] += epsilon inv_mat_safe[3][3] += epsilon inv_mat_safe.invert() ''' inv_mat_safe = Matrix(((1.0, -0.5, 0.0, -0.5), (0.222222, -0.111111, -0.0, 0.0), (-333333344.0, 316666656.0, 100000000.0, 150000000.0), (0.888888, -0.9444444, 0.0, -0.5))) ''' self.assertEqual(mat.inverted_safe(), inv_mat_safe)
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 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 * c if d == 0: return False, 0, 0 t = (c * (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 * c if d == 0: return False, 0, 0, 0 dp = line.p - self.p c2 = self.cross_z u = (c * dp) / d v = (c2 * 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 * 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.objects.link(curve_obj) curve_obj.select = 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 * c if d == 0: return line v = line.p - last.p t = (c * v) / d c2 = last.cross_z u = (c2 * 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 Turtle: """This is the base class of the turtle that does not create any objects, but can be used to perform a dry-run interpretation to query the turtle state at different moments""" def __init__(self, _linewidth, _materialindex): # turtle state consists of a 4x4 matrix and some drawing attributes self.mat = Matrix() self.linewidth = _linewidth self.materialindex = _materialindex # stack to save and restore turtle state self.stack = [] # rotate such that heading is in +Z (we want to grow upwards in blender) # we thus have heading = +Z, left = -Y, up = +X self.mat @= Matrix.Rotation(radians(270), 4, 'Y') def push(self): """Push turtle state to stack""" # push state to stack self.stack.append( (self.mat.copy(), self.linewidth, self.materialindex)) def pop(self): """Pop last turtle state from stack and use as current""" (self.mat, self.linewidth, self.materialindex) = self.stack.pop() def move(self, stepsize): """Move turtle in its heading direction.""" vec = self.mat @ Vector((stepsize, 0, 0, 0)) self.mat.col[3] += vec def turn(self, angle_degrees): self.mat @= Matrix.Rotation(radians(angle_degrees), 4, 'Z') def pitch(self, angle_degrees): self.mat @= Matrix.Rotation(radians(angle_degrees), 4, 'Y') def roll(self, angle_degrees): self.mat @= Matrix.Rotation(radians(angle_degrees), 4, 'X') def look_at(self, target): """ Let turtle look at a given 3D targed vector point. The heading vector will point toward x, y, z and the heading, up, and left vectors will have the same relative orientation (handedness) as before. """ turtle_pos = self.mat.col[3].xyz turtle_to_target = target - turtle_pos turtle_to_target.normalize() result_mat = Matrix() # position stays same result_mat.col[3] = self.mat.col[3] # heading towards target result_mat.col[0] = turtle_to_target.resized(4) # use old up vector to compute orthogonal left vector # the cross product defaults to right hand order but we store a left vector # thus we negate the cross product vector old_up = self.mat.col[1].xyz.normalized() left = Vector.cross(old_up, turtle_to_target).normalized() result_mat.col[2] = left.resized(4) # compute new up vector from left and heading vector # since the left and new up vectors were constructed using the same left hand order # the left hand order is preserved. result_mat.col[1] = Vector.cross( left, turtle_to_target).normalized().resized(4) self.mat = result_mat def draw_internode_module(self, length=None, width=None): """DELIBERATRELY NOT IMPLEMENTED""" pass def draw_module_from_custom_object(self, objname=None, objscale=None): """DELIBERATRELY NOT IMPLEMENTED""" pass
class Render(): def __init__(self): self.view_handler = None self._matrix = Matrix() self.gridmatrix = None self.amount = 0 self.shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') self.batch = None def enable(self): if self.view_handler: return self.disable() self.view_handler = bpy.types.SpaceView3D.draw_handler_add( self.draw, (), 'WINDOW', 'POST_VIEW') self.tag_redraw_all() def disable(self): if self.view_handler: bpy.types.SpaceView3D.draw_handler_remove(self.view_handler, 'WINDOW') self.view_handler = None self.tag_redraw_all() def tag_redraw_all(self): workplane.util.all_view3d(lambda region: region.tag_redraw()) def screen_coord_to_workplane_intersection(self, region, rv3d, co): vec = region_2d_to_vector_3d(region, rv3d, co) loc = region_2d_to_location_3d(region, rv3d, co, vec) x = intersect_line_plane(loc, loc + vec, self._matrix.translation, self._matrix.col[2]) return x def update_grid_matrix(self, matrix): self._matrix = matrix # translation = Matrix() # if self.gridmatrix and not workplane.data.is_simple_preview(): # translation = Matrix.Translation(self.gridmatrix.translation) self.gridmatrix = self._matrix.copy() # self.gridmatrix @= translation #print(self.gridmatrix) def calc_grid_size(self, region, rv3d): left = (0, int(region.height * 0.5)) right = (region.width, int(region.height * 0.5)) top = (int(region.width * 0.5), region.height) bottom = (int(region.width * 0.5), 0) center = (int(region.width * 0.5), int(region.height * 0.5)) coords = (top, right, bottom, left) #grab intersection of workplane and screen center, and round to nearest even middle = self.screen_coord_to_workplane_intersection( region, rv3d, center) v = self._matrix.inverted_safe() @ middle middle = self._matrix @ Vector((round(v.x), round(v.y), 0)) dist = 500 for co in coords: intersection = self.screen_coord_to_workplane_intersection( region, rv3d, co) if intersection and middle: d = (middle - intersection).length dist = min(d, dist) amount = int(dist) * 2 amount = min(amount, 1000) #cap this... amount += 20 - (amount % 20) #round to next even 10 return amount, middle def create_grid_buffers(self, amount, middle): step = 1 if amount >= 1024: step = 10 elif amount >= 512: step = 5 elif amount >= 256: step = 2 if workplane.data.is_simple_preview(): amount = 20 half = int((amount - (amount % step)) * 0.5) offset = Vector((-half, -half, 0)) lines = [] colors = [] for i in range(0, amount): scale = 0.5 x = i / float(amount) #bell like graph shape for the alpha to get nice fading: #alpha = math.pow(4, scale) * math.pow(x *(1-x), scale) #triang graph: alpha = self.tri(0.5, x * 2 * 3.141) * 0.5 + 0.5 #gray = bpy.context.user_preferences.themes[0].view_3d.grid #gray = (gray.r, gray.g, gray.b, alpha) gray = (1.0, 1.0, 1.0, alpha * 0.1) # gray = 1.0, 1.0, 1.0, 1.0 if i % step != 0: continue ''' if i % 5 == 0: gray = (1.0, 1.0, 1.0, 0.1 ) ''' a1 = Vector((0, i, 0)) + offset a2 = Vector((amount, i, 0)) + offset b1 = Vector((i, 0, 0)) + offset b2 = Vector((i, amount, 0)) + offset lines.append((a1, a2, b1, b2)) colors.append((gray, gray, gray, gray)) if i == amount - 1: a1 = Vector((0, i + 1, 0)) + offset a2 = Vector((amount, i + 1, 0)) + offset b1 = Vector((i + 1, 0, 0)) + offset b2 = Vector((i + 1, amount, 0)) + offset lines.append((a1, a2, b1, b2)) colors.append((gray, gray, gray, gray)) lines = np.array(lines) lines = lines.ravel() lines = lines.reshape(-1, 3) lines = lines.tolist() # print(lines) colors = np.array(colors) colors = colors.ravel() colors = colors.reshape(-1, 4) colors = colors.tolist() self.batch = batch_for_shader(self.shader, 'LINES', { "pos": lines, "color": colors }) def draw(self): if not workplane.data.get_visibility(): return # if not workplane.update.WorkPlaneUpdater.Running: # bpy.ops.workplane.internal_workplane_updater() context = bpy.context region = context.region rv3d = context.space_data.region_3d amount, middle = self.calc_grid_size(region, rv3d) if not workplane.data.is_simple_preview(): self.gridmatrix.translation = middle if self.amount != amount: self.amount = amount self.create_grid_buffers(amount, middle) # print(self.lines) if self.batch: bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_DEPTH_TEST) bgl.glEnable(bgl.GL_LINE_SMOOTH) bgl.glLineWidth(2) view_matrix = bpy.context.region_data.perspective_matrix gpu.matrix.push() gpu.matrix.load_matrix(self.gridmatrix) gpu.matrix.push_projection() gpu.matrix.load_projection_matrix(view_matrix) self.shader.bind() self.batch.draw(self.shader) # restore opengl defaults gpu.matrix.pop() gpu.matrix.pop_projection() bgl.glDisable(bgl.GL_BLEND) bgl.glDisable(bgl.GL_DEPTH_TEST) bgl.glDisable(bgl.GL_LINE_SMOOTH) bgl.glLineWidth(1) #merci: http://www.iquilezles.org/apps/graphtoy/utils.js?v=1 def tri(self, a, x): x = x / (2.0 * 3.141) x = x % 1.0 if x < 0.0: x += 1.0 if x < a: x = x / a else: x = 1.0 - (x - a) / (1.0 - a) return -1.0 + 2.0 * x