def applyconsistenrotationtoflats(surfacemesh):
    ptsF = surfacemesh["fpts"] * (
        1, -1)  # reflect in Y using numpy.array multiplication
    offsetloopuv = surfacemesh["offsetloopuv"]
    offsetloopptsF = [
        P2(ptsF[i][0], ptsF[i][1]) for i in surfacemesh["offsetloopI"]
    ]
    offsetloopuvCentre = sum(offsetloopuv, start=P2(
        0, 0)) * (1.0 / len(offsetloopuv))
    offsetloopptsFCentre = sum(offsetloopptsF, start=P2(
        0, 0)) * (1.0 / len(offsetloopptsF))
    voff = offsetloopuvCentre - offsetloopptsFCentre

    # this proves all the polygons are reflected
    orientOrg = orientation(surfacemesh["uvpts"], surfacemesh["offsetloopI"])
    orientReflFlatttened = orientation(ptsF, surfacemesh["offsetloopI"])
    assert orientOrg == orientReflFlatttened

    # try and rotate so we align with the first edge
    i0 = surfacemesh["offsetloopI"][-10]
    i1 = surfacemesh["offsetloopI"][-5]
    if surfacemesh["patchname"] == "US2":
        i0 = surfacemesh["offsetloopI"][10]
        i1 = surfacemesh["offsetloopI"][15]
    if surfacemesh["patchname"] == "TSR":
        i0 = surfacemesh["offsetloopI"][150]
        i1 = surfacemesh["offsetloopI"][155]

    #v = P2(*surfacemesh["uvpts"][i1]) - offsetloopuvCentre
    #vF = P2(*ptsF[i1]) - offsetloopptsFCentre
    v = P2(*surfacemesh["uvpts"][i1]) - P2(*surfacemesh["uvpts"][i0])
    vF = P2(*ptsF[i1]) - P2(*ptsF[i0])

    xv = P2.ZNorm(P2(P2.Dot(vF, v), P2.Dot(P2.APerp(vF), v)))
    yv = P2.APerp(xv)

    explodev = (offsetloopuvCentre - P2(3, 0)) * 0.8
    if surfacemesh["patchname"] == "TSM3":
        offsetloopuvCentre -= P2(1.0, -0.3)

    def transF(p):
        p0 = p - offsetloopptsFCentre
        return xv * p0[0] + yv * p0[1] + offsetloopuvCentre + explodev

    surfacemesh["fptsT"] = fptsT = numpy.array([transF(p) for p in ptsF])
    vFT = P2(*surfacemesh["fptsT"][i1]) - P2(*surfacemesh["fptsT"][i0])
    #print(v.Arg(), vF.Arg(), vFT.Arg())
    surfacemesh["textpos"] = offsetloopuvCentre + explodev
def cpolyuvvectorstransC(uvpts, fptsT):
    assert len(uvpts) == len(fptsT)
    n = len(uvpts)
    area = abs(
        P2.Dot(fptsT[1] - fptsT[0], P2.APerp(fptsT[2] - fptsT[0])) * 0.5)
    uvarea = abs(
        P2.Dot(uvpts[1] - uvpts[0], P2.APerp(uvpts[2] - uvpts[0])) * 0.5)
    if uvarea == 0:
        return {"uvarea": 0.0}

    cpt = sum(uvpts, P2(0, 0)) * (1.0 / n)
    cptT = sum(fptsT, P2(0, 0)) * (1.0 / n)
    jp = max(
        (abs(P2.Dot(uvpts[j] - cpt, P2.APerp(uvpts[(j + 1) % n] - cpt))), j)
        for j in range(n))
    vj = uvpts[jp[1]] - cpt
    vj1 = uvpts[(jp[1] + 1) % n] - cpt

    urvec = P2(vj1.v, -vj.v)
    #if P2.Dot(urvec, P2(vj.u, vj1.u)) == 0:
    #	print(jp[1], uvpts, uvarea)
    urvec = urvec * (1.0 / P2.Dot(urvec, P2(vj.u, vj1.u)))
    vrvec = P2(vj1.u, -vj.u)
    vrvec = vrvec * (1.0 / P2.Dot(vrvec, P2(vj.v, vj1.v)))
    # this has gotten muddled.  Should be simpler since the following two are negative of each other
    # P2.Dot(urvec, P2(vj.u, vj1.u)) = vj1.v*vj.u - vj.v*vj1.u
    # P2.Dot(vrvec, P2(vj.v, vj1.v)) = vj1.u*vj.v - vj.u*vj1.v
    # set solve: (urvec.u*vj + urvec.v*vj1).v = 0, which is why it uses only v components

    vjT = fptsT[jp[1]] - cptT
    vj1T = fptsT[(jp[1] + 1) % n] - cptT

    # vc = p - cc["cpt"]
    #vcp = cc["urvec"]*vc.u + cc["vrvec"]*vc.v
    #vcs = cc["vj"]*vcp.u + cc["vj1"]*vcp.v ->  vc

    return {
        "cpt": cpt,
        "cptT": cptT,
        "urvec": urvec,
        "vrvec": vrvec,
        "vj": vj,
        "vj1": vj1,
        "vjT": vjT,
        "vj1T": vj1T,
        "area": area,
        "uvarea": uvarea
    }
def cpolyuvvectorstransF(uvpts, fptsT, cpoly):
    cpt = sum((P2(*uvpts[ci]) for ci in cpoly), P2(0, 0)) * (1.0 / len(cpoly))
    cptT = sum((P2(*fptsT[ci]) for ci in cpoly), P2(0, 0)) * (1.0 / len(cpoly))
    n = len(cpoly)
    jp = max((abs(
        P2.Dot((P2(*uvpts[cpoly[j]]) -
                cpt), P2.APerp(P2(*uvpts[cpoly[(j + 1) % n]]) - cpt))), j)
             for j in range(n))
    vj = P2(*uvpts[cpoly[jp[1]]]) - cpt
    vj1 = P2(*uvpts[cpoly[(jp[1] + 1) % n]]) - cpt

    urvec = P2(vj1.v, -vj.v)
    urvec = urvec * (1.0 / P2.Dot(urvec, P2(vj.u, vj1.u)))
    vrvec = P2(vj1.u, -vj.u)
    vrvec = vrvec * (1.0 / P2.Dot(vrvec, P2(vj.v, vj1.v)))
    # this has gotten muddled.  Should be simpler since the following two are negative of each other
    # P2.Dot(urvec, P2(vj.u, vj1.u)) = vj1.v*vj.u - vj.v*vj1.u
    # P2.Dot(vrvec, P2(vj.v, vj1.v)) = vj1.u*vj.v - vj.u*vj1.v
    # set solve: (urvec.u*vj + urvec.v*vj1).v = 0, which is why it uses only v components

    vjT = P2(*fptsT[cpoly[jp[1]]]) - cptT
    vj1T = P2(*fptsT[cpoly[(jp[1] + 1) % n]]) - cptT

    # vc = p - cc["cpt"]
    #vcp = cc["urvec"]*vc.u + cc["vrvec"]*vc.v
    #vcs = cc["vj"]*vcp.u + cc["vj1"]*vcp.v ->  vc

    return {
        "cpt": cpt,
        "cptT": cptT,
        "urvec": urvec,
        "vrvec": vrvec,
        "vj": vj,
        "vj1": vj1,
        "vjT": vjT,
        "vj1T": vj1T
    }
                assert ccnode.i != -1
                ccpoly.append(ccnode.i)
            if ccnode == cnode:
                break
            ccbar = ccbar.GetForeRightBL(ccbar.nodefore == ccnode)
            if not (ccnode.pointzone.izone == cizone):
                tbarcycles.remove((ccbar, ccnode))
        cpolys.append(ccpoly)
    return tnodes, cpolys


def cpolytriangulate(ptsF, cpoly):
    if (n := len(cpoly)) == 3:
        return [cpoly]
    assert n == len(cpoly)
    jp = max((min(P2.Dot((ptsF[cpoly[(j+i)%n]]-ptsF[cpoly[j]]), P2.APerp(ptsF[cpoly[(j+i+1)%n]]-ptsF[cpoly[j]]))  \
                 for i in range(1, n-1)), j)  for j in range(n))
    j = jp[1]
    return [(cpoly[j], cpoly[(j + i) % n], cpoly[(j + i + 1) % n])
            for i in range(1, n - 1)]


def orientation(ptfs, polyi):
    jbl, ptbl = min(enumerate(ptfs[i] for i in polyi),
                    key=lambda X: (X[1][1], X[1][0]))
    ptblFore = ptfs[polyi[(jbl + 1) % len(polyi)]]
    ptblBack = ptfs[polyi[(jbl + len(polyi) - 1) % len(polyi)]]
    angFore = P2(ptblFore[0] - ptbl[0], ptblFore[1] - ptbl[1]).Arg()
    angBack = P2(ptblBack[0] - ptbl[0], ptblBack[1] - ptbl[1]).Arg()
    return (angBack < angFore)