EihiS

May 16, 2017

3D photogrammetry on Linux with CUDA + COLMAP

Filed under: linux — Tags: , , , , , — admin @ 6:45 am

Ce post regroupe des notes concernant l’installation de CUDA et du logiciel COLMAP pour photogrammetrie
( Reconstruction de modele 3D en utilisant des photos multiples d’un même élément depuis plusieurs points de vues )

Target config : portable x86_64, 8Go,NVIDIA GT540M , Linux Ubuntu 16.04

Part I : CUDA drivers

  • Nvidia-CUDA drivers,téléchargement (1.8 Go) : http://developer.nvidia.com/cuda-downloads
  • >> choix des OPTIONS du download :  Linux, x86-64, ubuntu, 16.04, deb(local)
  • Installation (depuis le dossier : ‘Downloads’ )

  • sudo dpkg -i ./Downloads/cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64.deb
  • sudo apt-get update
  • sudo apt-get install cuda
    • Note : des warnings “ldconfig.real,   **warnings** sur egl.so ‘not a symbolic link’ ” apparaissent (more on this later )
    • Redémarrer…

    Quick starting guide, PDF : http://developer.download.nvidia.com/compute/DevZone/docs/html/C/doc/CUDA_C_Getting_Started_Linux.pdf

    • Ajouts à PATH & LD_LIBRARY_PATH des librairies CUDA :
    1. $ export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
    2. $ export PATH=/usr/local/cuda/bin:$PATH
    3. (pour ajout permanent, ajouter ces lignes en fin du fichier “.profile” du dossier “home” utilisateur )
    Tester , compiler un exemple :
    • les examples sont localisés dans /usr/local/cuda/samples
    Pour compiler dans un dossir autre ( droits d’accès.. ) , exemple ‘test_cuda’ :
    • recopier le dossier complet ’samples’ de /usr/local/cuda/samples dans l’endroit de votre choix de votre ‘home’
    • Puis, ‘make’ depuis le sous-dossier d’exemple
    • le dossier ./test_cuda/bin/ est créé
    • dans ./test_cuda/bin/x86_64/linux/release/ se trouve l’executable du nom de l’exemple compilé

    Part II : COLMAP software

    • git clone https://github.com/colmap/colmap
    apt-get , dependances :
    sudo apt-get install \
        cmake \
        build-essential \
        libboost-all-dev \
        libeigen3-dev \
        libsuitesparse-dev \
        libfreeimage-dev \
        libgoogle-glog-dev \
        libgflags-dev \
        libglew-dev \
        freeglut3-dev \
        qt5-default \
        libxmu-dev \
        libxi-dev
    executer : $ dpkg -l | grep libeigen
    Si la version n'est pas 3.2.10, voir footnote , réinstaller la version 3.2.10 avant de continuer
    Installer "Ceres Solver" :
    sudo apt-get install libatlas-base-dev libsuitesparse-dev
    git clone https://ceres-solver.googlesource.com/ceres-solver
    cd ceres-solver
    mkdir build
    cd build
    cmake ..
    make -j1 ( <- your taste..)
    sudo make install
    Le cache de ma machine reste plein apres la manip, si c'est votre cas, essayez (flush du cache linux) :
    • sudo sh -c ‘echo 1 >/proc/sys/vm/drop_caches’
    Ensuite, compilation+installation COLMAP :
    • cd repertoire de download colmap
    • mkdir build
    • cd build
    • cmake ..
    • make -j2
    • sudo make install

    FOOTNOTE :

    si problemes de crashes COLMAP en lancant ‘reconstruction’ (SEGFAULTS, EIGEN visible dans le crash resume):
    Vérifier la version de eigen , avec : dpkg -l | grep libeigen
    Il faut la 3.2.10 et non la 3.3(beta) comme c’etait le cas sur ma machine..
    Donc, en résumé :
    • Télécharger Eigen 3.2.10 https://bitbucket.org/eigen/eigen/downloads?tab=tags
    • mkdir build,cd build,cmake ..,make install
    • ->uninstall Eigen 3.3 depuis synaptic package manager
    • Recompiler Ceres ( il FAUT specifier les chemins modifiés pour votre lib EIGEN version 3.2.10 fraichement recompilée )
        dans ceres-solver/ -> cd build, make clean, cmake -D EIGEN_INCLUDE_DIR=/usr/local/include/eigen3 ..
    • make -j2
    • Recompiler Colmap ( il FAUT specifier les chemins modifiés pour votre lib EIGEN version 3.2.10 fraichement recompilée )
    • dans colmap/ -> cd build,make clean,cmake -D EIGEN3_VERSION=3.2.10 -D EIGEN3_INCLUDE_DIRS=/usr/local/include/eigen3 -D EIGEN_INCLUDE_DIR=/usr/local/include/eigen3/ ..
    • make -j2
    314159265358979323846264338327950288
    419716939937510582097494459230781640
    628620899862803482534211706798214808
    

    December 11, 2016

    Neural nets, deep learning, bases en C - part 2

    Filed under: linux, neuron — Tags: , , , , , , — admin @ 9:32 am

    Si vous avez testé différentes combinaisons d’entrées / sortie attendue, vous aurez remarqué que certaines ne convergent pas vers une solution.

    Pour ceux qui débuteraient, c’est ici qu’il faut aborder la notion de BIAS , tout d’abord.

    Si on pose les conditions suivantes en entrée du réseau :

    #define _INA	0.0 // était 3.0 sur la version d'origine
    #define _INB	0.0+sin(ang) // inchangé
    #define _INC	0.0+cos(ang) // inchangé
    //
    #define _REPON	1.0+2.0*cos(ang)+3*sin(ang) // inchangé

    Si on execute le code, on voit que le reseau ne converge pas et ’stagne’ avec une erreur RMS de l’ordre 1.011,
    après des époques dépassant les 1000000.

    La modification de l’entrée A du reseau , passant de 3.0 a 0.0 , interdit au reseau de pouvoir générer la premiere partie de l’equation de sortie attendue, qui est une constant.

    En effet, puisque la sortie (la valeur du neurone) est egale a : (inA*wA)+(inB*wB)+(inC*wC) , on peut voir que, quels  que soient wA,wB, ou wC (les poids) , il faut AU MOINS une des entrées qui ait une valeur autre que ZERO si l’on souhaite générer une sortie qui ait un ‘offset’ (valuer constante) non nul.

    En mettant inA à zero, on a donc supprimé une entrée indispensable à n’importe quel reseau de ce type, qu’on appelle l’entrée de ‘BIAS’.

    Cette entrée est la même qu’une entrée normale, mais sa valeur est fixée au démarrage de l’apprentissage. l’apprentissage (fonction BACKWARD + APPLY )  viendra modifier le poids de cette entrée, de manière à utiliser la valeur de ce BIAS comme nécéssaire.

    On peut vérifier la chose en posant de nouveau ces conditions :

    #define _INA	1.0
    #define _INB	0.0+sin(ang)
    #define _INC	0.0+cos(ang)
    //
    #define _REPON	1.0+2.0*cos(ang)+3*sin(ang)

    le reseau converge de nouveau, avec au final les poids suivants :

    Final Parts :
    (+16.667%)(+50.000%)(+33.333%)
    Final Weights:
    (+1.000 )(+3.000 )(+2.000 )

    la partie constant de la formule de sortie attendue est générée grace à inA*wA = (+1.0*+1.0) = +1.0

    Mais  on peut aussi proposer en entrée :

    #define _INA	-1.0
    #define _INB	0.0+sin(ang)
    #define _INC	0.0+cos(ang)
    //
    #define _REPON	1.0+2.0*cos(ang)+3*sin(ang)

    On éxecute, s’attendant à ce que le poids final wA soit -1.0, de facon à obtenir le premier terme de la formule attendue en sortie ( qui est +1.0 =constante ) , avec inA*wA = (-1.0 * -1.0) = +1.0

    Hors le réseau ne converge pas.
    La répartition des poids montre que wA a été réduit à zero, et reste dans cet etat, interdisant au réseau de converger.

    C’est là qu’il est temps de jeter un oeil sur la fonction :

    void RESO_apply(reso* n,float rate)
    {
    	uint32_t k;
    	for(k=0;k<n->synapses;k++)
    	{
    		n->w[k]+=n->back[k]*rate;//
    	}
    }

    Dans celle-ci, on multiplie le learning rate par la valeur de ‘back[] ‘, qui est la valeur de l’entrée calculée pour le réseau en le parcourant à l’envers (sortie vers entrées)

    hors back[ ] est définie dans la fonction RESO_back() par le calcul suivant :

    n->back[k]=n->neuron*n->part[k]*n->in[k];
    avec :
    n->part[k]=(fabs(n->w[k])/n->wtot);
    et:
    n->error=expected - n->neuron;
    n->neuron=n->error;

    On réécrit la fonction executée pour chaque entrée :
    Remplacant neuron par sa valeur, qui est expected - neuron , c’est a dire l’erreur :

    n->back[k]=n->neuron*n->part[k]*n->in[k];

    peut s’écrire : back[k] = (expected-neuron) * ( FABS( w[k] ) / wtot ) * in [k] ;

    le ‘problème‘ vient du terme w[k] / wtot

    Dans le cas d’un poids qui évolue vers une valeur positive croissante (avec w initial positif), tout ce passe bien.
    Mais dans le cas d’une évolution décroissante, on va rapidement stagner, avec un terme w[k]/wtot qui va ressembler de plus en plus à zero (tendant vers zero pour etre précis) ,  divisé par wtot ( qui au passage ne doit pas etre egal a zero sous peine d’exception ‘DIVIDE BY ZERO’ du programme) , ce qui donne une valeur tendant vers zéro.

    Hors ce terme w[k]/wtot est multiplié par in[k] et ‘error’
    L’ensemble va donc tendre vers ZERO, et , de ce fait, plus le poids va s’approcher de zéro, moins il sera modifié par la fonction ‘APPLY’.

    Dans le cas de l’exemple donné, on a inA=-1.0 , hors le terme constant de la formule attendue en sortie est +1.0+(…)

    Le poids de inA devrait donc être -1.0, pour obtenir cette valeur constante :

    inA * wA =  (-1.0* -1.0) = 1.0

    Donc, puisque le poids wA au départ vaut +1.0 , il faut qu’il décroisse vers -1.0 : c’est ce qui se produit lors de l’apprentissage, et,à l’approche de 0.0, l’apprentissage se verrouille puisque le poids sera de moins en moins modifié, plus il tendra vers ZERO.
    Le cap du ‘ZERO’ semble etre un point de blocage infranchissable avec la méthode actuelle de calcul et la réciproque ( poids initial négatif, et croissant vers le positif du fait de l’apprentissage), et aussi valable.
    On va pour l’heure modifier la fonction, pour passer ce CAP , de la facon suivante :

     void RESO_apply(reso* n,float rate)
    {
    	uint32_t k;
    	for(k=0;k<n->synapses;k++)
    	{
    		n->w[k]+=n->back[k]*rate;// inchangé
    		if(fabs(n->w[k])<0.001) { n->w[k]=-n->w[k]; } // switch : évite le ZERO fatidique...
    	}
    }

    On relance avec les même valeurs d’entrée que précédement, et miracle, le réseau converge, avec au final :

    Final Parts :
    (+25.000%)(+75.000%)(+50.000%)
    Final Weights:
    (-1.000 )(+3.000 )(+2.000 )

    On essaye une modification sur une autre entrée par exemple inA, en inversant sa valuer de sin() mais pas la formule attendue en sortie :

    #define _INA	-1.0
    #define _INB	0.0-sin(ang) // inversée->  -sin(ang)
    #define _INC	0.0+cos(ang)
    //
    #define _REPON	1.0+2.0*cos(ang)+3*sin(ang) // terme +3.0*sin(ang) ..

    Le reseau converge, avec comme valeurs finales (ainsi que les 20 essais de vérification ) :

    Final Parts :
    (+16.667%)(+50.000%)(+33.333%)
    Final Weights:
    (-1.000 )(-3.000 )(+2.000 )
    
    Cycle[000001] EXPECTED(-2.269685) , ACTUAL{-2.26969}
    Cycle[000002] EXPECTED(+2.999692) , ACTUAL{2.99969}
    Cycle[000003] EXPECTED(+0.855610) , ACTUAL{0.85561}
    Cycle[000004] EXPECTED(-0.753175) , ACTUAL{-0.75318}
    Cycle[000005] EXPECTED(+4.137586) , ACTUAL{4.13759}
    Cycle[000006] EXPECTED(-2.603627) , ACTUAL{-2.60363}
    Cycle[000007] EXPECTED(+4.014887) , ACTUAL{4.01489}
    Cycle[000008] EXPECTED(-0.543690) , ACTUAL{-0.54369}
    Cycle[000009] EXPECTED(+0.620656) , ACTUAL{0.62066}
    Cycle[000010] EXPECTED(+3.191344) , ACTUAL{3.19134}
    Cycle[000011] EXPECTED(-2.361939) , ACTUAL{-2.36194}
    Cycle[000012] EXPECTED(+4.548495) , ACTUAL{4.54849}
    Cycle[000013] EXPECTED(-1.696408) , ACTUAL{-1.69641}
    Cycle[000014] EXPECTED(+2.055083) , ACTUAL{2.05508}
    Cycle[000015] EXPECTED(+1.895064) , ACTUAL{1.89506}
    Cycle[000016] EXPECTED(-1.583227) , ACTUAL{-1.58323}
    Cycle[000017] EXPECTED(+4.515279) , ACTUAL{4.51528}
    Cycle[000018] EXPECTED(-2.418411) , ACTUAL{-2.41841}
    Cycle[000019] EXPECTED(+3.320974) , ACTUAL{3.32097}
    Cycle[000020] EXPECTED(+0.455810) , ACTUAL{0.45581}
    314159265358979323846264338327950288
    419716939937510582097494459230781640
    628620899862803482534211706798214808
    

    March 24, 2016

    Real Time kernel patching and cross compilation

    Filed under: Raspberry 3.14, linux — Tags: , , , , , , — admin @ 4:03 pm

    This is a log of the recompilation of the latest available branch 4.1 for the raspberry pi, with the RT patch applied.

    The target machine is a raspberry pi, with actual kernel : 4.1.13+ #826 PREEMPT.

    I assume the  ‘raspberrypi_target’ directory exists. (see an old post about kernel cross compilation..)

    So , you have to update the kernel sources before doing the patching.

    to avoid errors while fetching the new sources, from the raspberrypi_target/linux directory, you can split the fetching of the new files like this:
    for m in `seq 1 100`;do git fetch –depth=$m;done

    once done,switch to the latest stable branch 'rpi-4.1.y' (at the time i write this post) :

    git checkout -b rpi-4.1.y
    
    

    Now its time to get the patch.

    I assume you have a complete linux, branch 4.1.y … at the time i’m writing, the working patch for this version is located at :

    https://www.kernel.org/pub/linux/kernel/projects/rt/4.1/patch-4.1.19-rt22.patch.gz

    perform a save of your original,clean directory :

    from the “raspberrypi_target”  dir :

    cp -a linux linux_before_patching

    so, let’s get the patch:

    wget https://www.kernel.org/pub/linux/kernel/projects/rt/4.1/patch-4.1.19-rt22.patch.gz

    ..And check for any error (do it!) before patching (option –dry-run performs a ‘blank’ patching with no file modifications ) :

    from the linux dir where the patch .gz is :

    zcat patch-4.1.19-rt22.patch.gz | patch -p1 –dry-run | grep FAIL

    if the command returns nothing, everything is OK, now effectively apply the patch :

    zcat patch-4.1.19-rt22.patch.gz | patch -p1

    okay, now you have to get some base config for the configuration menu.

    The simple way : use the raspberry pi connected to your network, and issue a : sudo modprobe configs

    then download the config.gz file from the raspberry to your cross-compiler’s machine :

    scp <username>@<ip.ip.ip.ip>:/proc/config.gz ./

    now you should have the config.gz file into the /linux/ folder where you issued this command (on the compiling machine )

    now deflate it :

    zcat config.gz > .config

    And be a little curious ! (ie :   cat .config )

    Okay, time to launch the menuconfig :

    make ARCH=arm CROSS_COMPILE=/usr/bin/arm-linux-gnueabi- menuconfig

    Now, the menu is on the screen. have to activate your options and customize, Then

    make ARCH=arm CROSS_COMPILE=/usr/bin/arm-linux-gnueabihf- -k -j5

    and voila(oups ..no ..), an error throws :

    HOSTCC  scripts/genksyms/lex.lex.o
    arch/arm/kernel/asm-offsets.c:54:2: error: #error Your compiler is too buggy; it is known to miscompile kernels

    #error Your compiler is too buggy; it is known to miscompile kernels

    arch/arm/kernel/asm-offsets.c:55:2: error: #error and result in filesystem corruption and oopses.
    #error and result in filesystem corruption and oopses.
    ^
    make[1]: *** [arch/arm/kernel/asm-offsets.s] Error 1
    make[1]: Target `__build’ not remade because of errors.
    make: *** [prepare0] Error 2
    HOSTCC  scripts/dtc/data.o
    HOSTCC  scripts/kallsyms

    Damn…

    Okay, lets get another cross compiler, form the github’s “raspberrypi/tools” dir :

    cd to the home for your user name ( cd ~ )

    then git clone https://github.com/raspberrypi/tools.git

    … time to drink a cofee…

    once done, the ‘tools’ folder is there.

    okay, lets try rebuilding the menuconfig, this time using linaro gnueabi , from the tools.
    the kernel modules will go to the ‘rtkern’ folder ..(create it : “mkdir rtkern” from your home folder )

    make ARCH=arm CROSS_COMPILE=~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf- INSTALL_MOD_PATH=~/rtkern

    Once again,errors, on my machine : missing some “libstdc++.so.6″ libraries, fix it ( we need the 6th version)

    Okay, got it.. i used the 32bit version but i’m on a 64bit machine with ubuntu , so :

    first, lets add the path, arch and destination for modules to the system :

    export ARCH=arm
    export INSTALL_MOD_PATH=~/rtkern
    export CROSS_COMPILE=~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-

    notice i specified the -x64 version of the crosscompiler.
    now, from the ‘linux’ directory, try again the menuconfig, it should work fine now :

    make menuconfig

    >> it’s time to activate the options we want, in the menu. for the RealTime aspects, you’ll have to activate ‘KERNEL PREEMPT FULL’ , and also the HIgh-Resolution timers .

    then after customisation and save+exit , in the ‘linux’ folder, execute :

    make

    make modules

    make modules_install” <- the module are installed in the folder specified by the ‘INSTALL_MOD_PATH’ variable, obviously

    now “cd ~/tools/mkimage

    check out your built image , it should be listed when doing :

    ls ~/raspberrypi_target/linux/arch/arm/boot -al

    notice there is Image and zImage that have been created.

    first method :

    • type "./imagetool-uncompressed.py ../../raspberrypi_target/linux/arch/arm/boot/Image"
    • the created boot image is in the current folder, named ‘kernel.img
    • now you have to replace the files into the target pi ,  from ~/raspberrypi_target/linux/arch/arm/boot/kernel.img into the /boot/ folder
      ( NOTICE : the /boot/ folder depends on the image you installed on your SD card. beware in case of a ‘noobs’ image’ , because in this case , the right ‘/boot’ folder is not at the regular place )
    • and rename/erase the target pi’s /lib/modules and /lib/firmware with the one freshly compiled,
    Second method :
    • Use the mkknimg utility , located in the same folder. this one generates a kernel image from the zImage source, and the generated kernel.img file is configured to make use the of the device tree overlay (DTS) , then copy the created file to the /boot folder.
    • Remove / rename the target pi’s /boot/overlays folder by the one contained into ~/raspberrypi_target/linux/arch/arm/boot/dts/overlays

    Note : more infos about the DTS , DTB files, syntax and examples here :

    https://www.raspberrypi.org/documentation/configuration/device-tree.md

    and for the tool to convert from .DTS to .DTB (bin) :

    sudo apt-get install device-tree-compiler

    .. the source of mkknlimg tool here :

    https://github.com/raspberrypi/tools/blob/master/mkimage/mkknlimg

    once done, do not forget to “sync” to ensure the data are written onto the SD.

    then plug the fresh thing into the raspberry en enjoy.

    uname -a gives a “Linux raspberrypi 4.1.20-rt22 #1 PREEMPT RT

    314159265358979323846264338327950288
    419716939937510582097494459230781640
    628620899862803482534211706798214808
    

    June 26, 2014

    Blender 2.69 python script for vertex,normals, and UV coords export

    Filed under: Raspberry 3.14, linux — Tags: , , , , , , — admin @ 11:36 am

    A simple script that can be run from a ‘text’ window in blender 2.69.
    This a raw script,up to you to get what’s useful from it.

    Obviously ,the script assumes the mesh is called ‘Cube’.
    It writes to the file each vertices composing a face of the mesh, with each vertice’s normal and each face’s normal plus the face’s vertices group number + the face’s material number.
    a face’s description is 3 lines, with the start char beein ‘A’,'B’ or ‘C’, corresponding to the 3 vertices of the face.

    If a vertex is in multiple groups, the script takes the first group’s number for that vertex.
    Groups and Materials output are Numbers, from 0…n , directly linked to the Material or Group’s blender - SLOT , whatever the name is.
    Vertices are treated as individual (the real vertices coords are used , assuming the user’s program wich will use this output file will make it’s own ‘indexing’ of vertices wich have same coords  in common )
    Finally, faces have to be triangulated :the script outputs only the 3 first vertices of a meshe’s face .

    Hope this helps!

    #!BPY
    
    import bpy
    
    cubedata = bpy.data.meshes['Cube']
    v=cubedata.vertices
    f=cubedata.polygons
    print("\n\n-------------------------------------\n\n")
    out = open("/home/path_to_your_dir/custom_output.txt", "w")
    idx=0
    for f in cubedata.polygons :
        # FACES MUST BE TRIANGULATED
        g=cubedata.vertices[f.vertices[0]]
        uvx=0
        uvy=0
        if cubedata.uv_layers.active!=None:
            uvx=cubedata.uv_layers.active.data[idx].uv.x
            uvy=cubedata.uv_layers.active.data[idx].uv.y
        else:
            print("UV error,face vertex[0]\n")
        out.write("A=%3.3f %3.3f %3.3f VN=%2.5f %2.5f %2.5f FN=%2.5f %2.5f %2.5f G=%u M=%u U=%2.5f V=%2.5f\n" % (g.co.x,g.co.y,g.co.z,g.normal.x,g.normal.y,g.normal.z,f.normal[0],f.normal[1],f.normal[2],g.groups[0].group,f.material_index,uvx,uvy))
        print("A=%3.3f %3.3f %3.3f VN=%2.5f %2.5f %2.5f FN=%2.5f %2.5f %2.5f G=%u M=%u U=%2.5f V=%2.5f\n" % (g.co.x,g.co.y,g.co.z,g.normal.x,g.normal.y,g.normal.z,f.normal[0],f.normal[1],f.normal[2],g.groups[0].group,f.material_index,uvx,uvy))
        g=cubedata.vertices[f.vertices[1]]
        uvx=0
        uvy=0
        if cubedata.uv_layers.active!=None:
            uvx=cubedata.uv_layers.active.data[idx+1].uv.x
            uvy=cubedata.uv_layers.active.data[idx+1].uv.y
        else:
            print("UV error,face vertex[1]\n")  
        out.write("B=%3.3f %3.3f %3.3f VN=%2.5f %2.5f %2.5f FN=%2.5f %2.5f %2.5f G=%u M=%u U=%2.5f V=%2.5f\n" % (g.co.x,g.co.y,g.co.z,g.normal.x,g.normal.y,g.normal.z,f.normal[0],f.normal[1],f.normal[2],g.groups[0].group,f.material_index,uvx,uvy))
        print("B=%3.3f %3.3f %3.3f VN=%2.5f %2.5f %2.5f FN=%2.5f %2.5f %2.5f G=%u M=%u U=%2.5f V=%2.5f\n" % (g.co.x,g.co.y,g.co.z,g.normal.x,g.normal.y,g.normal.z,f.normal[0],f.normal[1],f.normal[2],g.groups[0].group,f.material_index,uvx,uvy))
        g=cubedata.vertices[f.vertices[2]]
        uvx=0
        uvy=0
        if cubedata.uv_layers.active!=None:
            uvx=cubedata.uv_layers.active.data[idx+2].uv.x
            uvy=cubedata.uv_layers.active.data[idx+2].uv.y
        else:
            print("UV error,face vertex[2]\n")
        out.write("C=%3.3f %3.3f %3.3f VN=%2.5f %2.5f %2.5f FN=%2.5f %2.5f %2.5f G=%u M=%u U=%2.5f V=%2.5f\n" % (g.co.x,g.co.y,g.co.z,g.normal.x,g.normal.y,g.normal.z,f.normal[0],f.normal[1],f.normal[2],g.groups[0].group,f.material_index,uvx,uvy))
        print("C=%3.3f %3.3f %3.3f VN=%2.5f %2.5f %2.5f FN=%2.5f %2.5f %2.5f G=%u M=%u U=%2.5f V=%2.5f\n" % (g.co.x,g.co.y,g.co.z,g.normal.x,g.normal.y,g.normal.z,f.normal[0],f.normal[1],f.normal[2],g.groups[0].group,f.material_index,uvx,uvy))
        #
        idx=idx+3
        #   
    out.close()

    The output file (ascii) will look like :

    A=0.351 -0.679 0.733 VN=0.93976 -0.23081 0.25208 FN=0.88620 -0.21390 0.41097 G=0 M=9 U=0.74219 V=0.25781
    B=0.349 -0.221 0.975 VN=0.93954 -0.07318 0.33445 FN=0.88620 -0.21390 0.41097 G=0 M=9 U=0.74219 V=0.50000
    C=0.249 -0.311 1.144 VN=0.41011 0.22208 0.88455 FN=0.88620 -0.21390 0.41097 G=0 M=9 U=0.74219 V=0.50000
    A=0.245 1.119 -0.397 VN=0.41014 0.89917 0.15235 FN=-0.00437 0.84659 -0.53222 G=0 M=0 U=0.50781 V=0.49219
    B=0.246 0.843 -0.835 VN=0.41194 0.25422 -0.87500 FN=-0.00437 0.84659 -0.53222 G=0 M=0 U=0.50781 V=0.74219
    C=-0.251 1.116 -0.397 VN=-0.41353 0.89776 0.15146 FN=-0.00437 0.84659 -0.53222 G=0 M=0 U=0.25781 V=0.49219
    A=-0.345 -0.957 0.295 VN=-0.93915 -0.32841 0.10056 FN=-0.00010 0.03756 0.99929 G=0 M=9 U=0.00000 V=1.00000
    B=-0.245 -1.148 0.302 VN=-0.41090 -0.65740 0.63161 FN=-0.00010 0.03756 0.99929 G=0 M=9 U=0.00000 V=1.00000
    C=0.251 -1.146 0.302 VN=0.41246 -0.65545 0.63262 FN=-0.00010 0.03756 0.99929 G=0 M=9 U=0.25781 V=1.00000
    A=0.352 -0.954 0.295 VN=0.93982 -0.32621 0.10154 FN=0.88526 -0.46476 0.01753 G=0 M=9 U=0.25781 V=1.00000
    B=0.251 -1.146 0.302 VN=0.41246 -0.65545 0.63262 FN=0.88526 -0.46476 0.01753 G=0 M=9 U=0.25781 V=1.00000
    C=0.251 -1.165 -0.215 VN=0.41252 -0.70116 -0.58150 FN=0.88526 -0.46476 0.01753 G=0 M=9 U=0.25781 V=0.75000
    A=0.247 0.216 -1.166 VN=0.41008 0.58296 -0.70138 FN=0.00010 -0.03762 -0.99929 G=0 M=0 U=1.00781 V=0.75781
    314159265358979323846264338327950288
    419716939937510582097494459230781640
    628620899862803482534211706798214808
    
    Older Posts »

    cat{ } { post_578 } { } 2009-2015 EIhIS Powered by WordPress