Skip to content
Snippets Groups Projects
mesh_edge_roundifier.py 51.3 KiB
Newer Older
  • Learn to ignore specific revisions
  •         # to origin position and then perform calculations
            # At least that is my understanding :) <komi3D>
    
            # V1 V2 stores Local Coordinates
            V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge)
    
            debugPrintNew(d_Plane, "PLANE: " + parameters["plane"])
            lineAB = self.calc.getLineCoefficientsPerpendicularToVectorInPoint(
                                                    edgeCenter, edgeVector,
                                                    parameters["plane"]
                                                    )
            circleMidPoint = V1
            circleMidPointOnPlane = self.calc.getCircleMidPointOnPlane(
                                                    V1, parameters["plane"]
                                                    )
            radius = parameters["radius"]
    
            angle = 0
            if (parameters["entryMode"] == 'Angle'):
                if (parameters["angleEnum"] != 'Other'):
                    radius, angle = self.CalculateRadiusAndAngleForAnglePresets(
                                                    parameters["angleEnum"], radius,
                                                    angle, edgeLength
                                                    )
                else:
                    radius, angle = self.CalculateRadiusAndAngle(edgeLength)
            debugPrintNew(d_Radius_Angle, "RADIUS = " + str(radius) + "  ANGLE = " + str(angle))
            roots = None
            if angle != pi:  # mode other than 180
                if lineAB is None:
                    roots = self.calc.getLineCircleIntersectionsWhenXPerpendicular(
                                                    edgeCenter, circleMidPointOnPlane,
                                                    radius, parameters["plane"]
                                                    )
                else:
                    roots = self.calc.getLineCircleIntersections(
                                                    lineAB, circleMidPointOnPlane, radius
                                                    )
    
                if roots is None:
                    debugPrintNew(True,
                                 "[Edge Roundifier]: No centers were found. Change radius to higher value")
                    return None
                roots = self.addMissingCoordinate(roots, V1, parameters["plane"])  # adds X, Y or Z coordinate
            else:
                roots = [edgeCenter, edgeCenter]
            debugPrintNew(d_Roots, "roots=" + str(roots))
    
            refObjectLocation = None
            objectLocation = bpy.context.active_object.location  # Origin Location
    
            if parameters["refObject"] == "ORG":
                refObjectLocation = [0, 0, 0]
            elif parameters["refObject"] == "CUR":
                refObjectLocation = bpy.context.scene.cursor.location - objectLocation
            else:
                refObjectLocation = self.calc.getEdgeReference(edge, edgeCenter, parameters["plane"])
    
            debugPrintNew(d_RefObject, parameters["refObject"], refObjectLocation)
            chosenSpinCenter, otherSpinCenter = self.getSpinCenterClosestToRefCenter(
                                                                refObjectLocation, roots
                                                                )
    
            if (parameters["entryMode"] == "Radius"):
                halfAngle = self.calc.getAngle(edgeCenter, chosenSpinCenter, circleMidPoint)
                angle = 2 * halfAngle[0]  # in radians
                self.a = degrees(angle)   # in degrees
    
            spinAxis = self.getSpinAxis(parameters["plane"])
            steps = parameters["segments"]
            angle = -angle  # rotate clockwise by default
    
            return [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation]
    
        def drawSpin(self, edge, edgeCenter, roundifyParams, parameters, bm, mesh):
            [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams
    
            v0org, v1org = (edge.verts[0], edge.verts[1])
    
            if parameters["flip"]:
                angle = -angle
                spinCenterTemp = chosenSpinCenter
                chosenSpinCenter = otherSpinCenter
                otherSpinCenter = spinCenterTemp
    
            if(parameters["invertAngle"]):
                if angle < 0:
                    angle = two_pi + angle
                elif angle > 0:
                    angle = -two_pi + angle
                else:
                    angle = two_pi
    
            if(parameters["fullCircles"]):
                angle = two_pi
    
            v0 = bm.verts.new(v0org.co)
    
            result = bmesh.ops.spin(
                            bm, geom=[v0], cent=chosenSpinCenter, axis=spinAxis,
                            angle=angle, steps=steps, use_duplicate=False
                            )
    
            # it seems there is something wrong with last index of this spin
            # I need to calculate the last index manually here
            vertsLength = len(bm.verts)
            bm.verts.ensure_lookup_table()
            lastVertIndex = bm.verts[vertsLength - 1].index
            lastSpinVertIndices = self.getLastSpinVertIndices(steps, lastVertIndex)
    
            self.sel.refreshMesh(bm, mesh)
    
            alternativeLastSpinVertIndices = []
            bothSpinVertices = []
            spinVertices = []
            alternate = False
    
            if ((angle == pi or angle == -pi) and not parameters["bothSides"]):
    
                midVertexIndex = lastVertIndex - round(steps / 2)
                bm.verts.ensure_lookup_table()
                midVert = bm.verts[midVertexIndex].co
    
                midVertexDistance = (Vector(refObjectLocation) - Vector(midVert)).length
                midEdgeDistance = (Vector(refObjectLocation) - Vector(edgeCenter)).length
    
                if ((parameters["invertAngle"]) or (parameters["flip"])):
                    if (midVertexDistance > midEdgeDistance):
                        alternativeLastSpinVertIndices = self.alternateSpin(
                                                            bm, mesh, angle, chosenSpinCenter,
                                                            spinAxis, steps, v0, v1org, lastSpinVertIndices
                                                            )
                else:
                    if (midVertexDistance < midEdgeDistance):
                        alternativeLastSpinVertIndices = self.alternateSpin(
                                                            bm, mesh, angle, chosenSpinCenter,
                                                            spinAxis, steps, v0, v1org, lastSpinVertIndices
                                                            )
            elif (angle != two_pi):  # to allow full circles
                if (result['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD:
                    alternativeLastSpinVertIndices = self.alternateSpin(
                                                            bm, mesh, angle, chosenSpinCenter,
                                                            spinAxis, steps, v0, v1org, lastSpinVertIndices
                                                            )
                    alternate = True
    
            self.sel.refreshMesh(bm, mesh)
            if alternativeLastSpinVertIndices != []:
                lastSpinVertIndices = alternativeLastSpinVertIndices
    
            if lastSpinVertIndices.stop <= len(bm.verts):  # make sure arc was added to bmesh
                spinVertices = [bm.verts[i] for i in lastSpinVertIndices]
                if alternativeLastSpinVertIndices != []:
                    spinVertices = spinVertices + [v0]
                else:
                    spinVertices = [v0] + spinVertices
    
            if (parameters["bothSides"]):
                # do some more testing here!!!
                if (angle == pi or angle == -pi):
                    alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
                                                            bm, mesh, -angle, chosenSpinCenter,
                                                            spinAxis, steps, v0, v1org, []
                                                            )
                elif alternate:
                    alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
                                                            bm, mesh, angle, otherSpinCenter,
                                                            spinAxis, steps, v0, v1org, []
                                                            )
                elif not alternate:
                    alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
                                                            bm, mesh, -angle, otherSpinCenter,
                                                            spinAxis, steps, v0, v1org, []
                                                            )
                bothSpinVertices = [bm.verts[i] for i in lastSpinVertIndices]
                alternativeSpinVertices = [bm.verts[i] for i in alternativeLastSpinVertIndices]
                bothSpinVertices = [v0] + bothSpinVertices + alternativeSpinVertices
                spinVertices = bothSpinVertices
    
            if (parameters["fullCircles"]):
                v1 = bm.verts.new(v1org.co)
                spinVertices = spinVertices + [v1]
    
            if (parameters['drawArcCenters']):
                centerVert = bm.verts.new(chosenSpinCenter)
                spinVertices.append(centerVert)
    
            return spinVertices, [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation]
    
        def deleteSpinVertices(self, bm, mesh, lastSpinVertIndices):
            verticesForDeletion = []
            bm.verts.ensure_lookup_table()
            for i in lastSpinVertIndices:
                vi = bm.verts[i]
                vi.select = True
                debugPrintNew(True, str(i) + ") " + str(vi))
                verticesForDeletion.append(vi)
    
            bmesh.ops.delete(bm, geom=verticesForDeletion, context = 'VERTS')
    
            bmesh.update_edit_mesh(mesh, loop_triangles=True)
    
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.mode_set(mode='EDIT')
    
        def alternateSpinNoDelete(self, bm, mesh, angle, chosenSpinCenter,
                                  spinAxis, steps, v0, v1org, lastSpinVertIndices):
            v0prim = v0
    
            result2 = bmesh.ops.spin(bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis,
                                     angle=angle, steps=steps, use_duplicate=False)
            vertsLength = len(bm.verts)
            bm.verts.ensure_lookup_table()
            lastVertIndex2 = bm.verts[vertsLength - 1].index
    
            lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2)
            return lastSpinVertIndices2
    
        def alternateSpin(self, bm, mesh, angle, chosenSpinCenter,
                          spinAxis, steps, v0, v1org, lastSpinVertIndices):
    
            self.deleteSpinVertices(bm, mesh, lastSpinVertIndices)
            v0prim = v0
    
            result2 = bmesh.ops.spin(
                            bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis,
                            angle=-angle, steps=steps, use_duplicate=False
                            )
            # it seems there is something wrong with last index of this spin
            # I need to calculate the last index manually here
            vertsLength = len(bm.verts)
            bm.verts.ensure_lookup_table()
            lastVertIndex2 = bm.verts[vertsLength - 1].index
    
            lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2)
            # second spin also does not hit the v1org
            if (result2['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD:
    
                self.deleteSpinVertices(bm, mesh, lastSpinVertIndices2)
                self.deleteSpinVertices(bm, mesh, range(v0.index, v0.index + 1))
                return []
            else:
                return lastSpinVertIndices2
    
        def getLastSpinVertIndices(self, steps, lastVertIndex):
            arcfirstVertexIndex = lastVertIndex - steps + 1
            lastSpinVertIndices = range(arcfirstVertexIndex, lastVertIndex + 1)
            return lastSpinVertIndices
    
        def rotateArcAroundSpinAxis(self, bm, mesh, vertices, parameters, edgeCenter):
            axisAngle = parameters["axisAngle"]
            plane = parameters["plane"]
            # compensate rotation center
            objectLocation = bpy.context.active_object.location
            center = objectLocation + edgeCenter
    
            rot = Euler((0.0, 0.0, radians(axisAngle)), 'XYZ').to_matrix()
            if plane == YZ:
                rot = Euler((radians(axisAngle), 0.0, 0.0), 'XYZ').to_matrix()
            if plane == XZ:
                rot = Euler((0.0, radians(axisAngle), 0.0), 'XYZ').to_matrix()
    
            indexes = [v.index for v in vertices]
    
            bmesh.ops.rotate(
                bm,
                cent=center,
                matrix=rot,
                verts=vertices,
                space=bpy.context.edit_object.matrix_world
            )
            self.sel.refreshMesh(bm, mesh)
            bm.verts.ensure_lookup_table()
            rotatedVertices = [bm.verts[i] for i in indexes]
    
            return rotatedVertices
    
        def CalculateRadiusAndAngle(self, edgeLength):
            degAngle = self.a
            angle = radians(degAngle)
            self.r = radius = edgeLength / (2 * sin(angle / 2))
            return radius, angle
    
        def CalculateRadiusAndAngleForAnglePresets(self, angleEnum, initR, initA, edgeLength):
            radius = initR
            angle = initA
            try:
                # Note - define an integer string in the angleEnum
                angle_convert = int(angleEnum)
                self.a = angle_convert
            except:
                self.a = 180  # fallback
                debugPrintNew(True,
                              "CalculateRadiusAndAngleForAnglePresets problem with int conversion")
    
            return self.CalculateRadiusAndAngle(edgeLength)
    
        def getSpinCenterClosestToRefCenter(self, objLocation, roots):
            root0Distance = (Vector(objLocation) - Vector(roots[0])).length
            root1Distance = (Vector(objLocation) - Vector(roots[1])).length
    
            chosenId = 0
            rejectedId = 1
            if (root0Distance > root1Distance):
                chosenId = 1
                rejectedId = 0
            return roots[chosenId], roots[rejectedId]
    
        def addMissingCoordinate(self, roots, startVertex, plane):
            if roots is not None:
                a, b = roots[0]
                c, d = roots[1]
                if plane == XY:
                    roots[0] = Vector((a, b, startVertex[2]))
                    roots[1] = Vector((c, d, startVertex[2]))
                if plane == YZ:
                    roots[0] = Vector((startVertex[0], a, b))
                    roots[1] = Vector((startVertex[0], c, d))
                if plane == XZ:
                    roots[0] = Vector((a, startVertex[1], b))
                    roots[1] = Vector((c, startVertex[1], d))
            return roots
    
        def selectEdgesAfterRoundifier(self, context, edges):
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.mode_set(mode='EDIT')
            mesh = context.view_layer.objects.active.data
            bmnew = bmesh.new()
            bmnew.from_mesh(mesh)
    
            self.deselectEdges(bmnew)
            for selectedEdge in edges:
                for e in bmnew.edges:
                    if (e.verts[0].co - selectedEdge.verts[0].co).length <= self.threshold \
                       and (e.verts[1].co - selectedEdge.verts[1].co).length <= self.threshold:
                        e.select_set(True)
    
            bpy.ops.object.mode_set(mode='OBJECT')
            bmnew.to_mesh(mesh)
            bmnew.free()
            bpy.ops.object.mode_set(mode='EDIT')
    
        def deselectEdges(self, bm):
            for edge in bm.edges:
                edge.select_set(False)
    
        def getSpinAxis(self, plane):
            axis = (0, 0, 1)
            if plane == YZ:
                axis = (1, 0, 0)
            if plane == XZ:
                axis = (0, 1, 0)
            return axis
    
    
        @classmethod
        def poll(cls, context):
            return (context.view_layer.objects.active.type == 'MESH') and (context.view_layer.objects.active.mode == 'EDIT')
    
    def draw_item(self, context):
        self.layout.operator_context = 'INVOKE_DEFAULT'
        self.layout.operator('mesh.edge_roundifier')
    
    
    classes = (
               EdgeRoundifier,
               )
    
    reg_cls, unreg_cls = bpy.utils.register_classes_factory(classes)
    
    
    def register():
        reg_cls()
        bpy.types.VIEW3D_MT_edit_mesh_edges.append(draw_item)
    
    
    def unregister():
        unreg_cls()
        bpy.types.VIEW3D_MT_edit_mesh_edges.remove(draw_item)
    
    if __name__ == "__main__":
        register()