#######################
#-*- coding: utf-8 -*-


######## DM*
######## agruber@tugraz.at and iam@richdank.com
######## library



################
######## basic library data and functions
################

######## credits
lib_cc =   "    DM LIBRARY ~~~~~~~~~~~~~~~S"
lib_cc += "\n    2025-01-10       ver w25_01"
lib_cc += "\n    CC by-nc-sa "
lib_cc += "diag & richDank" 

#######################

######## import
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import RhinoPython.Host as _host
from datetime import datetime
import random
import time
import math
import os
from itertools import combinations
from itertools import permutations
from itertools import product
import System, System.DateTime
import scriptcontext

rs.UnitAbsoluteTolerance(tolerance=0.00001, in_model_units=True)


pi = math.pi
global timeX
timeX = time.time()

def setTime():
    global timeX
    timeX = time.time()
#setTime()


def getTime(verbose=0, prenull=1):
    global timeX
    tim = float(time.time())
    tim -=  float(timeX)
    if verbose: print str(round(tim,2))+"s" # str(round(tim,4))+"secs"
    #return preNull( round(tim,1), prenull)
    return round(tim,2)

################
######## functions / setUPs
################

######## DM2_w21

### hu_02 PARALL.EL.EPIPED
lenX = 10       
lenY = random.randint(lenX, lenX*4)
lenZ = random.randint(lenX, lenX*9)
#print "\t _____________/################"
#print "\t my randomized para.llel.epiped"
#print "\t X/Y/Z =", str(lenX)+"/"+str(lenY)+"/"+str(lenZ)+" _____________"
#print "\t ################/  "

def getParalellEpipedCoords(randVec=0, drawPoints=0):
    lenX = 10       
    lenY = random.randint(lenX, lenX*4)
    lenZ = random.randint(lenX, lenX*9)

    print "\t _____________/################"
    print "\t my randomized para.llel.epiped"
    print "\t X/Y/Z =", str(lenX)+"/"+str(lenY)+"/"+str(lenZ)+" _____________"
    print "\t ################/  "


    #______base vectors
    vecX = [1, 0, 0]
    vecY = rs.VectorUnitize([ random.uniform(0,0.5), random.uniform(0.5,1), 0 ])
    vecZ = rs.VectorUnitize([ random.uniform(-0.5,0.5), random.uniform(-0.5,0.5), 1 ])
    rs.ObjectColor( rs.AddCurve([ [0,0,0], rs.VectorScale(vecX, lenX) ] ), [200,0,0] )
    rs.ObjectColor( rs.AddCurve([ [0,0,0], rs.VectorScale(vecY, lenY) ] ), [0,200,0] )
    rs.ObjectColor( rs.AddCurve([ [0,0,0], rs.VectorScale(vecZ, lenZ) ] ), [0,0,200] )
    rs.AddPoints( [rs.VectorScale(vecX, lenX), rs.VectorScale(vecY, lenY), rs.VectorScale(vecZ, lenZ)] )
    #______here comes my para.llel.epiped:
    allCoords = []
    for x in range(lenX+1):
        for y in range(lenY+1):
            for z in range (lenZ+1):
                if randVec:
                    x=random.uniform(0, lenX)
                    y=random.uniform(0, lenY)
                    z=random.uniform(0, lenZ)
                cor = rs.VectorAdd( rs.VectorAdd( rs.VectorScale( vecX, x), rs.VectorScale( vecY, y)), rs.VectorScale( vecZ, z) )  
                allCoords.append( cor )
    if drawPoints:
        rs.AddPoints( allCoords )
    return allCoords
    

### hu_03 SPHERI.FICATION
#print "\t   _____      ____"
#print "\t  |     | >  /   .\\"
#print "\t  |     | < /    . \\   sphere"
#print "\t  |     | > |    . |       if"
#print "\t  |     | > \\      /  ication"
#print "\t  |_____| <  \____/"
def coords2sphere ( coords, center, radius, makePts=1, makeCrv=0, makeProjectCrv=0 ):


    if center in coords:
        coords.remove( center )
    coordsOnSphere=[]
    for vec in coords:
        cor = vec
        if not rs.Distance( vec, center ):
            continue
        vec = rs.VectorSubtract( vec, center ) # == vec center..cor
        vec = rs.VectorUnitize( vec )
        vec = rs.VectorScale( vec, radius )
        vec = rs.VectorAdd( center, vec)
        coordsOnSphere.append( vec )
        if makeProjectCrv==1:
            rs.AddLine( center, vec )
        if makeProjectCrv==2:
            rs.AddLine( cor, vec )
    if makePts:
        rs.ObjectColor(rs.AddPoints( coordsOnSphere ), [10,10,10] )
    if makeCrv:
        rs.AddCurve( coordsOnSphere, makeCrv )
    return coordsOnSphere

if 1:
    ### hu_04 TETRAEDER
    #print "                            /\\       "
    #print "                           /_|\\      "
    #print "                      TETR/__|_\\EDER _01"
    #print "........................./   |  \\....... h = a/3.0*(6.0**0.5)\n"
    
    pointsX = [ [0, 30, 0], [50, 0, 0], [30, 20, 0] ]
    pointsX = [ [0, random.uniform(10,50), 0], [random.uniform(10,50), random.uniform(10,50), random.uniform(10,50)], [random.uniform(10,50), random.uniform(10,50), 0] ]
    points = ["x","y","z"]

def getTetraCoords( points=points, flipH=0, flipAB=0, drawTetra=1, drawAll=1 ):
    ptA=points[0+flipAB]
    ptB=points[1-flipAB]
    ptX=points[2]
    normVec = rs.VectorCrossProduct( rs.VectorSubtract(ptX, ptA), rs.VectorSubtract(ptX, ptB) )
    a = rs.Distance(ptA, ptB)
    h = a/3.0*(6.0**0.5)*(1-2*flipH)       ### "a drittel mal wurzel aus 6"
    vecH = rs.VectorScale( rs.VectorUnitize( normVec ), h )
    vec_AC = rs.VectorRotate(rs.VectorSubtract( ptB, ptA ), 60.0, vecH)
    ptC = rs.VectorAdd(ptA, rs.VectorRotate(rs.VectorSubtract( ptB, ptA ), 60.0, vecH))
    ptH = pntCentroid( [ptA, ptB, ptC] )
    ptT = rs.VectorAdd( ptH, vecH )
    tetraCoords = [ [ptA, ptB, list(ptC)], [ptA, ptB, list(ptT)], [ptB, list(ptC), list(ptT)], [list(ptC), ptA, list(ptT)] ]
    if drawTetra:
        for coords in tetraCoords:
            rs.AddCurve( coords, 1 )
    if drawAll:
        rs.AddPoints( [ptA, ptB, ptX, ptH] )
        rs.AddLine( ptH, ptT )
        rs.ObjectColor( rs.AllObjects()[0:5], [100,100,0] )
        print "***************"
        print "*** points     ",points
        print "*** edgeLength ", rs.Distance(ptA, ptB)
        print "*** tetraHeight", h
        print "***************"
    if drawAll==2:
        textDots([ptA], "A", 1)
        textDots([ptB], "B", 1)
        textDots([ptX], "X", 1)
        rs.ObjectColor( rs.AllObjects()[0:3], [100,100,0] )
    return tetraCoords



################
######## functions 4duerer 2020
######## functions cage 4_corona 2021
################

def getLodge( p_width=1.2, l_dist=10, d_dist=8, h_dist=6, pos_x=0, pos_y=0, d_grid=0, d_distord=1, demo=0, verbose=0 ) :
    rs.EnableRedraw(0)
    """
    Generate a small archetypal house, distorted along perspective algorithms
    A setup for the development of individual panels
    Inspired by
      Albrecht Duerer's "Underweysung" (1525)
      https://de.wikipedia.org/wiki/Perspektive#Geschichte
      https://en.wikipedia.org/wiki/Albrecht_Duerer
    And
      MVRDVs "Porterlodges for the National Park De Hoge Veluwe" (1996)
      https://mvrdv.com/projects/167/hoenderloo-lodge?photo=15877
      https://www.miesarch.com/work/2994
    Parameters:
      p_width (float, optional): Approx. panel width in units
      l_dist (float, optional): Approx. length of the lodge in units
      d_dist (float, optional): Approx. depth of the lodge in units
      h_dist (float, optional): Approx. height of the lodge in units
      x_pos (float, optional): Move origin of the lodge in x
      y_pos (float, optional): Move origin of the lodge in y
      d_grid (boolean, optional): Fixed depth grid on the the small sides
      d_distord (boolean, optional): Distord basic section of the house
      demo (boolean, optional): Run demo to get an object
      verbose (int, optional): talk & sleep / Print and depict generation of the lodge
    Returns:
      list(float): list[][][] of all panels, containing groups of 4 points
      list(float): list[][][] of just the sleeve's panels
      list(float): list[][][] of just the right section's panels
      list(float): list[][][] of just the left section's panels
      list(float): list[][][] of the sections, containing groups of 5 points
      list(float): list[][] of all the cvs on the sleeve
      list(float): list[][] of all the cvs on the right section
      list(float): list[][] of all the cvs on the left section
    Example:
      import dm2_lib as dm2
      all_data = dm2.getLodge( verbose=1000, demo=1 )
      panels = all_data[0]
      panels_sleeve = all_data[1]
      panels_right = all_data[2]
      panels_left = all_data[3]
      sections = all_data[4]
      cvs_sleeve= all_data[5]
      cvs_right= all_data[6]
      cvs_left= all_data[7]
    """
    if 0 or verbose:
        print "                      ________"
        print "                     /_/_/_|__\\"
        print "### dmLODGE          |_|_|_|__|\n###########################2022 all data returned 2 allLodgeData[]"
        print "*** allLodgeData  = [ panels, panels_sleeve, panels_right, panels_left, sections, cvs_sleeve, cvs_right, cvs_left ]"
    if verbose:
        rs.ObjectColor( rs.AddCurve( [[0,0,0], [pos_x, pos_y, 0]] ), [200,0,20] )
        #rs.ZoomExtents()
    ######## focal point
    focus = [ 
        (-1)**random.randint(0,1) * random.uniform( l_dist*4, l_dist*6 )
        , random.uniform( -d_dist*2, d_dist*2 )
        , 0 
        ]
    ######## planes to project on
    plane0 = rs.PlaneFromNormal(
        [ random.uniform(l_dist/2,l_dist/3), 0, 0 ]
        , [ 3, random.uniform(-1,1), random.uniform(-1,1) ]
        )
    plane1 = rs.PlaneFromNormal(
        [ -random.uniform(l_dist/2,l_dist/3), 0, 0 ]
        , [ 3, random.uniform(-1,1), random.uniform(-1,1) ]
        )

    ######## standard section
    section0 = []
    section1 = []
    if d_distord :
        a = [ 0, random.uniform(-d_dist/2,-d_dist/3), 0 ]
        b = [ 0, random.uniform(-d_dist/2,-d_dist/3), random.uniform(h_dist/3,h_dist/2) ]
        c = [ 0, random.uniform(-d_dist/6,d_dist/6), random.uniform(h_dist/2,h_dist) ]
        d = [ 0, random.uniform(d_dist/3,d_dist/2), random.uniform(h_dist/3,h_dist/2) ]
        e = [ 0, random.uniform(d_dist/3,d_dist/2), 0 ]
    else :
        a = [ 0, -d_dist/2, 0 ]
        b = [ 0, -d_dist/2, random.uniform(h_dist/3,h_dist/2) ]
        c = [ 0, 0, random.uniform(h_dist/2,h_dist) ]
        d = [ 0, d_dist/2, random.uniform(h_dist/3,h_dist/2) ]
        e = [ 0, d_dist/2, 0 ]
    section = [ a, b, c, d, e ]
    ### section_id = rs.AddCurve( section, 1 );
    if verbose : rs.Sleep( verbose )
    ######## get all the cvs alongside (sleeve)
    cvs_sleeve = []
    subdiv_l = int( rs.Distance(plane0[0],plane1[0]) / p_width )
    if verbose : print "the long sides / the sleeve will be separated in", subdiv_l, "segments."

    for pnt in section :
        ######## optic rays
        vec = rs.VectorSubtract( pnt, focus )
        end = rs.VectorAdd( pnt, vec )
        crv_id = rs.AddLine( focus, end )
        ######## projected on the planes
        pierce0 = rs.PlaneCurveIntersection( plane0, crv_id )[0][1]
        pierce1 = rs.PlaneCurveIntersection( plane1, crv_id )[0][1]
        section0.append( pierce0 )
        section1.append( pierce1 )
        #if verbose : rs.ZoomExtents()
        if verbose :
            foc_id = rs.AddPoint( focus )
            pnt_id = rs.AddPoint( pnt )
            ##rs.AddCircle( plane0, max(d_dist,l_dist,h_dist) )
            ##rs.AddCircle( plane1, max(d_dist,l_dist,h_dist) )
            section0_ids = rs.AddPoints( section0 )
            section1_ids = rs.AddPoints( section1 )
            rs.Sleep( verbose )
            rs.DeleteObjects( section0_ids )
            rs.DeleteObjects( section1_ids )
            rs.DeleteObject( foc_id )
            rs.DeleteObject( pnt_id )
            #rs.ZoomExtents() ###
        #if verbose : rs.ZoomExtents() ###
        rs.DeleteObject( crv_id )
        ######## subdivide the edges alongside (sleeve)
        for s in range(subdiv_l+1):
            fac = float(s) / subdiv_l
            coord = pntInbetween( pierce0, pierce1, fac )
            cvs_sleeve.append( coord )
    #if verbose : rs.ZoomExtents() ###
    sections = [ section0, section1 ]
    ######## get the sections alongside (sleeve)
    sections = []
    for u in range( subdiv_l+1 ) :
        pnts = []
        for v in range( 5 ) :
            pnts.append( cvs_sleeve[(v+0)*(subdiv_l+1)+(u+0)] )
        sections.append( pnts )
    ######## get the panels alongside (sleeve)
    panels_sleeve = []
    for u in range( subdiv_l ) :
        for v in range( 5-1 ) :
            pnt0 = cvs_sleeve[ (v+0)*(subdiv_l+1)+(u+0) ]
            pnt1 = cvs_sleeve[ (v+0)*(subdiv_l+1)+(u+1) ]
            pnt2 = cvs_sleeve[ (v+1)*(subdiv_l+1)+(u+1) ]
            pnt3 = cvs_sleeve[ (v+1)*(subdiv_l+1)+(u+0) ]
            panel = [ pnt0, pnt1, pnt2, pnt3 ]
            panels_sleeve.append( panel )
    ######## general subdivision for the sections
    subdiv_d = int(round( rs.Distance(a,e) / p_width / 2 )) * 2
    ######## end section right cvs
    cvs_right = []
    subdiv_d0 = int( rs.Distance(section0[0],section0[4]) / p_width )
    fac_d0a = rs.Distance(section0[1],section0[2])/(rs.Distance(section0[1],section0[2])+rs.Distance(section0[3],section0[2]))
    subdiv_d0a = int(round( subdiv_d0 * fac_d0a ))
    subdiv_d0b = subdiv_d0 - subdiv_d0a
    if d_grid :
        subdiv_d0 = subdiv_d
        subdiv_d0a = int( subdiv_d/2 )
        subdiv_d0b = int( subdiv_d/2 )
    if verbose : print "the section on the right is devided in", subdiv_d0a, "and", subdiv_d0b, "panels."
    for p in range(subdiv_d0+1) :
        fac = float(p) / subdiv_d0
        coord = pntInbetween( section0[4], section0[0], fac )
        cvs_right.append( coord )
    for p in range(subdiv_d0+1) :
        if ( p<subdiv_d0b ) :
            fac = float(p) / subdiv_d0b
            coord = pntInbetween( section0[3], section0[2], fac )
            cvs_right.append( coord )
        else :
            fac = (float(p)-subdiv_d0b) / subdiv_d0a
            coord = pntInbetween( section0[2], section0[1], fac )
            cvs_right.append( coord )
    ######## get the panels on end section right
    panels_right = []
    for p in range(subdiv_d0) :
        pnt0 = cvs_right[ (p+0) ]
        pnt1 = cvs_right[ (p+1) ]
        pnt2 = cvs_right[ (p+1)+(subdiv_d0+1) ]
        pnt3 = cvs_right[ (p+0)+(subdiv_d0+1) ]
        panel = [ pnt0, pnt1, pnt2, pnt3 ]
        panels_right.append( panel )
    ######## end section left cvs
    cvs_left = []
    subdiv_d1 = int( rs.Distance(section1[0],section1[4]) / p_width )
    fac_d1a = rs.Distance(section1[3],section1[2])/(rs.Distance(section1[1],section1[2])+rs.Distance(section1[3],section1[2]))
    subdiv_d1a = int(round( subdiv_d1 * fac_d1a ))
    subdiv_d1b = subdiv_d1 - subdiv_d1a
    if d_grid :
        subdiv_d1 = subdiv_d
        subdiv_d1a = int( subdiv_d/2 )
        subdiv_d1b = int( subdiv_d/2 )
    if verbose : print "the section on the other end is split in", subdiv_d1a, "and", subdiv_d1b, "panels."
    for p in range(subdiv_d1+1) :
        fac = float(p) / subdiv_d1
        coord = pntInbetween( section1[0], section1[4], fac )
        cvs_left.append( coord )
    for p in range(subdiv_d1+1) :
        if ( p<subdiv_d1b ) :
            fac = float(p) / subdiv_d1b
            coord = pntInbetween( section1[1], section1[2], fac )
            cvs_left.append( coord )
        else :
            fac = (float(p)-subdiv_d1b) / subdiv_d1a
            coord = pntInbetween( section1[2], section1[3], fac )
            cvs_left.append( coord )
    ######## get the panels on end section right
    panels_left = []
    for p in range(subdiv_d1) :
        pnt0 = cvs_left[ (p+0) ]
        pnt1 = cvs_left[ (p+1) ]
        pnt2 = cvs_left[ (p+1)+(subdiv_d1+1) ]
        pnt3 = cvs_left[ (p+0)+(subdiv_d1+1) ]
        panel = [ pnt0, pnt1, pnt2, pnt3 ]
        panels_left.append( panel )
    ######## all the panels
    panels = []
    panels.extend( panels_sleeve )
    panels.extend( panels_right )
    panels.extend( panels_left )
    ######## display features
    if verbose: section0_id = rs.AddCurve( section0, 1 );
    if verbose: section1_id = rs.AddCurve( section1, 1 );
    
    #if verbose : rs.ZoomExtents() ###
    ### rs.ObjectColor( rs.AllObjects()[:2], [100,200,100])
    ### rs.DeleteObject( section_id )
    if verbose : 
        rs.Sleep( verbose )
        cvs_ids = rs.AddPoints( cvs_sleeve )
        rs.Sleep( verbose*1 ) ## was *2 ag
        rs.DeleteObjects( cvs_ids )
        cvs_ids = rs.AddPoints( cvs_right )
        rs.Sleep( verbose*1 ) ## was *2 ag
        rs.DeleteObjects( cvs_ids )
        cvs_ids = rs.AddPoints( cvs_left )
        rs.Sleep( verbose*1 ) ## was *2 ag
        rs.DeleteObjects( cvs_ids )
        rs.DeleteObjects( [section0_id,section1_id] )
    ######## move lodge, if desired
    if pos_x or pos_y:
        movVec = [pos_x, pos_y, 0]
        if verbose :
            print "new origin position =", movVec
            rs.ObjectColor(rs.AddCurve( [[0,0,0],movVec] ), [200,0,200]) 
        for i,panel in enumerate(panels):        panels[i]        = [ rs.VectorAdd(cor, movVec) for cor in panel ]
        for i,panel in enumerate(panels_sleeve): panels_sleeve[i] = [ rs.VectorAdd(cor, movVec) for cor in panel ]
        for i,panel in enumerate(panels_right):  panels_right[i]  = [ rs.VectorAdd(cor, movVec) for cor in panel ]
        for i,panel in enumerate(panels_left):   panels_left[i]   = [ rs.VectorAdd(cor, movVec) for cor in panel ]
        for i,section in enumerate(sections):    sections[i]      = [ rs.VectorAdd(cor, movVec) for cor in section ]
        cvs_sleeve = [rs.VectorAdd(crv, movVec) for crv in cvs_sleeve]
        cvs_right  = [rs.VectorAdd(crv, movVec) for crv in cvs_right]
        cvs_left   = [rs.VectorAdd(crv, movVec) for crv in cvs_left]
    ######## depict demo
    if demo :
        ######## the sections
        if 1 :
            for section in sections :
                rs.AddCurve( section, 1 )
                rs.Sleep( verbose/10 )
        ######## default panels
    else: # if not demo
        pass
        #rs.AddCurve( sections[0], 1 )
        #rs.AddCurve( sections[-1], 1 )
        #rs.ObjectColor(rs.AllObjects()[:2], [100,100,120])
        #rs.ObjectName( rs.AllObjects()[:2], "lodgeSections")
        #rs.ZoomExtents()
    rs.EnableRedraw(1)
    return [ panels, panels_sleeve, panels_right, panels_left, sections, cvs_sleeve, cvs_right, cvs_left ]



################
######## functions 4giza & ra
################
##################
# list of pyramids
# list_name    "name"      height*)  OSM_ID         *)lt wiki
cheops    =  [ "cheops",    146.60, "4420397"] 
chefren   =  [ "chefren",   143.50, "4420396"]
mykerinos =  [ "mykerinos",  65.00, "4420398"]
henutsen   = [ "henutsen",   29.60, "219797726"]    # Princess: King's daughter: Said to be a daughter of Khufu on a stela placed in the temple during the 26th dynasty, but more likely to be a wife
meritetis  = [ "meritetis",  30.00, "219797724"]   # King's wife: Wife of Khufu
hetepheres = [ "hetepheres", 30.25, "198032492"]  # Queen HetepheresKing's wife, king's daughter: Wife of Sneferu and mother of Khufu 
khentkawes = [ "khentkawes", 18.50, "73174154"]  # Queen 
queen_A = [ "queen_A", 21.2, "25416060"]        # Queen A
queen_B = [ "queen_B", 21.2, "25416067"]       # Queen B
queen_C = [ "queen_C", 21.2, "25416093"]      # Queen C

#################################################
# collect all pyramid_list into one pyramids_list
pyramids = [ cheops, chefren, mykerinos
            ,henutsen, meritetis, hetepheres
            ,khentkawes
            ,queen_A, queen_B, queen_C
           ]


######################### 4 pyramids mit_ohne OSM
########## action directe / 2020 12 15 / diag / sorry dafuer :)
rs.EnableRedraw(0)
pyras=[
["cheops", 146.6, 4420397, [1248.52044692,1177.13508851,146.6],
[[1119.11710484,1044.36437918,0.0],[1378.68076153,1045.09689965,0.0],[1377.91265704,1309.92508270,0.0],[1118.37126425,1309.15399252,0.0]]],
["chefren", 143.5, 4420396, [863.452413325,765.592578692,143.5],
[[739.094627173,642.396896715,0.0],[988.160855873,642.743869166,0.0],[987.804633503,888.788264014,0.0],[738.749536751,888.441284875,0.0]]],
["mykerinos", 65.0, 4420398, [592.158465300,320.622683574,65.0],
[[534.010729284,264.613746413,0.0],[650.128090131,264.420989649,0.0],[650.306201316,376.631619887,0.0],[534.188840470,376.824378347,0.0]]],
["henutsen", 29.6, 219797726, [1472.76242917,1010.17695143,29.6],
[[1446.28508828,984.182182777,0.0],[1498.62751285,983.578177545,0.0],[1499.23977005,1036.16529323,0.0],[1446.89734548,1036.78215218,0.0]]],
["meritetis", 30.0, 219797724, [1472.74294826,1069.51108927,30.0],
[[1446.34074803,1042.51380133,0.0],[1498.47166557,1041.87123964,0.0],[1499.13958251,1096.51480147,0.0],[1447.01979692,1097.14451462,0.0]]],
["hetepheres", 30.25, 198032492, [1471.22622020,1137.33379761,30.25],
[[1446.04018540,1111.85924919,0.0],[1496.12282431,1111.57652051,0.0],[1496.41225499,1162.80191979,0.0],[1446.32961608,1163.09750096,0.0]]],
["khentkawes", 18.5, 73174154, [1392.15876887,434.391845831,18.5],
[[1369.09615337,407.048940709,0.0],[1420.91537633,413.114422728,0.0],[1415.21581840,461.741187826,0.0],[1363.40772739,455.662832060,0.0]]],
["queen_A", 21.2, 25416060, [588.713127060,209.858105857,21.2],
[[570.234091589,190.903771808,0.0],[607.047447194,190.903771808,0.0],[607.047447194,228.812439906,0.0],[570.523522264,228.812439906,0.0]]],
["queen_B", 21.2, 25416067, [534.712042076,207.590007688,21.2],
[[519.650514972,191.212180857,0.0],[549.628853842,191.083677085,0.0],[549.762437231,223.967834353,0.0],[519.806362259,224.096338458,0.0]]],
["queen_C", 21.2, 25416093, [482.467022060,206.526636076,21.2],
[[468.554868698,191.263582365,0.0],[496.573984530,191.456338026,0.0],[496.373609447,221.783264813,0.0],[468.365625564,221.603359102,0.0]]]
]

#pyras = pyramids[:]
pyramids = pyras
#########################################
############### make pyramid mit_ohne OSM / 2020 12 15 / diag
def makePyramid (NameOrNumber=2, SrfsOrCrvs=0, verbose = 0):
    #rs.EnableRedraw(0)
    pyramids = pyras
    rs.UnitSystem(4) # meters
    if "GIZA::pyramids" not in rs.LayerNames():
        newEmptyLayer( "GIZA::pyramids", [240,230,110] )
    if verbose: rs.Redraw()
    for p, pyra in enumerate(pyramids):
        if type(NameOrNumber)==int and p==NameOrNumber:break# or pyramids[i][0] == str(NameOrNumber):
        if type(NameOrNumber)==str and pyra[0]==NameOrNumber:break
    if verbose: print "pyramids["+str(p)+"] :", pyramids[p][0]
    pyraNam     = pyramids[p][0]
    top         = pyramids[p][3]
    baseCoords  = pyramids[p][4]
    ############################
    rs.CurrentLayer("GIZA::pyramids")
    rs.DeleteObjects(rs.ObjectsByName(pyraNam+"_face_*"))
    rs.DeleteObjects(rs.ObjectsByName(pyraNam+"_base"))
    rs.ObjectName(rs.AddCurve( baseCoords, 1), pyraNam+"_base" )
    if verbose: rs.Redraw()
    for i in range(4):
        p0 = baseCoords[i]                      # some rocket science :
        p1 = baseCoords[i + 1 - 4*(i==3)]       # need last + first pnt of baseCoords
        if SrfsOrCrvs==0: obj = rs.AddSrfPt( [p0, p1, top] )  # surface - or..
        else: obj = rs.AddCurve( [p0, p1, top], 1 )           # .. just curves
        rs.ObjectName( obj, pyraNam+"_face_"+str(i) )         # name it for later adressing
        if verbose==2: rs.Redraw()
    if verbose:
        rs.ZoomBoundingBox(rs.BoundingBox(rs.ObjectsByLayer( "GIZA::pyramids" )))
        rs.Redraw()
    pyramids = pyras
    rs.CurrentLayer("Default")
    return pyraNam


################
######## functions for interaction
################

######## break if you need to
def esc(wo=""):
    if _host.EscapePressed(0):
        raise Exception(wo+'***ESCape*Key*Pressed***')
        #print rs.CommandHistory()


def TimeConsumingTask():
    scriptcontext = dm.scriptcontext    
    for i in range(10000):
        # Was escape key pressed?
        if (scriptcontext.escape_test(False)):
            print "TimeConsumingTask cancelled."
            break
        print i




################
######## text and display functions
################

######## adding leading zeros
def textPre0( val, sign="0", sollLen=2 ):
    # eng leading zero
    anz = sollLen-len(str(val))
    if anz>0:# and val < pow(10, anz):
        for i in range(anz): val = sign+str(val)
    return str(val)

######## adding leading zeros
def textDots(coordList, strX="", justName=0):
    state = rs.EnableRedraw(0)
    ids = []
    if len(coordList):
        for i, pt in enumerate(coordList):
            if not justName: 
                ids.append(rs.AddTextDot(str(i)+strX, pt))
            else:
                ids.append(rs.AddTextDot(strX, pt))
    else:
        print "***________no list > no textDots"
    rs.EnableRedraw(state)
    return ids




################
######## timing functions
################

######## timing variables

######## set time
def timeSet():
    global timeX
    timeX = time.time()

######## get time
def timeGet(verbose=0, prenull=0):
    '''
    timeGet(verbose=0, prenull=0)
    Parameters:
        verbose: int 0/1 print
        prenull: int, textPre0(tim, "0", prenull)
    Returns:
        round(tim,2)
    '''
    global timeX
    tim = float(time.time())
    tim -=  float(timeX)
    tim = round(tim,2)
    txt = str(tim)
    if prenull:
        txt = textPre0(tim, sign="0", sollLen=prenull)
        print "txt", txt
    if verbose:
        print str(txt)+"s"
    return round(tim,2)


######## transpose seconds to minutes
def timeSec2Min( secs ):
    min = int(secs/60.0)
    sec = int(secs-min*60)
    if min > 59:
        hor = int(min/60.0)
        min = int(min-hor*60)
        return textPre0(hor,1)+":"+textPre0(min,1)+":"+textPre0(sec,1)+" "
    min = textPre0(min,1)
    sec = textPre0(sec,1)
    return textPre0(min,1)+"min"+textPre0(sec,1)+"s"



################
######## date/time/location- and osm-related functions
################

def getDateNow():
    now = datetime.now()
    #print "day :", type(now.day), now.day
    #printTime = str(now.year)+textPre0(now.month)+textPre0(now.day)+textPre0(now.hour)+textPre0( now.minute)+textPre0(now.second)
    #print printTime
    return [now.year, now.month, now.day, now.hour, now.minute, now.second]#, printTime]

dat = getDateNow()

def daysInMonth(year=dat[0]):
    return [31, 28+(int(year)%4==0), 31,30,31,30,31,31,30,31,30,31]
    
def date2number( year=dat[0], mon=12, day=30, verbose=0 ):
    if type(year)==list:
        return date2number( year[0], year[1], year[2], verbose=0 )
    months = daysInMonth(year)
    if day > daysInMonth(year)[int(mon)-1]:
        if verbose: print "*** ________no good date: date2number("+str(year)+(", ")+str(mon)+", "+str(day)+") | return =",
        return 0
    number = 0
    for m in range(1, int(mon)): number += months[m-1]
    return number+int(day)
#print date2number(year=2020, mon=2, day=31)

def number2minute(num):
    minu = int(num%60)
    hor = int((num-minu)/60)
    return [hor, minu]


### https://www.timeanddate.de/stadt/zeitumstellung/oesterreich?year=2024
dlSaving=0 # sommerzeit o.a.
dlSaving = 300 < date2number( *dat[0:3] ) < 90 ### 
if 0:
    print "heute ", date2number( *dat[0:3], verbose=0 )
    print "26.10.", date2number( dat[0], 10, 26, verbose=0 )
    print "30.03. ", date2number( dat[0], 3, 30, verbose=0 )
    print "dlSaving", dlSaving*1

def ortX(ortX):
    ort = "guinea"
    lat = 0#
    lon = 0#
    timeZone = 0#
    ### graz
    if ortX == "graz": 
        ort = "graz"
        lat = 47.0654 #=kronesgasse / opernring 47.07086780
        lon = 15.4486 #                         15.43827860
        timeZone = 1+dlSaving ## UTC+1 .. sommerzeit: UTC+2

    
    if ortX == "schulgasse":
        ort = "schulgasse"
        lat = 47.067994 # lt google maps
        lon = 15.466236 
        timeZone = 1+dlSaving
        
    ### NYC, UN headquaters
    if ortX == "NYC UN": 
        lat = 40.7492161
        lon = -73.96867250
        timeZone = -5
    ### stonehenge, UK
    if ortX == "stonehenge": 
        lat = 51.178889
        lon = -1.826111
        timeZone = 0
    ### gizeh, egypt
    if ortX == "giza": ### cairo 30 2 N .. 31 14 E
        ort == "giza"
        lat = 31.1241000
        lon = 29.9694000
        timeZone = 2
    if ortX == "koenigsberg": 
        ort == "koenigsberg"
        lat = 54.6969000
        lon = 20.4943000
        timeZone = 2
    if ortX == "shepperton": 
        ort == "shepperton"
        lat = 51.3928000
        lon = -0.4964000
        timeZone = 0
    if ortX == "barcelona": 
        ort == "barcelona"
        lat = 41.3737000
        lon = 2.1133000
        timeZone = 1
    if ortX == "HONOLULU": 
        ort == "HONOLULU"
        lat = 21.306944
        lon = -157.858333
        timeZone = -10
    if ortX == "TIRANA": 
        ort == "TIRANA"
        lat = 41.332
        lon = 19.832
        timeZone = 1

    return [lat, lon, timeZone]

def daysInMonth(year):
    return [31, 28+(int(year)%4==0), 31,30,31,30,31,31,30,31,30,31]

def number2date( number=31, year=2025):
    months = daysInMonth(year)
    for i in range(0, 11):
        if number <= months[i]:
            return [i+1, number]
        number -= months[i]
        if number <= months[i+1]:
            return [i+2, number]
#print number2date( number=32+28, year=2020)

def date2number_old( year=2024, mon=12, day=30, verbose=0 ):
    months = daysInMonth(year)
    if day > daysInMonth(year)[int(mon)-1]:
        if verbose: print "*** ________no good date: date2number("+str(year)+(", ")+str(mon)+", "+str(day)+") | return =",
        return 0
    number = 0
    for m in range(1, int(mon)): number += months[m-1]
    return number+int(day)
#print date2number(year=2020, mon=2, day=31)
# diag 20 12 18 21:00:



_2day = getDateNow()
dayNumber2day = date2number(_2day[0], _2day[1], _2day[2])
dat = getDateNow()
#ort = "graz" # default
#ort = "giza" # default
#ort = "koenigsberg" # default
#ort = "shepperton" # default

def setSun ( year=dat[0], mon=dat[1], day=dat[2], hour=dat[3], min=dat[4], sec=dat[5], verbose=0):
    sun = sc.doc.Lights.Sun
    sun.Enabled = 1
    sun.TimeZone = ortX(ort)[2]
    sun.DaylightSavingMinutes = dlSaving
    if day <= daysInMonth(year)[mon-1]:
        sun.SetPosition(System.DateTime(year, mon, day, hour, min, sec), ortX(ort)[0],ortX(ort)[1]) ##28/10/2024-12/44/39
        if verbose:
            print "    setSun @", year, textPre0(mon, sign="0", sollLen=2), textPre0(day, sign="0", sollLen=2), "(day# "+str(date2number(year,mon,day))+")","@", str(hour)+":"+str(min)+":"+str(sec)+" @ "+ort.upper()+" @ lat/lon =",str(round(ortX(ort)[0],4))+"Â°/"+str(round(ortX(ort)[1],4))+"Â°", "| tZ = "+str(ortX(ort)[2]),
            print "| azi/alti =", str(round(sun.Azimuth,4))+"Â°/"+str(round(sun.Altitude,4))+"Â°"#,":"
            #print "*** [ azimut", round(sun.Azimuth,2), " / altitude",round(sun.Altitude,2),"/ sunVector [", sun.Vector, "] ]"
        #return [ sun.Azimuth, sun.Altitude, [sun.Vector[0], sun.Vector[1], sun.Vector[2]] ]
        sunVec = sun.Vector
        return [ sun.Azimuth, sun.Altitude, sun.Vector ]
    else:
        print "*** ________no good date: setSun("+str(year)+(", ")+str(mon)+", "+str(day)+") | return =",
        return [0,0,[0,0,0]]


################
######## delete functions
################

######## delete selected
def eS() :
    rs.DeleteObjects( rs.SelectedObjects() )

######## delete visible
def eV() :
    rs.SelectObjects( rs.AllObjects() )
    eS()

######## erase all
def eA() :
    rs.DeleteObjects( rs.AllObjects() )
    rs.Redraw()

######## delete AbsolutelyAll
def eAA():
    """Deletes Absolutely All objects, incl hidden and locked
    """
    rs.UnlockObjects(rs.AllObjects())
    rs.ShowObjects  (rs.AllObjects())
    eA()
    rs.Redraw()
    #print ("''' all objects deleted")

def eDup(verbose=0):
    selected = rs.SelectedObjects()
    rs.UnselectAllObjects()
    vorher = len(rs.AllObjects())
    rs.Command("_selDup", 0)
    if selected and rs.SelectedObjects():
        for obj in rs.SelectedObjects():
            if obj in selected: selected.remove( obj )
    rs.DeleteObjects( rs.SelectedObjects() )
    if selected: rs.SelectObjects(selected)
    if verbose: print "*** dm.eDup() deleted", vorher - len(rs.AllObjects()), "objects ***"
#eDup()
################
######## display functions
################

######## zoom all
def zA( proz=0.95 ) :
    rs.ZoomExtents( all=1 )
    rs.Command("-_zoom Factor "+str(proz)+" _enter", 0) ### diag 2021 11 07
    rs.Redraw()

######## zoom selected
def zS() :
    rs.ZoomSelected( all=1 )

def printDisplay(state=1, scale=1000, thickness=500, color="Color"):
    if state: 
        cmd = "_PrintDisplay State=On Color="+str(color)+" Scale="+str(scale)+" _enter"
        #print cmd
        rs.Command(cmd, 0)
    else: rs.Command("_PrintDisplay State=Off _enter", 0)

#printDisplay(state=1, scale=1, color="Display")


def mathRound(number, nachkomma):
    return math.floor(number*pow(10, nachkomma))/pow(10, nachkomma)


def getCameraTarget(view=rs.CurrentView(), verbose=1):
    if verbose==2: print "***\nview: \"%s\" : [camPos], [targetPos], lens \n" % (rs.CurrentView()),
    cam   = rs.ViewCameraTarget(rs.CurrentView())
    plane = rs.ViewCameraPlane(rs.CurrentView())
    camPos    = "%s, %s, %s" % (mathRound(cam[0][0], 8),mathRound(cam[0][1], 8),mathRound(cam[0][2], 8))
    targetPos = "%s, %s, %s" % (mathRound(cam[1][0], 8),mathRound(cam[1][1], 8),mathRound(cam[1][2], 8))
    lensVal   = round( rs.ViewCameraLens(rs.CurrentView()), 8)
    
    upVec = rs.ViewCameraUp(rs.CurrentView(), up_vector=None)
    if verbose: print "dm.setCameraTarget( [%s], [%s], lens=%s, rota=0, upVec=[0,0,1] ) # ... danke, andi !" % (camPos, targetPos, lensVal )# upVec)
    return [rs.ViewCameraTarget(rs.CurrentView())[0], rs.ViewCameraTarget(rs.CurrentView())[1], lensVal, upVec]


def setCameraTarget(camera=[0, 0, 0], target=[0, 0, 1], lens=50, rota=0, upVec=0, verbose=0 ):
    rs.ViewCameraTarget(rs.CurrentView(), camera, target )
    if lens : rs.ViewCameraLens(rs.CurrentView(), lens)
    if upVec: rs.ViewCameraUp(rs.CurrentView(), upVec)
    if 1 or rota : 
        #rs.RotateView(rs.CurrentView(), direction=0, angle=rota)
        #rs.RotateCamera(view=None, direction=0, angle=rota)
        rs.Command("-_viewportproperties rotation "+str(rota)+" enter enter", 0)
    if verbose :
        rs.Redraw()
        getCameraTarget(view=rs.CurrentView(), verbose=verbose)
    #rs.Command("-ViewportProperties Rotation 0 -enter",1)



################
######## math and array functions
################

######## very simple remapping of a value
def reMap(i, iMin, iMax, oMin, oMax, limit=0):
    '''

    '''
    inFac  = float(i-iMin)/float(iMax-iMin)# 
    outFac = (oMax-oMin)
    if limit and (oMin + outFac*inFac) < oMin:
        return oMin
    if limit and  (oMin + outFac*inFac) > oMax:
        return oMax
    return (oMin + outFac*inFac)

######## list mixxing  / diag
def permutate( list, num):
    return [ perm for perm in permutations(list, num) ]
    
def combinate( list, num):
    return [ comb for comb in combinations(list, num) ]


################
######## basic point and vector functions
################

######## get a point between two points
def pntInbetween( coord0, coord1, fac=.5 ) :
    vec = rs.VectorSubtract( coord1, coord0 )
    vec = rs.VectorScale( vec, fac )
    coord = rs.VectorAdd( coord0, vec )
    
    return coord

def normVec3pnts( p0, p1,p2):
    return rs.VectorUnitize(rs.VectorCrossProduct( rs.VectorSubtract(p0,p1), rs.VectorSubtract(p0,p2) ))

### _diag 2021 11 13
######## get segmets+1 points between two points
######## similar 2 rs.DivideCurve(..)
def pntsInbetween( coord0, coord1, segments, create_points=0, create_line=0):
    '''
    return = coords
    '''
    coords = []
    vec = rs.VectorSubtract( coord1, coord0 )
    for i in range(segments+1):
        coords.append(rs.VectorAdd( coord0, rs.VectorScale(vec, float(i)/segments) ) )
        print i/segments
    if create_points: rs.AddPoints( coords )
    if create_line  : rs.AddLine( coord1, coord0 )
    print len(coords)
    return coords



def circumcircleOfTriangle(p0, p1, p2, verbose = 1):
    lineA = symmetryLine(p0, p1)
    lineB = symmetryLine(p1, p2)
    center= rs.LineLineIntersection(lineA, lineB)[0]
    if verbose:
        rs.AddCircle(center, rs.Distance(center,p0) )
    return [center, rs.Distance(center,p0)]

### diag 2022 12 11 / for delaunay_3D

def circumcircleOfTriangle(p0, p1, p2, verbose = 0):
    '''
    arguments:
        p0, p1, p2 .. 3 coords
        verbose 0/1 generate circle
    returns:
        list [center, radius]
    '''
    p2N = rs.LineClosestPoint([p0,p1], p2)
    p2Nvec = rs.VectorSubtract(p2, p2N)
    p0p1M = rs.VectorAdd( p0, rs.VectorScale( rs.VectorSubtract( p1, p0 ), .5 ) )
    p0p1N = rs.VectorAdd(p0p1M, rs.VectorScale(p2Nvec, 100000) )
    p0p1_N = rs.VectorAdd(p0p1M, rs.VectorScale(p2Nvec, -100000) )
    
    p1p2M = rs.VectorAdd( p1, rs.VectorScale( rs.VectorSubtract( p2, p1 ), .5 ) )
    p1p2N = rs.VectorAdd(p1p2M, rs.VectorScale(rs.VectorSubtract(p0, rs.LineClosestPoint([p1,p2], p0)), 100000)  )
    p1p2_N = rs.VectorAdd(p1p2M, rs.VectorScale(rs.VectorSubtract(p0, rs.LineClosestPoint([p1,p2], p0)), -100000)  )
    
    line01 = [p0p1_N, p0p1N]
    line02 = [p1p2_N, p1p2N]
    #rs.ObjectColor(rs.AddLine( p0p1M, p0p1N ), [0,200,0])
    #rs.ObjectColor(rs.AddLine( p1p2M, p1p2N ), [220,100,0])
    #print line01,"---",line02, "///",
    print rs.Distance(p0p1M, p0p1N),"---",  rs.Distance(p1p2M, p1p2N)
    center = rs.LineLineIntersection(line01, line02)[0]
    #rs.AddPoint( center )
    if 0 or verbose:
        rs.ObjectColor(rs.AddCurve( [center,p0,p1,p2,p0], 1), [100,200,100])
        pln = rs.PlaneFromPoints(center,p0,p1)
        rs.AddCircle(pln, rs.Distance(center,p0) )
    return [center, rs.Distance(center,p0)]
    

######## get a point in center/centroid/mitte von coords/points/pointcloud
def pntCentroid( coords ) :
    x_sum = 0
    y_sum = 0
    z_sum = 0
    for coord in coords :
        x_sum += coord[ 0 ]
        y_sum += coord[ 1 ]
        z_sum += coord[ 2 ]
    num = len( coords )
    x = x_sum / num
    y = y_sum / num
    z = z_sum / num
    coord = [ x, y, z ]
    return coord

######## sort a li^st of coordinates depending on their distance to one point
def pntsSortDistancePnt( origin, coords ) :
    dists = []
    for coord in coords :
        dist = rs.Distance( origin, coord )
        dists.append( abs(dist) )
    zipped_lists = zip( *sorted( zip(dists,coords) ) )
    return list(zipped_lists[1])

######## sort a list of coordinates depending on their distance to one plane
def pntsSortDistancePln( plane, coords ) :
    dists = []
    for coord in coords :
        dist = rs.DistanceToPlane( plane, coord )
        dists.append( abs(dist) )
    zipped_lists = zip( *sorted( zip(dists,coords) ) )
    return zipped_lists[1]



################
######## point and vector functions for curves
################

######## perpendicular frame with normalized parameters
### ?? fehler diag 2021 11 21 ??
def plnCurvePerp( crv, crv_parameter=0.5 ) :
    ###crv_parameter = rs.CurveParameter( crv, crv_parameter ) 
    #pln = rs.CurvePerpFrame( crv, crv_parameter )
    return rs.CurvePerpFrame( crv, crv_parameter )

######## perpendicular distance ("normalabstand") point..curve
def pntCurveDist( pnt, crv  ):
    # para = rs.CurveClosestPoint(crv, pnt)
    # poin = rs.EvaluateCurve(crv, param)
    # dist = rs.Distance(pnt, poin)
    return rs.Distance(pnt, rs.EvaluateCurve(crv, rs.CurveClosestPoint(crv, pnt)))

######## perpendicular distance ("normalabstand") point..Line / _diag 2021 11 04
def pntLineDist( pnt, line):
    '''
    arguments:  pnt: [x,y,z]
               line: [point, point] 
       return: distance of nearest point on LINE from tst_pnt
    '''
    #line = [point0, point1]
    #poin = rs.LineClosestPoint(line, pnt)
    #dist = rs.Distance(pnt, poin)
    return rs.Distance(pnt, rs.LineClosestPoint(line, pnt))

######## perpendicular distance ("normalabstand") point..curve
def pntCurveClosest( pnt, crv  ):
    '''
    arguments: pnt: [x,y,z]
               crv: ID of curve
       return: pnt [x,y,z] on curve that is closest to pnt
    '''
    para = rs.CurveClosestPoint(crv, pnt)
    return rs.EvaluateCurve( crv, rs.CurveClosestPoint(crv, pnt) )

######## origin of the perpendicular frame
def pntCurvePerp( crv, crv_parameter=0.5 ) :
    prepframe = plnCurvePerp( crv, crv_parameter )
    pnt = prepframe[0]
    return pnt

######## axis of the perpendicular frame
def vecCurvePerpX( crv, crv_parameter=0.5 ) :
    prepframe = plnCurvePerp( crv, crv_parameter )
    vec = rs.VectorUnitize( prepframe[1] )
    return vec
def vecCurvePerpY( crv, crv_parameter=0.5 ) :
    prepframe = plnCurvePerp( crv, crv_parameter )
    vec = rs.VectorUnitize( prepframe[2] )
    return vec
def vecCurvePerpZ( crv, crv_parameter=0.5 ) :
    prepframe = plnCurvePerp( crv, crv_parameter )
    vec = rs.VectorUnitize( prepframe[3] )
    return vec

######## projection of the curve direction onto a plane
def vecCurvePerpNormal( crv, crv_parameter=0.5, nor=[pi,pi,pi] ) :
    prepframe = plnCurvePerp( crv, crv_parameter )
    dir = rs.VectorUnitize( prepframe[3] )
    vec = rs.VectorCrossProduct( dir, nor )
    ### vec = rs.VectorRotate( vec, 90, nor ) ### korr diag 2022 11 04
    vec = rs.VectorUnitize( vec )
    return vec

######## projections of the curve direction onto default planes
def vecCurvePerpXY( crv, crv_parameter=0.5 ) :
    vec = vecCurvePerpNormal( crv, crv_parameter, [0,0,1] )
    return vec
def vecCurvePerpXZ( crv, crv_parameter=0.5 ) :
    vec = vecCurvePerpNormal( crv, crv_parameter, [0,1,0] )
    return vec
def vecCurvePerpYZ( crv, crv_parameter=0.5 ) :
    vec = vecCurvePerpNormal( crv, crv_parameter, [1,0,0] )
    return vec

### new 2020 12 15 / diag
def pnt2str( pnt ): # [x,y,z] > "x,y,z" | useful at rs.command(..)
    return str(pnt[0])+","+str(pnt[1])+","+str(pnt[2])

def pnt2XY0( pnt ): # [x,y,z] > [x,y,0]
    return [pnt[0], pnt[1], 0]
def pnt2XZ0( pnt ): # [x,y,z] > [x,y,0]
    return [pnt[0],0, pnt[2]]
def pnt2YZ0( pnt ): # [x,y,z] > [x,y,0]
    return [0, pnt[1],pnt[2]]

def pnt2cor( pnt ): # [<Rhino.Geometry.Point3d object at 0x0000000000000104 [x,y,z]>] or 'vector' etc > [x,y,z]
    return [ val for val in pnt ]

def pnts2cors( pnts ): # [<Rhino.Geometry.Point3d object at 0x0000000000000104 [x,y,z]>] or 'vector' etc > [x,y,z]
    return [ [pnt[0],pnt[1],pnt[2]] for pnt in pnts]

def cPlane3pts( p0, p1, p2):
    p0 = pnt2str(p0) 
    p1 = pnt2str(p1) 
    p2 = pnt2str(p2)
    cmd = "cPlane 3Point "+p0+" "+p1+" "+p2+" enter"
    rs.Command(cmd, 0)

# cPlane3pts( [0,0,0], [0,0,1], [0,1,0] )

#########################
### new 2022 11 29 / diag

def pnts2XY0( coords ):
    return [ pnt2XY0(cor) for cor in coords]

#########################
### new 2022 12 08 / diag
coords=[]
def pntRemoveByDist(coords=coords, minDist=1.0, randomize=1, verbose=1):
    lenVorher = len(coords)
    for i in range(1001):
        if randomize:
            random.shuffle(coords)
        distList = sorted( [ [pair, rs.Distance(pair[0],pair[1])] for pair in combinate( coords, 2) ], key=lambda sortKey: (sortKey[1]) )
        if verbose: print i, "len(coords)", len(coords), "min ",distList[0][1]
        if distList[0][1] < minDist:
            coords.remove(distList[0][0][0] )
            #rs.AddCurve( distList[0][0] )
            #rs.AddPoint( distList[0][0][0] )
        else:
            print "XXX pntRemoveByDist done ",
            if 1 or verbose: print ": cnt =", i,  "len(coords) vorher/nachher "+str(lenVorher)+"/"+str(len(coords))+" - minDist", distList[0][1], "\nXXXXXXXXXXXXXXXXXXXXXXXX"
            break
    return coords

'''
USAge
anz=100
a=10
coords = [ [random.uniform(0,a),random.uniform(0,a),0] for i in range(anz) ]
#pnts = rs.AddPoints( coords )
coords = pntRemoveByDist(coords=coords, minDist=0.5, verbose=0)
'''
    
##########
# ConvHull
# worx @ xy_plane only
def pntConvHull(coordList, verbose=0):
    avg=pntCentroid(coordList)
    start = a = min(coordList)
    coords = [start]
    for i in range(1,10001):
        esc()
        o = a
        a = avg
        for b in coordList:
            if (a[0]-o[0])*(b[1]-o[1])-(a[1]-o[1])*(b[0]-o[0]) < 0:
                a = b
        coords.append(a)
        if a == start:
            break
    if a == start:# or i == 1000:
        if verbose: print "*** convHull: i=",i,": len convCoords=", len(coords), "(from "+str(len(coordList))+"coords)"

    return coords

### new 2022 12 09 ff / diag
#########################
def pntRandVec (a=-1, b=1, anzahl=1):
    return  [[random.uniform(a,b) for i in range(3) ] for j in range(anzahl)]

### new 2024 10 29 / diag

def randVec (a=-1, b=1):
    '''
        randVec (a=-1, b=1)
        Parameters:
            a: value von
            b: value bis
        Returns:
            list: [rnd(a,b), rnd(a,b), rnd(a,b)]
    '''
    return  [random.uniform(a,b) for i in range(3) ]

def pntRandCoords (a=-1, b=1, anzahl=100):
    return  [[random.uniform(a,b) for i in range(3) ] for j in range(anzahl)]

def getSurfacePoints( objID ): 
    rs.UnselectAllObjects()
    if rs.ObjectType(objID)==16 or rs.ObjectType(objID)==1073741824:
        rs.Command("_solidPtOn _selID "+str(objID)+" _enter _enter", 0)
        coords = rs.ObjectGripLocations( objID )
        rs.Command("_pointsOff _enter", 0)
    rs.UnselectAllObjects()
    if rs.ObjectType(objID)==8:
        rs.EnableObjectGrips(objID, 1)
        coords = rs.ObjectGripLocations( objID )
        rs.EnableObjectGrips(objID, 0)
    return coords ### danke, andi !

def setSurfacePoints( objID, coords ): 
    rs.UnselectAllObjects()
    if rs.ObjectType(objID)==16 or rs.ObjectType(objID)==1073741824:
        rs.Command("_solidPtOn _selID "+str(objID)+" _enter _enter", 0)
        rs.ObjectGripLocations( objID, coords )
        rs.Command("_pointsOff _enter", 0)
    rs.UnselectAllObjects()
    if rs.ObjectType(objID)==8:
        rs.EnableObjectGrips(objID, 1)
        rs.ObjectGripLocations( obj, coords )
        rs.EnableObjectGrips(objID, 0)

################
######## layer and material functions
################

def getObjsLayerAndSubs(layer):
    """ 
    get - recursively - objects on sub_layers
    """
    objs = rs.ObjectsByLayer(layer)
    subLayers = rs.LayerChildren(layer)
    if(subLayers):
        #layer has one or more sublayers
        for sLayer in subLayers:
            #recurse
            objs.extend(getObjsLayerAndSubs(sLayer))
    return objs


######## set up an empty new layer in rhino
def newEmptyLayer( lay, color=[105,105,105], parent="", transe=0.0, gloss=0.0, refl=0.0 ):
    #rs.CurrentLayer( "Default")
    layX = lay
    if parent: layX = parent+"::"+lay
    rs.AddLayer( lay, color, parent=parent )
    if rs.IsLayer( layX ) :
        rs.ShowObjects( rs.ObjectsByLayer( layX ) )
        rs.DeleteObjects( rs.ObjectsByLayer( layX ) )
        #rs.PurgeLayer( layX )
    rs.LayerColor(layX, color)
    rgbMat2Lay( layX, *color, T=transe, Gloss=gloss, Refl=refl, matNam=layX+"_layMat" )
    #rs.Redraw()
    rs.CurrentLayer( lay )

######## assign a material to an object 
def rgbMat2Obj(obj_id = "", R=127, G=127, B=127, T=0.0, Gloss=0, Refl=0.0, matNam="none"):
    """ Adds Material to Objects.
    Parameters:
      obj_id = Guid of Geometry in RhinoDoc
      Red[opt] = 0-255
      Green[opt] = 0-255
      Blue[opt] = 0-255
      Transparency[opt] = 0.0 - 1.0, 1.0 being transparent
      Glossiness[opt] = 0.0 - 255.0, 255.0 being glossy
      Reflectivity[opt] = 0.0 - 1.0, 1.0 being fully reflectant
      matName[opt] = Materialname as String
    Returns:
      Material index
    """
    if obj_id: index = rs.AddMaterialToObject(obj_id)
    else: index = sc.doc.Materials.Add()
    R=int(R)
    G=int(G)
    B=int(B)
    rs.MaterialColor( index, (R, G, B) )
    rs.MaterialTransparency( index, T-0.000001 ) # T 0.0..1.0
    rs.MaterialShine( index, Gloss*255 )         # Gloss 0.0..1.0 // diag 2022 01 28
    #rs.MaterialReflectiveColor( index, (191, 191, 255) )
    newMat = sc.doc.Materials[index]
    newMat.Reflectivity=Refl
    sc.doc.Materials.Modify(newMat,index,1)
    if matNam == "none": matNam = "mat_%s_%s_%s_%s_ind_%s" % (R, G, B, int(T*100), index)
    #print "***", matNam#, #" .. created (ind", index, ")"
    rs.MaterialName (index, matNam)
    return index

######## assign a material to a layer 
def rgbMat2Lay(lay_nam = "", R=127, G=127, B=127, T=0.0, Gloss=0.0, Refl=0.0, matNam="none"):
    """ Adds Material to LAYER.
    Parameters:
      lay_nam = layerName
      Red[opt] = 0-255
      Green[opt] = 0-255
      Blue[opt] = 0-255
      Transparency[opt] = 0.0 - 1.0, 1.0 being transparent
      Glossiness[opt] = 0.0 - 1.0, 1.0 being glossy
      Reflectivity[opt] = 0.0 - 1.0, 1.0 being fully reflectant
      matName[opt] = Materialname as String
    Returns:
      Material index
    """
    if lay_nam: index = rs.AddMaterialToLayer(lay_nam)
    else: index = sc.doc.Materials.Add()
    rs.ResetMaterial(index)
    R=int(R)
    G=int(G)
    B=int(B)
    #rs.MaterialReflectiveColor( index, (191, 191, 255) )
    newMat = sc.doc.Materials[index]
    newMat.Reflectivity=Refl
    sc.doc.Materials.Modify(newMat, index, 1)
    if matNam == "none": matNam = "mat_%s_%s_%s_%s_ind_%s" % (R, G, B, int(T*100), index)
    #print "***", matNam#, #" .. created (ind", index, ")"
    rs.MaterialColor( index, (R, G, B) )
    rs.MaterialTransparency( index, T-0.000001 ) # T 0.0..1.0
    rs.MaterialShine( index, Gloss*255 )             # Gloss 0.0..1.0
    rs.MaterialName (index, matNam)
    return index


### experimental
def PointRadius(displayModeX=0, rad=3, styl=3,  verbose=0 ):
    '''
        arguments:
                displayModeX: int .. number of displayMode
                rad  = 3 : point radius
                styl = 3 : point style
                verbose : quasselig
                point style:
                    https://developer.rhino3d.com/api/rhinocommon/rhino.display.pointstyle
                    0 ControlPoint "square with center"
                    1 RoundControlPoint "round with center"
                    2 Simple "solid square"
                    3 RoundSimple "solid circle"
        Standard displayModes_numbers
        0 Wireframe
        1 Shaded
        2 Rendered
        3 Ghosted
        4 XRay
        5 Technical
        6 Artistic
        7 Pen
        8 Arctic
        9 Raytraced
        10 etc ! 
    '''
    display_modes = Rhino.Display.DisplayModeDescription.GetDisplayModes()
    if styl>12:
        print "!!! print nix style>12 > set_2 \"3\""
        styl = 3
    if styl==0: styx = Rhino.Display.PointStyle.ControlPoint
    if styl==1: styx = Rhino.Display.PointStyle.RoundControlPoint
    if styl==2: styx = Rhino.Display.PointStyle.Simple
    if styl==3: styx = Rhino.Display.PointStyle.RoundSimple
    if styl==4: styx = Rhino.Display.PointStyle.ActivePoint
    if styl==5: styx = Rhino.Display.PointStyle.RoundActivePoint
    if styl==6: styx = Rhino.Display.PointStyle.ArrowTip
    if styl==7: styx = Rhino.Display.PointStyle.Heart
    if styl==8: styx = Rhino.Display.PointStyle.Chevron
    if styl==9: styx = Rhino.Display.PointStyle.Triangle
    if styl==10: styx = Rhino.Display.PointStyle.X
    if styl==11: styx = Rhino.Display.PointStyle.Pin
    if styl==12: styx = Rhino.Display.PointStyle.Asterisk

    #print display_modes[displayModeX].EnglishName+"("+str(displayModeX)+") >> pointStyle", "\""+str(styx)+"\""
    if displayModeX != "all" and displayModeX <= len(Rhino.Display.DisplayModeDescription.GetDisplayModes()):
        selected_description = Rhino.Display.DisplayModeDescription.GetDisplayModes()[int(displayModeX)]
        selected_description.DisplayAttributes.PointRadius =  rad
        selected_description.DisplayAttributes.PointStyle = styx # Rhino.Display.PointStyle.Simple
        Rhino.Display.DisplayModeDescription.UpdateDisplayMode(selected_description)
        if 0 or verbose : print "*** style \""+(display_modes[int(displayModeX)].EnglishName+"\":::::::::::::: ")[0:15]+" PointRadius =", rad, "geaendert, danke Eszter & danke Andi fuer PointStyle = \""+str(styx)+"\""
    else:
        #if 0 or verbose : print "ALL"
        for displayModeX in range(len(display_modes)):
            selected_description = display_modes[displayModeX]
            selected_description.DisplayAttributes.PointRadius =  rad
            selected_description.DisplayAttributes.PointStyle = styx # Rhino.Display.PointStyle.Simple
            Rhino.Display.DisplayModeDescription.UpdateDisplayMode(selected_description)
            #if 0 or verbose : print "*** style \""+(display_modes[displayModeX].EnglishName+"\":::::::::::::: ")[0:15]+" PointRadius =", rad, "geaendert, danke Eszter & danke Andi fuer PointStyle = \""+str(styx)+"\""
    if 1:
        ###DISPLAY_MODE##
        #print displayModeX
        if isinstance(displayModeX, int) and int(displayModeX) < 11:
            selected_description = display_modes[int(displayModeX)]
        ###RADIUS POINTS###
        disX = displayModeX
        if verbose:
            print "PointStyle 0: square outline"
            print "PointStyle 1: circle outline"
            print "PointStyle 2: square solid"
            print "PointStyle 3: circle solid"
            print "PointStyle 4: square ActivePoint"
            print "PointStyle 5: round ActivePoint"
        for i, disX in enumerate(display_modes):
            english_name = disX.EnglishName
            english_name = english_name.translate(None, "_ -,.")
            if 0 or verbose:
                num = str(i)
                if i<10:num= " "+num
                print num+" = "+(str(english_name)[0:]+":::::::::::::::::")[0:13],
                print "pointRadius=", int(display_modes[i].DisplayAttributes.PointRadius),
                print "/ pointStyle =", display_modes[i].DisplayAttributes.PointStyle



def UNcutTool(dist=50, offs = 3, deleteIt = 1):
    if dist < 10: dist = 100
    currView = rs.ViewDisplayMode()
    rs.ViewDisplayMode(view=None, mode="wireframe")
    p0 = uno0
    p1 = rs.VectorAdd( uno0, rs.VectorScale(unoY,lenY) )
    p2 = rs.VectorAdd( p1, rs.VectorScale(unoX, lenX) )
    p3 = rs.VectorAdd( p0, rs.VectorScale(unoX, lenX) )
    center = pntInbetween(p0, p2)
    crv0 = rs.AddCurve( [p0, p1, p2, p3, p0], 1 )
    crv1 = rs.OffsetCurve(crv0, [0,0,0], dist)
    rs.MoveObject(crv1, [0,0,-dist] )
    aussen = rs.ExtrudeCurveStraight( crv1, [0,0,0], [0,0,lenZ+dist*2] )
    rs.CapPlanarHoles(aussen)
    #rs.DeleteObjects( [crv0, crv1] )
    
    p0 = rs.VectorAdd( uno0, rs.VectorScale(unoX, lenX+dist*-3) )
    p1 = rs.VectorAdd( p0, rs.VectorScale(unoY,lenY) )
    p2 = rs.VectorAdd( p1, rs.VectorScale(unoX, lenX+dist*4) )
    p3 = rs.VectorAdd( p0, rs.VectorScale(unoX, lenX+dist*4) )
    crv0 = rs.AddCurve( [p0, p1, p2, p3, p0], 1)
    crv1 = rs.OffsetCurve(crv0, uno0, 20)
    crv1o = rs.OffsetCurve(crv1, [0,0,0], 20+offs)
    rs.MoveObject(crv1o, [0,0,-offs])
    innen = rs.ExtrudeCurveStraight( crv1o, [0,0,0], [0,0,lenZ+2*offs] )
    rs.CapPlanarHoles(innen)
    #rs.DeleteObjects( [crv0,crv1, crv1o] )
    cutBody = rs.BooleanDifference(aussen, innen)
    rs.DeleteObjects( [crv0,crv1, innen, aussen] ) 
    rs.UnselectAllObjects()
    rs.SelectObjects(rs.AllObjects())
    rs.UnselectObject(cutBody)
    rs.Command("_split _selID "+str(cutBody)+" _enter _enter",0)
    rs.UnselectAllObjects()
    rs.SelectObject(cutBody)
    rs.Command("_SelVolumeObject _enter", 0)
    #rs.DeleteObject(cutBody)
    anz = len(rs.SelectedObjects())
    rs.Redraw()
    rs.ZoomSelected()
    rs.Sleep(1000)
    print "*** UNcutTool split", anz,"objects",
    if deleteIt:
        rs.DeleteObjects( rs.SelectedObjects())
        print ".. und weg sind sie !"
    rs.ViewDisplayMode(view=None, mode=currView)



def getPixels(imagePath, steps_x=5, steps_y=5):
    # https://discourse.mcneel.com/t/how-to-store-list-of-list-when-reading-rgb-values-of-an-image/88276/6
    """
    Gets the pixel colors of an image.
    Args:
      image_path: An absolute path to an image file.
      steps_x: A number of pixels to sample in image x-direction.
      steps_y: A number of pixels to sample in image y-direction.
    Returns:
      A list of lists with a sublist of pixel *colors*
        for each sampled row of the image .. starting linx_unten
    """
    ###
    print "imagePath =", imagePath
    ###
    bitmap = System.Drawing.Bitmap.FromFile(imagePath)
    print "wid", bitmap.Width, "/ steps_x("+str(steps_x)+") >>", int(bitmap.Width/steps_x),"px"
    print "hig", bitmap.Height, "/ steps_y("+str(steps_y)+") >>", int(bitmap.Height/steps_y),"px .. =", (bitmap.Width/steps_x)*(bitmap.Height/steps_y),"pixelz"
    pixels = []
    for y in xrange(bitmap.Height-1,-1,-1):
        if y % steps_y == 0:
            row_pixels = []
            for x in xrange(bitmap.Width):
                if x % steps_x == 0:
                    pixel = bitmap.GetPixel(x, y)
                    col = [pixel[0],  pixel[1],  pixel[2] ]
                    row_pixels.append(col)
            pixels.append(row_pixels)
    return pixels
    

def exportStringList( list2export=list, exportedListName="names", path="P:/", fileName="exportedStrings"):
    setTime()
    #list2export.sort( key=lambda cor: (cor[1],  cor[0]) )
    if not os.path.exists(path):
        os.makedirs(path)
    fo = open(path+fileName+".py", 'w')
    fo.write( exportedListName+"=[\n")
    list = list2export[0:]
    #for list in list2export:
    #    fo.write( "=[\n")
    for i, stringX  in enumerate(list2export):
        #print i
        out = "\""+stringX+"\""
        if i<len(list)-1:
            out += ",\n"
        else:
            out += "\n] ### fini"
        fo.write( out  )
        #fo.write( "]\n")
    fo.close()
    print "***", len(list2export), "strings     exported to file", path+fileName+".py \t| in", str(getTime())+"s"
    
def exportCoordList( list2export=coords, exportedListName="GIS_coords", path="P:/", fileName="dachsteinGis"):
    setTime()
    #list2export.sort( key=lambda cor: (cor[1],  cor[0]) )
    if not os.path.exists(path):
        os.makedirs(path)
    fo = open(path+fileName+".py", 'w')
    fo.write( exportedListName+"=[\n")
    list = list2export[0:]
    #for list in list2export:
    #    fo.write( "=[\n")
    for i, cor  in enumerate(list):
        #print i
        out = "["+str(cor[0])+","+str(cor[1])+","+str(cor[2])+"]"
        if i<len(list)-1:
            out += ",\n"
        else:
            out += "]"
        fo.write( out  )
        #fo.write( "]\n")
    fo.close()
    print "***", len(list2export), "coords exported to file", path+fileName+".py \t| in", str(getTime())+"s"


def exportCoordLists( list2export=[], exportedListName="More_Coord_Lists", path="P:/", fileName="More_Coord_Lists"):
    #setTime()
    if not os.path.exists(path):
        os.makedirs(path)
    fo = open(path+fileName+".py", 'w')
    fo.write( exportedListName+" = [ ### START")
    list = list2export[0:]
    for j,list in enumerate(list2export):
        fo.write( "\n[ ### list_"+str(j)+"\n")
        if 1:
            for i, coords  in enumerate(list):
                fo.write( "[")
                for c,cor in enumerate(coords):
                    out = "["+str(cor[0])+","+str(cor[1])+","+str(cor[2])+"]"
                    if c<len(coords)-1:
                        out += ","
                    else:
                        out += "]"
                    fo.write( out  )
                if i<len(list)-1:
                    fo.write( ",\n"  )
        fo.write( "\n]")
        if j<len(list2export)-1:
            fo.write( ", ###")

    fo.write( "\n] ### END\n")
    fo.close()
    print "***", len(list2export), "coord_Lists exported to file", path+fileName+".py | in", str(getTime())+"s"
    print path+fileName+".py"


def exportCoordLists( list2export=[], exportedListName="allCoords", path="P:/", fileName="allCoords"):
    setTime()
    if not os.path.exists(path): os.makedirs(path)
    fo = open(path+fileName+".py", 'w')
    fo.write( exportedListName+" = [")
    #list = list2export[0:]
    for j,list in enumerate(list2export):
        fo.write( "\n[ ### list_"+str(j)+"\n")
        #print j, len(list)
        if 1:
            for i, cor  in enumerate(list):
                out = "["+str(cor[0])+","+str(cor[1])+","+str(cor[2])+"]"
                fo.write( out  )
                if i<len(list)-1:
                    fo.write( ",\n"  )
        fo.write( "\n]")
        if j<len(list2export)-1:
            fo.write( ", ###")

    fo.write( "\n] ### END\n")
    fo.close()
    print "***", len(list2export), "coord_Lists exported to file", path+fileName+".py | in", str(getTime())+"s"
    #print path+fileName+".py"

############################
### DM2_w** demo UN_NYC_slab / simplyfied version
unoX = [ 0.876922678230265,-0.480631476711064,0 ]
unoY = [ 0.480631476711061,0.876922678230267,0  ]
uno0 = [ 727.495136230253,531.705385598354,0.00 ]

slabs = L = 11
depth = D = 4
floors= H = 40
fH = 3.9
lenY = 116.56
lenX =  28.18
lenZ = 156.00

### correcter :)
lenY = 87.50
lenX = 22.00


UNerror = 0
def getUnoCoord( l, d, h ):
    if UNerror and (  l >= L or d >= D or h >= H or l<0 or d<0 or h<0 ):
        print "*** getUnoCoord: any parameter too big or neg:",l,d,h,"? ______________***"
        #return UnoGridCoords[ 0 ]
        return [0,0,0]
    else:
        #print "index :", l + d*L + h*D*L 
        return UnoGridCoords[ l + d*L + h*D*L ]

UnoGridCoords = []  # just 4 check in hu_05_basic_script_structure
                    # diag 201107_00:00

def getUnoGridCoords(L=11, D=4, H=40, fH = 4.0):
    #baseCoords = rs.CurveEditPoints(rs.ObjectsByName("_bldg3D_crv_137989966")[0])
    #baseXYZ = baseCoords[14]
    #baseXYZ = [727.495136230253,531.705385598354,0.00]
    #print baseXYZ
    #    dirY = rs.VectorSubtract( baseCoords[10], baseCoords[14] )
    #    dirX = [dirY[1], dirY[0]*-1.0,0] # normal vec
    #    lenY = rs.VectorLength( dirY ) 
    #    lenX = rs.VectorLength( rs.VectorSubtract( baseCoords[15], baseCoords[14] ) )
    #    uniX = rs.VectorUnitize(dirX)
    #    uniY = rs.VectorUnitize(dirY)
    #    uniX = rs.VectorUnitize(dirX)
    #    uniY = rs.VectorUnitize(dirY)
    D += D<2
    slab_spacing = lenY/(L-1)
    UnoGridCoords = []
    for z in range(H):
        for x in range(D):
            for y in range(L):
                cor = uno0
                cor = rs.VectorAdd( cor, rs.VectorScale(unoX, lenX*x/(D-1)))
                cor = rs.VectorAdd( cor, rs.VectorScale(unoY, slab_spacing*y))
                cor = rs.VectorAdd( cor, [0,0, fH*z])
                UnoGridCoords.append( cor )
    #print "*** UnoGridCoords = [",L,"*",D,"*", H,"=", len(UnoGridCoords),"coords]   by getUnoGridCoords() @ DM2_lib ***"
    #print "**************************************************************************************\n"
    return UnoGridCoords
UnoGridCoords = getUnoGridCoords(L=11, D=4, H=40, fH = 4.0)


def UN_slab(showAll=0):
    #eA()
    slabs = 11
    depth = 4
    floors= 40
    fH = 3.9
    #lenY = 116.56
    #lenX =  28.18
    lenZ = 156.00
    rs.ObjectColor( rs.AddLine( uno0, rs.VectorAdd( uno0, rs.VectorScale(unoX, lenX) ) ), [200,0,0] )# x/Red
    rs.ObjectColor( rs.AddLine( uno0, rs.VectorAdd( uno0, rs.VectorScale(unoY, lenY) ) ), [0,200,0] )# y/Green
    rs.ObjectColor( rs.AddLine( uno0, rs.VectorAdd( uno0, [0,0,40*fH] ) ),                [0,0,200] )# z/Blue
    
    linX = rs.AllObjects()[2]
    linY = rs.AllObjects()[1]
    linZ = rs.AllObjects()[0]
    if showAll:
        for y in range(0, slabs+1):
            rs.CopyObject( linZ, rs.VectorScale( unoY, lenY/slabs*y ) )
            for x in range(1, depth+1):
                rs.CopyObject( rs.AllObjects()[0], rs.VectorScale( unoX, lenX/depth) )
        rs.CopyObject( linY, rs.VectorScale( unoX, lenX) )
        for f in range(1, floors+1):
            rs.CopyObject( linY, [0,0,fH*f] )
        rs.CopyObject( rs.AllObjects()[0], rs.VectorScale( unoX, lenX) )
    
        for y in range(0, slabs+1):
            pass
            rs.CopyObject( linX, rs.VectorScale( unoY, lenY/slabs*y ) )
            rs.CopyObject( rs.AllObjects()[0], [0,0,fH*floors] )
        #eDup()
    zA()

def getUNpanelCoords( anzL=10, anzH=39, anzD=3, stepL=1, stepH=4, stepD=1, verbose=0):
    depthVec  = dVec = rs.VectorUnitize(rs.VectorSubtract( getUnoCoord(0, 1, 0), getUnoCoord(0, 0, 0) ))
    lengthVec = lVec = rs.VectorUnitize(rs.VectorSubtract( getUnoCoord(1, 0, 0), getUnoCoord(0, 0, 0) ))
    frontPanels = []
    sidePanels = []
    backPanels = []
    upSidePanels = []
    topPanels = []
    allPanels = []
    lenL = rs.Distance( getUnoCoord(0, 0, 0), getUnoCoord(10, 0, 0) )
    lenD = rs.Distance( getUnoCoord(0, 0, 0), getUnoCoord(0, 3, 0) )
    lenH = rs.Distance( getUnoCoord(0, 0, 0), getUnoCoord(0, 0, 39) )

    anzY = int(anzL/stepL)
    anzZ = int(anzH/stepH)
    anzX = int(anzD/stepD)
    deltaL = lenL/(anzY)
    deltaH = lenH/(anzZ)
    deltaD = rs.Distance( getUnoCoord(0, 0, 0), getUnoCoord(0, 3, 0) )/anzX

    for z in range(anzZ+0):
        for y in range(anzY+0):
            pass
            p0 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(lVec, deltaL*y)), [0,0,deltaH*z] )
            p1 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(lVec, deltaL*(y+1))), [0,0,deltaH*z] )
            p2 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(lVec, deltaL*(y+1))), [0,0,deltaH*(z+1)] )
            p3 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(lVec, deltaL*(y+0))), [0,0,deltaH*(z+1)] )
            coords = [p0, p1, p2, p3]
            coords = [p0, p3, p2, p1]
            frontPanels.append( coords )
            allPanels.append( coords )
    
    for z in range(anzZ+0):
        for x in range(anzX+0):
            pass
            p0 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(dVec, deltaD*x)), [0,0,deltaH*z] )
            p1 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(dVec, deltaD*(x+1))), [0,0,deltaH*z] )
            p2 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(dVec, deltaD*(x+1))), [0,0,deltaH*(z+1)] )
            p3 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(0, 0, 0), rs.VectorScale(dVec, deltaD*(x+0))), [0,0,deltaH*(z+1)] )
            coords = [p1, p2, p3, p0]
            sidePanels.append( coords )
            allPanels.append( coords )

    for panel in frontPanels:
        coords = [ rs.VectorAdd(rs.VectorSubtract( getUnoCoord(0, 3, 0), getUnoCoord(0, 0, 0) ), cor) for cor in panel ]
        #coords = reversed([ rs.VectorAdd(rs.VectorSubtract( getUnoCoord(0, 3, 0), getUnoCoord(0, 0, 0) ), cor) for cor in panel ])
        coords.reverse()
        backPanels.append(coords)
        allPanels.append( coords )
    
    for panel in sidePanels:
        coords = [ rs.VectorAdd(rs.VectorSubtract( getUnoCoord(10, 0, 0), getUnoCoord(0, 0, 0) ), cor) for cor in panel ]
        coords.reverse()
        upSidePanels.append(coords)
        allPanels.append( coords )
    if 1:
        ### topPanels / 2025 01 09
        for y in range(anzY-0):
            for x in range(anzX+0):
                pass
                p0 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(y, 0, 0), rs.VectorScale(dVec, deltaD*x)), [0,0,4*39] )  
                p1 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(y+1, 0, 0), rs.VectorScale(dVec, deltaD*x)), [0,0,4*39] ) 
                p2 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(y+1, 0, 0), rs.VectorScale(dVec, deltaD*(x+1))), [0,0,4*39] ) 
                p3 = rs.VectorAdd(rs.VectorAdd(getUnoCoord(y+0, 0, 0), rs.VectorScale(dVec, deltaD*(x+1))), [0,0,4*39] ) 
                coords = [p0, p1, p2, p3]
                topPanels.append( coords )
                allPanels.append( coords )

    
    #allPanels.extend(backPanels)
    #allPanels.extend(upSidePanels)
    if verbose:
        print "    ### ################ DM2_w24 / hu_06 / UN_panelling"
        print "    ### getUNpanelCoords( anzL="+str(anzL)+",anzH="+str(anzH)+",anzD="+str(anzD)+",stepL="+str(stepL)+",stepH="+str(stepH)+",stepD="+str(stepD)+") ..",len(allPanels), "panelCoordLists generated:"
        print "    ### allPanels = [frontPanels, backPanels, sidePanels, upSidePanels, allPanels, topPanels]"
        print "    ### ##########################################################################"
    return [frontPanels, backPanels, sidePanels, upSidePanels, allPanels, topPanels]

if 0: ### tst
    topPanels = getUNpanelCoords( anzL=10, anzH=39, anzD=3, stepL=1, stepH=4, stepD=1, verbose=0)[5]
    for panel in topPanels:
        rs.AddCurve( panel )

### UN_slab(showAll=1)
### DM2_w22 demo UN_NYC_slab
############################

global coordsCub, coordsCir
def setUp_hu_03( anzahl=64, rotateCub=1 ):
    global coordsCub, coordsCir
    rCircle = random.uniform(4,12)
    aCube   = random.uniform(4,12)
    mCube   = [0,0,random.uniform(4,12)]
    mCircle = [ 0, 0, 0]
    anz = anzahl
    anz -= anz%4
    vecRadial = [-rCircle*.5,-rCircle*.5,0]
    coordsCir = []
    dAng = 360.0/anz
    #print anz, dAng, dAng*anz
    for i in range(anz):
        #vec = rs.VectorRotate(vecRadial, random.uniform(0,360), [0,0,1] )
        vec = rs.VectorRotate(vecRadial, dAng*i, [0,0,1] )
        coordsCir.append( vec)
    movCube   = random.choice(coordsCir)
    #rs.AddLine(mCircle, mCube)
    coordsCub = []
    dA = aCube/(anz/4)
    
    for i in range( int(anz/4)+1 ):
        vec = [mCube[0]-aCube*0.5 + dA*i, mCube[1]-aCube*0.5, mCube[2]]
        coordsCub.append( vec)
    for vec in coordsCub[0:int(anz/4)]:
        #vec= rs.VectorRotate(vec, 90, [0,0,1] )
        vec= rs.VectorAdd(rs.VectorRotate(vec, 90, [0,0,1] ),[0,dA, 0])
        coordsCub.append( vec)
    for vec in coordsCub[1:int(anz/2)]:
        vec= rs.VectorRotate(vec, 180, [0,0,1] )
        coordsCub.append( vec)
    rota = 0
    if rotateCub:
        rota = random.randint(-60,60)
        for i,vec in enumerate(coordsCub):
            coordsCub[i] = rs.VectorRotate(vec, rota, [0,0,1])
    
    movCube = rs.VectorSubtract(random.choice(coordsCir), random.choice(coordsCub))
    movCube = rs.VectorSubtract(coordsCir[0], coordsCub[0])
    movCube[2] = 0

    for i,vec in enumerate(coordsCub):
        pass
        coordsCub[i] = rs.VectorAdd(vec, movCube)
    if 0:
        print "    *** *********** *********** *** DM2_w24 / hu_03 / cube_&_sphere"
        print "    *** setUp_hu_03("+str(anzahl)+") returned 2 arrays with", anz, "pointCoords each: [coordsCir, coordsCub]"
        print "    *** coordsCub are rotated "+str(rota)+".0Â°"
        print "    *** *********** *********** ***"
    return[coordsCir, coordsCub]



################
######## basic library data and functions
################


def echo( on ):
        Rhino.ApplicationSettings.AppearanceSettings.EchoCommandsToHistoryWindow = on
        Rhino.ApplicationSettings.AppearanceSettings.EchoPromptsToHistoryWindow = on

echo(1)
#rs.EnableRedraw(0)
Rhino.ApplicationSettings.AppearanceSettings.EchoCommandsToHistoryWindow = 1
Rhino.ApplicationSettings.AppearanceSettings.EchoPromptsToHistoryWindow  = 1

def checkSteigung( crv, checkProzent=5, checkUnit="m", verbose=0):
    prix=1
    coords = rs.DivideCurve( crv, int(rs.CurveLength(crv))*(1+99*(checkUnit=="cm")), 0)
    deg = rs.CurveDegree(crv)
    
    coordsXY0 = pnts2XY0(coords)
    crvXY0 = rs.AddCurve(coordsXY0, deg )
    rs.ObjectColor( crvXY0, [0,100,100])
    lenX = rs.CurveLength(crvXY0)
    prozs = []
    radii = []
    for i in range(len(coords)-1):
        lenx = rs.Distance( pnt2XY0(coords[i]), pnt2XY0(coords[i+1]) )
        proz = round( abs(coords[i][2]-coords[i+1][2]) / lenx * 100, 2)
        prozs.append( proz )
        #if verbose==1: print i,proz, "%"
        rad=0
        if rs.CurveCurvature(crvXY0 , rs.CurveClosestPoint(crvXY0 , coords[i])):
            rad = round( rs.CurveCurvature(crvXY0 , rs.CurveClosestPoint(crvXY0 , coords[i]))[3], 2)
        radii.append( rad )

        if not verbose and proz > checkProzent and prix:
            prix=0
            print "steigg >",checkProzent,":", proz
        if verbose==2 and proz > checkProzent: print "i"+str(i)+"="+str(proz)+"%"
        #param = rs.CurveClosestPoint(crv, coords[i])
        if verbose:
            print i, str(proz)+"% - curvature/radius @ i:",i,":", rad,"meters"
    rs.DeleteObject(crvXY0)
    prozs=sorted(prozs)
    radii=sorted(radii)
    if len(radii): print "*** min radius:", radii[0],checkUnit,"*** max radius:", radii[-1],checkUnit
    print "*** max steigg:", prozs[-1],"%    (", str(len(prozs))+checkUnit+" - last:", prozs[-3],">", prozs[-2],">", prozs[-1],")"
    
    higx = abs(rs.CurveStartPoint(crv)[2]-rs.CurveEndPoint(crv)[2])
    stgg = higx/lenX*100
    print "*** avg steigg:", abs(round(stgg, 2)),"% --",  higx, "/", lenX

###################### 4 animatzi
#################################
def makeName( name="tst", frameNumber=1110, anzahl=4, format="png"):
    pre = ("0"*10 + str(frameNumber))[-anzahl:]
    name = pre+"_"+name+"."+format
    return name

def captureFrames(path = "D:/DM2/ani_images/", name="_tst", fram=0, Width=960,  Height=540,  Scale=2, format="png", trans=0 ):
    framstr = "0"
    if fram<1000: framstr += "0"
    if fram<100: framstr += "0"
    if fram<10: framstr += "0"
    name = name+"_"+framstr+str(fram)
    transe = "no"
    if format == "png" and trans:
        transe = "yes"
    rs.Redraw()
    rs.Command("-viewCaptureToFile Unit=pixels Width="+str(Width)+"  Height="+str(Height)+"  Scale="+str(Scale)+"  LockAspectRatio=No TransparentBackground="+transe+" "+path+name+"."+format, 0)



#ort = "giza" # default
#ort = "shepperton" # default
ort = "NYC UN" # default
#ort = "HONOLULU" # default
#ort = "TIRANA" # default
#ort = "graz" # default
### ort = "schulgasse" # default
### live sonnenstand
### https://www.sonnenverlauf.de/#/47.068,15.4664,16/2024.08.15/19:16/1/3

#print

#print "changes___:"
#print "2023 09 19: exp PointRadius(displayMode=0, rad=3, sty=XXX  verbose=0 )" pointStyle added !
#print "2021 11 25: neu eDup() - delete duplicates / chg 2021 11 30"
#print "2021 11 20: kor plnCurvePerp()"
#print "          : neu getSurfacePoints(polySrf)"
#print "          : neu zA( proz=0.95 ) - zoom extents + % smaller"
#print "          : neu printDisplay( 1/0 )"
########################
rs.EnableRedraw(1)

if 1:##############################
    ######## feedback in status bar
    print lib_cc
    print "    sourced", datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "    coords = sorted( coords, key=lambda sortKey: (sortKey[1]) )"
    print "    ~~~~~~~~~~~~~~~~~~~~~~~~~~X",

    schlafTage = (date2number(2025,12,24) - dayNumber2day)%365
    print "    btw : "+str(schlafTage)+" tage sinds noch bis weihnachten'25 - dh, noch",schlafTage*4, "stunden schlafen"
    setSun(verbose=1)
    #setSun ( year=2024, mon=6, day=24, hour=15, min=30, sec=0, verbose=1)
    sun = sc.doc.Lights.Sun
    #sun.Enabled = 0
    #sc.doc.Lights.Sun.Enabled = 1
    #print
    ### https://en.wikipedia.org/wiki/Straightedge_and_compass_construction#Impossible_constructions
    #print


########################
if 0: ### GIZA PYRAMIDEZ
    #if "GIZA::pyramids" not in rs.LayerNames():
    newEmptyLayer( "GIZA::pyramids", [240,230,110] )
    for p in range(10): makePyramid (NameOrNumber=p, SrfsOrCrvs=1, verbose=1)
############################
if 0: ### DUERER LODGE MVRDV
    pass
    allLodgeData  = getLodge( demo=0, verbose=0 )
    ### DM2_w23
    ### hu_02 

if 1: ### Squaring the circle ## dm2_w24 hu_03
    allCoords = setUp_hu_03( anzahl=32*4, rotateCub=1  )
    coordsCub = allCoords[1]
    coordsCir = allCoords[0]###################

if 1: ### UN PANELS ## dm2_w24 hu_06
    pass
    UnoGridCoords = getUnoGridCoords(11, 4, 40, 4.0)
    UnoBaseCoords = [getUnoCoord(0,0,0), getUnoCoord(L-1,0,0), getUnoCoord(L-1,D-1,0), getUnoCoord(0,D-1,0), getUnoCoord(0,0,0)]
    UnoPanelCoords = getUNpanelCoords( anzL=10, anzH=39, anzD=3, stepL=1, stepH=4, stepD=1, verbose=0)

############# 2024 12 14
def makeDodekaeder( rad=10.0, pos=[0,0,0], makeCrv=1, makeSrf=1, verbose=1):
    ### https://de.wikipedia.org/wiki/Dodekaeder#/media/Datei:Dodekaeder-w.svg
    '''
    return: allCoords = 12 coordLists
    '''
    r = rad
    c = (5**0.5+1)*0.5*r
    a = rs.Distance([r,0,0], rs.VectorRotate( [r,0,0], 72, [0,0,1] ))
    if verbose:
        print "*** dodecahedron | pentagondodekaeder"
        print "*** radius\t\t r", r
        print "*** kantePentagon\t a", a
        print "*** kante Cubus\t c", c
        print "*** allCoords = [ rs.VectorAdd(pos, rs.VectorRotate(cor, 72.0*i, [0,0,1])) for cor in [[r,0,0], [c, 0, r], rs.VectorRotate([c,0,c], 36.0, [0,0,1] ), [c*.5,a*.5,r+c]] for i in range(5) ]"
    allCoords = [ rs.VectorAdd(pos, rs.VectorRotate(cor, 72.0*i, [0,0,1])) for cor in [[r,0,0], [c, 0, r], rs.VectorRotate([c,0,c], 36.0, [0,0,1] ), [c*.5,a*.5,r+c]] for i in range(5) ]
    indices=[[x+(i*5) for x in [0,1,2,3,4]] for i in range(0,4)] 
    pentaIndices = [[0,1,2,3,4,0]]
    pentaIndices.extend( [[indices[0][0+i], indices[0][1+i], indices[1][1+i], indices[2][0+i], indices[1][0+i],indices[0][0+i]] for i in range(4)] )
    pentaIndices.append( [4,0,5,14,9,4] )
    pentaIndices.append( [6,11,16,15,10,6] )
    pentaIndices.extend( [[pentaIndices[6][0]+i, pentaIndices[6][1]+i, pentaIndices[6][2]+i, pentaIndices[6][3]+i, pentaIndices[6][4]+i, pentaIndices[6][5]+i] for i in range(1,4)] )
    pentaIndices.append( [5,10,15,19,14,5] )
    pentaIndices.extend( [range(15,20)] )
    pentaIndices[-1].append(15)
    pentaIndices[0].reverse()
    allCoords = [[allCoords[i] for i in indlist] for indlist in pentaIndices ]
    if makeCrv:
        for coords in allCoords:
            crv=rs.AddCurve( coords, 1 )
            if makeSrf: rs.AddPlanarSrf(crv)
    return allCoords
### USAge
#coords = makeDodekaeder( rad=1.0, pos=[1,11,0], makeCrv=1, makeSrf=1, verbose=1)


def flowAlongSrf(obj, base="ID", target="ID"):
    rs.SelectObject( obj )
    xx, obj = Rhino.Input.RhinoGet.GetOneObject(" ", 1, Rhino.DocObjects.ObjectType.AnyObject)
    rs.UnselectAllObjects()
    rs.SelectObject( base )
    srf1 = rs.GetObject(" ", preselect=1)
    rs.UnselectAllObjects()
    rs.SelectObject( target )
    srf2 = rs.GetObject(" ", preselect=1)
    rs.UnselectAllObjects()

    morph = Rhino.Geometry.Morphs.SporphSpaceMorph(rs.coercesurface(srf1), rs.coercesurface(srf2))#, ptBase, ptTarg)
    geom = obj.Geometry().Duplicate()
    if (morph.Morph(geom)):
        sc.doc.Objects.Add(geom)
        sc.doc.Views.Redraw()
    return Rhino.Commands.Result.Success

#### USAge with P:/FINAL/lisaF/dachstein_small_lochblende.3dm



#rs.PlaneFromPoints(p0, p1, p2)
def setCplane( p0=[0,0,0], p1=[1,0,0], p2=[0,1,0], verbose=1):
    rs.ViewCPlane(view=rs.CurrentView(), plane=rs.PlaneFromPoints(p0, p1, p2) )
    if verbose:
        rs.ShowGrid(None, 1)
        rs.ShowGridAxes(None, 1)  
### USAge
'''
p0 = dm.getUnoCoord(0,0,0)
p1 = dm.getUnoCoord(0,1,0)
p2 = dm.getUnoCoord(1,0,0)
setCplane( p0, p1, p2, verbose=0)
'''

def cageIt( objs=[], xDir = 3,  yDir = 3,  zDir = 3, xDeg = 2,  yDeg = 2,  zDeg = 2, verbose=1):
    rs.SelectObjects( objs )
    cmd = "-CageEdit BoundingBox CPlane XPointCount="+str(xDir)+"  YPointCount="+str(yDir)+"  ZPointCount="+str(zDir)+" XDegree="+str(xDeg)+"  YDegree="+str(yDeg)+"  ZDegree="+str(zDeg)+" enter Global enter"
    #print cmd
    rs.Command(cmd, 0)
    rs.UnselectAllObjects()
    cageX = rs.AllObjects()[0] ###print cageX
    if 0:
        coords = rs.ObjectGripLocations( cageX )
        if verbose:
            dm.textDots(coords)
    rs.Command("_PointsOff", 0)
    return cageX

import DM_lib as dm ### reload (dm)
dm.getCameraTarget()