Skip to content
Snippets Groups Projects
mesh_carver.py 133 KiB
Newer Older
  • Learn to ignore specific revisions
  •                                     Help_Interval * 4, Help_FontSize, None, self)
    
                else:
                    DrawLeftText("[Space]", xHelp, yHelp + Help_Interval, Help_FontSize, UIColor, self)
    
                    DrawLeftText(": Difference", 150 + t_panel_width, yHelp + Help_Interval,
                                Help_FontSize, None, self)
    
                    DrawLeftText("[Shift][Space]", xHelp, yHelp, Help_FontSize, UIColor, self)
                    DrawLeftText(": Rebool", 150 + t_panel_width, yHelp, Help_FontSize, None, self)
                    DrawLeftText("[Alt][Space]", xHelp, yHelp - Help_Interval, Help_FontSize, UIColor, self)
    
                    DrawLeftText(": Duplicate", 150 + t_panel_width, yHelp - Help_Interval,
                                Help_FontSize, None, self)
    
                    DrawLeftText("[" + context.scene.Key_Scale + "]", xHelp, yHelp -
    
                                Help_Interval * 2, Help_FontSize, UIColor, self)
                    DrawLeftText(": Scale", 150 + t_panel_width, yHelp - Help_Interval * 2,
                                Help_FontSize, None, self)
    
                    DrawLeftText("[LMB][Move]", xHelp, yHelp - Help_Interval * 3, Help_FontSize, UIColor, self)
    
                    DrawLeftText(": Rotation", 150 + t_panel_width, yHelp - Help_Interval * 3,
                                Help_FontSize, None, self)
                    DrawLeftText("[Ctrl][LMB][Move]", xHelp, yHelp - Help_Interval * 4,
                                Help_FontSize, UIColor, self)
                    DrawLeftText(": Step Angle", 150 + t_panel_width, yHelp - Help_Interval * 4,
                                Help_FontSize, None, self)
    
                    if self.ProfileMode:
                        DrawLeftText("[" + context.scene.Key_Subadd + "][" + context.scene.Key_Subrem + "]",
    
                                    xHelp, yHelp - Help_Interval * 5, Help_FontSize, UIColor, self)
    
                        DrawLeftText(": Previous or Next Profile", 150 + t_panel_width,
                                     yHelp - Help_Interval * 5, Help_FontSize, None, self)
                    DrawLeftText("[ARROWS]", xHelp, yHelp - Help_Interval * 6, Help_FontSize, UIColor, self)
                    DrawLeftText(": Create / Delete rows or columns", 150 + t_panel_width,
    
                                yHelp - Help_Interval * 6, Help_FontSize, None, self)
    
                    DrawLeftText("[" + context.scene.Key_Gapy + "][" + context.scene.Key_Gapx + "]",
                                 xHelp, yHelp - Help_Interval * 7, Help_FontSize, UIColor, self)
                    DrawLeftText(": Gap between rows or columns", 150 + t_panel_width,
    
                                yHelp - Help_Interval * 7, Help_FontSize, None, self)
    
        # Opengl Initialize
    
        bgl.glEnable(bgl.GL_BLEND)
        bgl.glColor4f(0.512, 0.919, 0.04, 1.0)
        bgl.glLineWidth(2)
    
    
        bgl.glEnable(bgl.GL_POINT_SMOOTH)
        bgl.glPointSize(6)
    
        if self.ProfileMode:
            xrect = region.width - t_panel_width - 80
            yrect = 80
            bgl.glColor4f(0.0, 0.0, 0.0, 0.3)
            bgl.glRecti(xrect, yrect, xrect + 60, yrect - 60)
    
            faces = self.Profils[self.nProfil][3]
            vertices = self.Profils[self.nProfil][2]
            WidthProfil = 50
            location = Vector((region.width - t_panel_width - WidthProfil, 50, 0))
            ProfilScale = 20.0
            bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.7)
            for f in faces:
                if len(f) == 4:
                    bgl.glBegin(bgl.GL_QUADS)
                    bgl.glVertex3f(vertices[f[0]][0] * ProfilScale + location.x, vertices[f[0]][1] *
                                   ProfilScale + location.y, vertices[f[0]][2] * ProfilScale + location.z)
                    bgl.glVertex3f(vertices[f[1]][0] * ProfilScale + location.x, vertices[f[1]][1] *
                                   ProfilScale + location.y, vertices[f[1]][2] * ProfilScale + location.z)
                    bgl.glVertex3f(vertices[f[2]][0] * ProfilScale + location.x, vertices[f[2]][1] *
                                   ProfilScale + location.y, vertices[f[2]][2] * ProfilScale + location.z)
                    bgl.glVertex3f(vertices[f[3]][0] * ProfilScale + location.x, vertices[f[3]][1] *
                                   ProfilScale + location.y, vertices[f[3]][2] * ProfilScale + location.z)
    
                if len(f) == 3:
                    bgl.glBegin(bgl.GL_TRIANGLES)
                    bgl.glVertex3f(vertices[f[0]][0] * ProfilScale + location.x, vertices[f[0]][1] *
                                   ProfilScale + location.y, vertices[f[0]][2] * ProfilScale + location.z)
                    bgl.glVertex3f(vertices[f[1]][0] * ProfilScale + location.x, vertices[f[1]][1] *
                                   ProfilScale + location.y, vertices[f[1]][2] * ProfilScale + location.z)
                    bgl.glVertex3f(vertices[f[2]][0] * ProfilScale + location.x, vertices[f[2]][1] *
                                   ProfilScale + location.y, vertices[f[2]][2] * ProfilScale + location.z)
    
        if self.bDone:
            if len(self.mouse_path) > 1:
                x0 = self.mouse_path[0][0]
                y0 = self.mouse_path[0][1]
                x1 = self.mouse_path[1][0]
                y1 = self.mouse_path[1][1]
    
            # Cut Line
            if self.CutMode == LINE:
                if (self.shift) or (self.CreateMode and self.Closed):
    
                    bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5)
    
    
                    bgl.glBegin(bgl.GL_POLYGON)
                    for x, y in self.mouse_path:
                        bgl.glVertex2i(x + self.xpos, y + self.ypos)
                    bgl.glEnd()
    
                bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0)
                bgl.glBegin(bgl.GL_LINE_STRIP)
                for x, y in self.mouse_path:
                    bgl.glVertex2i(x + self.xpos, y + self.ypos)
                bgl.glEnd()
                if (self.CreateMode is False) or (self.CreateMode and self.Closed):
    
                    bgl.glBegin(bgl.GL_LINE_STRIP)
    
                    bgl.glVertex2i(self.mouse_path[len(self.mouse_path) - 1][0] + self.xpos,
                                   self.mouse_path[len(self.mouse_path) - 1][1] + self.ypos)
                    bgl.glVertex2i(self.mouse_path[0][0] + self.xpos, self.mouse_path[0][1] + self.ypos)
    
                bgl.glPointSize(6)
                bgl.glBegin(bgl.GL_POINTS)
                for x, y in self.mouse_path:
                    bgl.glVertex2i(x + self.xpos, y + self.ypos)
                bgl.glEnd()
    
            # Cut rectangle
            if self.CutMode == RECTANGLE:
                bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5)
    
                # if SHIFT, fill primitive
                if self.shift or self.CreateMode:
                    bgl.glBegin(bgl.GL_QUADS)
    
                    bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos)
                    bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos)
                    bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos)
                    bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos)
                    bgl.glEnd()
    
    
                bgl.glBegin(bgl.GL_LINE_STRIP)
                bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos)
                bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos)
                bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos)
                bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos)
                bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos)
                bgl.glEnd()
                bgl.glPointSize(6)
    
                bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0)
                bgl.glBegin(bgl.GL_POINTS)
                bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos)
                bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos)
                bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos)
                bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos)
                bgl.glEnd()
    
            # Circle Cut
            if self.CutMode == CIRCLE:
                DEG2RAD = 3.14159 / 180
                v0 = Vector((self.mouse_path[0][0], self.mouse_path[0][1], 0))
                v1 = Vector((self.mouse_path[1][0], self.mouse_path[1][1], 0))
                v0 -= v1
                radius = self.mouse_path[1][0] - self.mouse_path[0][0]
                DEG2RAD = 3.14159 / (180.0 / self.stepAngle[self.step])
                if self.ctrl:
                    self.stepR = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 25
                    shift = (3.14159 / (360.0 / 60.0)) * int(self.stepR)
                else:
                    shift = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 50
    
                if self.shift or self.CreateMode:
                    bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5)
                    bgl.glBegin(bgl.GL_TRIANGLE_FAN)
                    bgl.glVertex2f(x0 + self.xpos, y0 + self.ypos)
    
                    for i in range(0, int(360 / self.stepAngle[self.step])):
                        degInRad = i * DEG2RAD
                        bgl.glVertex2f(x0 + self.xpos + math.cos(degInRad + shift) * radius,
                                       y0 + self.ypos + math.sin(degInRad + shift) * radius)
    
                    bgl.glVertex2f(x0 + self.xpos + math.cos(0 + shift) * radius,
                                   y0 + self.ypos + math.sin(0 + shift) * radius)
    
                bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0)
                bgl.glBegin(bgl.GL_LINE_LOOP)
                for i in range(0, int(360 / self.stepAngle[self.step])):
                    degInRad = i * DEG2RAD
                    bgl.glVertex2f(x0 + self.xpos + math.cos(degInRad + shift) * radius,
                                   y0 + self.ypos + math.sin(degInRad + shift) * radius)
                bgl.glEnd()
    
    
        if self.ObjectMode or self.ProfileMode:
            if self.ShowCursor:
                region = context.region
                rv3d = context.space_data.region_3d
    
                if self.ObjectMode:
                    ob = self.ObjectBrush
                if self.ProfileMode:
                    ob = self.ProfileBrush
                mat = ob.matrix_world
    
                # 50% alpha, 2 pixel width line
                bgl.glEnable(bgl.GL_BLEND)
    
    
                bbox = [mat * Vector(b) for b in ob.bound_box]
    
    
                if self.shift:
                    bgl.glLineWidth(4)
                    bgl.glColor4f(0.5, 1.0, 0.0, 1.0)
                else:
                    bgl.glLineWidth(2)
                    bgl.glColor4f(1.0, 0.8, 0.0, 1.0)
                bgl.glBegin(bgl.GL_LINE_STRIP)
                idx = 0
                CRadius = ((bbox[7] - bbox[0]).length) / 2
                for i in range(int(len(self.CLR_C) / 3)):
    
                    vector3d = (self.CLR_C[idx * 3] * CRadius + self.CurLoc.x, self.CLR_C[idx * 3 + 1] *
                                CRadius + self.CurLoc.y, self.CLR_C[idx * 3 + 2] * CRadius + self.CurLoc.z)
    
                    vector2d = bpy_extras.view3d_utils.location_3d_to_region_2d(region, rv3d, vector3d)
                    if vector2d is not None:
                        bgl.glVertex2f(*vector2d)
                    idx += 1
                bgl.glEnd()
    
                bgl.glLineWidth(1)
                bgl.glDisable(bgl.GL_BLEND)
                bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
    
                # Object display
                if self.qRot is not None:
                    ob.location = self.CurLoc
    
                    v.x = v.y = 0.0
                    v.z = self.BrushDepthOffset
                    ob.location += self.qRot * v
    
                    e.x = 0.0
                    e.y = 0.0
                    e.z = self.aRotZ / 25.0
    
                    qe = e.to_quaternion()
                    qRot = self.qRot * qe
                    ob.rotation_mode = 'QUATERNION'
                    ob.rotation_quaternion = qRot
                    ob.rotation_mode = 'XYZ'
    
                    if self.ProfileMode:
                        if self.ProfileBrush is not None:
                            self.ProfileBrush.location = self.CurLoc
                            self.ProfileBrush.rotation_mode = 'QUATERNION'
                            self.ProfileBrush.rotation_quaternion = qRot
                            self.ProfileBrush.rotation_mode = 'XYZ'
    
        # Opengl defaults
        bgl.glLineWidth(1)
        bgl.glDisable(bgl.GL_BLEND)
        bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
        bgl.glDisable(bgl.GL_POINT_SMOOTH)
    
    
    # Intersection
    # intersection function (ideasman42)
    def isect_line_plane_v3(p0, p1, p_co, p_no, epsilon=1e-6):
        """
        p0, p1: define the line
        p_co, p_no: define the plane:
            p_co is a point on the plane (plane coordinate).
            p_no is a normal vector defining the plane direction; does not need to be normalized.
    
        return a Vector or None (when the intersection can't be found).
        """
    
        u = sub_v3v3(p1, p0)
        dot = dot_v3v3(p_no, u)
    
        if abs(dot) > epsilon:
            # the factor of the point between p0 -> p1 (0 - 1)
            # if 'fac' is between (0 - 1) the point intersects with the segment.
            # otherwise:
            #  < 0.0: behind p0.
            #  > 1.0: infront of p1.
            w = sub_v3v3(p0, p_co)
            fac = -dot_v3v3(p_no, w) / dot
            u = mul_v3_fl(u, fac)
            return add_v3v3(p0, u)
        else:
            # The segment is parallel to plane
            return None
    
    
    # ----------------------
    # generic math functions
    
    def add_v3v3(v0, v1):
        return (
            v0[0] + v1[0],
            v0[1] + v1[1],
            v0[2] + v1[2],
        )
    
    
    def sub_v3v3(v0, v1):
        return (
            v0[0] - v1[0],
            v0[1] - v1[1],
            v0[2] - v1[2],
        )
    
    
    def dot_v3v3(v0, v1):
        return (
            (v0[0] * v1[0]) +
            (v0[1] * v1[1]) +
            (v0[2] * v1[2])
        )
    
    
    def len_squared_v3(v0):
        return dot_v3v3(v0, v0)
    
    
    def mul_v3_fl(v0, f):
        return (
            v0[0] * f,
            v0[1] * f,
            v0[2] * f,
        )
    
    
    
    def CreateCutSquare(self, context):
        FAR_LIMIT = 10000.0
    
        # New mesh
        me = bpy.data.meshes.new('CMT_Square')
        # New object
        ob = bpy.data.objects.new('CMT_Square', me)
        # Save new object
        self.CurrentObj = ob
    
        region = context.region
        rv3d = context.region_data
        coord = self.mouse_path[0][0], self.mouse_path[0][1]
    
        depthLocation = region_2d_to_vector_3d(region, rv3d, coord)
        self.ViewVector = depthLocation
        if self.snapCursor:
            PlanePoint = context.scene.cursor_location
        else:
    
            PlanePoint = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
    
    
        PlaneNormal = depthLocation
        PlaneNormalised = PlaneNormal.normalized()
    
        # Link object to scene
        context.scene.objects.link(ob)
    
        # New bmesh
        t_bm = bmesh.new()
        t_bm.from_mesh(me)
        # Convert in 3d space
        v0 = self.mouse_path[0][0] + self.xpos, self.mouse_path[0][1] + self.ypos
        v1 = self.mouse_path[1][0] + self.xpos, self.mouse_path[1][1] + self.ypos
        v2 = self.mouse_path[1][0] + self.xpos, self.mouse_path[0][1] + self.ypos
        v3 = self.mouse_path[0][0] + self.xpos, self.mouse_path[1][1] + self.ypos
        vec = region_2d_to_vector_3d(region, rv3d, v0)
        loc0 = region_2d_to_location_3d(region, rv3d, v0, vec)
    
        vec = region_2d_to_vector_3d(region, rv3d, v1)
        loc1 = region_2d_to_location_3d(region, rv3d, v1, vec)
    
        vec = region_2d_to_vector_3d(region, rv3d, v2)
        loc2 = region_2d_to_location_3d(region, rv3d, v2, vec)
    
        vec = region_2d_to_vector_3d(region, rv3d, v3)
        loc3 = region_2d_to_location_3d(region, rv3d, v3, vec)
        p0 = loc0
        p1 = loc0 + PlaneNormalised * FAR_LIMIT
        loc0 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
        p0 = loc1
        p1 = loc1 + PlaneNormalised * FAR_LIMIT
        loc1 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
        p0 = loc2
        p1 = loc2 + PlaneNormalised * FAR_LIMIT
        loc2 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
        p0 = loc3
        p1 = loc3 + PlaneNormalised * FAR_LIMIT
        loc3 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
    
        t_v0 = t_bm.verts.new(loc0)
        t_v1 = t_bm.verts.new(loc2)
        t_v2 = t_bm.verts.new(loc1)
        t_v3 = t_bm.verts.new(loc3)
    
        # Update vertices index
        t_bm.verts.index_update()
        # New faces
        t_face = t_bm.faces.new([t_v0, t_v1, t_v2, t_v3])
        # Set mesh
        t_bm.to_mesh(me)
    
    
    # Cut Line
    def CreateCutLine(self, context):
        FAR_LIMIT = 10000.0
    
        me = bpy.data.meshes.new('CMT_Line')
    
        ob = bpy.data.objects.new('CMT_Line', me)
        self.CurrentObj = ob
    
        region = context.region
        rv3d = context.region_data
        coord = self.mouse_path[0][0], self.mouse_path[0][1]
        depthLocation = region_2d_to_vector_3d(region, rv3d, coord)
        self.ViewVector = depthLocation
    
    
        PlanePoint = context.scene.cursor_location if self.snapCursor else Vector((0.0, 0.0, 0.0))
    
        PlaneNormal = depthLocation
        PlaneNormalised = PlaneNormal.normalized()
    
        context.scene.objects.link(ob)
    
        t_bm = bmesh.new()
        t_bm.from_mesh(me)
    
        FacesList = []
        NbVertices = 0
    
        bLine = False
    
    
        if (len(self.mouse_path) == 2) or ((len(self.mouse_path) <= 3) and
                (self.mouse_path[1] == self.mouse_path[2])):
            PlanePoint = Vector((0.0, 0.0, 0.0))
    
            PlaneNormal = depthLocation
            PlaneNormalised = PlaneNormal.normalized()
            # Force rebool
            self.ForceRebool = True
            # It's a line
            bLine = True
            Index = 0
            for x, y in self.mouse_path:
                if Index < 2:
                    v0 = x + self.xpos, y + self.ypos
                    vec = region_2d_to_vector_3d(region, rv3d, v0)
                    loc0 = region_2d_to_location_3d(region, rv3d, v0, vec)
    
                    p0 = loc0
                    p1 = loc0 + PlaneNormalised * FAR_LIMIT
                    loc0 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
    
                    NbVertices += 1
                    Index += 1
                    if NbVertices == 1:
                        t_v0 = t_bm.verts.new(loc0)
                        LocInit = loc0
                        t_bm.verts.index_update()
                    else:
                        t_v1 = t_bm.verts.new(loc0)
                        t_edges = t_bm.edges.new([t_v0, t_v1])
                        NbVertices = 1
                        t_v0 = t_v1
        else:
            for x, y in self.mouse_path:
                v0 = x + self.xpos, y + self.ypos
                vec = region_2d_to_vector_3d(region, rv3d, v0)
                loc0 = region_2d_to_location_3d(region, rv3d, v0, vec)
    
                p0 = loc0
                p1 = loc0 + PlaneNormalised * FAR_LIMIT
                loc0 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
    
                NbVertices += 1
                if NbVertices == 1:
                    t_v0 = t_bm.verts.new(loc0)
                    LocInit = loc0
                    t_bm.verts.index_update()
                    FacesList.append(t_v0)
                else:
                    t_v1 = t_bm.verts.new(loc0)
                    t_edges = t_bm.edges.new([t_v0, t_v1])
                    FacesList.append(t_v1)
                    NbVertices = 1
                    t_v0 = t_v1
    
        if self.CreateMode:
    
            if self.Closed and (bLine is False):
    
                t_v1 = t_bm.verts.new(LocInit)
                t_edges = t_bm.edges.new([t_v0, t_v1])
                FacesList.append(t_v1)
                t_face = t_bm.faces.new(FacesList)
        else:
    
            if bLine is False:
    
                t_v1 = t_bm.verts.new(LocInit)
                t_edges = t_bm.edges.new([t_v0, t_v1])
                FacesList.append(t_v1)
                t_face = t_bm.faces.new(FacesList)
    
        t_bm.to_mesh(me)
    
    
    # Cut Circle
    def CreateCutCircle(self, context):
        FAR_LIMIT = 10000.0
    
        me = bpy.data.meshes.new('CMT_Circle')
    
        ob = bpy.data.objects.new('CMT_Circle', me)
        self.CurrentObj = ob
    
        region = context.region
        rv3d = context.region_data
        coord = self.mouse_path[0][0], self.mouse_path[0][1]
        depthLocation = region_2d_to_vector_3d(region, rv3d, coord)
        self.ViewVector = depthLocation
    
    
        PlanePoint = context.scene.cursor_location if self.snapCursor else Vector((0.0, 0.0, 0.0))
    
        PlaneNormal = depthLocation
        PlaneNormalised = PlaneNormal.normalized()
    
        context.scene.objects.link(ob)
    
        t_bm = bmesh.new()
        t_bm.from_mesh(me)
    
        x0 = self.mouse_path[0][0]
        y0 = self.mouse_path[0][1]
    
    
        v0 = Vector((self.mouse_path[0][0], self.mouse_path[0][1], 0))
        v1 = Vector((self.mouse_path[1][0], self.mouse_path[1][1], 0))
    
        v0 -= v1
        radius = self.mouse_path[1][0] - self.mouse_path[0][0]
        DEG2RAD = math.pi / (180.0 / self.stepAngle[self.step])
        if self.ctrl:
            self.stepR = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 25
            shift = (math.pi / (360.0 / self.stepAngle[self.step])) * (self.stepR)
        else:
            shift = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 50
    
        # Convert point in 3D Space
        FacesList = []
        for i in range(0, int(360.0 / self.stepAngle[self.step])):
            degInRad = i * DEG2RAD
    
            v0 = x0 + self.xpos + math.cos(degInRad + shift) * radius, \
                    y0 + self.ypos + math.sin(degInRad + shift) * radius
    
            vec = region_2d_to_vector_3d(region, rv3d, v0)
            loc0 = region_2d_to_location_3d(region, rv3d, v0, vec)
    
            p0 = loc0
            p1 = loc0 + PlaneNormalised * FAR_LIMIT
            loc0 = isect_line_plane_v3(p0, p1, PlanePoint, PlaneNormalised)
    
            t_v0 = t_bm.verts.new(loc0)
    
            FacesList.append(t_v0)
    
        t_bm.verts.index_update()
        t_face = t_bm.faces.new(FacesList)
    
        t_bm.to_mesh(me)
    
    
    # Object dimensions (SCULPT Tools tips)
    def objDiagonal(obj):
        return ((obj.dimensions[0]**2) + (obj.dimensions[1]**2) + (obj.dimensions[2]**2))**0.5
    
    
    # Bevel Update
    def update_bevel(context):
        selection = context.selected_objects.copy()
        active = context.active_object
    
        if len(selection) > 0:
            for obj in selection:
                bpy.ops.object.select_all(action='DESELECT')
                obj.select = True
                context.scene.objects.active = obj
    
                # Test object name
                if obj.data.name.startswith("S_") or obj.data.name.startswith("S "):
                    bpy.ops.object.mode_set(mode='EDIT')
                    bpy.ops.mesh.region_to_loop()
                    bpy.ops.transform.edge_bevelweight(value=1)
                    bpy.ops.object.mode_set(mode='OBJECT')
                else:
                    act_bevel = False
                    for mod in obj.modifiers:
                        if mod.type == 'BEVEL':
                            act_bevel = True
                    if act_bevel:
                        context.scene.objects.active = bpy.data.objects[obj.name]
                        active = obj
    
                        bpy.ops.object.mode_set(mode='EDIT')
    
                        # Edge mode
                        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
    
                        # Clear all
                        bpy.ops.mesh.select_all(action='SELECT')
                        bpy.ops.mesh.mark_sharp(clear=True)
                        bpy.ops.transform.edge_crease(value=-1)
    
                        bpy.ops.transform.edge_bevelweight(value=-1)
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
                        bpy.ops.mesh.mark_sharp()
                        bpy.ops.transform.edge_crease(value=1)
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
                        bpy.ops.transform.edge_bevelweight(value=1)
                        bpy.ops.mesh.select_all(action='DESELECT')
    
                        bpy.ops.object.mode_set(mode="OBJECT")
    
                        active.data.use_customdata_edge_bevel = True
    
                        for i in range(len(active.data.edges)):
    
                            if active.data.edges[i].select is True:
    
                                active.data.edges[i].bevel_weight = 1.0
                                active.data.edges[i].use_edge_sharp = True
    
                        Already = False
                        for m in active.modifiers:
                            if m.name == 'Bevel':
                                Already = True
    
    
                        if Already is False:
    
                            bpy.ops.object.modifier_add(type='BEVEL')
                            mod = context.object.modifiers[-1]
                            mod.limit_method = 'WEIGHT'
                            mod.width = 0.01
                            mod.profile = 0.699099
    
                            mod.use_clight_overlap = False
    
                            mod.segments = 3
                            mod.loop_slide = False
    
                        bpy.ops.object.shade_smooth()
    
                        context.object.data.use_auto_smooth = True
                        context.object.data.auto_smooth_angle = 1.0472
    
        bpy.ops.object.select_all(action='DESELECT')
    
        for obj in selection:
            obj.select = True
        context.scene.objects.active = active
    
    
    
    def CreateBevel(context, CurrentObject):
        # Save active object
        SavActive = context.active_object
        # Active "CurrentObject"
        context.scene.objects.active = CurrentObject
    
        bpy.ops.object.mode_set(mode='EDIT')
    
        # Edge mode
        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
    
        # Clear all
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.mark_sharp(clear=True)
        bpy.ops.transform.edge_crease(value=-1)
    
        bpy.ops.transform.edge_bevelweight(value=-1)
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
        bpy.ops.mesh.mark_sharp()
        bpy.ops.transform.edge_crease(value=1)
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
        bpy.ops.transform.edge_bevelweight(value=1)
        bpy.ops.mesh.select_all(action='DESELECT')
    
        bpy.ops.object.mode_set(mode="OBJECT")
    
        bpy.ops.object.mode_set(mode='OBJECT')
    
        Already = False
        for m in CurrentObject.modifiers:
            if m.name == 'Bevel':
                Already = True
    
    
        if Already is False:
    
            bpy.ops.object.modifier_add(type='BEVEL')
            mod = context.object.modifiers[-1]
            mod.limit_method = 'WEIGHT'
            mod.width = 0.01
            mod.profile = 0.699099
    
            mod.use_clight_overlap = False
    
            mod.segments = 3
            mod.loop_slide = False
    
        bpy.ops.object.shade_smooth()
    
        context.object.data.use_auto_smooth = True
        context.object.data.auto_smooth_angle = 1.0471975
    
    
        # Restore the active object
    
        context.scene.objects.active = SavActive
    
    
    # Picking (template)
    def Picking(context, event):
        # get the context arguments
        scene = context.scene
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y
    
        # get the ray from the viewport and mouse
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
        ray_target = ray_origin + view_vector
    
        def visible_objects_and_duplis():
            for obj in context.visible_objects:
                if obj.type == 'MESH':
                    yield (obj, obj.matrix_world.copy())
    
                if obj.dupli_type != 'NONE':
                    obj.dupli_list_create(scene)
                    for dob in obj.dupli_list:
                        obj_dupli = dob.object
                        if obj_dupli.type == 'MESH':
                            yield (obj_dupli, dob.matrix.copy())
    
                obj.dupli_list_clear()
    
        def obj_ray_cast(obj, matrix):
            # get the ray relative to the object
            matrix_inv = matrix.inverted()
            ray_origin_obj = matrix_inv * ray_origin
            ray_target_obj = matrix_inv * ray_target
            ray_direction_obj = ray_target_obj - ray_origin_obj
            # cast the ray
            success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
            if success:
                return location, normal, face_index
    
            return None, None, None
    
    
        # cast rays and find the closest object
        best_length_squared = -1.0
        best_obj = None
    
        # cast rays and find the closest object
        for obj, matrix in visible_objects_and_duplis():
            if obj.type == 'MESH':
                hit, normal, face_index = obj_ray_cast(obj, matrix)
                if hit is not None:
                    hit_world = matrix * hit
                    length_squared = (hit_world - ray_origin).length_squared
                    if best_obj is None or length_squared < best_length_squared:
                        scene.cursor_location = hit_world
                        best_length_squared = length_squared
                        best_obj = obj
                else:
                    if best_obj is None:
                        depthLocation = region_2d_to_vector_3d(region, rv3d, coord)
                        loc = region_2d_to_location_3d(region, rv3d, coord, depthLocation)
                        scene.cursor_location = loc
    
    
    def CreatePrimitive(self, _AngleStep, _radius):
        Angle = 0.0
        self.NbPointsInPrimitive = 0
        while(Angle < 360.0):
            self.CircleListRaw.append(math.cos(math.radians(Angle)) * _radius)
            self.CircleListRaw.append(math.sin(math.radians(Angle)) * _radius)
            self.CircleListRaw.append(0.0)
            Angle += _AngleStep
            self.NbPointsInPrimitive += 1
        self.CircleListRaw.append(math.cos(math.radians(0.0)) * _radius)
        self.CircleListRaw.append(math.sin(math.radians(0.0)) * _radius)
        self.CircleListRaw.append(0.0)
        self.NbPointsInPrimitive += 1
    
    
    def MoveCursor(qRot, location, self):
        if qRot is not None:
            self.CLR_C.clear()
    
            idx = 0
            for i in range(int(len(self.CircleListRaw) / 3)):
                vc.x = self.CircleListRaw[idx * 3] * self.CRadius
                vc.y = self.CircleListRaw[idx * 3 + 1] * self.CRadius
                vc.z = self.CircleListRaw[idx * 3 + 2] * self.CRadius
                vc = qRot * vc
                self.CLR_C.append(vc.x)
                self.CLR_C.append(vc.y)
                self.CLR_C.append(vc.z)
                idx += 1
    
    
    def RBenVe(Object, Dir):
        ObjectV = Object.normalized()
        DirV = Dir.normalized()
        cosTheta = ObjectV.dot(DirV)
    
        rotationAxis = Vector((0.0, 0.0, 0.0))
    
        if (cosTheta < -1 + 0.001):
    
            v = Vector((0.0, 1.0, 0.0))
    
            rotationAxis = ObjectV.cross(v)
            rotationAxis = rotationAxis.normalized()
    
            q.w = 0.0
            q.x = rotationAxis.x
            q.y = rotationAxis.y
            q.z = rotationAxis.z
            return q
        rotationAxis = ObjectV.cross(DirV)
        s = math.sqrt((1.0 + cosTheta) * 2.0)
        invs = 1 / s
    
        q.w = s * 0.5
        q.x = rotationAxis.x * invs
        q.y = rotationAxis.y * invs
        q.z = rotationAxis.z * invs
        return q
    
    
    def Pick(context, event, self, ray_max=10000.0):
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
        ray_target = ray_origin + (view_vector * ray_max)
    
        def obj_ray_cast(obj, matrix):
            matrix_inv = matrix.inverted()
            ray_origin_obj = matrix_inv * ray_origin
            ray_target_obj = matrix_inv * ray_target
            success, hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
            if success:
                return hit, normal, face_index
    
            return None, None, None
    
    
        best_length_squared = ray_max * ray_max
        best_obj = None
        for obj in self.CList:
            matrix = obj.matrix_world
            hit, normal, face_index = obj_ray_cast(obj, matrix)
    
            rotation = obj.rotation_euler.to_quaternion()
    
            if hit is not None:
                hit_world = matrix * hit
                length_squared = (hit_world - ray_origin).length_squared
                if length_squared < best_length_squared:
                    best_length_squared = length_squared
                    best_obj = obj
                    hits = hit_world
                    ns = normal
                    fs = face_index
    
        if best_obj is not None:
    
            return hits, ns, fs, rotation
    
    
    
    def SelectObject(self, copyobj):
        copyobj.select = True
    
        for child in copyobj.children:
            SelectObject(self, child)
    
        if copyobj.parent is None:
            bpy.context.scene.objects.active = copyobj
    
    
    # Undo
    def printUndo(self):
        for l in self.UList:
            print(l)
    
    
    def UndoAdd(self, type, OpsObj):
        if OpsObj is None:
            return
        if type != "DUPLICATE":
            ob = OpsObj
    
            # Create the 'backup' mesh
    
            bm = bmesh.new()
            bm.from_mesh(ob.data)
    
            self.UndoOps.append((OpsObj, type, bm))
        else:
            self.UndoOps.append((OpsObj, type, None))
    
    
    def UndoListUpdate(self):
        self.UList.append((self.UndoOps.copy()))
        self.UList_Index += 1
        self.UndoOps.clear()
    
    
    def Undo(self):
        if self.UList_Index < 0:
            return
        # get previous mesh
        for o in self.UList[self.UList_Index]:
            if o[1] == "MESH":
                bm = o[2]
                bm.to_mesh(o[0].data)
    
        SelectObjList = bpy.context.selected_objects.copy()
        Active_Obj = bpy.context.active_object
        bpy.ops.object.select_all(action='TOGGLE')
    
        for o in self.UList[self.UList_Index]:
            if o[1] == "REBOOL":
                o[0].select = True
                o[0].hide = False
    
            if o[1] == "DUPLICATE":
                o[0].select = True
                o[0].hide = False
    
        bpy.ops.object.delete(use_global=False)
    
        for so in SelectObjList:
            bpy.data.objects[so.name].select = True
        bpy.context.scene.objects.active = Active_Obj
    
        self.UList_Index -= 1
        self.UList[self.UList_Index + 1:] = []
    
    
    def duplicateObject(self):
        if self.Instantiate:
            bpy.ops.object.duplicate_move_linked(
                OBJECT_OT_duplicate={
                    "linked": True,
                    "mode": 'TRANSLATION',
                },
                TRANSFORM_OT_translate={
                    "value": (0, 0, 0),
                },
            )
        else:
            bpy.ops.object.duplicate_move(
                OBJECT_OT_duplicate={
                    "linked": False,
                    "mode": 'TRANSLATION',
                },
                TRANSFORM_OT_translate={
                    "value": (0, 0, 0),
                },
            )
    
        ob_new = bpy.context.active_object
    
        ob_new.location = self.CurLoc
    
        v.x = v.y = 0.0
        v.z = self.BrushDepthOffset
        ob_new.location += self.qRot * v
    
    
        if self.ObjectMode:
            ob_new.scale = self.ObjectBrush.scale
        if self.ProfileMode:
            ob_new.scale = self.ProfileBrush.scale
    
    
        e.x = e.y = 0.0
        e.z = self.aRotZ / 25.0
    
    
        # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
    
        if (self.alt is True) and ((self.nbcol + self.nbrow) < 3):
    
            if self.RandomRotation:
                e.z += random.random()
    
        qe = e.to_quaternion()
        qRot = self.qRot * qe
        ob_new.rotation_mode = 'QUATERNION'
        ob_new.rotation_quaternion = qRot
        ob_new.rotation_mode = 'XYZ'
    
    
        if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False):
    
            ob_new.hide = True
    
        if self.BrushSolidify:
    
            ob_new.display_type = "SOLID"
            ob_new.show_in_front = False
    
    
        for o in bpy.context.selected_objects:
            UndoAdd(self, "DUPLICATE", o)
    
        if len(bpy.context.selected_objects) > 0:
            bpy.ops.object.select_all(action='TOGGLE')
        for o in self.SavSel:
            o.select = True
    
        bpy.context.scene.objects.active = self.OpsObj
    
    
    def update_grid(self, context):
        """
        Thanks to batFINGER for his help :
        source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
        """
        verts = []
        edges = []
        faces = []
        numface = 0
    
        if self.nbcol < 1:
            self.nbcol = 1
        if self.nbrow < 1:
            self.nbrow = 1
        if self.gapx < 0:
            self.gapx = 0