Macro FreeCAD to Kerkythea/de

FreeCAD nach Kerkythea

Beschreibung
Makro zum Exportieren Ihres Modells in das Raytracing-Programm Kerkythea.

Versionsmakro : 1.0
Datum der letzten Änderung : 2015-05-30
FreeCAD version : 0.17 und höher
Herunterladen : Werkzeugleisten-Symbol
Autor: marmni
Autor
marmni
Herunterladen
Werkzeugleisten-Symbol
Links
Macro-Version
1.0
Datum der letzten Änderung
2015-05-30
FreeCAD-Version(s)
0.17 und höher
Standardverknüpfung
None
Siehe auch
None

Beschreibung

Makro zum Exportieren des Modells in das Raytracing-Programm Kerkythea.

Anwendung

Das Makro befindet sich im folgenden GitHub-Repository: FreeCAD to Kerkythea-Exporter

Das erklärt sich eigentlich von selbst. Momentan gibt es Probleme beim Exportieren von Lichtern und der Kameraposition.

Verweis

Diskussion im FreeCAD-Forum: Kerkythea Rendering System

Skript

Werkzeugleisten-Symbol

Macro ExportToKerkythea.py

# -*- coding: utf8 -*-
#**************************************************************************************
#*                                                                                    *
#*   Kerkythea exporter                                                               *
#*   Copyright (c) 2014,2015                                                          *
#*   marmni <marmni@onet.eu>                                                          *
#*                                                                                    *
#*   This program is free software; you can redistribute it and/or modify             *
#*   it under the terms of the GNU Lesser General Public License (LGPL)               *
#*   as published by the Free Software Foundation; either version 2 of                *
#*   the License, or (at your option) any later version.                              *
#*   for detail see the LICENCE text file.                                            *
#*                                                                                    *
#*   This program is distributed in the hope that it will be useful,                  *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
#*   GNU Library General Public License for more details.                             *
#*                                                                                    *
#*   You should have received a copy of the GNU Library General Public                *
#*   License along with this program; if not, write to the Free Software              *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307             *
#*   USA                                                                              *
#*                                                                                    *
#**************************************************************************************

#**************************************************************************************
#*                                                                                    *
#*                                 BASED ON                                           *
#*                                                                                    *
#* IOANNIS PANTAZOPOULOS                                                              *
#*                                                                                    *
#* Sample code exporting basic Kerkythea XML file.                                    *
#*                                                                                    *
#* Version v1.0                                                                       *
#*                                                                                    *
#* forum version now py3 runnable & qtnamespacing for qt5+                            *
#*                                                                                    *
#**************************************************************************************

__title__="Kerkythea exporter"
__author__ = "marmni <marmni@onet.eu>"
__url__ = ["https://freecad.org"]


import FreeCAD, FreeCADGui
import re
import random
import builtins
import Mesh
from PySide import QtCore, QtGui, QtWidgets
import sys
import os


##############################################
#
##############################################
class point3D:
    def __init__(self, point):
        self.x = "%.4f" % (point[0] * 0.001)
        self.y = "%.4f" % (point[1] * 0.001)
        self.z = "%.4f" % (point[2] * 0.001)

    def __str__(self):
        return '<P xyz="{0} {1} {2}"/>'.format(self.x, self.y, self.z)

    #def __eq__(self, other):
        #if self.x == other.x and self.y == other.y and self.z == other.z:
            #return True
        #else:
            #return False


##############################################
#
##############################################
class indexListPoint3D:
    def __init__(self, point):
        self.i = point[0]
        self.j = point[1]
        self.k = point[2]

    def __str__(self):
        return '<F ijk="{0} {1} {2}"/>'.format(self.i, self.j, self.k)


##############################################
#
##############################################
class Material:
    def __init__(self):
        self.diffuse = None  # Texture()
        self.shininess = 1000.0
        self.ior = 2.0

    def write(self, file):
        file.write('''<Object Identifier="Whitted Material" Label="Whitted Material" Name="" Type="Material">\n''')

        self.diffuse.write(file, "Diffuse")
        self.diffuse.write(file, "Translucent")
        self.diffuse.write(file, "Specular")
        self.diffuse.write(file, "Transmitted")

        file.write('''<Parameter Name="Shininess" Type="Real" Value="{shininess}"/>
<Parameter Name="Transmitted Shininess" Type="Real" Value="{shininess}"/>
<Parameter Name="Index of Refraction" Type="Real" Value="{ior}"/>
</Object>\n'''.format(shininess=self.shininess, ior=self.ior))


##############################################
#
##############################################
class Texture:
    def __init__(self, color):
        self.color = color

    def getColorSTR(self):
        return '{0} {1} {2}'.format(self.color[0], self.color[1], self.color[2])

    def toGrayscale(self):
        RGB = 0.299 * self.color[0] + 0.587 * self.color[1] + 0.114 * self.color[2]
        self.color = [RGB, RGB, RGB]

    def write(self, file, identifier):
        file.write('''<Object Identifier="./{identifier}/Constant Texture" Label="Constant Texture" Name="" Type="Texture">
<Parameter Name="Color" Type="RGB" Value="{color}"/>
</Object>\n'''.format(identifier=identifier, color=self.getColorSTR()))


##############################################
#
##############################################
class Model:
    def __init__(self):
        self.vertexList = []
        self.normalList = []
        self.indexList = []

        self.name = self.wygenerujID(5, 5)
        self.material = Material()

    def addFace(self, face):
        mesh = self.meshFace(face)
        for pp in mesh.Facets:
            num = len(self.vertexList)
            for kk in pp.Points:
                self.vertexList.append(point3D(kk))
            self.indexList.append(indexListPoint3D([num, num + 1, num + 2]))

    def meshFace(self, shape):
        faces = []
        triangles = shape.tessellate(1) # the number represents the precision of the tessellation
        for tri in triangles[1]:
            face = []
            for i in range(3):
                vindex = tri[i]
                face.append(triangles[0][vindex])
            faces.append(face)
        m = Mesh.Mesh(faces)
        #Mesh.show(m)
        return m

    def wygenerujID(self, ll, lc):
        ''' generate random model name '''
        numerID = ""

        for i in range(ll):
            numerID += random.choice('abcdefghij')
        numerID += "_"
        for i in range(lc):
            numerID += str(random.randrange(0, 99, 1))

        return numerID

    def write(self, file):
        file.write('''
<Object Identifier="./Models/{name}" Label="Default Model" Name="{name}" Type="Model">
<Object Identifier="Triangular Mesh" Label="Triangular Mesh" Name="" Type="Surface">
<Parameter Name="Vertex List" Type="Point3D List" Value="{pointListSize}">\n'''.format(name=self.name, pointListSize=len(self.vertexList)))

        for i in self.vertexList:
            file.write('{0}\n'.format(i))

        file.write('''</Parameter>
<Parameter Name="Normal List" Type="Point3D List" Value="{pointListSize}">\n'''.format(pointListSize=len(self.vertexList)))
        file.write('<P xyz="0 0 -1"/>\n' * len(self.vertexList))
        file.write('''</Parameter>
<Parameter Name="Index List" Type="Triangle Index List" Value="{indexListSize}">\n'''.format(indexListSize=len(self.indexList)))

        for i in self.indexList:
            file.write('{0}\n'.format(i))

        file.write('''</Parameter>\n</Object>\n''')

        self.material.write(file)

        file.write('</Object>\n')


##############################################
#
##############################################
class Camera:
    def __init__(self):
        self.name = "Camera_1"
        self.f_number = "Pinhole"
        self.resolution = "1024x768"
        self.focusDistance = "1"
        self.lensSamples = "3"
        self.blades = "3"
        self.diaphragm = "Circular"
        self.projection = "Planar"

    def addParameter(self, name, pType, value):
        return '<Parameter Name="{0}" Type="{1}" Value="{2}"/>\n'.format(name, pType, value)

    def write(self, file):
        cam = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
        camValues = cam.position.getValue()

        x = "%.4f" % (30 * .0254)
        y = "%.4f" % (30 * .0254)
        z = "%.4f" % (30 * .0254)

        file.write('<Object Identifier="./Cameras/{0}" Label="Pinhole Camera" Name="{0}" Type="Camera">\n'.format(self.name))
        file.write(self.addParameter("Resolution", "String", self.resolution))
        file.write(self.addParameter("Frame", "Transform", "-0.609474 0.471636 -0.63726 {0} 0.792806 0.362576 -0.489895 {1} 2.70762e-06 -0.803802 -0.594897 {2}".format(x, y, z)))
        file.write(self.addParameter("Focus Distance", "Real", self.focusDistance))
        file.write(self.addParameter("f-number", "String", self.f_number))
        file.write(self.addParameter("Lens Samples", "Integer",self.lensSamples ))
        file.write(self.addParameter("Blades", "Integer", self.blades))
        file.write(self.addParameter("Diaphragm", "String", self.diaphragm))
        file.write(self.addParameter("Projection", "String", self.projection))
        file.write('</Object>\n')


##############################################
#
##############################################
class exportTokerkythea:
    def __init__(self):
        self.models = []
        self.cameras = []
        modelsMultiColors = False

    def write(self, file, name):
        file = builtins.open(file, "w")
        self.writeHeader(file, name)

        if self.modelsMultiColors:
            for i, j in self.models.items():
                file.write('<Object Identifier="./Models/{0}" Label="Default Model" Name="{0}" Type="Model">\n'.format(i))
                for k in j:
                    k.write(file)
                file.write('</Object>\n')
        else:
            for i in self.models.values():
                i.write(file)
        # CAMERA
        activeCamera = self.cameras[0][0].name
        for i in self.cameras:
            i[0].write(file)
            if i[1]:
                activeCamera = i[0].name

        file.write('<Parameter Name="./Cameras/Active" Type="String" Value="{0}"/>\n'.format(activeCamera))

        self.writeFooter(file, name)

    def writeHeader(self, file, name):
        file.write('''<Root Label="Kernel" Name="" Type="Kernel">
<Object Identifier="./Ray Tracers/Metropolis Light Transport" Label="Metropolis Light Transport" Name="Metropolis Light Transport" Type="Ray Tracer">
</Object>
<Object Identifier="./Environments/Octree Environment" Label="Octree Environment" Name="Octree Environment" Type="Environment">
</Object>
<Object Identifier="./Filters/Simple Tone Mapping" Label="Simple Tone Mapping" Name="" Type="Filter">
</Object>
<Object Identifier="./Scenes/{0}" Label="Default Scene" Name="{0}" Type="Scene">\n
'''.format(name))

    def writeFooter(self, file, name):
        file.write('''</Object>
<Parameter Name="Mip Mapping" Type="Boolean" Value="1"/>
<Parameter Name="./Interfaces/Active" Type="String" Value="Null Interface"/>
<Parameter Name="./Modellers/Active" Type="String" Value="XML Modeller"/>
<Parameter Name="./Image Handlers/Active" Type="String" Value="Free Image Support"/>
<Parameter Name="./Ray Tracers/Active" Type="String" Value="Metropolis Light Transport"/>
<Parameter Name="./Irradiance Estimators/Active" Type="String" Value="Null Irradiance Estimator"/>
<Parameter Name="./Direct Light Estimators/Active" Type="String" Value="Null Direct Light Estimator"/>
<Parameter Name="./Environments/Active" Type="String" Value="Octree Environment"/>
<Parameter Name="./Filters/Active" Type="String" Value="Simple Tone Mapping"/>
<Parameter Name="./Scenes/Active" Type="String" Value="{0}"/>
<Parameter Name="./Libraries/Active" Type="String" Value="Material Librarian"/>
</Root>'''.format(name))


##############################################
#
##############################################

class exportKerkytheaDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle('Export to Kerkythea v1.1')
        self.setFixedSize(500, 550)

        self.buttonAccept = QtWidgets.QPushButton('Accept')

        tab = QtWidgets.QTabWidget()
        tab.addTab(self.addGeneralPage(), 'General')
        tab.addTab(self.addLightsPage(), 'Light')
        tab.addTab(self.addCmerasPage(), 'Camera')
        #
        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(tab)
        lay.addWidget(self.buttonAccept)

    def changePathF(self):
        path = QtWidgets.QFileDialog().getSaveFileName(self, "Save as", os.path.expanduser("~"), "*.xml")

        fileName = path[0]
        if not fileName == "":
            if not fileName.endswith('xml'):
                fileName = fileName + '.xml'
            self.filePath.setText(fileName)

    def addGeneralPage(self):
        self.filePath = QtWidgets.QLineEdit(os.path.join(os.path.expanduser("~"), 'Unnamed.xml'))
        self.filePath.setReadOnly(True)

        changePath = QtWidgets.QPushButton('...')
        changePath.setFixedWidth(30)
        #self.connect(changePath, QtCore.SIGNAL("clicked ()"), self.changePathF)
        changePath.pressed.connect(self.changePathF)

        generalBox = QtWidgets.QGroupBox('General')
        generalBoxLay = QtWidgets.QGridLayout(generalBox)
        generalBoxLay.addWidget(QtWidgets.QLabel('Path           '), 0, 0, 1, 1)
        generalBoxLay.addWidget(self.filePath, 0, 1, 1, 2)
        generalBoxLay.addWidget(changePath, 0, 3, 1, 1)

        generalBoxLay.setColumnStretch(1, 10)

        self.exportObjects_All = QtWidgets.QRadioButton('All visible objects')
        self.exportObjects_All.setChecked(True)
        self.exportObjects_Selected = QtWidgets.QRadioButton('All selected objects')
        self.exportObjects_SelectedFaces = QtWidgets.QRadioButton('All selected faces')
        self.exportObjects_SelectedFaces.setDisabled(True)

        exportObjectsBox = QtWidgets.QGroupBox('Export objects')
        exportObjectsBoxLay = QtWidgets.QVBoxLayout(exportObjectsBox)
        exportObjectsBoxLay.addWidget(self.exportObjects_All)
        exportObjectsBoxLay.addWidget(self.exportObjects_Selected)
        exportObjectsBoxLay.addWidget(self.exportObjects_SelectedFaces)

        self.exportObjectsAs_YES = QtWidgets.QRadioButton('Yes')
        self.exportObjectsAs_YES.setChecked(True)
        self.exportObjectsAs_NO = QtWidgets.QRadioButton('No')

        exportObjectsAsBox = QtWidgets.QGroupBox('Group models by color')
        exportObjectsAsBoxLay = QtWidgets.QVBoxLayout(exportObjectsAsBox)
        exportObjectsAsBoxLay.addWidget(self.exportObjectsAs_YES)
        exportObjectsAsBoxLay.addWidget(self.exportObjectsAs_NO)

        self.exportObjectColor_MulCol = QtWidgets.QRadioButton('Multi colors')
        self.exportObjectColor_Gray = QtWidgets.QRadioButton('Grayscale')
        self.exportObjectColor_SinCol = QtWidgets.QRadioButton('Single color (random)')
        self.exportObjectColor_SinCol.setChecked(True)

        self.exportObjectColorBox = QtWidgets.QGroupBox('Colors')
        self.exportObjectColorBox.setDisabled(True)
        exportObjectColorBoxLay = QtWidgets.QVBoxLayout(self.exportObjectColorBox)
        exportObjectColorBoxLay.addWidget(self.exportObjectColor_MulCol)
        exportObjectColorBoxLay.addWidget(self.exportObjectColor_Gray)
        exportObjectColorBoxLay.addWidget(self.exportObjectColor_SinCol)
        #####
        widget = QtWidgets.QWidget()

        lay = QtWidgets.QGridLayout(widget)
        lay.addWidget(generalBox, 0, 0, 1, 4)
        lay.addWidget(exportObjectsBox, 1, 0, 1, 4)
        lay.addWidget(exportObjectsAsBox, 2, 0, 1, 2)
        lay.addWidget(self.exportObjectColorBox, 2, 2, 1, 2)

        lay.setRowStretch(10, 10)
        #lay.setColumnStretch(10, 10)
        return widget

    def addLightsPage(self):
        widget = QtWidgets.QWidget()

        return widget

    def addCmerasPage(self):
        self.resolution = QtWidgets.QComboBox()
        self.resolution.addItems(['200x200', '320x200', '320x240', '500x500',
                                  '512x384', '640x480', '768x576', '800x600',
                                  '1024x768', '1280x1024', '1600x1200',
                                  '2048x1536', '2816x2112'])
        self.resolution.setCurrentIndex(self.resolution.findText('1024x768'))

        self.cameraName = QtWidgets.QLineEdit('Camera 1')

        filmBox = QtWidgets.QGroupBox('General')
        filmBoxLay = QtWidgets.QGridLayout(filmBox)
        filmBoxLay.addWidget(QtWidgets.QLabel('Camera name'), 0, 0, 1, 1)
        filmBoxLay.addWidget(self.cameraName, 0, 1, 1, 1)
        filmBoxLay.addWidget(QtWidgets.QLabel('Resolution'), 1, 0, 1, 1)
        filmBoxLay.addWidget(self.resolution, 1, 1, 1, 1)

        filmBoxLay.setHorizontalSpacing(50)

        self.fNumber = QtWidgets.QComboBox()
        self.fNumber.addItems(['1', '1.4', '2', '2.8', '4', '5.6', '8', '16', '22', 'Pinhole'])
        self.fNumber.setCurrentIndex(self.fNumber.findText('Pinhole'))

        self.focusDistance = QtWidgets.QDoubleSpinBox()
        self.focusDistance.setValue(1.0)
        self.focusDistance.setRange(0.0, 1000.0)

        self.lensSamples = QtWidgets.QSpinBox()
        self.lensSamples.setValue(3)
        self.lensSamples.setRange(0, 1000)

        lensBox = QtWidgets.QGroupBox('Lens')
        lensBoxLay = QtWidgets.QGridLayout(lensBox)
        lensBoxLay.addWidget(QtWidgets.QLabel('f-number'), 0, 0, 1, 1, QtCore.Qt.AlignHCenter)
        lensBoxLay.addWidget(self.fNumber, 1, 0, 1, 1)
        lensBoxLay.addWidget(QtWidgets.QLabel('Focus Distance'), 0, 1, 1, 1, QtCore.Qt.AlignHCenter)
        lensBoxLay.addWidget(self.focusDistance, 1, 1, 1, 1)
        lensBoxLay.addWidget(QtWidgets.QLabel('Lens Samples'), 0, 2, 1, 1, QtCore.Qt.AlignHCenter)
        lensBoxLay.addWidget(self.lensSamples, 1, 2, 1, 1)

        lensBoxLay.setHorizontalSpacing(50)

        self.projection = QtWidgets.QComboBox()
        self.projection.addItems(['Planar', 'Cylindrical', 'Spherical', 'Parallel'])
        self.projection.setCurrentIndex(self.projection.findText('Planar'))

        self.diaphragm = QtWidgets.QComboBox()
        self.diaphragm.addItems(['Circular', 'Polygonal'])
        self.diaphragm.setCurrentIndex(self.diaphragm.findText('Circular'))

        self.blades = QtWidgets.QSpinBox()
        self.blades.setValue(3)
        self.blades.setRange(3, 1000)

        geometryBox = QtWidgets.QGroupBox('Geometry')
        geometryBoxLay = QtWidgets.QGridLayout(geometryBox)
        geometryBoxLay.addWidget(QtWidgets.QLabel('Projection'), 0, 0, 1, 1, QtCore.Qt.AlignHCenter)
        geometryBoxLay.addWidget(self.projection, 1, 0, 1, 1)
        geometryBoxLay.addWidget(QtWidgets.QLabel('Diaphragm'), 0, 1, 1, 1, QtCore.Qt.AlignHCenter)
        geometryBoxLay.addWidget(self.diaphragm, 1, 1, 1, 1)
        geometryBoxLay.addWidget(QtWidgets.QLabel('Blades'), 0, 2, 1, 1, QtCore.Qt.AlignHCenter)
        geometryBoxLay.addWidget(self.blades, 1, 2, 1, 1)

        geometryBoxLay.setHorizontalSpacing(50)

        #####
        widget = QtWidgets.QWidget()
        lay = QtWidgets.QVBoxLayout(widget)
        lay.addWidget(filmBox)
        lay.addWidget(lensBox)
        lay.addWidget(geometryBox)
        lay.addStretch(10)

        return widget



class exportKerkythea(exportKerkytheaDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.buttonAccept.pressed.connect(self.acceptw)
        self.exportObjectsAs_YES.clicked.connect(self.setColors)
        self.exportObjectsAs_NO.clicked.connect(self.setColors)

    def setColors(self):
        if self.exportObjectsAs_YES.isChecked():
            self.exportObjectColor_SinCol.setChecked(True)
            self.exportObjectColorBox.setDisabled(True)
        else:
            self.exportObjectColorBox.setDisabled(False)

    def acceptw(self):
        if self.exportObjects_All.isChecked():
            projectObjects = [i for i in FreeCAD.ActiveDocument.Objects if i.ViewObject.Visibility]
        elif self.exportObjects_Selected.isChecked():
            projectObjects = []
            for i in FreeCADGui.Selection.getSelection():
                if i.ViewObject.Visibility and i not in projectObjects:
                    projectObjects.append(i)


        projectModels = {}
        for i in projectObjects:  # objects in document
            try:
                objectColors = i.ViewObject.DiffuseColor
                shape = i.Shape.Faces
            except:
                continue

            for j in range(len(i.Shape.Faces)):  # object faces
                # get face color
                if len(objectColors) == len(i.Shape.Faces):
                    modelType = objectColors[j]
                else:
                    modelType = objectColors[0]

                if self.exportObjectsAs_YES.isChecked():
                    modelID = str(modelType)
                else:
                    modelID = i.Label

                if self.exportObjectColor_SinCol.isChecked():
                    modelsMultiColors = False

                    if not modelID in projectModels:
                        model = Model()
                        if self.exportObjectsAs_NO.isChecked():
                            model.name = modelID
                        model.material.diffuse = Texture(modelType)
                        projectModels[modelID] = model
                    else:
                        model = projectModels[modelID]
                else:
                    modelsMultiColors = True

                    if not modelID in projectModels:
                        projectModels[modelID] = []

                    model = Model()
                    model.name = 'Face {0}'.format(j)
                    model.material.diffuse = Texture(modelType)
                    if self.exportObjectColor_Gray.isChecked():
                        model.material.diffuse.toGrayscale()
                    projectModels[modelID].append(model)

#                    model = Model()
#                    model.material.diffuse = Texture(modelType)
#                    projectModels[i.Label].append(model)

                model.addFace(i.Shape.Faces[j])

        exporter = exportTokerkythea()
        exporter.modelsMultiColors = modelsMultiColors
        exporter.models = projectModels
        # CAMERA
        camera = Camera()
        camera.name = self.cameraName.text()
        camera.f_number = self.fNumber.currentText()
        camera.resolution = self.resolution.currentText()
        camera.focusDistance = self.focusDistance.value()
        camera.lensSamples = self.lensSamples.value()
        camera.blades = self.blades.value()
        camera.diaphragm = self.diaphragm.currentText()
        camera.projection = self.projection.currentText()

        exporter.cameras.append([camera, True])

        exporter.write(self.filePath.text(), FreeCAD.ActiveDocument.Label)

        self.close()

exportKerkythea().exec_()