Skip to content
Snippets Groups Projects
utils.py 77.7 KiB
Newer Older
  • Learn to ignore specific revisions
  •             leafObj.vertex_groups.new(name=group)
    
                leafObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
    
    
        # Now we need the rotation mode to be 'XYZ' to ensure correct rotation
        bpy.ops.object.mode_set(mode='OBJECT')
        for p in armOb.pose.bones:
            p.rotation_mode = 'XYZ'
        treeOb.parent = armOb
    
    
    
    def kickstart_trunk(addstem, levels, leaves, branches, cu, curve, curveRes,
                        curveV, attractUp, length, lengthV, ratio, ratioPower,
                        resU, scale0, scaleV0, scaleVal, taper, minRadius, rootFlare):
    
        newSpline = cu.splines.new('BEZIER')
        cu.resolution_u = resU
        newPoint = newSpline.bezier_points[-1]
        newPoint.co = Vector((0, 0, 0))
        newPoint.handle_right = Vector((0, 0, 1))
        newPoint.handle_left = Vector((0, 0, -1))
    
        # (newPoint.handle_right_type, newPoint.handle_left_type) = ('VECTOR', 'VECTOR')
        branchL = scaleVal * length[0]
        curveVal = curve[0] / curveRes[0]
    
        # curveVal = curveVal * (branchL / scaleVal)
    
        if levels == 1:
            childStems = leaves
        else:
            childStems = branches[1]
    
        startRad = scaleVal * ratio * scale0 * uniform(1 - scaleV0, 1 + scaleV0)  # * (scale0 + uniform(-scaleV0, scaleV0))
    
        endRad = (startRad * (1 - taper[0])) ** ratioPower
        startRad = max(startRad, minRadius)
        endRad = max(endRad, minRadius)
        newPoint.radius = startRad * rootFlare
    
            stemSpline(
                    newSpline, curveVal, curveV[0] / curveRes[0], attractUp[0],
                    0, curveRes[0], branchL / curveRes[0],
                    childStems, startRad, endRad, 0, 0, None
                    )
                )
    
    
    def fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, curve, curveBack,
                        curveRes, curveV, attractUp, downAngle, downAngleV, leafDist, leaves, length,
                        lengthV, levels, n, ratioPower, resU, rotate, rotateV, scaleVal, shape, storeN,
                        taper, shapeS, minRadius, radiusTweak, customShape, rMode, segSplits,
    
                        useOldDownAngle, useParentAngle, boneStep):
    
    
        # prevent baseSize from going to 1.0
    
        baseSize = min(0.999, baseSize)
    
        # Store the old rotation to allow new stems to be rotated away from the previous one.
        oldRotate = 0
    
        # use fancy child point selection / rotation
    
        if (n == 1) and (rMode != "original"):
            childP_T = OrderedDict()
            childP_L = []
            for p in childP:
                if p.offset == 1:
                    childP_L.append(p)
                else:
                    if p.offset not in childP_T:
                        childP_T[p.offset] = [p]
                    else:
                        childP_T[p.offset].append(p)
    
            childP_T = [childP_T[k] for k in sorted(childP_T.keys())]
    
            childP = []
            rot_a = []
            for p in childP_T:
                if rMode == "rotate":
                    if rotate[n] < 0.0:
                        oldRotate = -copysign(rotate[n], oldRotate)
                    else:
                        oldRotate += rotate[n]
                    bRotate = oldRotate + uniform(-rotateV[n], rotateV[n])
    
    
                    # choose start point whose angle is closest to the rotate angle
    
                    a1 = bRotate % tau
                    a_diff = []
                    for a in p:
                        a2 = atan2(a.co[0], -a.co[1])
    
                        d = min((a1 - a2 + tau) % tau, (a2 - a1 + tau) % tau)
    
                        a_diff.append(d)
    
                    idx = a_diff.index(min(a_diff))
    
    
                    # find actual rotate angle from branch location
    
                    br = p[idx]
                    b = br.co
                    vx = sin(bRotate)
                    vy = cos(bRotate)
                    v = Vector((vx, vy))
    
                    bD = ((b[0] * b[0] + b[1] * b[1]) ** .5)
                    bL = br.lengthPar * length[1] * shapeRatio(shape, (1 - br.offset) / (1 - baseSize), custom=customShape)
    
    
                    # account for down angle
    
                    if downAngleV[1] > 0:
                        downA = downAngle[n] + (-downAngleV[n] * (1 - (1 - br.offset) / (1 - baseSize)) ** 2)
                    else:
                        downA = downAngle[n]
                    if downA < (.5 * pi):
                        downA = sin(downA) ** 2
                        bL *= downA
    
                    bL *= 0.33
                    v *= (bD + bL)
    
                    bv = Vector((b[0], -b[1]))
                    cv = v - bv
                    a = atan2(cv[0], cv[1])
    
                    # rot_a.append(a)
                    """
                    # add fill points at top  #experimental
                    fillHeight = 1 - degrees(rotateV[3]) # 0.8
                    if fillHeight < 1:
                        w = (p[0].offset - fillHeight) / (1- fillHeight)
                        prob_b = random() < w
                    else:
                        prob_b = False
    
                    if (p[0].offset > fillHeight): # prob_b and (len(p) > 1):  ##(p[0].offset > fillHeight) and
                        childP.append(p[randint(0, len(p)-1)])
                        rot_a.append(bRotate)# + pi)
                    """
    
                    childP.append(p[idx])
                    rot_a.append(a)
    
                else:
    
                    idx = randint(0, len(p) - 1)
    
                    childP.append(p[idx])
    
                # childP.append(p[idx])
    
    
            childP.extend(childP_L)
            rot_a.extend([0] * len(childP_L))
    
            oldRotate = 0
    
        for i, p in enumerate(childP):
    
            # Add a spline and set the coordinate of the first point.
            newSpline = cu.splines.new('BEZIER')
            cu.resolution_u = resU
            newPoint = newSpline.bezier_points[-1]
            newPoint.co = p.co
            tempPos = zAxis.copy()
            # If the -ve flag for downAngle is used we need a special formula to find it
    
            if useOldDownAngle:
                if downAngleV[n] < 0.0:
                    downV = downAngleV[n] * (1 - 2 * (.2 + .8 * ((1 - p.offset) / (1 - baseSize))))
                # Otherwise just find a random value
                else:
                    downV = uniform(-downAngleV[n], downAngleV[n])
    
                if downAngleV[n] < 0.0:
                    downV = uniform(-downAngleV[n], downAngleV[n])
                else:
    
                    downV = -downAngleV[n] * (1 - (1 - p.offset) / (1 - baseSize)) ** 2  # (110, 80) = (60, -50)
    
    
            if p.offset == 1:
                downRotMat = Matrix.Rotation(0, 3, 'X')
            else:
                downRotMat = Matrix.Rotation(downAngle[n] + downV, 3, 'X')
    
    
            # If the -ve flag for rotate is used we need to find which side of the stem
            # the last child point was and then grow in the opposite direction
    
                oldRotate = -copysign(rotate[n], oldRotate)
    
            # Otherwise just generate a random number in the specified range
            else:
    
                oldRotate += rotate[n]
            bRotate = oldRotate + uniform(-rotateV[n], rotateV[n])
    
            if (n == 1) and (rMode == "rotate"):
                bRotate = rot_a[i]
    
            rotMat = Matrix.Rotation(bRotate, 3, 'Z')
    
    
            # Rotate the direction of growth and set the new point coordinates
    
            tempPos.rotate(downRotMat)
    
            # use quat angle
    
            if (rMode == "rotate") and (n == 1) and (p.offset != 1):
                if useParentAngle:
                    edir = p.quat.to_euler('XYZ', Euler((0, 0, bRotate), 'XYZ'))
                    edir[0] = 0
                    edir[1] = 0
    
                    edir[2] = -edir[2]
                    tempPos.rotate(edir)
    
                    dec = declination(p.quat)
                    tempPos.rotate(Matrix.Rotation(radians(dec), 3, 'X'))
    
                    edir[2] = -edir[2]
                    tempPos.rotate(edir)
            else:
                tempPos.rotate(p.quat)
    
    
    
            # Make length variation inversely proportional to segSplits
    
            # lenV = (1 - min(segSplits[n], 1)) * lengthV[n]
    
    
            # Find branch length and the number of child stems.
            maxbL = scaleVal
    
            for l in length[:n + 1]:
    
            lMax = length[n]  # * uniform(1 - lenV, 1 + lenV)
    
            if n == 1:
                lShape = shapeRatio(shape, (1 - p.stemOffset) / (1 - baseSize), custom=customShape)
            else:
                lShape = shapeRatio(shapeS, (1 - p.stemOffset) / (1 - baseSize))
            branchL = p.lengthPar * lMax * lShape
            childStems = branches[min(3, n + 1)] * (0.1 + 0.9 * (branchL / maxbL))
    
            # If this is the last level before leaves then we need to generate the child points differently
    
            if (storeN == levels - 1):
    
                    childStems = leaves * (0.1 + 0.9 * (branchL / maxbL)) * shapeRatio(leafDist, (1 - p.offset))
    
            # print("n=%d, levels=%d, n'=%d, childStems=%s"%(n, levels, storeN, childStems))
    
            # Determine the starting and ending radii of the stem using the tapering of the stem
    
            startRad = min((p.radiusPar[0] * ((branchL / p.lengthPar) ** ratioPower)) * radiusTweak[n], p.radiusPar[1])
            if p.offset == 1:
                startRad = p.radiusPar[1]
            endRad = (startRad * (1 - taper[n])) ** ratioPower
            startRad = max(startRad, minRadius)
            endRad = max(endRad, minRadius)
    
    
            # stem curvature
            curveVal = curve[n] / curveRes[n]
            curveVar = curveV[n] / curveRes[n]
    
    
            # curveVal = curveVal * (branchL / scaleVal)
    
            # Add the new stem to list of stems to grow and define which bone it will be parented to
            addstem(
    
                stemSpline(
                    newSpline, curveVal, curveVar, attractUp[n],
                    0, curveRes[n], branchL / curveRes[n], childStems,
                    startRad, endRad, len(cu.splines) - 1, 0, p.quat
                    )
                )
    
            bone = roundBone(p.parBone, boneStep[n - 1])
    
            if p.offset == 1:
                isend = True
            else:
                isend = False
            addsplinetobone((bone, isend))
    
    def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, currentScale, curve,
    
                        curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, originalSplineToBone,
    
                        originalCo, originalCurv, originalCurvV, originalHandleL, originalHandleR, originalLength,
                        originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio, pruneWidth, pruneBase,
                        pruneWidthPeak, randState, ratio, scaleVal, segSplits, splineToBone, splitAngle, splitAngleV,
                        st, startPrune, branchDist, length, splitByLen, closeTip, nrings, splitBias, splitHeight,
                        attractOut, rMode, lengthV, taperCrown, boneStep, rotate, rotateV):
    
        while startPrune and ((currentMax - currentMin) > 0.005):
            setstate(randState)
    
    
            # If the search will halt after this iteration, then set the adjustment of stem
            # length to take into account the pruning ratio
    
            if (currentMax - currentMin) < 0.01:
                currentScale = (currentScale - 1) * pruneRatio + 1
                startPrune = False
                forceSprout = True
            # Change the segment length of the stem by applying some scaling
            st.segL = originalLength * currentScale
    
            # To prevent millions of splines being created we delete any old ones and
            # replace them with only their first points to begin the spline again
    
            if deleteSpline:
                for x in splineList:
                    cu.splines.remove(x.spline)
                newSpline = cu.splines.new('BEZIER')
                newPoint = newSpline.bezier_points[-1]
                newPoint.co = originalCo
                newPoint.handle_right = originalHandleR
                newPoint.handle_left = originalHandleL
                (newPoint.handle_left_type, newPoint.handle_right_type) = ('VECTOR', 'VECTOR')
                st.spline = newSpline
                st.curv = originalCurv
                st.curvV = originalCurvV
                st.seg = originalSeg
                st.p = newPoint
                newPoint.radius = st.radS
    
                splineToBone = originalSplineToBone
    
    
            # Initialise the spline list for those contained in the current level of branching
            splineList = [st]
    
            # split length variation
            stemsegL = splineList[0].segL  # initial segment length used for variation
            splineList[0].segL = stemsegL * uniform(1 - lengthV[n], 1 + lengthV[n])  # variation for first stem
    
            # For each of the segments of the stem which must be grown we have to add to each spline in splineList
            for k in range(curveRes[n]):
                # Make a copy of the current list to avoid continually adding to the list we're iterating over
                tempList = splineList[:]
    
                # print('Leng: ', len(tempList))
    
    
                # for curve variation
    
                if curveRes[n] > 1:
    
                    kp = (k / (curveRes[n] - 1))  # * 2
    
                # split bias
    
                splitValue = segSplits[n]
                if n == 0:
                    splitValue = ((2 * splitBias) * (kp - .5) + 1) * splitValue
                    splitValue = max(splitValue, 0.0)
    
    
                # For each of the splines in this list set the number of splits and then grow it
                for spl in tempList:
    
                    # adjust numSplit
    
                    lastsplit = getattr(spl, 'splitlast', 0)
                    splitVal = splitValue
                    if lastsplit == 0:
                        splitVal = splitValue * 1.33
                    elif lastsplit == 1:
                        splitVal = splitValue * splitValue
    
    
                    elif (n == 0) and (k < ((curveRes[n] - 1) * splitHeight)) and (k != 1):
    
                    elif (n == 0) and (k == int((curveRes[n] - 1) * splitHeight) + 1) and (splitVal > 0):
    
                        if (n >= 1) and splitByLen:
                            L = ((spl.segL * curveRes[n]) / scaleVal)
                            lf = 1
    
                            for l in length[:n + 1]:
    
                                lf *= l
                            L = L / lf
                            numSplit = splits2(splitVal * L)
                        else:
                            numSplit = splits2(splitVal)
    
                    if (k == int(curveRes[n] / 2 + 0.5)) and (curveBack[n] != 0):
    
                        spl.curv += 2 * (curveBack[n] / curveRes[n])  # was -4 *
    
                    growSpline(
                            n, spl, numSplit, splitAngle[n], splitAngleV[n], splineList,
                            handles, splineToBone, closeTip, kp, splitHeight, attractOut[n],
                            stemsegL, lengthV[n], taperCrown, boneStep, rotate, rotateV
                            )
    
            # If pruning is enabled then we must check to see if the end of the spline is within the envelope
    
            if prune:
                # Check each endpoint to see if it is inside
                for s in splineList:
                    coordMag = (s.spline.bezier_points[-1].co.xy).length
    
                    ratio = (scaleVal - s.spline.bezier_points[-1].co.z) / (scaleVal * max(1 - pruneBase, 1e-6))
    
                    if (n == 0) and (s.spline.bezier_points[-1].co.z < pruneBase * scaleVal):
    
                        insideBool = True  # Init to avoid UnboundLocalError later
    
                        (coordMag / scaleVal) < pruneWidth * shapeRatio(9, ratio, pruneWidthPeak, prunePowerHigh,
    
                                                                        prunePowerLow))
                    # If the point is not inside then we adjust the scale and current search bounds
                    if not insideBool:
                        oldMax = currentMax
                        currentMax = currentScale
                        currentScale = 0.5 * (currentMax + currentMin)
                        break
    
                # If the scale is the original size and the point is inside then
                # we need to make sure it won't be pruned or extended to the edge of the envelope
    
                if insideBool and (currentScale != 1):
                    currentMin = currentScale
                    currentMax = oldMax
                    currentScale = 0.5 * (currentMax + currentMin)
                if insideBool and ((currentMax - currentMin) == 1):
                    currentMin = 1
    
            # If the search will halt on the next iteration then we need
            # to make sure we sprout child points to grow the next splines or leaves
    
            if (((currentMax - currentMin) < 0.005) or not prune) or forceSprout:
    
                if (n == 0) and (rMode != "original"):
                    tVals = findChildPoints2(splineList, st.children)
                else:
                    tVals = findChildPoints(splineList, st.children)
    
                # print("debug tvals[%d] , splineList[%d], %s" % ( len(tVals), len(splineList), st.children))
    
                # If leaves is -ve then we need to make sure the only point which sprouts is the end of the spline
                if not st.children:
    
                    tVals = [1.0]
                # remove some of the points because of baseSize
                trimNum = int(baseSize * (len(tVals) + 1))
                tVals = tVals[trimNum:]
    
    
                # grow branches in rings
    
                if (n == 0) and (nrings > 0):
    
                    # tVals = [(floor(t * nrings)) / nrings for t in tVals[:-1]]
    
                    tVals = [(floor(t * nrings) / nrings) * uniform(.995, 1.005) for t in tVals[:-1]]
                    tVals.append(1)
                    tVals = [t for t in tVals if t > baseSize]
    
    
                # branch distribution
    
                    tVals = [((t - baseSize) / (1 - baseSize)) for t in tVals]
                    if branchDist < 1.0:
                        tVals = [t ** (1 / branchDist) for t in tVals]
                    else:
                        tVals = [1 - (1 - t) ** branchDist for t in tVals]
                    tVals = [t * (1 - baseSize) + baseSize for t in tVals]
    
    
                # For all the splines, we interpolate them and add the new points to the list of child points
    
                maxOffset = max([s.offsetLen + (len(s.spline.bezier_points) - 1) * s.segL for s in splineList])
    
                    # print(str(n)+'level: ', s.segMax*s.segL)
    
                    childP.extend(interpStem(s, tVals, s.segMax * s.segL, s.radS, maxOffset, baseSize))
    
    
            # Force the splines to be deleted
            deleteSpline = True
            # If pruning isn't enabled then make sure it doesn't loop
            if not prune:
                startPrune = False
        return ratio, splineToBone
    
    
    def findtaper(length, taper, shape, shapeS, levels, customShape):
        taperS = []
        for i, t in enumerate(length):
            if i == 0:
                shp = 1.0
            elif i == 1:
                shp = shapeRatio(shape, 0, custom=customShape)
            else:
                shp = shapeRatio(shapeS, 0)
            t = t * shp
            taperS.append(t)
    
        taperP = []
        for i, t in enumerate(taperS):
            pm = 1
    
            for x in range(i + 1):
    
                pm *= taperS[x]
            taperP.append(pm)
    
        taperR = []
        for i, t in enumerate(taperP):
            t = sum(taperP[i:levels])
            taperR.append(t)
    
        taperT = []
        for i, t in enumerate(taperR):
            try:
                t = taperP[i] / taperR[i]
            except ZeroDivisionError:
                t = 1.0
            taperT.append(t)
    
        taperT = [t * taper[i] for i, t in enumerate(taperT)]
    
        return taperT
    
    
    Andrew Hale's avatar
    Andrew Hale committed
    def addTree(props):
    
        # startTime = time.time()
    
        seed(props.seed)
    
        levels = props.levels
        length = props.length
        lengthV = props.lengthV
    
        taperCrown = props.taperCrown
    
        branches = props.branches
        curveRes = props.curveRes
        curve = toRad(props.curve)
        curveV = toRad(props.curveV)
        curveBack = toRad(props.curveBack)
        baseSplits = props.baseSplits
        segSplits = props.segSplits
    
        splitByLen = props.splitByLen
        rMode = props.rMode
    
        splitAngle = toRad(props.splitAngle)
        splitAngleV = toRad(props.splitAngleV)
        scale = props.scale
        scaleV = props.scaleV
        attractUp = props.attractUp
    
        attractOut = props.attractOut
    
        shape = int(props.shape)
        shapeS = int(props.shapeS)
    
        customShape = props.customShape
        branchDist = props.branchDist
        nrings = props.nrings
    
        baseSize_s = props.baseSize_s
        splitHeight = props.splitHeight
        splitBias = props.splitBias
    
        minRadius = props.minRadius
        closeTip = props.closeTip
        rootFlare = props.rootFlare
        autoTaper = props.autoTaper
    
        taper = props.taper
    
        radiusTweak = props.radiusTweak
    
        ratioPower = props.ratioPower
        downAngle = toRad(props.downAngle)
        downAngleV = toRad(props.downAngleV)
        rotate = toRad(props.rotate)
        rotateV = toRad(props.rotateV)
        scale0 = props.scale0
        scaleV0 = props.scaleV0
        prune = props.prune
        pruneWidth = props.pruneWidth
    
        pruneBase = props.pruneBase
    
        pruneWidthPeak = props.pruneWidthPeak
        prunePowerLow = props.prunePowerLow
        prunePowerHigh = props.prunePowerHigh
        pruneRatio = props.pruneRatio
    
        leafDownAngle = radians(props.leafDownAngle)
        leafDownAngleV = radians(props.leafDownAngleV)
        leafRotate = radians(props.leafRotate)
        leafRotateV = radians(props.leafRotateV)
    
        leafScale = props.leafScale
        leafScaleX = props.leafScaleX
    
        leafScaleT = props.leafScaleT
        leafScaleV = props.leafScaleV
    
        leafDupliObj = props.leafDupliObj
    
        bend = props.bend
    
        leafangle = props.leafangle
        horzLeaves = props.horzLeaves
    
        leafDist = int(props.leafDist)
        bevelRes = props.bevelRes
        resU = props.resU
    
        previewArm = props.previewArm
    
        leafAnim = props.leafAnim
        frameRate = props.frameRate
        loopFrames = props.loopFrames
    
    
        # windSpeed = props.windSpeed
        # windGust = props.windGust
    
    
        wind = props.wind
        gust = props.gust
        gustF = props.gustF
    
        af1 = props.af1
        af2 = props.af2
        af3 = props.af3
    
        makeMesh = props.makeMesh
        armLevels = props.armLevels
        boneStep = props.boneStep
    
        useOldDownAngle = props.useOldDownAngle
        useParentAngle = props.useParentAngle
    
        if not makeMesh:
            boneStep = [1, 1, 1, 1]
    
    
        if autoTaper:
            taper = findtaper(length, taper, shape, shapeS, levels, customShape)
    
            # pLevels = branches[0]
            # taper = findtaper(length, taper, shape, shapeS, pLevels, customShape)
    
        # Some effects can be turned ON and OFF, the necessary variables are changed here
        if not props.bevel:
            bevelDepth = 0.0
        else:
            bevelDepth = 1.0
    
        if not props.showLeaves:
            leaves = 0
        else:
            leaves = props.leaves
    
        if props.handleType == '0':
            handles = 'AUTO'
        else:
            handles = 'VECTOR'
    
    
        for ob in bpy.context.view_layer.objects:
    
            ob.select_set(state=False)
    
    
        # Initialise the tree object and curve and adjust the settings
    
        cu = bpy.data.curves.new('tree', 'CURVE')
        treeOb = bpy.data.objects.new('tree', cu)
    
        bpy.context.scene.collection.objects.link(treeOb)
    
        # treeOb.location=bpy.context.scene.cursor.location attractUp
    
    
        cu.dimensions = '3D'
        cu.fill_mode = 'FULL'
        cu.bevel_depth = bevelDepth
        cu.bevel_resolution = bevelRes
    
        # Fix the scale of the tree now
    
        scaleVal = scale + uniform(-scaleV, scaleV)
    
        scaleVal += copysign(1e-6, scaleVal)  # Move away from zero to avoid div by zero
    
        pruneBase = min(pruneBase, baseSize)
    
        # If pruning is turned on we need to draw the pruning envelope
        if prune:
            enHandle = 'VECTOR'
            enNum = 128
    
            enCu = bpy.data.curves.new('envelope', 'CURVE')
            enOb = bpy.data.objects.new('envelope', enCu)
    
            bpy.context.scene.collection.objects.link(enOb)
    
            newSpline = enCu.splines.new('BEZIER')
            newPoint = newSpline.bezier_points[-1]
    
            newPoint.co = Vector((0, 0, scaleVal))
            (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
    
            # Set the coordinates by varying the z value, envelope will be aligned to the x-axis
            for c in range(enNum):
    
                newSpline.bezier_points.add(1)
    
    Andrew Hale's avatar
    Andrew Hale committed
                newPoint = newSpline.bezier_points[-1]
    
                ratioVal = (c + 1) / (enNum)
                zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
                newPoint.co = Vector(
                                (
                                scaleVal * pruneWidth *
                                shapeRatio(9, ratioVal, pruneWidthPeak, prunePowerHigh, prunePowerLow),
                                0, zVal
                                )
                            )
    
                (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
    
            newSpline = enCu.splines.new('BEZIER')
            newPoint = newSpline.bezier_points[-1]
    
            newPoint.co = Vector((0, 0, scaleVal))
            (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
    
            # Create a second envelope but this time on the y-axis
            for c in range(enNum):
    
                newSpline.bezier_points.add(1)
    
    Andrew Hale's avatar
    Andrew Hale committed
                newPoint = newSpline.bezier_points[-1]
    
                ratioVal = (c + 1) / (enNum)
                zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
                newPoint.co = Vector(
                                (
                                0, scaleVal * pruneWidth *
                                shapeRatio(9, ratioVal, pruneWidthPeak, prunePowerHigh, prunePowerLow),
                                zVal
                                )
                            )
    
                (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
    
        childP = []
        stemList = []
    
        levelCount = []
    
        splineToBone = deque([''])
        addsplinetobone = splineToBone.append
    
    
        # Each of the levels needed by the user we grow all the splines
    
        for n in range(levels):
            storeN = n
            stemList = deque()
            addstem = stemList.append
    
            # If n is used as an index to access parameters for the tree
            # it must be at most 3 or it will reference outside the array index
    
            # closeTip only on last level
            closeTipp = all([(n == levels - 1), closeTip])
    
            # If this is the first level of growth (the trunk) then we need some special work to begin the tree
            if n == 0:
    
                kickstart_trunk(addstem, levels, leaves, branches, cu, curve, curveRes,
                                curveV, attractUp, length, lengthV, ratio, ratioPower, resU,
    
                                scale0, scaleV0, scaleVal, taper, minRadius, rootFlare)
    
            # If this isn't the trunk then we may have multiple stem to initialise
    
            else:
                # For each of the points defined in the list of stem starting points we need to grow a stem.
    
                fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, curve, curveBack,
                                curveRes, curveV, attractUp, downAngle, downAngleV, leafDist, leaves, length, lengthV,
                                levels, n, ratioPower, resU, rotate, rotateV, scaleVal, shape, storeN,
                                taper, shapeS, minRadius, radiusTweak, customShape, rMode, segSplits,
                                useOldDownAngle, useParentAngle, boneStep)
    
    
            # change base size for each level
    
                baseSize *= baseSize_s  # decrease at each level
    
            if (n == levels - 1):
                baseSize = 0
    
    
            childP = []
            # Now grow each of the stems in the list of those to be extended
            for st in stemList:
    
                # When using pruning, we need to ensure that the random effects
                # will be the same for each iteration to make sure the problem is linear
    
                randState = getstate()
                startPrune = True
                lengthTest = 0.0
    
                # Store all the original values for the stem to make sure
                # we have access after it has been modified by pruning
    
                originalLength = st.segL
                originalCurv = st.curv
                originalCurvV = st.curvV
                originalSeg = st.seg
                originalHandleR = st.p.handle_right.copy()
                originalHandleL = st.p.handle_left.copy()
                originalCo = st.p.co.copy()
                currentMax = 1.0
                currentMin = 0.0
                currentScale = 1.0
                oldMax = 1.0
                deleteSpline = False
    
                originalSplineToBone = copy.copy(splineToBone)
    
                # Now do the iterative pruning, this uses a binary search and halts once the difference
                # between upper and lower bounds of the search are less than 0.005
                ratio, splineToBone = perform_pruning(
                                            baseSize, baseSplits, childP, cu, currentMax, currentMin,
                                            currentScale, curve, curveBack, curveRes, deleteSpline, forceSprout,
    
                                            handles, n, oldMax, originalSplineToBone, originalCo, originalCurv,
    
                                            originalCurvV, originalHandleL, originalHandleR, originalLength,
                                            originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio,
                                            pruneWidth, pruneBase, pruneWidthPeak, randState, ratio, scaleVal,
                                            segSplits, splineToBone, splitAngle, splitAngleV, st, startPrune,
                                            branchDist, length, splitByLen, closeTipp, nrings, splitBias,
                                            splitHeight, attractOut, rMode, lengthV, taperCrown, boneStep,
                                            rotate, rotateV
                                            )
    
    
        # If we need to add leaves, we do it here
        leafVerts = []
        leafFaces = []
        leafNormals = []
    
    
        leafMesh = None  # in case we aren't creating leaves, we'll still have the variable
    
    
        leafP = []
        if leaves:
            oldRot = 0.0
    
            n = min(3, n + 1)
    
            # For each of the child points we add leaves
            for cp in childP:
                # If the special flag is set then we need to add several leaves at the same location
                if leaves < 0:
                    oldRot = -leafRotate / 2
                    for g in range(abs(leaves)):
    
                        (vertTemp, faceTemp, normal, oldRot) = genLeafMesh(
                                                                    leafScale, leafScaleX, leafScaleT,
                                                                    leafScaleV, cp.co, cp.quat, cp.offset,
                                                                    len(leafVerts), leafDownAngle, leafDownAngleV,
                                                                    leafRotate, leafRotateV,
                                                                    oldRot, bend, leaves, leafShape,
                                                                    leafangle, horzLeaves
                                                                    )
    
                        leafVerts.extend(vertTemp)
                        leafFaces.extend(faceTemp)
    
                        leafNormals.extend(normal)
                        leafP.append(cp)
    
                # Otherwise just add the leaves like splines
    
                    (vertTemp, faceTemp, normal, oldRot) = genLeafMesh(
                                                                leafScale, leafScaleX, leafScaleT, leafScaleV,
                                                                cp.co, cp.quat, cp.offset, len(leafVerts),
                                                                leafDownAngle, leafDownAngleV, leafRotate,
                                                                leafRotateV, oldRot, bend, leaves, leafShape,
                                                                leafangle, horzLeaves
                                                                )
    
                    leafVerts.extend(vertTemp)
                    leafFaces.extend(faceTemp)
                    leafNormals.extend(normal)
                    leafP.append(cp)
    
    
            # Create the leaf mesh and object, add geometry using from_pydata,
            # edges are currently added by validating the mesh which isn't great
    
            leafMesh = bpy.data.meshes.new('leaves')
            leafObj = bpy.data.objects.new('leaves', leafMesh)
    
            bpy.context.scene.collection.objects.link(leafObj)
    
            leafObj.parent = treeOb
            leafMesh.from_pydata(leafVerts, (), leafFaces)
    
    
            # set vertex normals for dupliVerts
    
            if leafShape == 'dVert':
                leafMesh.vertices.foreach_set('normal', leafNormals)
    
            # enable duplication
            if leafShape == 'dFace':
    
                leafObj.instance_type = "FACES"
                leafObj.use_instance_faces_scale = True
                leafObj.instance_faces_scale = 10.0
    
                    if leafDupliObj not in "NONE":
                        bpy.data.objects[leafDupliObj].parent = leafObj
    
                except KeyError:
                    pass
            elif leafShape == 'dVert':
    
                leafObj.instance_type = "VERTS"
                leafObj.use_instance_vertices_rotation = True
    
                    if leafDupliObj not in "NONE":
                        bpy.data.objects[leafDupliObj].parent = leafObj
    
            # add leaf UVs
    
            if leafShape == 'rect':
    
                leafMesh.uv_layers.new(name='leafUV')
    
                uvlayer = leafMesh.uv_layers.active.data
    
                u1 = .5 * (1 - leafScaleX)
                u2 = 1 - u1
    
                for i in range(0, len(leafFaces)):
    
                    uvlayer[i * 4 + 0].uv = Vector((u2, 0))
                    uvlayer[i * 4 + 1].uv = Vector((u2, 1))
                    uvlayer[i * 4 + 2].uv = Vector((u1, 1))
                    uvlayer[i * 4 + 3].uv = Vector((u1, 0))
    
    
            elif leafShape == 'hex':
    
                leafMesh.uv_layers.new(name='leafUV')
    
                uvlayer = leafMesh.uv_layers.active.data
    
                u1 = .5 * (1 - leafScaleX)
                u2 = 1 - u1
    
                for i in range(0, int(len(leafFaces) / 2)):
    
                    uvlayer[i * 8 + 0].uv = Vector((.5, 0))
                    uvlayer[i * 8 + 1].uv = Vector((u1, 1 / 3))
                    uvlayer[i * 8 + 2].uv = Vector((u1, 2 / 3))
                    uvlayer[i * 8 + 3].uv = Vector((.5, 1))
    
                    uvlayer[i * 8 + 4].uv = Vector((.5, 0))
                    uvlayer[i * 8 + 5].uv = Vector((.5, 1))
                    uvlayer[i * 8 + 6].uv = Vector((u2, 2 / 3))
                    uvlayer[i * 8 + 7].uv = Vector((u2, 1 / 3))
    
    
            leafMesh.validate()
    
        leafVertSize = {'hex': 6, 'rect': 4, 'dFace': 4, 'dVert': 1}[leafShape]
    
        armLevels = min(armLevels, levels)
        armLevels -= 1
    
        # unpack vars from splineToBone
        splineToBone1 = splineToBone
        splineToBone = [s[0] if len(s) > 1 else s for s in splineToBone1]
        isend = [s[1] if len(s) > 1 else False for s in splineToBone1]
        issplit = [s[2] if len(s) > 2 else False for s in splineToBone1]
        splitPidx = [s[3] if len(s) > 2 else 0 for s in splineToBone1]
    
        # If we need an armature we add it
    
            create_armature(
                        armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSize,
                        leaves, levelCount, splineToBone, treeOb, wind, gust, gustF, af1,
                        af2, af3, leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep
                        )
    
        # print(time.time()-startTime)
    
        # mesh branches
    
        if makeMesh:
            t1 = time.time()
    
            treeMesh = bpy.data.meshes.new('treemesh')
            treeObj = bpy.data.objects.new('treemesh', treeMesh)
    
            bpy.context.scene.collection.objects.link(treeObj)
    
    
            treeVerts = []
            treeEdges = []
            root_vert = []
            vert_radius = []
            vertexGroups = OrderedDict()
            lastVerts = []
    
            for i, curve in enumerate(cu.splines):
                points = curve.bezier_points
    
    
                # find branching level
    
                level = 0
                for l, c in enumerate(levelCount):
                    if i < c:
                        level = l
                        break
                level = min(level, 3)
    
                step = boneStep[level]
                vindex = len(treeVerts)
    
                p1 = points[0]
    
    
                # add extra vertex for splits
    
                if issplit[i]:
                    pb = int(splineToBone[i][4:-4])
    
                    pn = splitPidx[i]  # int(splineToBone[i][-3:])
    
                    p_1 = cu.splines[pb].bezier_points[pn]
    
                    p_2 = cu.splines[pb].bezier_points[pn + 1]
                    p = evalBez(p_1.co, p_1.handle_right, p_2.handle_left, p_2.co, 1 - 1 / (resU + 1))
    
                    treeVerts.append(p)
    
                    root_vert.append(False)
                    vert_radius.append((p1.radius * .75, p1.radius * .75))
    
                    treeEdges.append([vindex, vindex + 1])
    
                    vindex += 1
    
                if isend[i]:
                    parent = lastVerts[int(splineToBone[i][4:-4])]
                    vindex -= 1
                else:
    
                    # add first point
    
                    treeVerts.append(p1.co)
                    root_vert.append(True)
                    vert_radius.append((p1.radius, p1.radius))
    
                """
                # add extra vertex for splits
                if issplit[i]:
                    p2 = points[1]
                    p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, .001)
                    treeVerts.append(p)
                    root_vert.append(False)
                    vert_radius.append((p1.radius, p1.radius)) #(p1.radius * .95, p1.radius * .95)
                    treeEdges.append([vindex,vindex+1])
                    vindex += 1
                """
                # dont make vertex group if above armLevels
    
                if (i >= levelCount[armLevels]):
                    idx = i
                    groupName = splineToBone[idx]
                    g = True
                    while groupName not in vertexGroups:
    
                        # find parent bone of parent bone
    
                        b = splineToBone[idx]
                        idx = int(b[4:-4])
                        groupName = splineToBone[idx]
                else:
                    g = False
    
                for n, p2 in enumerate(points[1:]):
                    if not g:
                        groupName = 'bone' + (str(i)).rjust(3, '0') + '.' + (str(n)).rjust(3, '0')
                        groupName = roundBone(groupName, step)
                        if groupName not in vertexGroups:
                            vertexGroups[groupName] = []
    
                    # parent first vert in split to parent branch bone
                    if issplit[i] and n == 0:
                        if g:
                            vertexGroups[groupName].append(vindex - 1)
                        else:
                            vertexGroups[splineToBone[i]].append(vindex - 1)
    
    
                    for f in range(1, resU + 1):
    
                        pos = f / resU
                        p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, pos)
                        radius = p1.radius + (p2.radius - p1.radius) * pos
    
                        treeVerts.append(p)
                        root_vert.append(False)
                        vert_radius.append((radius, radius))
    
                        if (isend[i]) and (n == 0) and (f == 1):
                            edge = [parent, n * resU + f + vindex]
                        else:
                            edge = [n * resU + f + vindex - 1, n * resU + f + vindex]
    
                            # add vert to group
    
                            vertexGroups[groupName].append(n * resU + f + vindex - 1)
                        treeEdges.append(edge)
    
                    vertexGroups[groupName].append(n * resU + resU + vindex)
    
                    p1 = p2
    
    
                lastVerts.append(len(treeVerts) - 1)
    
    
            treeMesh.from_pydata(treeVerts, treeEdges, ())
    
            for group in vertexGroups:
    
                treeObj.vertex_groups.new(name=group)
    
                treeObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
    
    
            # add armature
    
            if useArm:
                armMod = treeObj.modifiers.new('windSway', 'ARMATURE')
                if previewArm:
    
                    bpy.data.objects['treeArm'].hide_viewport = True
    
                    bpy.data.armatures['tree'].display_type = 'STICK'
    
                armMod.object = bpy.data.objects['treeArm']